[
  {
    "path": ".gitignore",
    "content": "/omp_web/node_modules\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n### Example user template template\n### Example user template\n\n# IntelliJ project files\n.idea\n*.iml\nout\ngen\n\n# General\n.DS_Store\nomp_web/.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon��\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n/jon_test_files/\n/package_hub/127.0.0.1/\n\ntemp\n/package_hub/omp_monitor_agent.tar.gz\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "default_stages: [commit]\nrepos:\n  - repo: https://github.com/yingzi113/pre-commit-hooks\n    rev: 5863e162f1bed1f63eeb716e77d622ff8e3d9af9\n    hooks:\n    - id: check-case-conflict\n  - repo: https://github.com/pre-commit/mirrors-autopep8\n    rev: v1.4.4\n    hooks:\n    - id: autopep8\n      args: [-i, --global-config=.flake8, -v, --ignore=E402]\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v2.4.0\n    hooks:\n    - id: flake8\n      args:\n      - --ignore=E501,E402\n      exclude: package_hub/_modules/init_host.py\n    - id: check-docstring-first\n    - id: trailing-whitespace\n    - id: check-ast\n    - id: check-json\n    - id: check-yaml\nexclude: migrations\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# Readme\n# OMP-运维管理平台\nOMP（Operation Management Platform）是云智慧公司自主设计、研发的轻量级、聚合型、智能运维管理平台。是一款为用户提供便捷运维能力和业务管理的综合平台。具备运维一应俱全的功能，目的是提升运维自动化、智能化，提高运维效率，提升业务连续性和安全性。\n\n# OMP设计初衷\n## 目前运维面临的痛点：\n- 主机环境多样性，难以统一管理：如：混合云、私有云、跨IDC、虚拟化、容器化等\n- 业务变更难度较大，自动编排能力较低\n- 业务状态监控，多平台难以数据联动\n- 业务出现异常，难以实现故障自愈\n- 业务运行状态，难以评估，更难以分析\n- 运维知识匮乏，缺少专家指导及专家解决方案\n\n运维管理平台(OMP)的设计初衷就是想打造一应俱全的运维平台，降低交付难度，提升运维自动化、智能化，提高运维效率，提升业务连续性和安全性。\n\n# OMP核心功能\n- **主机纳管**：纳管主机资源，实时监控主机运行状态，可在线管理、在线连接终端等\n- **应用管理**：平台已内置常用基础组件，也支持符合标准的自研服务发布到应用商店，从而提供便捷的应用管理，如：安装部署、变更发布、弹性扩缩容、在线配置、优化等\n- **应用监控**：涵盖标准监控、定制监控、链路监控、智能监控等多种业务场景，通过大数据智能测算，可感知未来趋势，将异常控制在发生前\n- **故障自愈**：当业务系统出现异常或故障时，可按照预定的自愈策略进行故障治理，极大降低故障对业务影响，减少企业损失\n- **状态巡检**：自动、手动进行业务指标、运行状态汇总，支持自动发送报告到指定邮箱\n- **备份/恢复**：针对核心数据进行本地+异地备份，支持自动执行备份并将数据发送至指定邮箱，达到异地的存储效果，确保数据安全\n- **精简工具**：提供运维常用工具、命令、脚本、SQL等，为日常运维操作提供便利，减少误操作、减低技术门槛，支持自行维护、扩充更多工具\n- **权限管理**：针对不同用户、角色，进行权限控制，及操作审计\n\n# 架构设计\n![./doc/architecture.png](./doc/architecture.png)\n\n## Demo\n\n通过浏览器访问页面，访问入口为：http://omp.cloudwise.com/#/login    \\\n默认用户名：admin     \\\n默认密码：Yunweiguanli@OMP  \\\n每晚 00:00 将重置数据\n\n\n\n# 使用 OMP\n## 安装部署\n当前OMP安装包内部包含了其使用的所有组件，建议将OMP部署在 /data/ 下，当前版本部署流程如下：   \\\nstep0：下载解压安装包\n\n```shell\ntar -xvf omp_open-*.tar.gz -C /data\n```\n\nstep1：编辑文件，检查环境配置\n\n```shell\nvim /data/omp/config/omp.yaml\n```\n\n注意：当前版本已携带mysql、redis安装，配置信息如下，如需修改请在安装前修改\n\n```yaml\n# redis相关配置\nredis:\n  host: 127.0.0.1\n  port: 6380\n  password: common123\n# mysql相关配置\nmysql:\n  host: 127.0.0.1\n  port: 3307\n  username: common\n  password: Common@123\n```\n\nstep2：执行安装脚本\n```shell\ncd /data/omp && bash scripts/install.sh\n# 注意1：执行后根据提示选择本机ip，如主机上存在多网卡多IP情况，需要根据业务需求自行判断使用哪个ip地址\n# 注意2：当前执行操作的用户即为OMP中各个服务进程的运行用户，在以后的维护中，也应使用此用户进行操作\n```\n\n## 管理OMP\n\n注意：如需停止 OMP 相关服务，请先执行 “停止 OMP 定时保活任务” 操作\n\n```shell\n# [服务名称] 值为： all 为对所有组件操作\n# all|mysql|redis|tengine|uwsgi|worker|cron|salt|prometheus|alertmanager|grafana|loki\nbash /data/omp/scripts/omp [服务名称] [status|start|stop|restart]\n```\n\n停止 OMP 定时保活任务：\n\n```Apache\n# 查看定时任务\ncrontab -e\n\n# 删除或注释如下内容，否则定时任务会将 OMP 自动拉起\n# */5 * * * * bash /data/omp/scripts/omp all start &>/dev/null\n```\n\n## 卸载OMP\n\nomp节点上卸载操作如下：\n```shell\nbash /data/omp/scripts/uninstall.sh\n```\n## 升级 & 回滚 OMP\n\n```shell\n# 升级命令\nbash cmd_manager omp_upgrade [必填参数：升级目标路径(如:/data/omp，注意此处路径末尾无/)] [选填参数:从某个断点处升级,默认开头]\n# 例如\nbash 升级包路径/scripts/cmd_manager omp_upgrade /data/omp(当前正在运行的旧安装路径)\n\n# 回滚命令\nbash cmd_manager omp_upgrade [必填参数：升级目标路径(如:/data/omp，注意此处路径末尾无/)] [选填参数:从某个断点处升级,默认开头]\n# 例如\nbash 升级包路径/scripts/cmd_manager omp_rollback /data/omp(当前正在运行的旧安装路径)\n```\n\n## 断点执行\n\n常用于执行过程中某一步骤失败时，期望从失败步骤处再次执行时使用，正常情况无需考虑此参数，参数默认下标为0\n\n升级回滚可以理解成为jenkins的pipliene 是分步骤执行的，当我们在某一个位置出现异常时，手动修复后通过错误节点再次进行时使用，而跳过之前已经升级（回滚）正确的步骤\n\n```shell\n# 升级流程顺序如下：\n# PreUpdate, Mysql, Redis, Grafana, Tengine, OmpWeb, OmpServer, Python, PostUpdate\n```\n\n\n\n# 环境依赖\n\n## 技术栈\n\n### 后端技术栈\n\n- Python 3.8.7\n- Django 3.1.4\n- Saltstack 3002.2\n- Uwsgi 2.0.19.1\n\n### 数据库\n\n- mysql 5.7.37\n- redis 6.2.7\n\n### 前端技术栈\n\n- Tengine 1.22.0\n- React 17.0.1\n\n### 监控技术栈\n\n- Prometheus 2.25.1\n- Alertmanager 0.24.0\n- Grafana  9.3.8\n- Loki 2.4.1\n- Promtail 2.2.0\n\n## 内置组件概览\n\n| **组件名称** | **组件作用**                                     | **端口**     |\n| ------------ | ------------------------------------------------ | ------------ |\n| tengine      | 平台访问入口，代理前端页面及后端uwsgi程序        | 19001        |\n| uwsgi        | web容器，用于提供 python Django 后端程序访问入口 | 19003        |\n| salt         | 开源组件，服务器控制程序，提供主机 Agent 通信    | 19004、19005 |\n| worker       | 异步任务、定时任务执行程序，有进程无端口         | -            |\n| prometheus   | 开源组件，提供监控数据                           | 19011        |\n| grafana      | 开源组件，提供监控面板                           | 19014        |\n| alertmanager | 开源组件，提供日志告警                           | 19013、9094  |\n| loki         | 开源组件，提供日志采集                           | 19012、9095  |\n| redis        | 开源组件，提供缓存，消息队列                     | 6380         |\n| mysql        | 开源组件，数据存储                               | 3307         |\n| ntpd         | 开源组件，提供时间同步功能                       | 123（udp）   |\n\n\n\n# 关于应用商店\n\n## 如何制作一个OMP应用商店中的应用\n\n[OMP 社区版-应用商店发布说明文档](./doc/app_publish.md)\n> 内含\n - 基础组件打包规范\n - 应用服务打包规范\n - 目录和配置说明\n - postgreSql、redis、rocketmq等应用Demo\n\n## 卸载应用商店中已经发布的应用\n\n>已支持界面操作\n\n```shell\nexport LD_LIBRARY_PATH=/data/omp/component/env/lib && /data/omp/component/env/bin/python3.8 /data/omp/scripts/source/uninstall_app_store.py --product 产品名称 --app_name 组件/服务名称 --version 版本\n```\n\n 已经部署服务实例的安装包，无法卸载\n\n 参数说明：\n\n1.  ***--version*** 缺省时，卸载所有版本\n2.  卸载基础组件 ***--app_name 基础组件名称***\n3.  卸载应用/产品  ***--product 应用/产品名称***\n4.  卸载应用下指定服务 ***--product 应用/产品名称 --app_name 服务名称***\n\n\n\n\n\n欢迎加入\n获取更多关于OMP的技术资料，或加入OMP开发者交流群，可扫描下方二维码咨询\n\n<img src=\"./doc/contact-us.png\" width=\"600px\" height=\"400px\" />\n"
  },
  {
    "path": "UpdateLog.md",
    "content": "# 更新日志\n\n------\n\n\n\n\n\n## v0.1.0 (2021.11.30)\n- 【仪表盘】\n  - 全局状态概览\n  - 当前异常信息展示\n  - 各模块状态展示\n- 【主机管理】\n  - 主机纳管（添加、导入、编辑、维护、删除）\n  - 主机自动监控、告警\n- 【应用商店】\n  - 组件、应用WEB发布、服务端自动发现\n  - 组件、应用部署，自动编排解决依赖\n- 【服务管理】\n  - 服务管理（启动、停止、重启、删除）\n  - 服务监控（监控、日志、告警、自愈）\n- 【应用监控】\n  - 实时展示处于异常的主机、服务信息，呼应仪表盘的异常清单\n  - 告警历史记录查看，未读提醒，按添加检索\n  - 支持监控组件地址自定义，便于对接现有监控平台\n- 【状态巡检】\n  - 支持主机巡检、组件巡检、深度分析，且支持导出\n  - 支持定时自动执行巡检任务\n- 【系统管理】\n  - 用户账户管理\n  - 支持全局维护模式，避免人为操作时误报\n\n\n\n## v0.5.0 (2022.04.11)\n\n- 【应用商店】\n  - 组件、应用服务的升级及回滚\n  - 应用服务的增量安装\n- 【部署模板】\n  - 支持通过部署模版实现批量部署\n- 【应用监控】\n  - 支持告警邮件配置，将告警信息发送至指定邮箱\n- 【故障自愈】\n  - 展示故障自愈记录\n  - 支持监控到服务状态异常后自动进行重启\n  - 支持设置服务自愈尝试次数\n- 【指标中心】\n  - 支持添加自定义告警指标规则\n  - 添加自定义扩展采集指标\n- 【数据备份】\n  - 支持mysql、arangodb、postgreSql数据备份\n  - 备份记录展示、下载、删除\n  - 支持自定义保存路径、定时备份策略及邮件推送备份内容\n- 【实用工具】\n  - 内置部分运维实用小工具\n  - 展示小工具执行过程、输出展示及生成文件下载\n- 【系统管理】\n  - 增加邮件管理，支持设置smtp邮件服务器作为全局邮件发件箱\n\n- 【平台优化】\n  - 优化主机纳管逻辑，增加纳管成功率，支持删除主机\n  - 优化应用安装服务逻辑代码\n  - 优化巡检逻辑\n  - 优化部分前端页面显示及交互效果\n- 【其他】\n  - 修复已知bug\n\n\n\n\n## v0.6.0 (2022.11.29)\n- 升级内置基础组件和环境\n  - alertmanager 升级至 v0.24.0\n  - tengine 升级至 v1.22.0\n  - 扩充内置环境中部分第三方库\n  - 升级主机 Agent & 监控 Agent\n- 优化小工具异步任务执行逻辑\n- 更新 prometheus 和 loki 的配置\n- 修复 grafana 面板中 mysql 显示异常问题\n- 补充应用商店基础组件包：mysq、elasticsearch\n- 组件包从代码库抽离，减少源码 & 包体量\n\n\n## v0.7.0 (2022.12.30)\n- 完善 OMP 管理脚本功能\n  - 支持升级、回滚，支持断点重试\n  - 支持命令行卸载应用商店已发布服务\n- 内置 Redis 5.0.37 升级至 6.2.7\n- 验证码登陆\n- 修改密码长度异常问题\n\n\n## v0.8.0 (2023.01.30)\n- 新增监控功能\n  - 产品http请求 5XX 错误\n  - jvm 文件句柄使用率过高\n  - 修复部分服务无法获取 cpu、内存问题\n- 增加只读用户功能\n- 修复添加主机提示已经存在问题\n- 银河麒麟V10 ARM ，鲲鹏920 （ARM架构）2023.03.30\n\n## v0.9.0 (2023.05.31)\n\n- 内置Grafana版本升级至 9.3.8\n- 主机/服务详情页面布局调整\n- 主机/服务/安装/升级/回滚页面中文本溢出处理\n- 支持通过前端界面方式卸载应用商店中已经发布的应用\n- 更新readme文档\n\n## v1.0.0 (2023.07.30)\n\n- 新增功能【服务纳管】模块\n- 重构【服务自愈】模块\n- 新增 OOM 告警\n- 更新部分 Grafana 面板\n  - 服务面板： redis、victoriametrics、rocketmq\n  - 集群面板： redis、clickhouse、mysql、tengine\n- 修复 Grafana 无法登陆问题\n\n## v1.1.0 (2023.09.30)\n\n- 重构「数据备份」模块\n- 支持多端口服务监控 & 更新文档\n- 前端优化，步骤类交互型界面，刷新自动跳转\n- 前端优化，消除部分冗余导入\n- 修复bug：仪表盘异常清单类型缺失，环形统计图跳转增加类型过滤，nodeExporter、loki启动失败问题"
  },
  {
    "path": "component/.gitkeep",
    "content": ""
  },
  {
    "path": "component/alertmanager/.gitkeep",
    "content": ""
  },
  {
    "path": "component/grafana/.gitkeep",
    "content": ""
  },
  {
    "path": "component/loki/.gitkeep",
    "content": ""
  },
  {
    "path": "component/prometheus/.gitkeep",
    "content": ""
  },
  {
    "path": "config/omp.yaml",
    "content": "# 全局用户, 自动解析当前操作用户\nglobal_user: common\n# 初始化时由用户输入本机的ip地址\nlocal_ip: 10.0.1.160\n# SSH执行命令超时时间，单位秒\nssh_cmd_timeout: 60\n# SSH连通性校验超时时间，单位秒\nssh_check_timeout: 10\n# 线程池最大workers\nthread_pool_max_workers: 10\n# redis相关配置\nredis:\n  host: 127.0.0.1\n  port: 19034\n  password: common123\n# mysql相关配置\nmysql:\n  host: 127.0.0.1\n  port: 19033\n  username: common\n  password: Common@123\n# salt相关配置\nsalt_master:\n  publish_port: 19004\n  ret_port: 19005\n  timeout: 30\n# uwsgi的配置\nuwsgi:\n  socket: 127.0.0.1:19003\n  processes: 4\n  threads: 2\n# tengine相关的配置\ntengine:\n  access_port: 19001\n  runserver_port: 19002\n# 登录token过期时间，天\ntoken_expiration: 1\n#grafana认证字段，无需修改\ngrafana_api_key: test\n#grafana_auth\ngrafana_auth:\n  # grafana admin auth\n  grafana_admin_auth:\n    username: admin\n    plaintext_password: Yunweiguanli@OMP_123\n  # grafana viewer auth\n  grafana_viewer_auth:\n    username: omp\n    plaintext_password: Common@123\n# 关联邮件设置,谨慎修改\nalert_manager:\n  # 是否开启发送邮件配置，默认不开启\n  send_email: false\n  # 发件人邮箱配置\n  EMAIL_SEND: <发件人邮箱>\n  # smtp服务器地址端口配置\n  SMTP_SMARTHOST: <SMTP 服务器，比如 smtp.163.com:465>\n  # 解释待定？？？？\n  SMTP_HELLO: 163.com\n  # 发件人的用户名配置\n  EMAIL_SEND_USER: <发件人的用户名>\n  # 发件人邮箱秘钥\n  EMAIL_SEND_PASSWORD: <发件人邮箱密钥>\n  # 发送频率(同一条报警消息的发送频率,s、m、h对应 秒、分、小时)\n  EMAIL_SEND_INTERVAL: 30m\n  # 接收代号\n  RECEIVER: commonuser\n  # 收件人\n  EMAIL_ADDRESS: <收件人邮箱>\n  # webhook地址，仅在手动维护OMP各个组件时才可能用到，其他情况下不建议修改\n  WEBHOOK_URL: http://127.0.0.1:19001/api/promemonitor/receiveAlert/\n# prometheus basic auth\nprometheus_auth:\n  username: omp\n  plaintext_password: Yunweiguanli@OMP_123\n  ciphertext_password: $2b$12$R8WlKOEV2M9iBjEhnWQORepigbQoD1D/rAyEXwIu/aS5t94deTVDu\n# 监控使用相关端口配置\nmonitor_port:\n  # server各个端口\n  prometheus: 19011\n  loki: 19012\n  alertmanager: 19013\n  grafana: 19014\n  # agent端各个端口配置\n  blackboxExporter: 19015\n  promtail: 19016\n  nodeExporter: 19017\n  processExporter: 19018\n  mysqlExporter: 19019\n  redisExporter: 19020\n  kafkaExporter: 19021\n  zookeeperExporter: 19022\n  clickhouseExporter: 19023\n  postgreSqlExporter: 19024\n  beanstalkdExporter: 19025\n  tengineExporter: 19026\n  elasticsearchExporter: 19027\n  httpdExporter: 19028\n  igniteExporter: 19029\n  rocketmqExporter: 19032\n  # arangodb与nacos没有单独的exporter，使用的是arangodb和nacos的原生自带接口\n  arangodbExporter: 18119\n  nacosExporter: 18117\n  monitorAgent: 19031\n# Loki相关配置\nloki_config:\n  # promtail采集日志文件名等级过滤 [debug, info, warn, error, all]\n  scrape_log_level: error\n\n# 基础及公共组件间的等级划分，不要将自研服务类的服务放入下面的配置\n# 用于控制服务安装过程中的执行顺序、服务的启停控制顺序\n# 安装时顺序从小到大执行，同级别并发执行\nbasic_order:\n  0:\n    - jdk\n    - safeRM\n    - tengine\n    - mysql\n    - comLib\n    - redis\n    - arangodb\n    - minio\n    - postgreSql\n    - prometheus\n    - pushgateway\n    - mongodb\n    - nodejs\n    - beanstalk\n  1:\n    - keepalived\n    - httpd\n    - elasticsearch\n    - zookeeper\n    - rocketmq\n    - wkhtmltox\n    - tomcat\n    - jkbPhp\n  2:\n    - nacos\n    - xxlJob\n    - kafka\n    - clickhouse\n    - hadoop\n  3:\n    - flink\n    - aopsUtils\n    - filebeat\n    - victoriaMetrics\n    - sentinel\n# 自愈周期内服务key的存活时常 min\nhealth_redis_timeout: 60\n# 单次health接口检测最大次数\nhealth_request_count: 10\n# 单次health接口请求停留时常 s\nhealth_request_sleep: 6\n# 模板安装下，基础组件集群模式个数的严格校验\ntemplate_cluster_check: true\n#服务发现脚本,纳管自定义排查脚本\nservice_discovery:\nbackup_service:\n  - mysql\n  - postgreSql"
  },
  {
    "path": "config/private_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAwv4dqlvcYtrPJsCL/VuX0u4FZm2E0du1m01gUnp3afSkx+u2\nGTXptpS7dNfTLguu1HjJUzkEIGaJGG/x/PR2Zs6I/UmIFWj6tdmfBBlrVRETnm8t\nCAdO9/1zjzz4wB2yuHduBK6TYwhXfZOCg3LOj+QVpUYqyq3lqjPN+C6QbFzgk8Fw\nMHr+R3OzZe9nsaNZOHRbSmu6NU5zkIdnScQwIiWIe9nZMpoTUe45FtYPj7SHiCIt\nDOtbXGbyNOP5k5RhIQtJiEJgVOzGeRSaQAj6UfLClsa43ZXfXIZ+BfOO8GZBXmHe\nRHRs/Prw3Io4n0gXKpgrd6MxwfpoxmXEyoRUGwIDAQABAoIBAQCBjcL6CFSSHZ0a\nuz2HlQ53p4tQ9Z0Urayoxa0kv5eNf2zoI5T2hRqGI6W0yRzXcA21v5bLw4sZV+bo\npKAcF/R+8+SSnQNcbkZ9Al0jpRvqBhGJ54X82pY+MFhSKAmB43l2FGu1kqP8XXN7\nzMEfQu05Lyquh8MwrH92KTtFFPMB+z5BQqukXbZxhkVjEEuDtfvHyw5hQkv4PtM8\nZ+pd9Wh7/WMOq7dJn6nM+TVWQjz+nqsulCEHZ1oj2m4xk57b4QP9wm0DNe0k/bo5\n6b7U/2jvtLeqhFbN2hp1fzHpqo4hDZTuYv9lBFlLGMnKPoppi+oYW8APmy123DCn\nmWmKn3oJAoGBAOqKMRzLCGytbW+qeFhJgohhNnuYxRV5fzG5EUbOb8zO2nZO5QUw\nhJApWDW/mOIvezr53UgLvadw9pnJdDQoQX8gxBX6j6eWbJvXwDU+pBw9Nl3U0ZTZ\nMu8TDy1k/Up59B5BwUInDX2klPJQI/vjf9KoSKXinBbXRkKURq+fMi1lAoGBANTV\nlHWegOdKTzh9PDGbNcQ7KpO89WmLSJQXR54UsOs4x1Kcn0kOWTKV6piPJbAsGzXR\nucs9FfcQukDL/JabEeH0k3NedXCwR43SMTby7f1Sl+8nWdyu4Rf5Z4/iZq+JrbYu\nbJ10ijbnpVCBQxrZ1ce88fyF56mbG0wry4IljyN/AoGBANQed5ya49umXjuH6Z+v\nnCbMBQJzgIuTfr3xqvZm7iZFTr+BSxAOeVYIjobN6e9nEgScxszKEZTGTcF4uWgS\noGnhsHZQTmw7V676yhNdu/7uPaVPPN1qMu6WRjvAAnTBJ0/WGHtD5qejmjIs2N6P\nOqPDHzEoahMeT6UXhXaAfFkhAoGADB3YpNWQOxqk5e9jROO0LOa9ZsnEIu0WBbBJ\nmHtPEyUZW9+kxdD2TQXx5BuKJrxsFCVLcYGZxYYDRHsYdy5+1yFIX7IJ949hk3Za\n7OjpmZlhIvFXkVO3ZtkBB1T5SZcJ96wu7MvcroGDjNC/FEFAhW2BTUIGTUaSSETa\nAh/HRVsCgYEAuoJK9FDdCDsZmAfXSo4T3JtGx1yvKQ1tFCREC2CEQ98S2aEc0tRZ\nCvUUresVdN1AuxtYVDxUOtZPD5GUCrgR+z4heZNi25j6mCf51NAgnaV170sAOOn6\nFjRXOYf0UFAo1eGZjsBp4bQdmdXxynzuW2jbNRxw8mZlHCBBx5jb1O8=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "config/product.yaml",
    "content": "# 使用配置文件的方式更新产品的yaml，留出可更改接口\n# 更新安装参数，如下安装参数在安装过程中会进行更改替换\ninstall:\n  nacos:\n    - name: \"租户类型1单2多3saas\"\n      key: \"deploy_type\"\n      default: \"1\"\n      editable: true\n# 更新端口配置，如下端口在安装过程中会更新到端口中\nports:\n  testService:\n    - name: 服务端口\n      protocol: TCP\n      key: service_port\n      default: 18125\n"
  },
  {
    "path": "config/salt/master",
    "content": "interface: 0.0.0.0\npublish_port: 19004\nret_port: 19005\nuser: root\nenable_ssh_minions: False\npresence_events: True\nauto_accept: True\ntimeout: 30\nroot_dir: /data/omp/data/salt\nconf_file: /data/omp/config/salt/master\nfile_roots:\n  base:\n    - /data/omp/package_hub\nfile_recv: True\nfile_recv_max_size: 524288\n\n"
  },
  {
    "path": "config/salt/minion",
    "content": "master: ${MASTER_IP}\nmaster_port: ${MASTER_PORT}\nuser: ${USER}\nid: ${AGENT_ID}\nroot_dir: ${AGENT_DIR}/data/salt\nconf_file: ${AGENT_DIR}/config/salt/minion\n"
  },
  {
    "path": "config/salt/minion.d/_schedule.conf",
    "content": "schedule:\n  __mine_interval: {enabled: true, function: mine.update, jid_include: true, maxrunning: 2,\n    minutes: 60, return_job: false, run_on_start: true}\n"
  },
  {
    "path": "config/salt/minion.template",
    "content": "master: ${MASTER_IP}\nmaster_port: ${MASTER_PORT}\nuser: ${USER}\nid: ${AGENT_ID}\nroot_dir: ${AGENT_DIR}/data/salt\nconf_file: ${AGENT_DIR}/config/salt/minion\n"
  },
  {
    "path": "data/.gitkeep",
    "content": ""
  },
  {
    "path": "data/inspection_file/.gitkeep",
    "content": ""
  },
  {
    "path": "doc/app_publish.md",
    "content": "# OMP 社区版-应用商店发布说明文档\n\n[TOC]\n\n## 1. 说明\n\n用户可以在应用商店发布“基础组件”与“应用服务”两个维度的产品，在区分上，应用服务可以理解为完整的提供某一类服务的产品，产品内部可由一个或多个“服务”组成 ，比如gitlab、jenkins等。基础组件的角色更多是作为其他完成产品的一部分存在，以完成产品的某些功能需求，如mysql、redis等。\n\n## 2. 基础组件打包规范\n\n注：用户在发布基础组件安装包时，需按照以下规范打包上传才可以正常发布\n\n### 2.1. 目录规范\n\n以MySQL服务为例，需将涉及到的文件统一放在 mysql目录下，目录名称与该服务名称保持一致，目录中需要提供与该目录名称一致的配置文件(如：mysql.yaml)、产品图标（如：mysql.svg)  和其他所需文件（如安装脚本等）\n\n**示例：**\n\n```shell\n$ tree ./mysql -L 2\n./mysql                 # 目录名称，请与组件名称一致\n├── mysql.svg           # 平台展示组件图标，请使用 “组件名称.svg ” 命名，与目录名称保持一致\n├── mysql.yaml          # 组件配置文件, 记录该组件安装、升级等所需信息, 请使用 “组件名称.yaml” 命名，与目录名称保持一致\n└── scripts             # 组件的安装、启动等控制脚本，该脚本在安装时会调用\n│   ├── init.py         # 初始化脚本\n│   ├── install.py      # 组件安装脚本\n│   ├── mysql           # 组件启动、停止控制脚本，建议与服务名称一致\n│   ├── mysql_backup.py # 其他动作脚本，如备份等\n```\n\n**备注:**\n\n1. 组件图标请使用svg格式图片，如不添加会显示平台缺省图标；\n2. 确保目录名称(mysql)、配置文件(mysql.yaml) 、图标(mysql.svg) 名称统一, 上传安装包时，平台将根据名称校验对应文件合法性，如名称不一致，可能会导致校验不通过等问题；\n3. 确保安装包解压后是一个整体目录\n\n### 2.2. 压缩包命名规范\n\n请使用 `{name}-{version}-{others}-{package_md5}.tar.gz`  格式进行打包命名\n\n1. name:   安装包名称，建议字符:  `英文`  `数字`   `_`\n2. version: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n3. others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n4. package_md5:  安装包MD5 值\n\n例如：`mysql-5.7.31-beta-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz`\n\n```shell\n$ tar czf mysql-5.7.31.tar.gz mysql\n$ md5sum mysql-5.7.31.tar.gz\n8e955b24fefe7061eb79cfc61a9a02a1\n$ mv mysql-5.7.31.tar.gz mysql-5.7.31-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz\n```\n\n### 2.3.  配置文件(yaml)说明\n\n平台预留KEY值（该KEY值存在指定定义，请准确使用）：\n\n| KEY          | 说明         | 备注                                   |\n| ------------ | ------------ | -------------------------------------- |\n| service_port | 服务端口     | 供其他程序连接的端口号                 |\n| base_dir     | 应用安装目录 |                                        |\n| log_dir      | 应用日志目录 | 服务的日志采集会采集该目录下*.log 文件 |\n| data_dir     | 应用数据目录 |                                        |\n| username     | 用户名       |                                        |\n| password     | 密码         |                                        |\n\n```yaml\n# 类型定义，发布基础组件时 ,指定类型为 component （类型：string）\nkind: component\n# 组件在平台显示的名称，请与组件目录名称保持一致，建议字符：英文、数字、_ （类型：string）\nname: mysql\n# 上传后显示的组件版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion: 5.7.31\n# 组件描述信息，建议长度256字符之内，请针对组件书写贴切的描述文字 （类型：string）\ndescription: \"MySQL是一个关系型数据库管理系统，由瑞典MySQL AB 公司开发，属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一，在 WEB 应用方面，MySQL是最好的 RDBMS (Relational Database Management System，关系数据库管理系统) 应用软件之一。\"\n# 组件所属标签，请针对组件功能设置准确标签，平台会针对该标签对组件进行分类，（类型：list[string,string...]）\nlabels:\n  - 数据库\n# 指定该服务安装后是否需要启动 （类型：boolean)\nauto_launch: false\n# 指定组件是否为基础环境组件，如 jdk, 该类组件以基础环境方式安装 （类型：boolean)\nbase_env: flase\n# 定义组件所需端口号，如不启用端口，可留空 （类型：list[map,map...])\nports:\n    # 端口描述名称，用户在安装时会以该名称显示表单内容（类型：string)\n  - name: 服务端口\n    # 端口协议，支持 TCP/UDP\n    protocol: TCP\n    # 端口英文描述名称，该key会传入到安装脚本中 （类型： string）支持（英文、数字、_)\n    key: service_port  # 注：service_port 为保留关键词，表示 为 提供服务的端口\n    # 组件的默认端口号，在安装时，会以该值填入表单中（类型： int）\n    default: 3306\n# 组件监控相关配置，定义该组件在安装后如何监控 ，如果不需要监控可留空 （类型： map）\nmonitor:\n  # 监控进程名称，如“mysqld”，平台在发现mysqld进程不存在后，会发送告警提醒 ，不需要监控可留空（类型：string）\n  process_name:  \"mysqld\"\n  # 监控端口号，请根据 ports 中的变量设置，不需要监控可留空 （类型： {string}）\n  metric_port: {service_port}\n---\n# 设置集群模式方式，如果组件需要支持多种方式安装，可以在该字段中定义，如只支持单个实例安装，可留空（类型：map[list[map,map...]])\ndeploy:\n  # 定义单实例模式安装 （类型：list[map,map...])\n  single:\n      # 部署方式的中文描述名称，该值会在表单中选择集群模式时显示 （类型：string）\n    - name: 单实例\n      # 该模式的key值 （类型：string）\n      key: single\n  # 定义多种集群模式安装 （类型：list[map,map...])\n  complex:\n      # 部署方式的中文描述名称，该值会在表单中选择集群模式时显示 （类型：string）\n    - name: 主从模式\n      # 该模式的key值 （类型：string）\n      key: master_slave\n      # 集群节点设置 （类型： map）\n      nodes:\n        # 初始节点数量 （类型：int）\n        start: 2\n        # 增加节点步长 （类型：int）\n        step: 1\n# 定义该组件安装所需依赖组件名称与版本,如不需其他组件依赖，可留空 （类型： list[map,map..])\n#例：\n#dependencies:\n#  - name: jdk\n#    version: 8u223\ndependencies:\n# 该组件所需最小资源需求 （类型：map)\nresources:\n  # cpu最小需求 ，1000m 表示 1核  （类型：string）\n  cpu: 1000m\n  # 内存最小需求， 500m 表示 500兆内存 （类型：string）\n  memory: 500m\n\n---\n# 定义安装组件时所需参数，该参数会传入到 安装脚本中 （类型：list[map,map...]）\ninstall:\n    # 传入参数中文描述名称，该名称会在用户安装组件时显示到表单中 （类型： string）\n  - name: \"安装目录\"\n    # 传入参数key值，会将该key与值 传入到安装脚本中 （类型：string）\n    key: base_dir\n    # 上面key默认值 （类型： stirng）\n    default: \"{data_path}/mysql\"  # 注： {data_path} 为主机数据目录占位符，请勿使用其他代替\n  - name: \"数据目录\"\n    key: data_dir\n    default: \"{data_path}/mysql/data\"\n  - name: \"日志目录\"\n    key: log_dir\n    default: \"{data_path}/mysql/log\"\n  - name: \"用户名\"\n    key: username\n    default: root\n  - name: \"密码\"\n    key: password\n    default: \"123456\"\n# 程序控制脚本与服务目录的相对路径 （类型：map）\ncontrol:\n  # 启动脚本路径，如没有可留空 （类型：string）\n  start: \"./scripts/mysql start\"\n  # 停止脚本路径，如没有可留空 （类型：stirng）\n  stop: \"./scripts/mysql stop\"\n  # 重启脚本路径，如没有可留空 （类型：stirng）\n  restart: \"./scripts/mysql restart\"\n  # 重载脚本路径，如没有可留空 （类型：stirng）\n  reload:\n  # 安装脚本路径，必填 （类型：stirng）\n  install: \"./scripts/install.py\"\n  # 初始化脚本路径，必填 （类型：stirng）\n  init:  \"./scripts/init.py\"\n```\n\n### 2.4. 安装脚本编写说明\n\n在安装包成功发布后，可通过平台进行安装，平台会调用配置文件中指定的安装脚本进行程序安装，平台将会把安装脚本所需参数以如下形式进行传参，需要脚本在编写时对此进行支持。\n\n传参示例：\n\n```shell\n$ python ./scripts/install.py --local_ip 192.168.1.2 --data_json /data/LKJD82JDL.json\n```\n\n其中 local_ip 为安装主机的IP地址，data_json为安装所需数据文件路径\n\n安装脚本需要根据data_json内数据进行组件的安装、替换其他文件内的占位符\n\ndata.json示例:\n\n```json\n[\n    {\n        \"name\":\"nacos\",\n        \"ip\":\"1.1.1.1\",\n        \"version\":\"2.0.1\",\n        \"ports\":[\n            {\n                \"key\":\"service_port\",\n                \"name\":\"xxx端口\",\n                \"default\":8001\n            }\n        ],\n        \"install_arg\":[\n            {\n                \"key\":\"base_dir\",\n                \"name\":\"服务目录\",\n                \"default\":\"/data/app/nacos\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"数据目录\",\n                \"default\":\"/data/appData/nacos\"\n            },\n            {\n                \"key\":\"username\",\n                \"name\":\"用户名\",\n                \"default\":\"admin\"\n            },\n            {\n                \"key\":\"password\",\n                \"name\":\"密码\",\n                \"default\":\"admin123\"\n            }\n        ],\n        \"deploy_mode\":{\n\n        },\n        \"cluster_name\":\"\",\n        \"instance_name\":\"nacos-1\",\n        \"dependence\":[\n            {\n                \"name\":\"mysql\",\n                \"instance_name\":\"mysql-100\",\n                \"cluster_name\":\"mysql-JDLK3KA\"\n            }\n        ]\n    },\n    {\n        \"name\":\"mysql\",\n        \"ip\":\"192.1.2.3\",\n        \"version\":\"5.0.1\",\n        \"ports\":[\n            {\n                \"key\":\"service_port\",\n                \"name\":\"服务端口\",\n                \"default\":10601\n            }\n        ],\n        \"install_arg\":[\n            {\n                \"key\":\"base_dir\",\n                \"name\":\"服务目录\",\n                \"default\":\"/data/app/mysql\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"数据目录\",\n                \"default\":\"/data/appData/mysql\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"日志目录\",\n                \"default\":\"/data/appData/log\"\n            },\n            {\n                \"key\":\"username\",\n                \"name\":\"用户名\",\n                \"default\":\"root\"\n            },\n            {\n                \"key\":\"password\",\n                \"name\":\"密码\",\n                \"default\":\"root123\"\n            }\n        ],\n        \"deploy_mode\":{\n\n        },\n        \"cluster_name\":\"\",\n        \"instance_name\":\"mysql-100\",\n        \"dependence\":[\n\n        ]\n    }\n]\n```\n\n### 2.5 打包好的应用Demo\n查看目录： omp/package_hub/back_end_verified\n```shell\nfilebeat-7.12.0-20220311171107-3e85bed.tar.gz\nhttpd-2.4.46-20220120145141-b1a6fa6.tar.gz\nkeepalived-2.1.5-20220120145217-8c6aaf7.tar.gz\npostgreSql-13.5.0-20220411212618-6fabab8.tar.gz\nredis-5.0.14-20220411212556-6eb6e28.tar.gz\nrocketmq-4.8.0-20220410214539-74feff6.tar.gz\n```\n\n\n## 3. 应用服务打包规范\n\n### 3.1. 目录规范\n\n在发布类别为应用服务的产品时，需要将产品名称、所属产品的服务名称、版本号做到全局统一\n\n**目录示例：**\n\n发布产品名称为“omp\",其中包含 3个服务为“omp_server\",\"omp_web\",\"omp_component\" 的目录结构如下\n\n```shell\n$ tree omp\nomp\n├── omp.svg # 定义产品图标，会在平台中展示，如果不创建则平台会展示缺省图标\n├── omp     # 定义产品下服务配置文件目录，将所需服务的配置文件存在该目录\n│   ├── omp_server.yaml  # 服务 omp_server 配置文件，文件名需要与服务名称一致\n│   ├── omp_web.yaml  # 服务 omp_web 配置文件，文件名需要与服务名称一致\n│   └── omp_component.yaml  # 服务 omp_agent 配置文件，文件名需要与服务名称一致\n├── omp_server-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_server 压缩包，以“-” 为分隔符，第一个为服务名称，需要与服务名称一致，格式为 {service_name}-{service_version}-{others}-{package_md5}.tar.gz\n├── omp_web-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_web 压缩包\n├── omp_component-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_agent 压缩包\n└── omp.yaml        # 定义产品配置文件，文件名需要与产品名称一致\n```\n\n其中服务目录以omp_server为例：\n\n```shell\n$ tree omp_server\nomp_server    # 服务包解压后目录名称，与服务名一致\n├── bin     # 服务控制脚本目录，启动、停止等\n│   └── omp_server    # 服务控制脚本，与服务名称一致\n├── omp_server.yaml    # 服务配置文件，与产品包中保持一致\n└── scripts            # 安装、升级脚本目录\n    ├── init.py        # 初始化脚本\n    ├── install.py     # 安装脚本\n    └── update.py      # 升级脚本\n```\n\n### 3.2. 压缩包命名规范\n\n请使用 `{name}-{version}-{others}-{package_md5}.tar.gz`  格式进行打包命名\n\n1. name:   安装包名称，建议字符:  `英文`  `数字`   `_`\n2. version: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n3. others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n4. package_md5:  安装包MD5 值\n\n例如： omp-0.1.0-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz\n\n### 3.3. 配置文件yaml说明\n\n发布类别为应用服务的产品时，需分别对 产品配置文件和产品下服务配置文件进行配置\n\n#### 3.3.1. 产品配置文件（yaml）格式说明\n\n```yaml\n# 类型定义，发布应用服务时,产品指定类型为 product （类型：string）\nkind: product\n# 定义产品名称，此名称需要与产品目录名称、产品配置文件名称保持一致，建议字符：英文、数字、_  （类型: string)\nname: omp\n# 上传后显示的产品版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion:\n# 产品描述信息，建议长度256字符之内，请针对产品书写贴切的描述文字 （类型：string）\ndescription: \"运维管理平台（OperationManagementPlatform，以下简称OMP）以管理服务为中心，为服务的安装、管理提供便捷可靠的方式。\"\n# 组件所属标签，请针对组件功能设置准确标签，平台会针对该标签对组件进行分类，（类型：list[string,string...]）\nlabels:\n  - omp\n# 定义该产品安装所需依赖产品名称与版本,如不需其他产品依赖，可留空 （类型： list[map,map..])\ndependencies:\n\n# 定义该产品下包含的服务信息，请确保列表中的服务包都包含在目录中，并且名称保持一致 （类型： list[map,map...])\nservice:\n    # 包含服务名称，请与服务包名保持一致 （类型： string）\n  - name: omp_server\n    # 服务版本，请与服务包版本一致 （类型：string）\n    version: 0.1.0\n  - name: omp_web\n    version: 0.1.0\n  - name: omp_component\n    version: 0.1.0\n```\n\n#### 3.3.2. 服务配置文件（yaml）格式说明\n\n```yaml\n# 类型定义，发布应用服务时,产品包含的服务指定类型为 service （类型：string）\nkind: service\n# 服务在平台显示的名称，请与服务目录名称保持一致，建议字符：英文、数字、_ （类型：string）\nname: omp_server\n# 上传后显示的服务版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion: 0.1.0\n# 服务描述信息，建议长度256字符之内，请针对组件书写贴切的描述文字 （类型：string）\ndescription: \"服务描述内容...\"\n# 指定该服务安装后是否需要启动 （类型：boolean)\nauto_launch: true\n# 指定服务是否为基础环境组件，如 jdk, 该类组件以基础环境方式安装 （类型：boolean)\nbase_env: flase\n# 定义服务所需端口号，如不启用端口，可留空 （类型：list[map,map...])\nports:\n    # 端口描述名称，用户在安装时会以该名称显示表单内容（类型：string)\n  - name: 服务端口\n    # 端口协议，支持 TCP/UDP\n    protocol: TCP\n    # 端口英文描述名称，该key会传入到安装脚本中 （类型： string）支持（英文、数字、_)\n    key: service_port  # 注：service_port 为保留关键词，表示 为 提供服务的端口\n    # 组件的默认端口号，在安装时，会以该值填入表单中（类型： int）\n    default: 19001\n# 服务监控相关配置，定义该服务在安装后如何监控 ，如果不需要监控可留空 （类型： map）\nmonitor:\n  # 监控进程名称，如“service_a”，平台在发现service_a进程不存在后，会发送告警提醒,不需要监控可留空（类型：string）\n  process_name:  \"\"\n  # 监控端口号，请根据 ports 中的变量设置，不需要监控可留空 （类型： {string}）\n  metric_port: {service_port}\n---\n# 定义该组件安装所需依赖组件名称与版本,如不需其他组件依赖，可留空 （类型： list[map,map..])\ndependencies:\n  - name: mysql\n    version: 5.7.31\n  - name: redis\n    version: 5.0.1\n  - name: python\n    version: 3.8.3\n# 该组件所需最小资源需求 （类型：map)\nresources:\n  # cpu最小需求 ，1000m 表示 1核  （类型：string）\n  cpu: 1000m\n  # 内存最小需求， 500m 表示 500兆内存 （类型：string）\n  memory: 500m\n---\n# 定义安装组件时所需参数，该参数会传入到 安装脚本中 （类型：list[map,map...]）\ninstall:\n    # 传入参数中文描述名称，该名称会在用户安装组件时显示到表单中 （类型： string）\n  - name: \"安装目录\"\n    # 传入参数key值，会将该key与值 传入到安装脚本中 （类型：string）\n    key: base_dir\n    # 上面key默认值 （类型： stirng）\n    default: \"{data_path}/omp_server\"  # 注： {data_path} 为主机数据目录占位符，请勿使用其他代替\n#  - name: \"JVM设置\"\n#    key: jvm\n#    default: \"-XX:MaxPermSize=512m -Djava.awt.headless=true\"\n# 程序控制脚本与服务目录的相对路径 （类型：map）\ncontrol:\n  # 启动脚本路径，如没有可留空,脚本名称建议与服务名称一致  （类型：string）\n  start: \"./bin/omp_server start\"\n  # 停止脚本路径，如没有可留空，脚本名称建议与服务名称一致 （类型：stirng）\n  stop: \"./bin/omp_server stop\"\n  # 重启脚本路径，如没有可留空，脚本名称建议与服务名称一致 （类型：stirng）\n  restart: \"./bin/omp_server restart\"\n  # 重载脚本路径，如没有可留空 （类型：stirng）\n  reload:\n  # 安装脚本路径，必填 （类型：stirng）\n  install: \"./scripts/install.py\"\n  # 初始化脚本路径，必填 （类型：stirng）\n  init:  \"./scripts/init.py\"\n```\n"
  },
  {
    "path": "doc/changelogs.md",
    "content": "## 更新日志\n\nV0.1.0 (2021.11.30)\n\n- 新增功能:\n  【仪表盘】\n  - 全局状态概览\n  - 当前异常信息展示\n  - 各模块状态展示\n  【主机管理】\n  - 主机纳管（添加、导入、编辑、维护、删除）\n  - 主机自动监控、告警\n  【应用商店】\n  - 组件、应用WEB发布、服务端自动发现\n  - 组件、应用部署，自动编排解决依赖\n  【服务管理】\n  - 服务管理（启动、停止、重启、删除）\n  - 服务监控（监控、日志、告警、自愈）\n  【应用监控】\n  - 实时展示处于异常的主机、服务信息，呼应仪表盘的异常清单\n  - 告警历史记录查看，未读提醒，按添加检索\n  - 支持监控组件地址自定义，便于对接现有监控平台\n  【状态巡检】\n  - 支持主机巡检、组件巡检、深度分析，且支持导出\n  - 支持定时自动执行巡检任务\n  【 系统管理】\n  - 用户账户管理\n  - 支持全局维护模式，避免人为操作时误报"
  },
  {
    "path": "logs/.gitkeep",
    "content": ""
  },
  {
    "path": "omp_server/app_store/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/app_store/admin.py",
    "content": "# from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/app_store/app_store_filters.py",
    "content": "\"\"\"\n应用商店相关过滤器\n\"\"\"\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\n\nfrom db_models.models import (\n    Labels, ApplicationHub, ProductHub, UploadPackageHistory,\n    MainInstallHistory, Service\n)\n\n\nclass LabelFilter(FilterSet):\n    \"\"\" 标签过滤类 \"\"\"\n    label_type = django_filters.CharFilter(\n        help_text=\"标签类型: 0-组件 1-应用\", field_name=\"label_type\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = Labels\n        fields = (\"label_type\",)\n\n\nclass ComponentFilter(FilterSet):\n    \"\"\" 基础组件过滤类 \"\"\"\n    app_name = django_filters.CharFilter(\n        help_text=\"基础组件名称，模糊匹配\", field_name=\"app_name\", lookup_expr=\"icontains\")\n    type = django_filters.CharFilter(\n        help_text=\"类型名称\", field_name=\"app_labels__label_name\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = ApplicationHub\n        fields = (\"app_name\", \"type\")\n\n\nclass ServiceFilter(FilterSet):\n    \"\"\" 应用服务过滤器类 \"\"\"\n    pro_name = django_filters.CharFilter(\n        help_text=\"应用服务名称，模糊匹配\", field_name=\"pro_name\", lookup_expr=\"icontains\")\n    type = django_filters.CharFilter(\n        help_text=\"类型名称\", field_name=\"pro_labels__label_name\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = ProductHub\n        fields = (\"pro_name\", \"type\")\n\n\nclass UploadPackageHistoryFilter(FilterSet):\n    \"\"\" 发布-安装包校验结果接口 \"\"\"\n    operation_uuid = django_filters.CharFilter(\n        help_text=\"operation_uuid，查询\", field_name=\"operation_uuid\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = UploadPackageHistory\n        fields = (\"operation_uuid\",)\n\n\nclass PublishPackageHistoryFilter(FilterSet):\n    \"\"\" 发布-安装包校验结果接口 \"\"\"\n    operation_uuid = django_filters.CharFilter(\n        help_text=\"operation_uuid，查询\", field_name=\"operation_uuid\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = UploadPackageHistory\n        fields = (\"operation_uuid\",)\n\n\nclass ComponentEntranceFilter(FilterSet):\n    \"\"\" 基础组件安装入口过滤类 \"\"\"\n    app_name = django_filters.CharFilter(\n        help_text=\"基础组件名称，精确匹配\",\n        field_name=\"app_name\", lookup_expr=\"exact\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = (\"app_name\", )\n\n\nclass ProductEntranceFilter(FilterSet):\n    \"\"\" 基础组件安装入口过滤类 \"\"\"\n    pro_name = django_filters.CharFilter(\n        help_text=\"基础组件名称，精确匹配\",\n        field_name=\"pro_name\", lookup_expr=\"exact\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ProductHub\n        fields = (\"pro_name\", )\n\n\nclass InstallHistoryFilter(FilterSet):\n    \"\"\" 基础组件安装入口过滤类 \"\"\"\n    operation_uuid = django_filters.CharFilter(\n        help_text=\"唯一操作的uuid\",\n        field_name=\"operation_uuid\", lookup_expr=\"exact\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = MainInstallHistory\n        fields = (\"operation_uuid\", )\n\n\nclass ServiceInstallHistoryFilter(FilterSet):\n    \"\"\" 基础组件安装入口过滤类 \"\"\"\n    id = django_filters.NumberFilter(\n        help_text=\"服务id\",\n        field_name=\"id\", lookup_expr=\"exact\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\"id\", )\n"
  },
  {
    "path": "omp_server/app_store/app_store_serializers.py",
    "content": "\"\"\"\n应用商店\n\"\"\"\nimport json\nimport logging\nimport os\nimport time\nfrom django.conf import settings\n\nfrom rest_framework import serializers\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.serializers import Serializer\nfrom utils.common.exceptions import OperateError\nfrom utils.plugin.public_utils import check_is_ip_address, timedelta_strftime\nfrom app_store.tmp_exec_back_task import front_end_verified_init\n\nfrom db_models.models import (\n    ApplicationHub, ProductHub, UploadPackageHistory,\n    Service, DetailInstallHistory, MainInstallHistory, Product,\n    DeploymentPlan, ExecutionRecord)\nfrom db_models import models\n\nfrom app_store.install_utils import (\n    make_lst_unique, ServiceArgsSerializer,\n    SerDependenceParseUtils, ProDependenceParseUtils,\n    ValidateExistService, ValidateInstallService,\n    CreateInstallPlan\n)\nfrom utils.parse_config import HADOOP_ROLE\n\nlogger = logging.getLogger(\"server\")\n\n\nclass ComponentListSerializer(ModelSerializer):\n    \"\"\" 组件列表序列化器 \"\"\"\n    instance_number = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = (\"app_name\", \"app_version\", \"app_logo\",\n                  \"app_description\", \"instance_number\")\n\n    def get_instance_number(self, obj):\n        \"\"\" 获取组件已安装实例数量 \"\"\"\n        return Service.objects.filter(\n            service__app_name=obj.app_name).count()\n\n\nclass ServiceListSerializer(ModelSerializer):\n    \"\"\" 服务列表序列化器 \"\"\"\n    instance_number = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ProductHub\n        fields = (\"pro_name\", \"pro_version\", \"pro_logo\",\n                  \"pro_description\", \"instance_number\")\n\n    def get_instance_number(self, obj):\n        \"\"\" 获取组件已安装实例数量 \"\"\"\n        # return Service.objects.filter(\n        #     service__product__pro_name=obj.pro_name).count()\n        return Product.objects.filter(product=obj).count()\n\n\nclass UploadPackageSerializer(Serializer):\n    \"\"\"上传安装包序列化类\"\"\"\n\n    uuid = serializers.CharField(\n        help_text=\"上传安装包uuid\",\n        required=True,\n        error_messages={\"required\": \"必须包含[uuid]字段\"}\n    )\n    operation_user = serializers.CharField(\n        help_text=\"操作用户\",\n        required=True,\n        error_messages={\"required\": \"必须包含[operation_user]字段\"}\n    )\n    file = serializers.FileField(\n        help_text=\"上传的文件\",\n        required=True,\n        error_messages={\"required\": \"必须包含[file]字段\"}\n    )\n    md5 = serializers.CharField(\n        help_text=\"文件包的md5值\",\n        required=True,\n        error_messages={\"required\": \"必须包含[md5]字段\"}\n    )\n\n    def validate(self, attrs):\n        file = attrs.get(\"file\")\n        file_name = file.name\n        file_size = file.size\n        if not file_name.endswith('.tar') and not file_name.endswith('tar.gz'):\n            raise ValidationError({\n                \"file_name\": \"上传文件名仅支持.tar或.tar.gz\"\n            })\n        # 文件大小超过4G不支持\n        if file_size > 4294967296:\n            raise ValidationError({\n                \"file_size\": \"上传文件大小超过4G\"\n            })\n        return attrs\n\n    def create(self, validated_data):\n        uuid = validated_data.get(\"uuid\")\n        operation_user = validated_data.get(\"operation_user\")\n        request_file = validated_data.get(\"file\")\n        md5 = validated_data.get(\"md5\")\n        package_name = request_file.name\n        if not request_file:\n            raise OperateError(\"上传文件为空\")\n        destination_dir = os.path.join(\n            settings.PROJECT_DIR, 'package_hub/front_end_verified')\n        upload_obj = UploadPackageHistory(\n            operation_uuid=uuid,\n            operation_user=operation_user,\n            package_name=package_name,\n            package_md5=md5,\n            package_path=\"verified\")\n        upload_obj.save()\n        with open(os.path.join(destination_dir, request_file.name),\n                  'wb+') as f:\n            for chunk in request_file.chunks():\n                try:\n                    f.write(chunk)\n                except Exception:\n                    upload_obj.delete()\n                    raise OperateError(\"文件写入过程失败\")\n\n        front_end_verified_init(uuid, operation_user,\n                                package_name, upload_obj.id, md5)\n        return validated_data\n\n\nclass RemovePackageSerializer(Serializer):\n    \"\"\" 移除安装包序列化类 \"\"\"\n\n    uuid = serializers.CharField(\n        help_text=\"上传安装包uuid\",\n        required=True,\n        error_messages={\"required\": \"必须包含[uuid]字段\"}\n    )\n\n    package_names = serializers.ListField(\n        child=serializers.CharField(),\n        help_text=\"安装包名称列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[package_names]字段\"}\n    )\n\n    def validate(self, attrs):\n        \"\"\" 校验安装包名称 \"\"\"\n        operation_uuid = attrs.get(\"uuid\")\n        package_names = attrs.get(\"package_names\")\n        queryset = UploadPackageHistory.objects.filter(\n            operation_uuid=operation_uuid,\n            package_name__in=package_names,\n            package_parent__isnull=True,\n            is_deleted=False\n        )\n        if not queryset.exists() or \\\n                len(queryset) != len(package_names):\n            logger.error(f\"remove package error: uuid-{operation_uuid},\"\n                         f\"package_names-{package_names}\")\n            raise ValidationError({\"uuid\": \"该 uuid 未找到有效的操作记录\"})\n        attrs[\"queryset\"] = queryset\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 上传安装包记录表软删除 \"\"\"\n        queryset = validated_data.pop(\"queryset\", None)\n        if queryset is not None:\n            queryset.update(is_deleted=True)\n        return validated_data\n\n\nclass ApplicationDetailSerializer(ModelSerializer):  # NOQA\n    \"\"\" 组件详情序列化器 \"\"\"\n    app_instances_info = serializers.SerializerMethodField()\n    app_labels = serializers.SerializerMethodField()\n    app_package_md5 = serializers.SerializerMethodField()\n    app_operation_user = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = (\"app_name\", \"app_version\", \"app_logo\", \"app_description\",\n                  \"created\", \"app_dependence\", \"app_instances_info\",\n                  \"app_labels\", \"app_package_md5\", \"app_operation_user\")\n\n    def get_app_instances_info(self, obj):  # NOQA\n        \"\"\" 获取服务安装实例信息 \"\"\"\n        service_objs = Service.objects.filter(service__id=obj.id)\n        service_list = []\n        for so in service_objs:\n            service_dict = {\n                \"instance_name\": so.service_instance_name,\n                \"host_ip\": so.ip,\n                \"service_port\": None if not so.service_port else json.loads(so.service_port),\n                \"app_version\": so.service.app_version,\n                \"mode\": \"单实例\",  # TODO  后续根据cluster字段是否为空来判断是单实例还是集群模式\n                \"created\": so.created\n            }\n            service_list.append(service_dict)\n        return service_list\n\n    def get_app_labels(self, obj):  # NOQA\n        return list(obj.app_labels.all().values_list('label_name', flat=True))\n\n    def get_app_package_md5(self, obj):  # NOQA\n        md5 = \"-\"\n        if obj.app_package is not None:\n            md5 = obj.app_package.package_md5\n        return md5\n\n    def get_app_operation_user(self, obj):  # NOQA\n        return obj.app_package.operation_user\n\n\nclass ProductDetailSerializer(ModelSerializer):  # NOQA\n    \"\"\" 产品详情序列化器 \"\"\"\n\n    pro_instances_info = serializers.SerializerMethodField()\n    pro_labels = serializers.SerializerMethodField()\n    pro_package_md5 = serializers.SerializerMethodField()\n    pro_operation_user = serializers.SerializerMethodField()\n    pro_services = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ProductHub\n        fields = (\"pro_name\", \"pro_version\", \"pro_logo\", \"pro_description\",\n                  \"created\", \"pro_dependence\", \"pro_services\",\n                  \"pro_instances_info\",\n                  \"pro_labels\", \"pro_package_md5\", \"pro_operation_user\")\n\n    def get_pro_instances_info(self, obj):  # NOQA\n        \"\"\" 获取服务安装实例信息 \"\"\"\n        service_objs = Service.objects.filter(\n            service__product__id=obj.id)\n        service_list = []\n        for so in service_objs:\n            service_dict = {\n                \"instance_name\": so.service_instance_name,\n                \"version\": so.service.product.pro_version,\n                \"app_name\": so.service.app_name,\n                \"app_version\": so.service.app_version,\n                \"host_ip\": so.ip,\n                \"service_port\": None if not so.service_port else json.loads(so.service_port),\n                \"created\": so.created\n            }\n            service_list.append(service_dict)\n        return service_list\n\n    def get_pro_labels(self, obj):  # NOQA\n        return list(obj.pro_labels.all().values_list('label_name', flat=True))\n\n    def get_pro_package_md5(self, obj):  # NOQA\n        md5 = \"-\"\n        if obj.pro_package is not None:\n            md5 = obj.pro_package.package_md5\n        return md5\n\n    def get_pro_operation_user(self, obj):  # NOQA\n        try:\n            return obj.pro_package.operation_user\n        except Exception as e:\n            logger.error(e)\n            logger.error(\"获取服务包user值失败！\")\n\n    def get_pro_services(self, obj):  # NOQA\n        pro_services_list = []\n        apps = ApplicationHub.objects.filter(product_id=obj.id)\n        if not apps:\n            if not obj.pro_services:\n                return pro_services_list\n            pro_services_list.extend(json.loads(obj.pro_services))\n            return pro_services_list\n        pro_app_name_list = []\n        for app in apps:\n            uph = UploadPackageHistory.objects.get(id=app.app_package_id)\n            if not uph:\n                continue\n            app_dict = {\n                \"name\": app.app_name,\n                \"version\": app.app_version,\n                \"created\": time.strftime(\"%Y-%m-%d %H:%M:%S\", time.strptime(str(app.created), \"%Y-%m-%d %H:%M:%S.%f\")),\n                \"md5\": uph.package_md5\n            }\n            pro_services_list.append(app_dict)\n            pro_app_name_list.append(app.app_name)\n        for ps in json.loads(obj.pro_services):\n            if ps.get(\"name\") in pro_app_name_list:\n                continue\n            pro_services_list.append(ps)\n        return pro_services_list\n\n\nclass UploadPackageHistorySerializer(serializers.ModelSerializer):\n    \"\"\" 操作记录序列化类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = UploadPackageHistory\n        fields = [\"package_name\", \"package_status\",\n                  \"error_msg\", \"operation_uuid\"]\n\n\nclass PublishPackageHistorySerializer(serializers.ModelSerializer):\n    \"\"\" 操作记录序列化类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = UploadPackageHistory\n        fields = [\"package_name\", \"package_status\",\n                  \"error_msg\", \"operation_uuid\"]\n\n\nclass ExecuteLocalPackageScanSerializer(Serializer):\n    \"\"\" 本地安装包扫描执行序列化类 \"\"\"\n    pass\n\n\nclass ComponentEntranceSerializer(serializers.ModelSerializer):\n    \"\"\" 组件安装入口数据序列化 \"\"\"\n\n    app_port = serializers.SerializerMethodField()\n    app_dependence = serializers.SerializerMethodField()\n    app_install_args = serializers.SerializerMethodField()\n    deploy_mode = serializers.SerializerMethodField()\n    process_continue = serializers.SerializerMethodField()\n    process_message = serializers.SerializerMethodField()\n\n    def get_app_port(self, obj):  # NOQA\n        \"\"\" 获取服务端口 \"\"\"\n        return ServiceArgsSerializer().get_app_port(obj)\n\n    def get_app_dependence(self, obj):  # NOQA\n        \"\"\" 解析服务级别的依赖关系 \"\"\"\n        return ServiceArgsSerializer().get_app_dependence(obj)\n\n    def get_app_install_args(self, obj):  # NOQA\n        \"\"\" 解析服务安装过程中的参数 \"\"\"\n        return ServiceArgsSerializer().get_app_install_args(obj)\n\n    def get_deploy_mode(self, obj):  # NOQA\n        \"\"\" 解析服务的部署模式 \"\"\"\n        return ServiceArgsSerializer().get_deploy_mode(obj)\n\n    def get_process_continue(self, obj):  # NOQA\n        \"\"\" 服务能否安装的接口 \"\"\"\n        return ServiceArgsSerializer().get_process_continue(obj)\n\n    def get_process_message(self, obj):  # NOQA\n        return ServiceArgsSerializer().get_process_message(obj)\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = [\n            \"app_name\", \"app_version\", \"app_dependence\", \"app_port\",\n            \"app_install_args\", \"deploy_mode\", \"process_continue\",\n            \"process_message\"\n        ]\n\n\nclass ProductEntranceSerializer(serializers.ModelSerializer):\n    \"\"\" 产品、应用安装序列化类 \"\"\"\n\n    pro_services = serializers.SerializerMethodField()\n    pro_dependence = serializers.SerializerMethodField()\n    dependence_services_info = serializers.SerializerMethodField()\n\n    def get_pro_services(self, obj):  # NOQA\n        \"\"\" 获取服务列表 \"\"\"\n        if not obj.pro_services:\n            return list()\n        ser_lst = json.loads(obj.pro_services)\n        for item in ser_lst:\n            ser_obj = ApplicationHub.objects.filter(\n                app_name=item.get(\"name\"),\n                app_version=item.get(\"version\")\n            ).last()\n            if not ser_obj:\n                item[\"process_continue\"] = False\n                item[\"process_message\"] = f\"服务{item.get('name')}未发布\"\n                continue\n            item[\"app_port\"] = ServiceArgsSerializer().get_app_port(ser_obj)\n            item[\"process_continue\"] = True\n            item[\"app_install_args\"] = \\\n                ServiceArgsSerializer().get_app_install_args(ser_obj)\n            item[\"deploy_mode\"] = \\\n                ServiceArgsSerializer().get_deploy_mode(ser_obj)\n            item[\"app_dependence\"] = \\\n                ServiceArgsSerializer().get_app_dependence(ser_obj)\n        return ser_lst\n\n    def get_pro_dependence(self, obj):  # NOQA\n        \"\"\" 获取产品依赖关系 \"\"\"\n        _pro = ProDependenceParseUtils(obj.pro_name, obj.pro_version)\n        _dep = _pro.run_pro()\n        return _dep\n\n    def get_dependence_services_info(self, obj):  # NOQA\n        \"\"\" 获取服务所依赖的信息 \"\"\"\n        _service_lst = self.get_pro_services(obj=obj)\n        if not _service_lst:\n            return []\n        _all_dependence_ser_info = list()\n        for item in _service_lst:\n            _ser = SerDependenceParseUtils(\n                item.get(\"name\"), item.get(\"version\"))\n            _el_lst = _ser.run_ser()\n            _all_dependence_ser_info.extend(_el_lst)\n        _all_dependence_ser_info = make_lst_unique(\n            _all_dependence_ser_info, \"name\", \"version\")\n        return _all_dependence_ser_info\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ProductHub\n        fields = [\n            \"pro_name\", \"pro_version\", \"pro_dependence\",\n            \"pro_services\", \"dependence_services_info\"\n        ]\n\n\nclass ExecuteInstallSerializer(Serializer):\n    \"\"\"\n        执行安装时需要解析前端上传的数据的准确性，服务间的关联依赖关系\n        目标服务器上实际安装的数据信息等内容\n    \"\"\"\n\n    INSTALL_COMPONENT = 0\n    INSTALL_PRODUCT = 1\n    INSTALL_TYPE_CHOICES = (\n        (INSTALL_COMPONENT, \"组件安装\"),\n        (INSTALL_PRODUCT, \"产品安装\")\n    )\n    install_type = serializers.ChoiceField(\n        choices=INSTALL_TYPE_CHOICES,\n        help_text=\"选择安装方式: 0-组件; 1-应用\",\n        required=True, allow_null=False, allow_blank=False,\n        error_messages={\"required\": \"必须包含[install_type]字段\"}\n    )\n    use_exist_services = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"复用已安装的服务列表，eg: [{'name': 'ser1', 'id': 1}]\",\n        required=True, allow_empty=True,\n        error_messages={\"required\": \"必须包含[use_exist_services]字段\"}\n    )\n    install_services = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"需要安装的服务列表，eg: [{'name': 'ser1', 'version': 1}]\",\n        required=True, allow_empty=False,\n        error_messages={\n            \"required\": \"必须包含[install_services]字段\",\n            \"empty\": \"必须包含将要安装的服务信息\"\n        }\n    )\n    is_valid_flag = serializers.BooleanField(\n        read_only=True, required=False,\n        help_text=\"数据准确性校验返回标志\"\n    )\n    is_valid_msg = serializers.CharField(\n        read_only=True, required=False, max_length=4096,\n        help_text=\"数据准确性校验结果信息\"\n    )\n    operation_uuid = serializers.CharField(\n        read_only=True, required=False, max_length=128,\n        help_text=\"成功下发部署计划后返回的uuid\"\n    )\n\n    def validate_use_exist_services(self, data):  # NOQA\n        \"\"\"\n        校验已经存在的服务是否准确\n        :param data:\n        :return:\n        \"\"\"\n        if not data:\n            return data\n        return ValidateExistService(data=data).run()\n\n    def validate_install_services(self, data):  # NOQA\n        \"\"\"\n        校验即将安装的服务及参数\n        :param data:\n        :return:\n        \"\"\"\n        return ValidateInstallService(data=data).run()\n\n    def check_lst_valid(self, lst):  # NOQA\n        \"\"\"\n        根据列表、字典格式确定安装参数是否符合要求\n        :param lst:\n        :return:\n        \"\"\"\n        for el in lst:\n            if not isinstance(el, dict):\n                return False\n            if \"check_flag\" in el and not el[\"check_flag\"]:\n                return False\n        return True\n\n    def validate(self, attrs):\n        \"\"\"\n        安装校验最终要执行的方法，根据安装参数解析结果决定如下操作：\n            安装参数解析成功：调用安装参数入库方法\n            安装参数解析失败：直接返回相关安装参数\n        :param attrs:\n        :return:\n        \"\"\"\n        valid_lst = list()\n        use_exist_services = attrs.get(\"use_exist_services\", [])\n        valid_lst.append(self.check_lst_valid(use_exist_services))\n        install_services = attrs.get(\"install_services\", [])\n        valid_lst.append(self.check_lst_valid(install_services))\n        for item in install_services:\n            app_install_args = item.get(\"app_install_args\", [])\n            valid_lst.append(self.check_lst_valid(app_install_args))\n            app_port = item.get(\"app_port\", [])\n            valid_lst.append(self.check_lst_valid(app_port))\n        logger.info(f\"Check install info res: {valid_lst}\")\n        if len(set(valid_lst)) != 1 or valid_lst[0] is False:\n            attrs[\"is_valid_flag\"] = False\n            attrs[\"is_valid_msg\"] = \"数据校验出错\"\n            return attrs\n        # 数据入库逻辑\n        _create_data_obj = CreateInstallPlan(install_data=attrs)\n        flag, msg = _create_data_obj.run()\n        if not flag:\n            attrs[\"is_valid_flag\"] = False\n            attrs[\"is_valid_msg\"] = msg\n            return attrs\n        attrs[\"is_valid_flag\"] = True\n        attrs[\"is_valid_msg\"] = \"\"\n        attrs[\"operation_uuid\"] = msg\n        return attrs\n\n\nclass InstallHistorySerializer(ModelSerializer):\n    \"\"\" 安装历史记录序列化类 \"\"\"\n    install_status_msg = serializers.CharField(\n        source=\"get_install_status_display\")\n    detail_lst = serializers.SerializerMethodField()\n\n    def parse_single_obj(self, obj):  # NOQA\n        \"\"\"\n        解析单个服务安装记录信息\n        :param obj:\n        :type obj: DetailInstallHistory\n        :return:\n        \"\"\"\n        _status = obj.install_step_status\n        # 拼接日志\n        _log = \"\"\n        if obj.send_flag != 0 and obj.send_msg:\n            _log += obj.send_msg\n        if obj.unzip_flag != 0 and obj.unzip_msg:\n            _log += obj.unzip_msg\n        if obj.install_flag != 0 and obj.install_msg:\n            _log += obj.install_msg\n        if obj.init_flag != 0 and obj.init_msg:\n            _log += obj.init_msg\n        if obj.start_flag != 0 and obj.start_msg:\n            _log += obj.start_msg\n        return {\n            \"ip\": obj.service.ip,\n            \"status\": _status,\n            \"log\": _log,\n            \"service_name\": obj.service.service.app_name,\n            \"service_instance_name\": obj.service.service_instance_name\n        }\n\n    def get_detail_lst(self, obj):  # NOQA\n        \"\"\"\n        获取安装细节表\n        :param obj:\n        :return:\n        \"\"\"\n        lst = DetailInstallHistory.objects.filter(\n            main_install_history=obj\n        )\n        return [self.parse_single_obj(el) for el in lst]\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = MainInstallHistory\n        fields = (\n            \"operation_uuid\", \"install_status\", \"install_status_msg\",\n            \"install_args\", \"install_log\", \"detail_lst\"\n        )\n\n\nclass ServiceInstallHistorySerializer(ModelSerializer):\n    \"\"\" 安装历史记录序列化类 \"\"\"\n    install_step_status = serializers.SerializerMethodField()\n    log = serializers.SerializerMethodField()\n\n    def get_install_step_status(self, obj):\n        \"\"\"\n        获取服务安装状态\n        :param obj:\n        :return:\n        \"\"\"\n        detail_obj = DetailInstallHistory.objects.filter(service=obj).last()\n        return detail_obj.install_step_status\n\n    def get_log(self, obj):\n        \"\"\"\n        获取服务日志信息\n        :param obj:\n        :return:\n        \"\"\"\n        detail_obj = DetailInstallHistory.objects.filter(service=obj).last()\n        _log = \"\"\n        if detail_obj.send_flag != 0 and detail_obj.send_msg:\n            _log += detail_obj.send_msg\n        if detail_obj.unzip_flag != 0 and detail_obj.unzip_msg:\n            _log += detail_obj.unzip_msg\n        if detail_obj.install_flag != 0 and detail_obj.install_msg:\n            _log += detail_obj.install_msg\n        if detail_obj.init_flag != 0 and detail_obj.init_msg:\n            _log += detail_obj.init_msg\n        if detail_obj.start_flag != 0 and detail_obj.start_msg:\n            _log += detail_obj.start_msg\n        return _log\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\n            \"install_step_status\", \"log\"\n        )\n\n\nclass DeploymentPlanValidateSerializer(Serializer):\n    \"\"\" 部署计划服务信息验证序列化类 \"\"\"\n\n    instance_name_ls = serializers.ListField(\n        child=serializers.CharField(),\n        help_text=\"主机实例名列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[instance_name_ls]字段\"}\n    )\n\n    service_data_ls = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"服务数据列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[host_list]字段\"}\n    )\n\n    def validate(self, attrs):\n        \"\"\" 校验主机数据列表 \"\"\"\n        instance_name_ls = attrs.get(\"instance_name_ls\")\n        service_data_ls = attrs.get(\"service_data_ls\")\n        result_dict = {\n            \"correct\": [],\n            \"error\": []\n        }\n        logger.info(\"deployment plan validate start\")\n\n        # 查询所有 application 信息\n        service_name_ls = list(\n            map(lambda x: x.get(\"service_name\"), service_data_ls))\n        _queryset = ApplicationHub.objects.filter(\n            app_name__in=service_name_ls, is_release=True)\n        # 所有 application 默认取最新\n        new_app_id_list = []\n        for app in _queryset:\n            new_version = _queryset.filter(\n                app_name=app.app_name\n            ).order_by(\"-created\").first().app_version\n            if new_version == app.app_version:\n                new_app_id_list.append(app.id)\n        app_queryset = _queryset.filter(\n            id__in=new_app_id_list, is_release=True\n        ).select_related(\"product\")\n\n        # 获取 application 对应的 product 信息\n        app_now = app_queryset.exclude(product__isnull=True)\n        pro_id_list = app_now.values_list(\"product_id\", flat=True).distinct()\n        # 验证 product 的依赖项均已包含\n        pro_queryset = ProductHub.objects.filter(id__in=pro_id_list)\n        app_target_all = ApplicationHub.objects.filter(\n            product_id__in=pro_id_list)\n        # 考虑到同产品下会有同名服务情况，做去重处理，按照时间版本号取最新\n        app_target = []\n        for pro in pro_queryset:\n            for ser in json.loads(pro.pro_services):\n                app_target.append(\n                    app_target_all.filter(\n                        app_name=ser[\"name\"], app_version=ser[\"version\"]\n                    ).last()\n                )\n            # 无依赖项则跳过\n            if not pro.pro_dependence:\n                continue\n            dependence_list = json.loads(pro.pro_dependence)\n            # 校验依赖项的指定版本是否存在\n            for dependence in dependence_list:\n                name = dependence.get(\"name\")\n                # version = dependence.get(\"version\")\n                pro_obj = pro_queryset.filter(\n                    pro_name=name).order_by(\"-created\").first()\n                # if not pro_obj or not pro_obj.pro_version.startswith(version):\n                if not pro_obj:\n                    result_dict[\"error\"].append({\n                        \"row\": -2,\n                        \"instance_name\": \"待补充\",\n                        \"service_name\": \"待补充\",\n                        \"validate_error\": f\"产品 {pro.pro_name}-{pro.pro_version} \"\n                                          f\"缺失依赖产品 {name}\"\n                    })\n\n        # 验证所有 product 下的 application 都已经包含\n        app_target_all = ApplicationHub.objects.filter(\n            product_id__in=pro_id_list)\n\n        # 所有 affinity 为 tengine 字段 (Web 服务)，不参与比较\n        now_set = set(filter(\n            lambda x: x.extend_fields.get(\"affinity\") != \"tengine\", app_now))\n        target_set = set(filter(\n            lambda x: x.extend_fields.get(\"affinity\") != \"tengine\", app_target))\n        diff_set = target_set - now_set\n\n        # 存在遗漏的 application\n        if diff_set:\n            for app in diff_set:\n                result_dict[\"error\"].append({\n                    \"row\": -1,\n                    \"instance_name\": \"待补充\",\n                    \"service_name\": f\"{app.app_name}\",\n                    \"validate_error\": f\"产品 {app.product.pro_name} \"\n                                      f\"缺失依赖服务 {app.app_name}\"\n                })\n\n        # 验证所有 application 的依赖项均已包含\n        base_env_queryset = ApplicationHub.objects.filter(is_base_env=True)\n        for app in app_queryset:\n            if not app.app_dependence:\n                continue\n            dependence_list = json.loads(app.app_dependence)\n            # 校验依赖项的指定版本是否存在\n            for dependence in dependence_list:\n                name = dependence.get(\"name\")\n                # version = dependence.get(\"version\")\n                app_obj = app_queryset.filter(\n                    app_name=name).order_by(\"-created\").first()\n                # if not app_obj or not app_obj.app_version.startswith(version):\n                if not app_obj:\n                    # 如果为 base_env 则跳过\n                    # if base_env_queryset.filter(\n                    #         app_name=name, app_version=version\n                    # ).exists():\n                    if base_env_queryset.filter(app_name=name).exists():\n                        continue\n                    result_dict[\"error\"].append({\n                        \"row\": -2,\n                        \"instance_name\": \"待补充\",\n                        \"service_name\": f\"{name}\",\n                        \"validate_error\": f\"服务 {app.app_name}-{app.app_version} \"\n                                          f\"缺失依赖服务 {name}\"\n                    })\n\n        # hadoop 实例列表、角色集合\n        hadoop_instance_ls = []\n        hadoop_role_set = set()\n\n        for service_data in service_data_ls:\n            # 校验主机数据是否已经存在\n            if service_data.get(\"instance_name\") not in instance_name_ls:\n                service_data[\"validate_error\"] = \"主机不在表格中\"\n                result_dict[\"error\"].append(service_data)\n                continue\n            # 校验服务是否存在\n            app_name = service_data.get(\"service_name\", \"unKnow\")\n            if not app_queryset.filter(\n                    app_name=app_name\n            ).order_by(\"-created\").exists():\n                service_data[\"validate_error\"] = f\"{app_name}服务不在应用商店中\"\n                result_dict[\"error\"].append(service_data)\n                continue\n            # 如果含 vip 字段，校验是否为 IP 格式\n            if service_data.get(\"vip\"):\n                is_valid, _ = check_is_ip_address(\n                    service_data.get(\"vip\"))\n                if not is_valid:\n                    service_data[\"validate_error\"] = \"虚拟IP不合法\"\n                    result_dict[\"error\"].append(service_data)\n                    continue\n            # 如果含 role 字段，校验是否含中文逗号\n            if service_data.get(\"role\") and \\\n                    \"，\" in service_data.get(\"role\"):\n                service_data[\"validate_error\"] = \"角色请用英文逗号分隔\"\n                result_dict[\"error\"].append(service_data)\n                continue\n            # 当服务名为 hadoop 时，记录 hadoop 实例列表、角色集合\n            if app_name == \"hadoop\":\n                hadoop_instance_ls.append(service_data)\n                if service_data.get(\"role\"):\n                    hadoop_role_set = hadoop_role_set | set(\n                        service_data.get(\"role\").split(\",\"))\n                continue\n            result_dict[\"correct\"].append(service_data)\n\n        # 如果存在 hadoop 实例，则校验角色\n        if hadoop_instance_ls:\n            key_name = \"single\"\n            if len(hadoop_instance_ls) > 1:\n                key_name = \"cluster\"\n            diff = set(HADOOP_ROLE.get(key_name)) - hadoop_role_set\n            if diff:\n                for hadoop_instance in hadoop_instance_ls:\n                    hadoop_instance[\"validate_error\"] = f\"缺少角色{','.join(diff)}\"\n                    result_dict[\"error\"].append(hadoop_instance)\n            else:\n                for hadoop_instance in hadoop_instance_ls:\n                    result_dict[\"correct\"].append(hadoop_instance)\n\n        # 按照 row 行号对列表进行排序\n        for v in result_dict.values():\n            if len(v) > 0:\n                v.sort(key=lambda x: x.get(\"row\", 999))\n        attrs[\"result_dict\"] = result_dict\n        logger.info(\"deployment plan validate end\")\n        return attrs\n\n\nclass DeploymentImportSerializer(Serializer):\n    \"\"\" 部署计划导入序列化类 \"\"\"\n\n    instance_info_ls = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"主机信息列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[instance_info_ls]字段\"}\n    )\n\n    service_data_ls = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"服务数据列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[host_list]字段\"}\n    )\n\n    operation_uuid = serializers.CharField(\n        max_length=64,\n        help_text=\"唯一操作id\",\n        required=False\n    )\n\n\nclass DeploymentPlanListSerializer(ModelSerializer):\n    \"\"\" 部署计划列表序列化类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = DeploymentPlan\n        fields = \"__all__\"\n\n\nclass ExecutionRecordSerializer(ModelSerializer):\n    state_display = serializers.CharField(source=\"get_state_display\")\n    can_rollback = serializers.SerializerMethodField()\n    duration = serializers.SerializerMethodField()\n\n    def get_can_rollback(self, obj):\n        if obj.module != \"UpgradeHistory\":\n            return False\n        module_obj = getattr(models, obj.module).objects.get(\n            id=int(obj.module_id)\n        )\n        return module_obj.can_roll_back\n\n    def get_duration(self, obj):\n        if not obj.end_time:\n            return \"-\"\n        return timedelta_strftime(obj.end_time - obj.created)\n\n    class Meta:\n        model = ExecutionRecord\n        fields = (\"id\", \"operator\", \"count\", \"state\", \"state_display\",\n                  \"can_rollback\", \"duration\", \"created\", \"end_time\",\n                  \"module\", \"module_id\")\n\n\nclass ProductCompositionSerializer(ModelSerializer):\n    pro_ser_others = serializers.SerializerMethodField()\n    pro_services = serializers.CharField(\n        # child=serializers.DictField(),\n        help_text=\"产品包含服务列表\",\n        required=True,\n        error_messages={\"required\": \"必须包含[pro_services]字段\"}\n    )\n    pro_name = serializers.CharField(help_text=\"产品名称\", required=True,\n                                     error_messages={\"required\": \"请填写产品名称\"})\n    pro_version = serializers.CharField(help_text=\"产品版本\", required=True,\n                                        error_messages={\"required\": \"请填写产品版本\"})\n\n    def get_pro_ser_others(self, obj, **kwargs):\n        res_list = []\n        all_apps_set, all_true_apps_set = set(), set()\n        if obj.applicationhub_set.exists():\n            all_apps = obj.applicationhub_set.values_list(\"app_name\", \"app_version\")\n            for app in all_apps:\n                all_apps_set.add(\",\".join(app))\n        pro_ser = kwargs.get('pro_ser')\n        pro_services = pro_ser if pro_ser else json.loads(obj.pro_services)\n        for t_app in pro_services:\n            all_true_apps_set.add(f\"{t_app['name']},{t_app['version']}\")\n        # 一部分用作校验用\n        if pro_ser:\n            return all_true_apps_set - all_apps_set\n\n        for r_app in all_apps_set - all_true_apps_set:\n            r_app_ls = r_app.split(\",\")\n            res_list.append(\n                {\n                    \"name\": r_app_ls[0],\n                    \"version\": r_app_ls[1],\n                }\n            )\n        return res_list\n\n    def validate_pro_services(self, pro_services):\n        pro_services = json.loads(pro_services)\n        if not isinstance(pro_services, list):\n            raise ValidationError({\n                \"pro_services\": \"pro_services必须是list\"\n            })\n        pro_ser_len = len(pro_services)\n        ser_name = {}\n        for app in pro_services:\n            ser_name[app.get('name', \"\")] = app.get('version', '')\n        if len(ser_name) != pro_ser_len:\n            raise ValidationError({\n                \"pro_services\": \"产品内服务名称需保证唯一或字段传递异常\"\n            })\n        return pro_services\n\n    def validate(self, attrs):\n        pro_name = attrs.get(\"pro_name\")\n        pro_version = attrs.get(\"pro_version\")\n        pro_obj = ProductHub.objects.filter(pro_name=pro_name, pro_version=pro_version).first()\n        if not pro_obj:\n            raise ValidationError({\n                \"pro_services\": \"请填写正确的产品名称和版本\"\n            })\n\n        diff_ser = self.get_pro_ser_others(pro_obj, pro_ser=attrs.get(\"pro_services\"))\n        if diff_ser:\n            raise ValidationError({\n                \"pro_services\": f\"存在不归属于当前产品的服务{diff_ser}\"\n            })\n        return attrs\n\n    class Meta:\n        model = ProductHub\n        fields = (\"pro_name\", \"pro_version\", \"pro_services\", \"pro_ser_others\")\n\n\nclass DeleteComponentSerializer(ModelSerializer):\n    \"\"\"\n    基础组件序列化\n    \"\"\"\n    name = serializers.SerializerMethodField()\n    versions = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = (\"name\", \"versions\")\n\n    def get_name(self, obj):\n        return obj.app_name\n\n    def get_versions(self, obj):\n        return [obj.app_version]\n\n\nclass DeleteProDuctSerializer(ModelSerializer):\n    \"\"\"\n    产品序列化\n    \"\"\"\n    name = serializers.SerializerMethodField()\n    versions = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ProductHub\n        fields = (\"name\", \"versions\")\n\n    def get_name(self, obj):\n        return f\"{obj.pro_name}|{obj.pro_version}\"\n\n    def get_versions(self, obj):\n        app_ls = []\n        app_values = ApplicationHub.objects.filter(\n            product=obj).values_list(\"app_name\", \"app_version\")\n        for app in app_values:\n            app_ls.append(f\"{app[0]}|{app[1]}\")\n        return app_ls\n"
  },
  {
    "path": "omp_server/app_store/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass AppStoreConfig(AppConfig):\n    name = 'app_store'\n"
  },
  {
    "path": "omp_server/app_store/cmd_install_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: cmd_install_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2022-01-07 15:10\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport os\n\nimport xlrd\n\n\nclass ReadDeploymentExcel(object):\n    \"\"\" 读取部署表格使用类 \"\"\"\n\n    def __init__(self, excel_path):\n        \"\"\"\n        :param excel_path: 表格文件绝对路径\n        \"\"\"\n        self.excel_path = excel_path\n\n    @staticmethod\n    def _read(start_row=1, keys=None, table=None):\n        \"\"\"\n        真正读取表格数据，返回tuple (True, list) or (False, str)\n        :param start_row:\n        :param keys:\n        :param table:\n        :return:\n        \"\"\"\n        # 获取总行数 总列数\n        row_num = table.nrows\n        col_num = table.ncols\n\n        _res = []\n        # 这是第一行数据，作为字典的key值\n        key = table.row_values(0)\n\n        if row_num <= start_row:\n            return False, \"所需要数据行数下无数据\"\n        # 读取行数的控制逻辑\n        for i in range(row_num - 1):\n            # 行数据字典\n            row_data = {}\n            # 如果当前获取到的数据的行数小于想要的行数时，则跳过，不留存\n            if i < start_row:\n                continue\n            values = table.row_values(i + 1)\n            # 循环每列获取数据\n            for x in range(col_num):\n                _key = None\n                if key[x] not in keys:\n                    continue\n                _key = keys[key[x]]\n                # 提取真正的值\n                value = int(values[x]) if isinstance(values[x], float) \\\n                    else values[x]\n                row_data[_key] = str(value).strip()\n                row_data[\"row\"] = i + 1\n            if not row_data:\n                continue\n            # 把字典加到列表中\n            _res.append(row_data)\n        return _res\n\n    def read_host_info(self, table):\n        \"\"\"\n        获取节点信息\n        :param table: 节点信息页对象\n        :return:\n        \"\"\"\n        keys_map = {\n            \"实例名[必填]\": \"instance_name\",\n            \"IP[必填]\": \"ip\",\n            \"端口[必填]\": \"port\",\n            \"用户名[必填]\": \"username\",\n            \"密码[必填]\": \"password\",\n            \"数据分区[必填]\": \"data_folder\",\n            \"操作系统[必填]\": \"operate_system\",\n            \"是否执行初始化\": \"init_host\",\n            \"运行用户\": \"run_user\",\n            \"时间同步服务器\": \"ntpd_server\"\n        }\n        return self._read(start_row=4, keys=keys_map, table=table)\n\n    def read_service_info(self, table):\n        \"\"\"\n        获取服务分布表格信息\n        :param table: 服务分布页对象\n        :return:\n        \"\"\"\n        keys_map = {\n            \"主机实例名[必填]\": \"instance_name\",\n            \"服务名[必填]\": \"service_name\",\n            \"运行内存\": \"memory\",\n            \"虚拟IP\": \"vip\",\n            \"角色\": \"role\",\n            \"模式\": \"mode\"\n        }\n        return self._read(start_row=3, keys=keys_map, table=table)\n\n    def read_excel(self):\n        \"\"\"\n        读取表格数据\n        :return:\n        \"\"\"\n        if not os.path.exists(self.excel_path) or \\\n                not os.path.isfile(self.excel_path):\n            return False, f\"无法找到此文件: {self.excel_path}\"\n        # 打开excel表，填写路径\n        book = xlrd.open_workbook(self.excel_path)\n        # 找到sheet页\n        host_table = book.sheet_by_name(\"节点信息\")\n        host_info = self.read_host_info(table=host_table)\n        for item in host_info:\n            if item[\"username\"] == \"root\":\n                item[\"init_host\"] = True\n            else:\n                item[\"init_host\"] = False\n            if \"ntpd_server\" in item and item[\"ntpd_server\"] != \"\":\n                item[\"use_ntpd\"] = True\n            else:\n                item[\"use_ntpd\"] = False\n                item.pop(\"ntpd_server\", \"\")\n        service_table = book.sheet_by_name(\"服务分布\")\n        service_info = self.read_service_info(table=service_table)\n        return True, {\n            \"host\": host_info,\n            \"service\": {\n                \"instance_name_ls\": [el[\"instance_name\"] for el in host_info],\n                \"service_data_ls\": service_info\n            }\n        }\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-16 16:44\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n服务部署模式\n\"\"\"\n\nfrom app_store.deploy_mode_utils.mysql import MysqlUtils\n# from app_store.deploy_mode_utils.normal import NormalUtils\nfrom app_store.deploy_mode_utils.odd_num import OddNumUtils\n# from app_store.deploy_mode_utils.even_num import EvenNumUtils\nfrom app_store.deploy_mode_utils.tengine import TengineUtils\nfrom app_store.deploy_mode_utils.rocketmq import RocketmqUtils\n\n\nSERVICE_MAP = {\n    \"mysql\": MysqlUtils,\n    \"zookeeper\": OddNumUtils,\n    \"kafka\": OddNumUtils,\n    \"nacos\": OddNumUtils,\n    \"tengine\": TengineUtils,\n    \"rocketmq\": RocketmqUtils\n}\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/base.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: base\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-16 17:10\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\nclass BaseUtils(object):\n    \"\"\" 部署模式基础类 \"\"\"\n\n    def __init__(self, host_num, high_availability):\n        \"\"\"\n        :param host_num: 主机数量\n        :type host_num: int\n        :param high_availability: 是否使用高可用\n        :type high_availability: bool\n        \"\"\"\n        self.host_num = host_num\n        self.high_availability = high_availability\n\n    def get(self):\n        \"\"\"\n        获取部署模式\n        :return:\n        \"\"\"\n        raise NotImplementedError(f\"{self}必须实现get方法!\")\n\n    def check(self, mode):\n        \"\"\"\n        校验部署模式\n        :param mode: 部署模式\n        :return:\n        \"\"\"\n        raise NotImplementedError(f\"{self}必须实现check方法!\")\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/even_num.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: even_num\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-23 15:55\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n偶数服务集群部署模式\n偶数服务集群最小为4\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass EvenNumUtils(BaseUtils):\n    \"\"\" 偶数集群控制 \"\"\"\n\n    def get(self):\n        \"\"\"\n        当服务集群为偶数个时，返回部署模式\n        :return:\n        \"\"\"\n        if self.host_num < 4:\n            return {\n                \"default\": 1,\n                \"step\": 0\n            }\n        return {\n            \"default\": 4,\n            \"step\": 2\n        }\n\n    def check(self, mode):\n        \"\"\"\n        检查服务集群部署规则 TODO 待完善\n        :param mode:\n        :type mode: int\n        :return:\n        \"\"\"\n        if mode > self.host_num:\n            return False\n        if self.host_num < 4 and mode > 1:\n            return False\n        return True\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/mysql.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: mysql\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-16 16:45\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nmysql部署模式工具\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass MysqlUtils(BaseUtils):\n    \"\"\" MySQL的部署模式 \"\"\"\n\n    def get(self):\n        \"\"\"\n        获取mysql的部署模式\n        :return:\n        \"\"\"\n        if self.host_num == 1:\n            return [\n                {\n                    \"key\": \"single\",\n                    \"name\": \"单实例\"\n                }\n            ]\n        elif self.high_availability and self.host_num >= 2:\n            return [\n                {\n                    \"key\": \"master-slave\",\n                    \"name\": \"主从\"\n                },\n                {\n                    \"key\": \"master-master\",\n                    \"name\": \"主主(vip)\"\n                },\n                {\n                    \"key\": \"single\",\n                    \"name\": \"单实例\"\n                }\n            ]\n        return [\n            {\n                \"key\": \"single\",\n                \"name\": \"单实例\"\n            },\n            {\n                \"key\": \"master-slave\",\n                \"name\": \"主从\"\n            },\n            {\n                \"key\": \"master-master\",\n                \"name\": \"主主(vip)\"\n            }\n        ]\n\n    def check(self, mode):\n        \"\"\"\n        检查部署模式是否符合要求\n        :param mode: 部署模式\n        :type mode: str\n        :return:\n        \"\"\"\n        if self.host_num == 1:\n            if mode == \"single\":\n                return True\n            return False\n        if self.host_num >= 2 and \\\n                mode in (\"single\", \"master-slave\", \"master-master\"):\n            return True\n        return False\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/normal.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: normal\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-23 16:06\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n普通服务的集群模式\n起始为1 步长为1\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass NormalUtils(BaseUtils):\n    \"\"\" 普通集群控制 \"\"\"\n\n    def get(self):\n        \"\"\"\n        普通服务，返回部署模式\n        :return:\n        \"\"\"\n        if self.host_num == 1:\n            return {\n                \"default\": 1,\n                \"step\": 0\n            }\n        return {\n            \"default\": 1,\n            \"step\": 1\n        }\n\n    def check(self, mode):\n        \"\"\"\n        检查服务集群部署规则 TODO 待完善\n        :param mode:\n        :type mode: int\n        :return:\n        \"\"\"\n        if mode > self.host_num:\n            return False\n        return True\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/odd_num.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: odd_num\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-23 15:55\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n奇数服务集群部署模式\n奇数服务集群最小为3\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass OddNumUtils(BaseUtils):\n    \"\"\" 奇数集群控制 \"\"\"\n\n    def get(self):\n        \"\"\"\n        当服务集群为奇数个时，返回部署模式\n        :return:\n        \"\"\"\n        if self.host_num < 3:\n            return {\n                \"default\": 1,\n                \"step\": 0\n            }\n        if not self.high_availability:\n            return {\n                \"default\": 1,\n                \"step\": 2\n            }\n        return {\n            \"default\": 3,\n            \"step\": 2\n        }\n\n    def check(self, mode):\n        \"\"\"\n        检查服务集群部署规则 TODO 待完善\n        :param mode:\n        :type mode: int\n        :return:\n        \"\"\"\n        if mode > self.host_num:\n            return False\n        if self.host_num < 3 and mode > 1:\n            return False\n        return True\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/rocketmq.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: mysql\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-16 16:45\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nrocketmq部署模式工具\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass RocketmqUtils(BaseUtils):\n    \"\"\" Rocket的部署模式 \"\"\"\n\n    def get(self):\n        \"\"\"\n        获取mysql的部署模式\n        :return:\n        \"\"\"\n        if self.host_num == 1:\n            return {\n                \"default\": 1,\n                \"step\": 0\n            }\n        if self.high_availability:\n            if self.host_num >= 2:\n                return {\n                    \"default\": 2,\n                    \"step\": 2\n                }\n            return {\n                \"default\": 1,\n                \"step\": 0\n            }\n        return {\n            \"default\": 1,\n            \"step\": 1\n        }\n\n    def check(self, mode):\n        \"\"\"\n        检查部署模式是否符合要求\n        :param mode: 部署模式\n        :type mode: str\n        :return:\n        \"\"\"\n        return True\n"
  },
  {
    "path": "omp_server/app_store/deploy_mode_utils/tengine.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: tengine\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-23 16:26\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\ntengine部署模式工具\n\"\"\"\n\nfrom app_store.deploy_mode_utils.base import BaseUtils\n\n\nclass TengineUtils(BaseUtils):\n    \"\"\" tengine的部署模式 \"\"\"\n\n    def get(self):\n        \"\"\"\n        获取tengine的部署模式\n        :return:\n        \"\"\"\n        # return {\n        #     \"default\": 1,\n        #     \"step\": 1\n        # }\n        return [\n            {\n                \"key\": \"single\",\n                \"name\": \"单实例\"\n            },\n            {\n                \"key\": \"master-master\",\n                \"name\": \"主主(vip)\"\n            }\n        ]\n\n    def check(self, mode):\n        \"\"\"\n        检查部署模式是否符合要求\n        :param mode: 部署模式\n        :type mode: int\n        :return:\n        \"\"\"\n        # if mode != 1:\n        #     return False\n        return True\n"
  },
  {
    "path": "omp_server/app_store/deploy_role_utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jerry.zhang@yunzhihui.com\n# Create time: 2021-12-15 15:46\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom app_store.deploy_role_utils.hadoop import Hadoop\nfrom app_store.deploy_role_utils.redis import Redis\nfrom app_store.deploy_role_utils.mysql import Mysql\n\nDEPLOY_ROLE_UTILS = {\n    \"hadoop\": Hadoop,\n    \"redis\": Redis,\n    \"mysql\": Mysql\n}\n"
  },
  {
    "path": "omp_server/app_store/deploy_role_utils/hadoop.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jerry.zhang@yunzhihui.com\n# Create time: 2021-12-16 10:10\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport logging\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Hadoop(object):\n    @staticmethod\n    def update_service(service_list):\n        \"\"\"\n        分配hadoop服务角色\n        :param service_list: 服务数据列表\n        :return:\n        \"\"\"\n        need_distribution = [\n            \"namenode,zkfc\", \"resourcemanager\", \"namenode,zkfc\", \"resourcemanager\"]\n        base_role = \"datanode,nodemanager,journalnode\"\n        # worker_role = \"datanode,nodemanager\" 后期优化分配\n        if len(service_list) == 1:\n            service_list[0]['roles'] = \"namenode,secondarynamenode,\" \\\n                                       \"datanode,resourcemanager,nodemanager\"\n            return service_list\n        for index, i in enumerate(service_list):\n            if i.get('roles'):\n                continue\n            if index == len(service_list) - 1 and len(need_distribution) > 1:\n                i['roles'] = \"{0},{1}\".format(\n                    base_role, \",\".join(need_distribution))\n            elif index == 0 and len(service_list) == 2:\n                i['roles'] = \"{0},{1}\".format(\n                    base_role, \",\".join(need_distribution[:2]))\n                need_distribution = need_distribution[2:]\n            elif len(service_list) >= 5 and need_distribution[0] == \"namenode,zkfc\":\n                role = need_distribution.pop(0)\n                i['roles'] = f\"journalnode,{role}\"\n            else:\n                role = need_distribution.pop(0)\n                i['roles'] = f\"{base_role},{role}\"\n        return service_list\n"
  },
  {
    "path": "omp_server/app_store/deploy_role_utils/mysql.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: mysql\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-12-21 20:22\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\nimport logging\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Mysql(object):\n    @staticmethod\n    def update_service(service_list):\n        \"\"\"\n        分配mysql的角色\n        :param service_list: 服务数据列表\n        :return:\n        \"\"\"\n        mysql_index_lst = list()\n        mysql_vip_flag = False\n        for index, item in enumerate(service_list):\n            logger.info(f\"{mysql_vip_flag}; {item}\")\n            if item.get(\"name\") == \"mysql\":\n                mysql_index_lst.append(index)\n            if item.get(\"roles\") == \"mysql\":\n                mysql_vip_flag = True\n        if len(mysql_index_lst) == 1:\n            service_list[mysql_index_lst[0]][\"roles\"] = \"single\"\n        elif len(mysql_index_lst) == 2:\n            if not mysql_vip_flag:\n                service_list[mysql_index_lst[0]][\"roles\"] = \"master\"\n                service_list[mysql_index_lst[1]][\"roles\"] = \"slave\"\n            else:\n                service_list[mysql_index_lst[0]][\"roles\"] = \"master\"\n                service_list[mysql_index_lst[1]][\"roles\"] = \"master\"\n        return service_list\n"
  },
  {
    "path": "omp_server/app_store/deploy_role_utils/redis.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jerry.zhang@yunzhihui.com\n# Create time: 2021-12-16 10:10\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport logging\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Redis(object):\n    @staticmethod\n    def update_service(service_list):\n        \"\"\"\n        分配redis服务角色\n        :param service_list: 服务数据列表\n        :return:\n        \"\"\"\n        if len(service_list) == 1:\n            service_list[0]['roles'] = \"master\"\n        for index, i in enumerate(service_list):\n            if i.get('roles'):\n                continue\n            if index == 0:\n                i['roles'] = \"master\"\n            else:\n                i['roles'] = \"slave\"\n        return service_list\n"
  },
  {
    "path": "omp_server/app_store/high_availability_utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jerry.zhang@yunzhihui.com\n# Create time: 2021-12-15 15:46\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom app_store.high_availability_utils.hadoop import Hadoop\n\nHIGH_AVAILABILITY_UTILS = {\n    \"hadoop\": Hadoop\n}\n"
  },
  {
    "path": "omp_server/app_store/high_availability_utils/hadoop.py",
    "content": "import logging\nimport os\nimport json\nfrom utils.common.exceptions import GeneralError\nfrom db_models.models import (\n    Service, DetailInstallHistory,\n    Host, ServiceHistory,\n    ClusterInfo\n)\nfrom django.db.models import F\nfrom django.db import transaction\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom utils.plugin.salt_client import SaltClient\n\nTHREAD_POOL_MAX_WORKERS = 20\nlogger = logging.getLogger(\"server\")\n\n\nclass Hadoop(object):\n    ACTION_LS = (\"send\", \"unzip\", \"install\")\n    hadoop_init = [\n        (\"init\", \"zkfc\"), (\"start\", \"journalnode\"),\n        (\"format\", \"namenode\"), (\"sync\", \"namenode\"),\n        (\"start\", \"zkfc\"), (\"start\", \"datanode\"),\n        (\"start\", \"resourcemanager\"), (\"start\", \"nodemanager\")\n    ]\n\n    def __init__(self, install_exec_obj, detail_list_obj):\n        \"\"\"\n        解析数据\n        :param install_exec_obj InstallServiceExecutor对象实例\n        :parm detail_list_obj detail的orm对象列表\n        :return:\n        \"\"\"\n        self.install_obj = install_exec_obj\n        self.detail_list = detail_list_obj\n        self.timeout = 300\n        self.error = False\n        self.target_set = set()\n        self.count = 0\n        self.detail_dict = {}  # 中间结果，任意一个hadoop_init失败所有的detail对象全部失败\n        self.port = {}\n        self.base_dir = {}\n\n    def check_result(self, future_list):\n        for future in as_completed(future_list):\n            is_success, message = future.result()\n            if not is_success:\n                self.install_obj.is_error = True\n                self.error = True\n                break\n        self.count += 1\n\n    def init_hadoop(self, detail_obj, target_ip, service_controllers_dict, action):\n        try:\n            # 获取服务初始化脚本绝对路径\n            salt_client = SaltClient()\n            init_script_path = service_controllers_dict.get(\"init\", \"\")\n            # 获取 json 文件路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            json_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            cmd_str = f\"python {init_script_path} --local_ip {target_ip} \" \\\n                      f\"--data_json {json_path} --action_type {action[0]} --action_object {action[1]}\"\n            # 执行初始化\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise GeneralError(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            detail_obj.install_msg += \\\n                f\"{self.install_obj.now_time()} 初始化脚本执行成功，脚本输出如下:\\n\" \\\n                f\"{message}\\n\"\n            detail_obj.save()\n            return True, \"success\"\n        except Exception as err:\n            for obj, name in self.detail_dict.items():\n                logger.error(f\"Init Failed -> [{name}]: {err}\")\n                obj.init_flag = 3\n                obj.init_msg += f\"{self.install_obj.now_time()} {name} \" \\\n                                f\"初始化服务失败: {err}\\n\"\n                # 更新安装流程状态为 '失败'，服务状态为 '安装失败'\n                obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_FAILED\n                obj.save()\n                obj.service.service_status = \\\n                    Service.SERVICE_STATUS_INSTALL_FAILED\n                obj.service.save()\n                # 创建历史记录\n                self.install_obj.create_history(obj, is_success=False)\n            return False, err\n\n    def init(self, action, detail_obj_list):\n        \"\"\" 初始化服务 \"\"\"\n        # 获取初始化使用参数\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            future_list = []\n            for detail_obj in detail_obj_list:\n                target_ip = detail_obj.service.ip\n                service_name = detail_obj.service.service_instance_name\n                service_controllers_dict = detail_obj.service.service_controllers\n                if target_ip not in self.target_set:\n                    # 中间结果，全部成功后更新状态\n                    self.detail_dict[detail_obj] = service_name\n                    self.target_set.add(target_ip)\n                    logger.info(f\"Init Begin -> [{service_name}]\")\n                    detail_obj.init_flag = 1\n                    detail_obj.init_msg += f\"{self.install_obj.now_time()} {service_name} 开始初始化服务\\n\"\n                    detail_obj.save()\n                future_obj = executor.submit(\n                    self.init_hadoop, detail_obj,\n                    target_ip, service_controllers_dict,\n                    action\n                )\n                future_list.append(future_obj)\n            self.check_result(future_list)\n        # 安装成功\n        if self.count == 9:\n            for obj, name in self.detail_dict.items():\n                logger.info(f\"Init Success -> [{name}]\")\n                obj.init_flag = 2\n                obj.init_msg += f\"{self.install_obj.now_time()} {name} 成功初始化服务\\n\"\n                # 完成安装流程，更新状态为 '安装成功'\n                obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                # 创建历史记录\n                self.install_obj.create_history(obj, is_success=True)\n            return True, \"Init Success\"\n\n    def single_service_executor(self, detail_obj):\n        \"\"\"\n        单独服务的安装执行器\n        :param detail_obj:\n        :type detail_obj: DetailInstallHistory\n        :return:\n        \"\"\"\n        # 更改服务状态为安装中状态\n        detail_obj.service.service_status = Service.SERVICE_STATUS_INSTALLING\n        detail_obj.service.save()\n        # 跳过单个服务的已经成功的单个步骤不再重复执行\n        for action in self.ACTION_LS:\n            if getattr(detail_obj, f\"{action}_flag\") == 2:\n                continue\n            _flag, _msg = getattr(self.install_obj, action)(detail_obj)\n            if not _flag:\n                return _flag, _msg\n        return True, \"success\"\n\n    def sync_port(self, service_obj):\n        \"\"\"\n        同步要监控的port\n        \"\"\"\n        ports = json.loads(service_obj.service_port)\n        role_key = {\n            \"namenode_rpc_port\": \"namenode\",\n            \"journalnode_rpc_port\": \"journalnode\",\n            \"resourcemanager_webapp_port\": \"resourcemanager\",\n            \"nodemanager_port\": \"nodemanager\",\n            \"datanode_rpc_port\": \"datanode\",\n            \"zkfc_rpc_port\": \"zkfc\",\n            \"secondarynamenode_rpc_port\": \"secondarynamenode\"\n        }\n        for port in ports:\n            port_key = role_key.get(port.get(\"key\", \"\"))\n            if port_key:\n                self.port[port_key] = port.get(\"default\", \"\")\n\n    def sync_dir(self, obj_list):\n        for detail in obj_list:\n            install_file = detail.service.service_controllers.get(\n                \"install\", \"\")\n            if install_file:\n                install_file = install_file.replace(\"install.py\", \"hadoop\")\n            self.base_dir[detail.service.ip] = install_file\n\n    def _get_service_port(self, role):\n        port = self.port.get(role, \"\")\n        port_json = [{\n            \"name\": role,\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": port\n        }]\n        return json.dumps(port_json, ensure_ascii=False)\n\n    def _get_service_controllers(self, ip, role):\n        script_dir = self.base_dir.get(ip, \"\").split()[0]\n        controllers = {\n            \"init\": \"\",\n            \"start\": f\"{script_dir} start {role}\",\n            \"stop\": f\"{script_dir} stop {role}\",\n            \"restart\": f\"{script_dir} restart {role}\",\n            \"install\": \"\"\n        }\n        return controllers\n\n    def _create_service(self, role, detail_obj):\n        ip = detail_obj.service.ip\n        instance_name = role + \"_\" + \"_\".join(ip.split(\".\")[2:])\n        # if \"secondarynamenode\" in detail_obj.service.service_role:\n        #    cluster = ClusterInfo.objects.get_or_create(\n        #        cluster_service_name=\"hadoop\",\n        #        cluster_name=detail_obj.service.service_instance_name,\n        #    )[0]\n        # else:\n        #    cluster = detail_obj.service.cluster\n        service_obj = Service.objects.create(\n            ip=ip,\n            service_instance_name=instance_name,\n            service_status=0,\n            service=detail_obj.service.service,\n            service_port=self._get_service_port(role),\n            service_controllers=self._get_service_controllers(ip, role),\n            # cluster=cluster,\n            env=detail_obj.service.env,\n            service_split=2\n        )\n        # TODO 操作用户\n        ServiceHistory.objects.create(\n            username=\"admin\",\n            description=\"安装服务\",\n            result=\"success\",\n            service=service_obj)\n        Host.objects.filter(ip=ip).update(\n            service_num=F(\"service_num\") + 1)\n        return service_obj\n\n    def _create_detail(self, service_obj, detail_obj):\n        \"\"\"\n        创建detail表\n        \"\"\"\n        status = DetailInstallHistory.INSTALL_STATUS_SUCCESS\n        DetailInstallHistory.objects.create(\n            service=service_obj,\n            main_install_history=detail_obj.main_install_history,\n            install_step_status=status,\n            send_flag=status,\n            unzip_flag=status,\n            install_flag=status,\n            init_flag=status,\n            start_flag=status,\n            post_action_flag=status,\n            install_detail_args=detail_obj.install_detail_args\n        )\n\n    def high_thread_executor(self):\n        \"\"\"\n        多线程执行器\n        \"\"\"\n        logger.info(f\"Start thread poll executor for {self.detail_list}\")\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            _future_list = []\n            for detail_obj in self.detail_list:\n                # 更新单条安装记录的状态\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_INSTALLING\n                detail_obj.save()\n                future_obj = executor.submit(\n                    self.single_service_executor, detail_obj\n                )\n                _future_list.append(future_obj)\n            self.check_result(_future_list)\n            for action in self.hadoop_init:\n                if not self.error:\n                    self.init(action, self.detail_list)\n        if not self.error:\n            # 创建表数据\n            with transaction.atomic():\n                self.sync_port(self.detail_list[0].service)\n                self.sync_dir(self.detail_list)\n                for obj in self.detail_list:\n                    host_ip = obj.service.ip\n                    roles_name = obj.service.service_role\n                    for role in roles_name.split(\",\"):\n                        ser_obj = self._create_service(role, obj)\n                        self._create_detail(ser_obj, obj)\n                    obj.service.service_status = Service.SERVICE_STATUS_UNKNOWN\n                    obj.service.service_split = 1\n                    obj.service.save()\n                    # obj.service.delete()\n                    # 更新拆分前服务detail状态\n                    obj.install_step_status = DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                    obj.save()\n                    Host.objects.filter(ip=host_ip).update(\n                        service_num=F(\"service_num\") - 1)\n            logger.info(\"Finish thread poll executor!\")\n"
  },
  {
    "path": "omp_server/app_store/install_exec.py",
    "content": "\"\"\"\n安装执行器\n前提：所有的公共组件都由OMP进行安装处理\n目标：根据数据库中给出的安装记录进行服务安装\n\"\"\"\nimport copy\nimport json\nimport os\nimport time\nimport queue\nimport logging\nimport re\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\n# from django.db.models import F\nfrom django.conf import settings\n\nfrom app_store.high_availability_utils import HIGH_AVAILABILITY_UTILS\nfrom db_models.models import (\n    Host, Service, HostOperateLog, ServiceHistory,\n    MainInstallHistory, DetailInstallHistory, ApplicationHub,\n    PreInstallHistory, PostInstallHistory\n)\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.parse_config import BASIC_ORDER\nfrom utils.parse_config import THREAD_POOL_MAX_WORKERS\nfrom utils.common.exceptions import GeneralError\nfrom omp_server.settings import PROJECT_DIR\nfrom app_store.post_install_utils import POST_INSTALL_SERVICE\nfrom app_store.service_splitting import service_splitting\n\nUNZIP_CONCURRENT_ONE_HOST = 3\nOPENSSL_VERSION_LEVEL = 102\nDOIM_APP_NAME = 'doim'\nlogger = logging.getLogger(\"server\")\n\n\nclass InstallServiceExecutor:\n    \"\"\" 安装服务执行器 \"\"\"\n    ACTION_LS = (\"send\", \"unzip\", \"install\", \"init\", \"start\")\n\n    def __init__(self, main_id, username, timeout=300):\n        self.main_id = main_id\n        self.username = username\n        self.timeout = timeout\n        # 安装中是否发生错误，用于流程控制\n        self.is_error = False\n        # 控制安装过程中单主机上的安装包解压并发数 TODO 暂时使用阻塞等待方式进行处理！！\n        self.unzip_concurrent_controller = dict()\n\n    def parse_origin_data(self, json_source_path):\n        \"\"\"\n        解析数据\n        :param json_source_path:\n        :return:\n        \"\"\"\n        _file_name = os.path.join(PROJECT_DIR, \"package_hub\", json_source_path)\n        with open(_file_name, \"r\") as fp:\n            content = json.loads(fp.read())\n            if not isinstance(content, list):\n                raise GeneralError(\"json文件不符合格式要求!\")\n        ip_user_map = dict()\n        for item in content:\n            if item[\"ip\"] not in ip_user_map:\n                ip_user_map[item[\"ip\"]] = list()\n            if \"install_args\" not in item:\n                continue\n            for el in item[\"install_args\"]:\n                if el.get(\"key\") == \"run_user\" and el.get(\"default\"):\n                    ip_user_map[item[\"ip\"]].append(el.get(\"default\"))\n                    break\n        return ip_user_map\n\n    def set_hostname_analysis(self, ips_data, ip, salt_client):\n        test_write_host_func = \"cat /tmp/init_host.py | grep write_hostname\"\n        flag, msg = salt_client.cmd(\n            target=ip,\n            command=test_write_host_func,\n            timeout=60\n        )\n        logger.info(f\"测试主机[{ip}]init_host.py脚本write_host功能，结果 {msg}\")\n        if not flag or \"write_hostname\" not in msg:\n            is_success, message = salt_client.cp_file(\n                target=ip,\n                source_path=\"_modules/init_host.py\",\n                target_path=\"/tmp/init_host.py\"\n            )\n            logger.info(f\"主机[{ip}]发送init_host.py脚本成功！\")\n            if not is_success:\n                return f\"{self.now_time()} 执行主机名解析失败！请手动添加集群主机名解析！\\n\"\n        host_data = []\n        for k, v in ips_data.items():\n            if not v.get(\"host_name\"):\n                logger.error(f\"未获取到主机[{ip}]的主机名，添加主机名解析失败！\")\n                return f\"未获取到主机[{ip}]的主机名，添加主机名解析失败！\" \\\n                       f\"请重启主机agent重试或手动添加主机名解析\"\n            host_data.append({\"ip\": k, \"hostname\": v.get(\"host_name\")})\n        hosts_data = json.dumps(host_data, separators=(',', ':'))\n        # 直接用sudo执行，报错即进行警告\n        write_host = f\"sudo python /tmp/init_host.py write_hostname '{hosts_data}'\"\n        flag, msg = salt_client.cmd(\n            target=ip,\n            command=write_host,\n            timeout=60\n        )\n        if not flag:\n            return f\"{self.now_time()} 执行主机名解析失败！请手动添加集群主机名解析！\\n\"\n        return f\"{self.now_time()} 执行主机名解析成功！\\n\"\n\n    def _execute_pre_install(\n            self, ips_data,\n            key, value, main_obj,\n            json_source_path, salt_client, pre_install_obj\n    ):\n        \"\"\"\n\n        :param ips_data:\n        :param key:\n        :param value:\n        :param main_obj:\n        :param json_source_path:\n        :param salt_client:\n        :param pre_install_obj:\n        :return:\n        \"\"\"\n        try:\n            message = self.set_hostname_analysis(ips_data, key, salt_client)\n        except Exception as e:\n            logger.error(f\"执行主机名解析报错：{str(e)}\")\n            message = f\"{self.now_time()} 执行主机名解析失败！请手动添加集群主机名解析！\\n\"\n        pre_install_obj.install_log += message\n        pre_install_obj.save()\n\n        json_target_path = os.path.join(\n            ips_data[key].get(\"data_folder\", \"/data\"),\n            f\"omp_packages/{main_obj.operation_uuid}.json\")\n        pre_install_obj.install_log += f\"{self.now_time()} 开始发送json文件\\n\"\n        pre_install_obj.save()\n        # 发送 json 文件\n        is_success, message = salt_client.cp_file(\n            target=key,\n            source_path=json_source_path,\n            target_path=json_target_path)\n        if not is_success:\n            pre_install_obj.install_log += \\\n                f\"{self.now_time()} 发送json文件失败: {message}\\n\"\n            pre_install_obj.install_flag = 3\n            pre_install_obj.save()\n            raise GeneralError(f\"发送 json 文件失败: {message}\")\n        for el in set(value):\n            _cmd = f\"id {el} || useradd -s /bin/bash {el}\"\n            pre_install_obj.install_log += \\\n                f\"{self.now_time()} 开始执行创建用户命令: {_cmd}\\n\"\n            pre_install_obj.save()\n            flag, msg = salt_client.cmd(\n                target=key,\n                command=_cmd,\n                timeout=60\n            )\n            logger.info(f\"为主机 [{key}] 创建用户 [{el}] 结果 {msg}\")\n            if not flag:\n                pre_install_obj.install_log += \\\n                    f\"{self.now_time()} 创建用户命令执行失败: {msg}\\n\"\n                pre_install_obj.install_flag = 3\n                pre_install_obj.save()\n                raise GeneralError(f\"为主机 [{key}] 创建用户 [{el}] 失败!\")\n        # 适配doim\n        doim_queryset = DetailInstallHistory.objects.select_related(\n            \"service\", \"service__service\"\n        ).filter(main_install_history_id=self.main_id, service__service__app_name__iexact=DOIM_APP_NAME).exclude(\n            install_step_status=DetailInstallHistory.INSTALL_STATUS_SUCCESS)\n        doim_ips = set([item.service.ip for item in doim_queryset])\n        pre_install_obj.install_log += \\\n            f\"{self.now_time()} 升级openssl version参数位:[doim_queryset] == {doim_queryset};[key]={key}; [doim_ips]={doim_ips}\\n\"\n        pre_install_obj.save()\n        if doim_queryset and key in doim_ips:\n            # 1.run_user 必须为root\n            if \"root\" not in value:\n                pre_install_obj.install_log += \\\n                    f\"{self.now_time()} 安装doim执行失败，用户[{value}]为非root\\n\"\n                pre_install_obj.install_flag = 3\n                pre_install_obj.save()\n                raise GeneralError(\n                    f\"主机 [{key}] 的run_user[{value}]为非root，不支持doim安装\")\n            # 2.获取openssl版本号\n            openssl_version_cmd = \"openssl version\"\n            ssl_version = self.get_openssl_version_from_cmd(ip=key, salt_client=salt_client,\n                                                            cmd_str=openssl_version_cmd)\n            if ssl_version < OPENSSL_VERSION_LEVEL:\n                pre_install_obj.install_log += f\"{self.now_time()} 当前系统的openssl version 为{ssl_version}; 开始升级openssl version\\n\"\n                pre_install_obj.save()\n                upgrade_flag, upgrade_msg = self.upgrade_openssl(\n                    ip=key, salt_client=salt_client)\n                if not upgrade_flag:\n                    pre_install_obj.install_log += \\\n                        f\"{self.now_time()} 升级openssl version执行失败: {upgrade_msg}\\n\"\n                    pre_install_obj.install_flag = 3\n                    pre_install_obj.save()\n                    raise GeneralError(f\"为主机 [{key}] 升级openssl version执行失败!\")\n                pre_install_obj.install_log += f\"{self.now_time()} 当前系统的openssl version成功升级\\n\"\n                pre_install_obj.save()\n\n        pre_install_obj.install_log += \\\n            f\"{self.now_time()} 前置安装操作完成\\n\"\n        pre_install_obj.install_flag = 2\n        pre_install_obj.save()\n\n    def create_user_and_send_json(self, main_obj):\n        \"\"\"\n        下发json文件并创建run_user用户\n        :return:\n        \"\"\"\n        # 获取 json 文件路径\n        salt_client = SaltClient()\n        json_source_path = os.path.join(\n            \"data_files\",\n            f\"{main_obj.operation_uuid}.json\")\n        ips_data = {\n            host[\"ip\"]: host for host in list(\n                Host.objects.all().values(\n                    \"ip\", \"data_folder\", \"username\", \"host_name\"\n                )\n            )\n        }\n\n        ip_user_map = self.parse_origin_data(json_source_path)\n        for key, value in ip_user_map.items():\n            pre_install_obj = PreInstallHistory.objects.filter(\n                main_install_history=main_obj, ip=key\n            ).last()\n            if not pre_install_obj or pre_install_obj.install_flag == 2:\n                continue\n            try:\n                pre_install_obj.install_flag = 1\n                pre_install_obj.install_log += \\\n                    f\"{self.now_time()} 开始执行前置安装操作\\n\"\n                pre_install_obj.save()\n                self._execute_pre_install(\n                    ips_data, key, value, main_obj,\n                    json_source_path, salt_client, pre_install_obj\n                )\n            except GeneralError as e:\n                pre_install_obj.install_log += \\\n                    f\"{self.now_time()} 前置安装操作失败: {str(e)}\\n\"\n                pre_install_obj.install_flag = 3\n                pre_install_obj.save()\n\n    @staticmethod\n    def now_time():\n        \"\"\" 当前时间格式 \"\"\"\n        return time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n\n    def create_history(self, detail_obj, is_success=True):\n        \"\"\" 创建历史记录 \"\"\"\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        # 写入主机历史记录、服务历史记录\n        target_host = Host.objects.filter(ip=target_ip).first()\n        HostOperateLog.objects.create(\n            username=self.username,\n            description=f\"安装服务 [{service_name}]\",\n            result=\"success\" if is_success else \"failed\",\n            host=target_host)\n        ServiceHistory.objects.create(\n            username=self.username,\n            description=\"安装服务\",\n            result=\"success\" if is_success else \"failed\",\n            service=detail_obj.service)\n        # 主机服务数量+1\n        # 屏蔽安装完成后服务数 +1 逻辑\n        # if is_success:\n        #     Host.objects.filter(ip=target_ip).update(\n        #         service_num=F(\"service_num\") + 1)\n\n    def send(self, detail_obj):\n        \"\"\" 发送服务包 \"\"\"\n        # 获取发送使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        package_name = detail_obj.service.service.app_package.package_name\n\n        # 更新状态为 '发送中'，记录日志\n        logger.info(f\"Send Begin -> [{service_name}] package [{package_name}]\")\n        detail_obj.send_flag = 1\n        detail_obj.send_msg += f\"{self.now_time()} {service_name} 开始发送服务包\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取目标路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n\n            # 获取 json 文件路径 已将json发送前置  jon.liu\n            # json_source_path = os.path.join(\n            #     \"data_files\",\n            #     f\"{detail_obj.main_install_history.operation_uuid}.json\")\n            # json_target_path = os.path.join(\n            #     target_host.data_folder, \"omp_packages\",\n            #     f\"{detail_obj.main_install_history.operation_uuid}.json\")\n            #\n            # # 发送 json 文件\n            # is_success, message = salt_client.cp_file(\n            #     target=target_ip,\n            #     source_path=json_source_path,\n            #     target_path=json_target_path)\n            # if not is_success:\n            #     raise GeneralError(f\"发送 json 文件失败: {message}\")\n\n            # 获取服务包路径\n            source_path = os.path.join(\n                detail_obj.service.service.app_package.package_path,\n                package_name)\n            target_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                package_name)\n\n            # 发送服务包\n            is_success, message = salt_client.cp_file(\n                target=target_ip,\n                source_path=source_path,\n                target_path=target_path)\n            if not is_success:\n                raise GeneralError(message)\n\n        except Exception as err:\n            logger.error(f\"Send Failed -> [{service_name}]: {err}\")\n            detail_obj.send_flag = 3\n            detail_obj.send_msg += f\"{self.now_time()} {service_name} \" \\\n                                   f\"发送服务包失败: {err}\\n\"\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 发送成功\n        logger.info(\n            f\"Send Success -> [{service_name}] package [{package_name}]\")\n        detail_obj.send_flag = 2\n        detail_obj.send_msg += f\"{self.now_time()} {service_name} 成功发送服务包\\n\"\n        detail_obj.save()\n        return True, \"Send Success\"\n\n    def unzip(self, detail_obj):\n        \"\"\" 解压服务包 \"\"\"\n        # 获取解压使用参数\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        package_name = detail_obj.service.service.app_package.package_name\n        salt_client = SaltClient()\n\n        # 更新状态为 '解压中'，记录日志\n        logger.info(\n            f\"Unzip Begin -> [{service_name}] package [{package_name}]\")\n        detail_obj.unzip_flag = 1\n        detail_obj.unzip_msg += f\"{self.now_time()} {service_name} 开始解压服务包\\n\"\n        detail_obj.save()\n\n        try:\n            # 控制单主机上的服务包解压操作，向控制队列中添加一项\n            # 如果能加入成功则继续，否则等待其加入成功\n            self.unzip_concurrent_controller[target_ip].put(service_name)\n            # 解析获取目录\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            package_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                package_name)\n            # 获取解压目标路径\n            detail_args = detail_obj.install_detail_args\n            assert detail_args is not None\n            app_name = detail_args.get(\"name\", None)\n            assert app_name is not None\n            target_path = None\n            for info in detail_args.get(\"install_args\", []):\n                if info.get(\"key\", \"\") == \"base_dir\":\n                    target_path = info.get(\"default\")\n                    break\n            if target_path is None:\n                raise GeneralError(\"未获取到解压目标路径\")\n            # 切分判断路径\n            path_ls = os.path.split(target_path)\n            # 创建服务目录，解压服务包\n            if path_ls[1] == app_name:\n                _target_path = path_ls[0]\n                test_path_cmd_str = f\"(test -d {_target_path} || mkdir -p {_target_path}) && \" \\\n                                    f\"tar -xmf {package_path} -C {_target_path}\"\n            else:\n                # 当路径结尾与服务名不一致时\n                _target_path = path_ls[0]\n                real_path = os.path.join(_target_path, app_name)\n                test_path_cmd_str = f\"(test -d {_target_path} || mkdir -p {_target_path}) && \" \\\n                                    f\"tar -xmf {package_path} -C {_target_path} && mv {real_path} {target_path}/\"\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=test_path_cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise GeneralError(message)\n\n        except Exception as err:\n            # 解压流程运行完成后报错，释放资源\n            self.unzip_concurrent_controller[target_ip].get()\n            logger.error(f\"Unzip Failed -> [{service_name}]: {err}\")\n            detail_obj.unzip_flag = 3\n            detail_obj.unzip_msg += \\\n                f\"{self.now_time()} {service_name} \" \\\n                f\"解压服务包失败: {err}\\n\"\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 解压流程运行完成后报错，释放资源\n        self.unzip_concurrent_controller[target_ip].get()\n        # 解压成功\n        logger.info(\n            f\"Unzip Success -> [{service_name}] package [{package_name}]\")\n        detail_obj.unzip_flag = 2\n        detail_obj.unzip_msg += \\\n            f\"{self.now_time()} {service_name} 成功解压服务包\\n\"\n        detail_obj.save()\n        return True, \"Unzip Success\"\n\n    def install(self, detail_obj):\n        \"\"\" 安装服务 \"\"\"\n        # 获取安装使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        app_name = detail_obj.service.service.app_name\n        # edit by jon.liu service_controllers 为json字段，无需json.loads\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '安装中'，记录日志\n        logger.info(f\"Install Begin -> [{service_name}]\")\n        detail_obj.install_flag = 1\n        detail_obj.install_msg += \\\n            f\"{self.now_time()} {service_name} 开始安装服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务安装脚本绝对路径\n            install_script_path = service_controllers_dict.get(\"install\", \"\")\n            if install_script_path == \"\":\n                raise GeneralError(\"未找到安装脚本路径\")\n\n            # 获取 json 文件路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            json_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            cmd_str = f\"python {install_script_path} --local_ip {target_ip} \" \\\n                      f\"--data_json {json_path}\"\n            # doim定制化安装\n            if app_name.lower() == DOIM_APP_NAME:\n                install_script_path = os.path.realpath(install_script_path)\n                app_dir, script_name = os.path.split(install_script_path)\n                doim_install_script_path = os.path.join(app_dir, 'install.sh')\n                install_dir, _service_name = os.path.split(app_dir)\n                cmd_str = f\"sed -i -e \\\"s#InstallRoot=.*#InstallRoot={install_dir}/DOIM #g\\\" -e \\\"s#char=.*#char=\\\"y\\\"#g\\\" {doim_install_script_path}; python {install_script_path} --local_ip {target_ip} \" \\\n                          f\"--data_json {json_path}\"\n\n            # 执行安装\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise GeneralError(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += \\\n                    f\"{self.now_time()} 安装脚本执行成功，脚本输出如下:\\n\" \\\n                    f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Install Failed -> [{service_name}]: {err}\")\n            detail_obj.install_flag = 3\n            detail_obj.install_msg += f\"{self.now_time()} {service_name} \" \\\n                                      f\"安装服务失败: {err}\\n\"\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n\n        # 安装成功\n        logger.info(f\"Install Success -> [{service_name}]\")\n        detail_obj.install_flag = 2\n        detail_obj.install_msg += \\\n            f\"{self.now_time()} {service_name} 成功安装服务\\n\"\n        detail_obj.save()\n        return True, \"Install Success\"\n\n    def init(self, detail_obj):\n        \"\"\" 初始化服务 \"\"\"\n        # 获取初始化使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '初始化中'，记录日志\n        logger.info(f\"Init Begin -> [{service_name}]\")\n        detail_obj.init_flag = 1\n        detail_obj.init_msg += f\"{self.now_time()} {service_name} 开始初始化服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务初始化脚本绝对路径\n            init_script_path = service_controllers_dict.get(\"init\", \"\")\n            if init_script_path == \"\":\n                logger.info(f\"Init Un Do -> [{service_name}]\")\n                detail_obj.init_flag = 2\n                detail_obj.init_msg += \\\n                    f\"{self.now_time()} {service_name} 无需执行初始化\\n\"\n                # 完成安装流程，更新状态为 '安装成功'\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                detail_obj.save()\n                # 创建历史记录\n                self.create_history(detail_obj, is_success=True)\n                return True, \"Init Un Do\"\n\n            # 获取 json 文件路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            json_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            cmd_str = f\"python {init_script_path} --local_ip {target_ip} \" \\\n                      f\"--data_json {json_path}\"\n            # 执行初始化\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise GeneralError(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += \\\n                    f\"{self.now_time()} 初始化脚本执行成功，脚本输出如下:\\n\" \\\n                    f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Init Failed -> [{service_name}]: {err}\")\n            detail_obj.init_flag = 3\n            detail_obj.init_msg += f\"{self.now_time()} {service_name} \" \\\n                                   f\"初始化服务失败: {err}\\n\"\n            # 更新安装流程状态为 '失败'，服务状态为 '安装失败'\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 安装成功\n        logger.info(f\"Init Success -> [{service_name}]\")\n        detail_obj.init_flag = 2\n        detail_obj.init_msg += f\"{self.now_time()} {service_name} 成功初始化服务\\n\"\n        # 完成安装流程，更新状态为 '安装成功'\n        # 如果是自研服务，初始化完成即认为其安装成功\n        if detail_obj.service.service.app_type == \\\n                ApplicationHub.APP_TYPE_SERVICE:\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_SUCCESS\n            detail_obj.save()\n        # 创建历史记录\n        self.create_history(detail_obj, is_success=True)\n        return True, \"Init Success\"\n\n    def start(self, detail_obj):\n        \"\"\" 启动服务 \"\"\"\n        # 获取启动使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '启动中'，记录日志\n        logger.info(f\"Start Begin -> [{service_name}]\")\n        detail_obj.start_flag = 1\n        detail_obj.start_msg += f\"{self.now_time()} {service_name} 开始启动服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务启动脚本绝对路径\n            start_script_path = service_controllers_dict.get(\"start\", \"\")\n            if start_script_path == \"\":\n                logger.info(f\"Start Un Do -> [{service_name}]\")\n                detail_obj.start_flag = 2\n                detail_obj.start_msg += \\\n                    f\"{self.now_time()} {service_name} 无需执行启动\\n\"\n                # 如果服务无需启动，则认可其为安装成功\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                # 服务状态更新为 '正常'\n                detail_obj.service.service_status = \\\n                    Service.SERVICE_STATUS_NORMAL\n                detail_obj.service.save()\n                detail_obj.save()\n                return True, \"Start Un Do\"\n\n            if \"start\" not in start_script_path:\n                start_script_path += \" start\"\n            cmd_str = f\"bash {start_script_path}\"\n\n            # 执行启动\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout,\n                real_timeout=self.timeout\n            )\n            if not is_success:\n                raise GeneralError(message)\n            result_str = message.upper()\n            if \"FAILED\" in result_str or \\\n                    \"NO RUNNING\" in result_str or \\\n                    \"NOT RUNNING\" in result_str:\n                raise GeneralError(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += \\\n                    f\"{self.now_time()} 启动脚本执行成功，脚本输出如下:\\n\" \\\n                    f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Start Failed -> [{service_name}]: {err}\")\n            detail_obj.start_flag = 3\n            detail_obj.start_msg += f\"{self.now_time()} {service_name} \" \\\n                                    f\"启动服务失败: {err}\\n\"\n            # 如果是基础组件服务的启动步骤，如果启动失败则认为其安装失败\n            if detail_obj.service.service.app_type == \\\n                    ApplicationHub.APP_TYPE_COMPONENT:\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            # 服务状态更新为 '停止'\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_STOP\n            detail_obj.service.save()\n            return False, err\n        # 安装成功\n        logger.info(f\"Start Success -> [{service_name}]\")\n        detail_obj.start_flag = 2\n        detail_obj.start_msg += f\"{self.now_time()} {service_name} 成功启动服务\\n\"\n        detail_obj.save()\n        # 服务状态更新为 '正常'\n        detail_obj.service.service_status = \\\n            Service.SERVICE_STATUS_NORMAL\n        # 服务启动成功，则认为其已经安装成功\n        detail_obj.install_step_status = \\\n            DetailInstallHistory.INSTALL_STATUS_SUCCESS\n        detail_obj.save()\n        detail_obj.service.save()\n        return True, \"Start Success\"\n\n    def execute_post_action(self, queryset, post_obj):  # NOQA\n        \"\"\"\n        执行安装后的操作，所有服务安装完成后，针对不同服务的个性化配置执行\n        目前仅支持shell脚本方式\n        :param queryset: 包含部署详情表的列表\n        :type queryset: [DetailInstallHistory]\n        :param post_obj:\n        :return:\n        \"\"\"\n        try:\n            salt_client = SaltClient()\n            for detail_obj in queryset:\n                target_ip = detail_obj.service.ip\n                _script = detail_obj.service.service_controllers.get(\n                    \"post_action\")\n                command = f\"chmod +x {_script.split()[0]} && {_script}\"\n                post_obj.install_log += \\\n                    f\"{self.now_time()} 在{target_ip}节点执行 {command}\\n\"\n                post_obj.save()\n                flag, msg = salt_client.cmd(\n                    target=target_ip,\n                    command=command,\n                    timeout=60\n                )\n                post_obj.install_log += \\\n                    f\"{self.now_time()} \" \\\n                    f\"在{target_ip}节点执行 {command} 标志为: {flag}; 结果为: {msg}\\n\"\n                post_obj.save()\n                logger.info(f\"Execute {_script}, flag: {flag}; msg: {msg}\")\n                detail_obj.post_action_msg += str(msg)\n                if not flag:\n                    self.is_error = True\n                    detail_obj.post_action_flag = 3\n                    detail_obj.save()\n                    post_obj.install_flag = 3\n                    post_obj.save()\n                    break\n                detail_obj.post_action_flag = 2\n                detail_obj.save()\n        except Exception as e:\n            logger.error(f\"Error while execute post_action: {str(e)}\")\n            self.is_error = True\n            post_obj.install_flag = 3\n            post_obj.save()\n\n    def single_service_executor(self, detail_obj):\n        \"\"\"\n        单独服务的安装执行器\n        :param detail_obj:\n        :type detail_obj: DetailInstallHistory\n        :return:\n        \"\"\"\n        # 更改服务状态为安装中状态\n        detail_obj.service.service_status = Service.SERVICE_STATUS_INSTALLING\n        detail_obj.service.save()\n        # 针对单个服务执行循环(\"send\", \"unzip\", \"install\", \"init\", \"start\")\n        # 跳过单个服务的已经成功的单个步骤不再重复执行\n        for action in self.ACTION_LS:\n            if getattr(detail_obj, f\"{action}_flag\") == 2:\n                continue\n            _flag, _msg = getattr(self, action)(detail_obj)\n            if not _flag:\n                return _flag, _msg\n        return True, \"success\"\n\n    def high_availability_executor(self, detail_obj_lst):\n        \"\"\"\n        过滤专属高可用阻塞部署\n        :param detail_obj_lst: [detail_obj, detail_obj]\n        :return:\n        \"\"\"\n        deep_detail_obj_lst = copy.deepcopy(detail_obj_lst)\n        tmp_dict = {}\n        for detail_obj in deep_detail_obj_lst:\n            app_name = detail_obj.service.service.app_name\n            if app_name in HIGH_AVAILABILITY_UTILS.keys():\n                tmp_dict[app_name] = tmp_dict.get(app_name, []) + [detail_obj]\n                detail_obj_lst.remove(detail_obj)\n        for name, obj in tmp_dict.items():\n            # TODO 这个需要多线程处理\n            HIGH_AVAILABILITY_UTILS[name](self, obj).high_thread_executor()\n        return detail_obj_lst\n\n    def thread_poll_executor(self, detail_obj_lst):\n        \"\"\"\n        多线程执行器\n        :param detail_obj_lst: [detail_obj, detail_obj]\n        :return:\n        \"\"\"\n        logger.info(f\"Start thread poll executor for {detail_obj_lst}\")\n        # 阻塞部署hadoop等\n        detail_obj_lst = self.high_availability_executor(detail_obj_lst)\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            _future_list = []\n            for detail_obj in detail_obj_lst:\n                # 更新单条安装记录的状态\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_INSTALLING\n                detail_obj.save()\n                future_obj = executor.submit(\n                    self.single_service_executor, detail_obj\n                )\n                _future_list.append(future_obj)\n            for future in as_completed(_future_list):\n                is_success, message = future.result()\n                if not is_success:\n                    self.is_error = True\n                    break\n        logger.info(\"Finish thread poll executor!\")\n\n    @staticmethod\n    def make_install_order(queryset):\n        \"\"\"\n        对所有的安装对象进行排序处理，控制其安装顺序\n        :param queryset: 即将部署的详情对象组成的列表\n        :return:\n        \"\"\"\n        # 安装顺序的二维数组\n        execute_lst = list()\n        # 对基础组件进行排序处理，其中基础配置中的 BASIC_ORDER 为基础组件的排序等级\n        # 如果有其他组件需要安装，怎需要在配置中进行额外的配置\n        for i in range(10):\n            if i not in BASIC_ORDER:\n                break\n            _lst = [\n                el for el in queryset\n                if el.service.service.app_name in BASIC_ORDER[i]\n            ]\n            execute_lst.append(_lst)\n        # 对自研服务进行排序处理，先过滤出自研服务的列表\n        _ser = [\n            el for el in queryset\n            if el.service.service.app_type == ApplicationHub.APP_TYPE_SERVICE\n        ]\n        # 自研服务level级别为0的服务，仅依赖于基础组件，无其他依赖\n        execute_lst.append(\n            [\n                el for el in _ser if\n                str(el.service.service.extend_fields.get(\"level\")) == \"0\"]\n        )\n        # 自研服务level级别为1或其他的服务\n        # 可依赖基础组件，也可依赖其他自研服务，文件级别位置依赖\n        execute_lst.append(\n            [\n                el for el in _ser if\n                str(el.service.service.extend_fields.get(\"level\")) != \"0\"]\n        )\n        return execute_lst\n\n    def execute_post_action_main(self, main_obj):\n        \"\"\"\n        执行注册操作 post_action\n        :param main_obj:\n        :return:\n        \"\"\"\n        post_obj = PostInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).last()\n        if not post_obj:\n            logger.info(\"No need execute post action!\")\n            return True, \"success\"\n        # 安装后执行动作范围过滤，排除无需执行操作以及排除已经执行成功的服务对象\n        # 判断整体执行安装完成后才执行\n        if not DetailInstallHistory.objects.filter(\n                install_step_status=DetailInstallHistory.INSTALL_STATUS_FAILED,\n                main_install_history_id=self.main_id\n        ).exists() and not self.is_error:\n            post_action_queryset = DetailInstallHistory.objects.select_related(\n                \"service\", \"service__service\",\n                \"service__service__app_package\"\n            ).filter(main_install_history_id=self.main_id).exclude(\n                post_action_flag__in=[2, 4]\n            )\n            main_obj.install_status = \\\n                MainInstallHistory.INSTALL_STATUS_REGISTER\n            main_obj.save()\n            self.execute_post_action(post_action_queryset, post_obj)\n        if not self.is_error:\n            post_obj.install_flag = 2\n            post_obj.save()\n            return True, \"success\"\n        post_obj.install_flag = 3\n        post_obj.save()\n        return False, \"failed\"\n\n    def execute_pre_install(self, main_obj):\n        \"\"\"\n        执行前置安装入口\n        :param main_obj:\n        :return:\n        \"\"\"\n        try:\n            self.create_user_and_send_json(main_obj)\n        except Exception as e:\n            logger.error(f\"Pre-install failed: {str(e)}\")\n        if PreInstallHistory.objects.filter(\n                main_install_history=main_obj,\n                install_flag__in=[0, 1, 3]\n        ).exists():\n            return False\n        return True\n\n    def execute_post_install(self, main_obj):\n        \"\"\"\n        执行安装后的操作，主要是针对nacos、tengine进行配置更新\n        :param main_obj:\n        :return:\n        \"\"\"\n        post_obj = PostInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).last()\n        if not post_obj:\n            logger.info(\"No need execute post action!\")\n            return True, \"success\"\n        if post_obj.install_flag == 2:\n            return True, \"success\"\n        post_obj.install_flag = 1\n        post_obj.save()\n        # 确定重新加载的服务 tengine & nacos & aopsUtils\n        # if DetailInstallHistory.objects.filter(\n        #         service__service__app_name__in=[\n        #             \"tengine\", \"nacos\", \"aopsUtils\"]\n        # ).exclude(main_install_history=main_obj).exists():\n        #     for key, value in POST_INSTALL_SERVICE.items():\n        #         if not DetailInstallHistory.objects.filter(\n        #                 service__service__app_name=key).exclude(\n        #             main_install_history=main_obj\n        #         ).exists():\n        #             continue\n        #         post_obj.install_log += \\\n        #             f\"{self.now_time()} 开始执行 {key} 安装后续任务\\n\"\n        #         post_obj.save()\n        #         _flag, _msg = value(main_obj=main_obj).run()\n        #         post_obj.install_log += \\\n        #             f\"{self.now_time()} \" \\\n        #             f\"{key} 安装后续任务执行标志: {_flag}; 执行结果为: {_msg}\\n\"\n        #         post_obj.save()\n        #         if not _flag:\n        #             post_obj.install_flag = 3\n        #             post_obj.save()\n        #             return False, \"execute post install action failed\"\n\n        # TODO 在增量安装的前提下，需要在加载nacos和tengine完成后，再启动其他自研服务\n        if self.is_error:\n            post_obj.install_flag = 3\n            post_obj.save()\n            return False, \"service start failed\"\n        post_obj.install_flag = 2\n        post_obj.save()\n        return True, \"success\"\n\n    @staticmethod\n    def get_openssl_version_from_cmd(ip, salt_client, cmd_str):\n        \"\"\"获取openssl版本号\"\"\"\n        ssl_flag, ssl_msg = salt_client.cmd(\n            target=ip,\n            command=cmd_str,\n            timeout=60\n        )\n        str_os_version = ssl_msg.strip()\n        res = re.findall(\n            r'(.*?) ([0-9]+)\\.([0-9]+)\\.([0-9]+).*', str_os_version)\n        str_version = \"\".join(res[0][1:]) if res else \"\"\n        if not ssl_flag:\n            return 0\n        try:\n            ssl_version = int(str_version)\n        except ValueError:\n            ssl_version = 0\n        return ssl_version\n\n    @staticmethod\n    def upgrade_openssl(ip, salt_client):\n        # 1.发送openssl升级包\n        source_package_path = \"openssl_upgrade/openssl-1.0.2k.tar.gz\"\n        dst_path = \"/tmp/upgrade_openssl\"\n        dst_path_package = os.path.join(dst_path, \"openssl-1.0.2k.tar.gz\")\n        package_flag, package_msg = salt_client.cp_file(\n            target=ip,\n            source_path=source_package_path,\n            target_path=dst_path_package)\n        if not package_flag:\n            return False, package_msg\n        # 2.发送openssl升级脚本\n        source_script_path = \"openssl_upgrade/upgrade_ssl.sh\"\n        dst_script_path = os.path.join(dst_path, \"upgrade_ssl.sh\")\n        script_flag, script_msg = salt_client.cp_file(\n            target=ip,\n            source_path=source_script_path,\n            target_path=dst_script_path)\n        if not script_flag:\n            return False, script_msg\n        # 3.执行openssl升级脚本\n        cmd_str = \"cd /tmp/upgrade_openssl && chmod +x upgrade_ssl.sh && bash upgrade_ssl.sh\"\n        cmd_flag, cmd_msg = salt_client.cmd(\n            target=ip,\n            command=cmd_str,\n            timeout=60\n        )\n        if not cmd_flag:\n            return False, cmd_msg\n        return True, \"upgrade openssl success\"\n\n    def main(self):\n        \"\"\" 主函数 \"\"\"\n        logger.info(f\"Main Install Begin, id[{self.main_id}]\")\n        # 获取主表对象，更新状态为 '安装中'\n        main_obj = MainInstallHistory.objects.filter(\n            id=self.main_id).first()\n        assert main_obj is not None\n        main_obj.install_status = \\\n            MainInstallHistory.INSTALL_STATUS_INSTALLING\n        main_obj.save()\n\n        # 执行安装前的操作\n        pre_install_flag = self.execute_pre_install(main_obj=main_obj)\n        if not pre_install_flag:\n            main_obj.install_status = MainInstallHistory.INSTALL_STATUS_FAILED\n            main_obj.save()\n            return\n\n        # 执行安装后的操作\n        _post_install_flag, _post_install_msg = self.execute_post_install(\n            main_obj=main_obj\n        )\n        if not _post_install_flag:\n            self.is_error = True\n            main_obj.install_status = MainInstallHistory.INSTALL_STATUS_FAILED\n            main_obj.save()\n            return\n\n        # 获取所有安装细节表，排除已经安装成功的记录，不再重复安装\n        queryset = DetailInstallHistory.objects.select_related(\n            \"service\", \"service__service\", \"service__service__app_package\"\n        ).filter(main_install_history_id=self.main_id).exclude(\n            install_step_status=DetailInstallHistory.INSTALL_STATUS_SUCCESS)\n        # assert queryset.exists()\n\n        # 构建 unzip_concurrent_controller 用于控制解压安装包时单主机的并发数量\n        ips = set([el.service.ip for el in queryset])\n        self.unzip_concurrent_controller = {\n            el: queue.Queue(maxsize=UNZIP_CONCURRENT_ONE_HOST) for el in ips\n        }\n\n        # 所有子流程状态更新为 '待安装'\n        queryset.update(\n            install_step_status=DetailInstallHistory.INSTALL_STATUS_READY,\n        )\n        # 将要安装的服务实例更新为 '待安装'\n        service_ids = queryset.values_list(\"service_id\", flat=True)\n        Service.objects.filter(\n            id__in=service_ids\n        ).update(service_status=Service.SERVICE_STATUS_READY)\n\n        # 对要执行安装的列表进行排序处理\n        tobe_execute_lst = self.make_install_order(queryset)\n        logger.info(f\"Tobe_execute_lst: {tobe_execute_lst}\")\n        for item in tobe_execute_lst:\n            if not item:\n                continue\n            # 根据安装顺序每层并发执行\n            self.thread_poll_executor(detail_obj_lst=item)\n            # 如果哪层的服务有安装失败的情况，那么直接退出循环\n            if self.is_error:\n                break\n\n        if self.is_error:\n            # 步骤失败，主流程失败\n            main_obj.install_status = \\\n                MainInstallHistory.INSTALL_STATUS_FAILED\n            main_obj.save()\n            # 安装完成后不再更改服务的状态\n            # 状态为 '待安装'/'安装中' 的记录，则记为 '失败'\n            # queryset.filter(install_step_status__in=(\n            #     DetailInstallHistory.INSTALL_STATUS_READY,\n            #     DetailInstallHistory.INSTALL_STATUS_INSTALLING\n            # )).update(\n            #     install_step_status=DetailInstallHistory.INSTALL_STATUS_FAILED\n            # )\n            logger.info(f\"Main Install Failed, id[{self.main_id}]\")\n            return self.is_error\n\n        post_flag, post_msg = self.execute_post_action_main(main_obj=main_obj)\n        if not post_flag:\n            main_obj.install_status = \\\n                MainInstallHistory.INSTALL_STATUS_FAILED\n            main_obj.save()\n            logger.error(f\"Main Install Failed, id[{self.main_id}]\")\n            return self.is_error\n\n        # 流程执行完整，主流程成功\n        main_obj.install_status = \\\n            MainInstallHistory.INSTALL_STATUS_SUCCESS\n        main_obj.save()\n        # doim安装成功后，相关操作\n        doim_ids = list(\n            DetailInstallHistory.objects.filter(\n                main_install_history_id=self.main_id,\n                service__service_instance_name__startswith=DOIM_APP_NAME\n            ).values_list(\"service_id\", flat=True)\n        )\n        if doim_ids:\n            service_splitting(doim_ids)\n        logger.info(f\"Main Install Success, id[{self.main_id}]\")\n        return self.is_error\n"
  },
  {
    "path": "omp_server/app_store/install_executor.py",
    "content": "\"\"\"\n安装执行器\n\"\"\"\nimport os\nimport time\nimport logging\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom django.db.models import F\nfrom db_models.models import (\n    Host, Service, HostOperateLog, ServiceHistory,\n    MainInstallHistory, DetailInstallHistory\n)\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.parse_config import THREAD_POOL_MAX_WORKERS\n\nlogger = logging.getLogger(\"server\")\n\n\nclass InstallServiceExecutor:\n    \"\"\" 安装服务执行器 \"\"\"\n    ACTION_LS = (\"send\", \"unzip\", \"install\", \"init\", \"start\")\n\n    def __init__(self, main_id, username, timeout=300):\n        self.main_id = main_id\n        self.username = username\n        self.timeout = timeout\n        # 安装中是否发生错误，用于流程控制\n        self.is_error = False\n\n    @staticmethod\n    def now_time():\n        return time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n\n    def create_history(self, detail_obj, is_success=True):\n        \"\"\" 创建历史记录 \"\"\"\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        # 写入主机历史记录、服务历史记录\n        target_host = Host.objects.filter(ip=target_ip).first()\n        HostOperateLog.objects.create(\n            username=self.username,\n            description=f\"安装服务 [{service_name}]\",\n            result=\"success\" if is_success else \"failed\",\n            host=target_host)\n        ServiceHistory.objects.create(\n            username=self.username,\n            description=\"安装服务\",\n            result=\"success\" if is_success else \"failed\",\n            service=detail_obj.service)\n        # 主机服务数量+1\n        if is_success:\n            Host.objects.filter(ip=target_ip).update(\n                service_num=F(\"service_num\") + 1)\n\n    def send(self, detail_obj):\n        \"\"\" 发送服务包 \"\"\"\n        # 获取发送使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        package_name = detail_obj.service.service.app_package.package_name\n\n        # 更新状态为 '发送中'，记录日志\n        logger.info(f\"Send Begin -> [{service_name}] package [{package_name}]\")\n        detail_obj.send_flag = 1\n        detail_obj.send_msg += f\"{self.now_time()} {service_name} 开始发送服务包\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取目标路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n\n            # 获取 json 文件路径\n            json_source_path = os.path.join(\n                \"data_files\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n            json_target_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            # 发送 json 文件\n            is_success, message = salt_client.cp_file(\n                target=target_ip,\n                source_path=json_source_path,\n                target_path=json_target_path)\n            if not is_success:\n                raise Exception(f\"发送 json 文件失败: {message}\")\n\n            # 获取服务包路径\n            source_path = os.path.join(\n                detail_obj.service.service.app_package.package_path,\n                package_name)\n            target_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                package_name)\n\n            # 发送服务包\n            is_success, message = salt_client.cp_file(\n                target=target_ip,\n                source_path=source_path,\n                target_path=target_path)\n            if not is_success:\n                raise Exception(message)\n\n        except Exception as err:\n            logger.error(f\"Send Failed -> [{service_name}]: {err}\")\n            detail_obj.send_flag = 3\n            detail_obj.send_msg += f\"{self.now_time()} {service_name} \" \\\n                                   f\"发送服务包失败: {err}\\n\"\n            detail_obj.install_step_status = DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 发送成功\n        logger.info(\n            f\"Send Success -> [{service_name}] package [{package_name}]\")\n        detail_obj.send_flag = 2\n        detail_obj.send_msg += f\"{self.now_time()} {service_name} 成功发送服务包\\n\"\n        detail_obj.save()\n        return True, \"Send Success\"\n\n    def unzip(self, detail_obj):\n        \"\"\" 解压服务包 \"\"\"\n        # 获取解压使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        package_name = detail_obj.service.service.app_package.package_name\n\n        # 更新状态为 '解压中'，记录日志\n        logger.info(\n            f\"Unzip Begin -> [{service_name}] package [{package_name}]\")\n        detail_obj.unzip_flag = 1\n        detail_obj.unzip_msg += f\"{self.now_time()} {service_name} 开始解压服务包\\n\"\n        detail_obj.save()\n\n        try:\n            # 解析获取目录\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            package_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                package_name)\n            # 获取解压目标路径\n            detail_args = detail_obj.install_detail_args\n            assert detail_args is not None\n            app_name = detail_args.get(\"name\", None)\n            assert app_name is not None\n            target_path = None\n            for info in detail_args.get(\"install_args\", []):\n                if info.get(\"key\", \"\") == \"base_dir\":\n                    target_path = info.get(\"default\")\n                    break\n            if target_path is None:\n                raise Exception(\"未获取到解压目标路径\")\n            # 切分判断路径\n            path_ls = os.path.split(target_path)\n            # 创建服务目录，解压服务包\n            if path_ls[1] == app_name:\n                _target_path = path_ls[0]\n                test_path_cmd_str = f\"(test -d {_target_path} || mkdir -p {_target_path}) && \" \\\n                                    f\"tar -xmf {package_path} -C {_target_path}\"\n            else:\n                # 当路径结尾与服务名不一致时\n                _target_path = path_ls[0]\n                real_path = os.path.join(_target_path, app_name)\n                test_path_cmd_str = f\"(test -d {_target_path} || mkdir -p {_target_path}) && \" \\\n                                    f\"tar -xmf {package_path} -C {_target_path} && mv {real_path} {target_path}/\"\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=test_path_cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise Exception(message)\n\n        except Exception as err:\n            logger.error(f\"Unzip Failed -> [{service_name}]: {err}\")\n            detail_obj.unzip_flag = 3\n            detail_obj.unzip_msg += f\"{self.now_time()} {service_name} \" \\\n                                    f\"解压服务包失败: {err}\\n\"\n            detail_obj.install_step_status = DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 解压成功\n        logger.info(\n            f\"Unzip Success -> [{service_name}] package [{package_name}]\")\n        detail_obj.unzip_flag = 2\n        detail_obj.unzip_msg += f\"{self.now_time()} {service_name} 成功解压服务包\\n\"\n        detail_obj.save()\n        return True, \"Unzip Success\"\n\n    def install(self, detail_obj):\n        \"\"\" 安装服务 \"\"\"\n        # 获取安装使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        # edit by jon.liu service_controllers 为json字段，无需json.loads\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '安装中'，记录日志\n        logger.info(f\"Install Begin -> [{service_name}]\")\n        detail_obj.install_flag = 1\n        detail_obj.install_msg += f\"{self.now_time()} {service_name} 开始安装服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务安装脚本绝对路径\n            install_script_path = service_controllers_dict.get(\"install\", \"\")\n            if install_script_path == \"\":\n                raise Exception(\"未找到安装脚本路径\")\n\n            # 获取 json 文件路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            json_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            cmd_str = f\"python {install_script_path} --local_ip {target_ip} --data_json {json_path}\"\n\n            # 执行安装\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise Exception(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += f\"{self.now_time()} 安装脚本执行成功，脚本输出如下:\\n\" \\\n                                          f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Install Failed -> [{service_name}]: {err}\")\n            detail_obj.install_flag = 3\n            detail_obj.install_msg += f\"{self.now_time()} {service_name} \" \\\n                                      f\"安装服务失败: {err}\\n\"\n            detail_obj.install_step_status = DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 安装成功\n        logger.info(f\"Install Success -> [{service_name}]\")\n        detail_obj.install_flag = 2\n        detail_obj.install_msg += f\"{self.now_time()} {service_name} 成功安装服务\\n\"\n        detail_obj.save()\n        return True, \"Install Success\"\n\n    def init(self, detail_obj):\n        \"\"\" 初始化服务 \"\"\"\n        # 获取初始化使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '初始化中'，记录日志\n        logger.info(f\"Init Begin -> [{service_name}]\")\n        detail_obj.init_flag = 1\n        detail_obj.init_msg += f\"{self.now_time()} {service_name} 开始初始化服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务初始化脚本绝对路径\n            init_script_path = service_controllers_dict.get(\"init\", \"\")\n            if init_script_path == \"\":\n                logger.info(f\"Init Un Do -> [{service_name}]\")\n                detail_obj.init_flag = 2\n                detail_obj.init_msg += f\"{self.now_time()} {service_name} 无需执行初始化\\n\"\n                # 完成安装流程，更新状态为 '安装成功'\n                detail_obj.install_step_status = \\\n                    DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                detail_obj.save()\n                # 创建历史记录\n                self.create_history(detail_obj, is_success=True)\n                return True, \"Init Un Do\"\n\n            # 获取 json 文件路径\n            target_host = Host.objects.filter(ip=target_ip).first()\n            assert target_host is not None\n            json_path = os.path.join(\n                target_host.data_folder, \"omp_packages\",\n                f\"{detail_obj.main_install_history.operation_uuid}.json\")\n\n            cmd_str = f\"python {init_script_path} --local_ip {target_ip} \" \\\n                      f\"--data_json {json_path}\"\n            # 执行初始化\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise Exception(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += \\\n                    f\"{self.now_time()} 初始化脚本执行成功，脚本输出如下:\\n\" \\\n                    f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Init Failed -> [{service_name}]: {err}\")\n            detail_obj.init_flag = 3\n            detail_obj.init_msg += f\"{self.now_time()} {service_name} \" \\\n                                   f\"初始化服务失败: {err}\\n\"\n            # 更新安装流程状态为 '失败'，服务状态为 '安装失败'\n            detail_obj.install_step_status = \\\n                DetailInstallHistory.INSTALL_STATUS_FAILED\n            detail_obj.save()\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_INSTALL_FAILED\n            detail_obj.service.save()\n            # 创建历史记录\n            self.create_history(detail_obj, is_success=False)\n            return False, err\n        # 安装成功\n        logger.info(f\"Init Success -> [{service_name}]\")\n        detail_obj.init_flag = 2\n        detail_obj.init_msg += f\"{self.now_time()} {service_name} 成功初始化服务\\n\"\n        # 完成安装流程，更新状态为 '安装成功'\n        detail_obj.install_step_status = \\\n            DetailInstallHistory.INSTALL_STATUS_SUCCESS\n        detail_obj.save()\n        # 创建历史记录\n        self.create_history(detail_obj, is_success=True)\n        return True, \"Init Success\"\n\n    def start(self, detail_obj):\n        \"\"\" 启动服务 \"\"\"\n        # 获取启动使用参数\n        salt_client = SaltClient()\n        target_ip = detail_obj.service.ip\n        service_name = detail_obj.service.service_instance_name\n        service_controllers_dict = detail_obj.service.service_controllers\n\n        # 更新状态为 '启动中'，记录日志\n        logger.info(f\"Start Begin -> [{service_name}]\")\n        detail_obj.start_flag = 1\n        detail_obj.start_msg += f\"{self.now_time()} {service_name} 开始启动服务\\n\"\n        detail_obj.save()\n\n        try:\n            # 获取服务启动脚本绝对路径\n            start_script_path = service_controllers_dict.get(\"start\", \"\")\n            if start_script_path == \"\":\n                logger.info(f\"Start Un Do -> [{service_name}]\")\n                detail_obj.start_flag = 2\n                detail_obj.start_msg += f\"{self.now_time()} {service_name} 无需执行启动\\n\"\n                # 服务状态更新为 '正常'\n                detail_obj.service.service_status = \\\n                    Service.SERVICE_STATUS_NORMAL\n                detail_obj.service.save()\n                detail_obj.save()\n                return True, \"Start Un Do\"\n            if \"start\" not in start_script_path:\n                start_script_path += \" start\"\n            cmd_str = f\"bash {start_script_path}\"\n\n            # 执行启动\n            is_success, message = salt_client.cmd(\n                target=target_ip,\n                command=cmd_str,\n                timeout=self.timeout)\n            if not is_success:\n                raise Exception(message)\n            result_str = message.upper()\n            if \"FAILED\" in result_str or \\\n                    \"NO RUNNING\" in result_str or \\\n                    \"NOT RUNNING\" in result_str:\n                raise Exception(message)\n            # 执行成功且 message 有值，则补充至服务日志中\n            if is_success and bool(message):\n                detail_obj.install_msg += \\\n                    f\"{self.now_time()} 启动脚本执行成功，脚本输出如下:\\n\" \\\n                    f\"{message}\\n\"\n                detail_obj.save()\n        except Exception as err:\n            logger.error(f\"Start Failed -> [{service_name}]: {err}\")\n            detail_obj.start_flag = 3\n            detail_obj.start_msg += f\"{self.now_time()} {service_name} \" \\\n                                    f\"启动服务失败: {err}\\n\"\n            detail_obj.save()\n            # 服务状态更新为 '停止'\n            detail_obj.service.service_status = \\\n                Service.SERVICE_STATUS_STOP\n            detail_obj.service.save()\n            return False, err\n        # 安装成功\n        logger.info(f\"Start Success -> [{service_name}]\")\n        detail_obj.start_flag = 2\n        detail_obj.start_msg += f\"{self.now_time()} {service_name} 成功启动服务\\n\"\n        detail_obj.save()\n        # 服务状态更新为 '正常'\n        detail_obj.service.service_status = \\\n            Service.SERVICE_STATUS_NORMAL\n        detail_obj.service.save()\n        return True, \"Start Success\"\n\n    @staticmethod\n    def _is_base_env(detail_obj):\n        \"\"\" 是否为依赖项，优先执行 \"\"\"\n        is_base_env = False\n        try:\n            base_env = detail_obj.service.service.extend_fields.get(\n                \"base_env\", \"\")\n            if isinstance(base_env, str):\n                base_env = base_env.lower()\n            if base_env in (True, \"true\"):\n                is_base_env = True\n        except Exception:\n            pass\n        return is_base_env\n\n    @staticmethod\n    def _is_dependency(detail_obj):\n        \"\"\" 是否为依赖项，优先执行 \"\"\"\n        return False\n\n    def main(self):\n        \"\"\" 主函数 \"\"\"\n        logger.info(f\"Main Install Begin, id[{self.main_id}]\")\n        # 获取主表对象，更新状态为 '安装中'\n        main_obj = MainInstallHistory.objects.filter(\n            id=self.main_id).first()\n        assert main_obj is not None\n        main_obj.install_status = \\\n            MainInstallHistory.INSTALL_STATUS_INSTALLING\n        main_obj.save()\n\n        # 获取所有安装细节表\n        queryset = DetailInstallHistory.objects.select_related(\n            \"service\", \"service__service\", \"service__service__app_package\"\n        ).filter(main_install_history_id=self.main_id)\n        assert queryset.exists()\n\n        # 所有子流程状态更新为 '安装中'\n        queryset.update(\n            install_step_status=DetailInstallHistory.INSTALL_STATUS_INSTALLING)\n\n        # 区分服务列表切分\n        base_env_ls = []\n        dependency_ls = []\n        no_dependency_ls = []\n        for detail_obj in queryset:\n            if self._is_base_env(detail_obj):\n                base_env_ls.append(detail_obj)\n            elif self._is_dependency(detail_obj):\n                dependency_ls.append(detail_obj)\n            else:\n                no_dependency_ls.append(detail_obj)\n        logger.info(f\"基础环境列表 [base_env_ls] -- {base_env_ls}\")\n        logger.info(f\"含依赖列表 [dependency_ls] -- {dependency_ls}\")\n        logger.info(f\"非依赖列表 [no_dependency_ls] -- {no_dependency_ls}\")\n        # TODO 含依赖项列表 -> 安装顺序排序？\n\n        # 先执行 base_env 基础环境列表安装流程\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            logger.info(\"Begin base_env install\")\n            for action in self.ACTION_LS:\n                # ---- 基础环境列表并发 ----\n                if self.is_error:\n                    break\n                _future_list_env = []\n                for detail_obj in base_env_ls:\n                    future_obj = executor.submit(\n                        getattr(self, action), detail_obj)\n                    _future_list_env.append(future_obj)\n                for future in as_completed(_future_list_env):\n                    is_success, message = future.result()\n                    if not is_success:\n                        self.is_error = True\n                        break\n            logger.info(\"End base_env install\")\n\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            # 轮询流程列表，进行安装\n            logger.info(\"Begin else install\")\n            for action in self.ACTION_LS:\n                logger.info(f\"Enter [{action}]\")\n                # ---- 含依赖项列表轮询 ----\n                if self.is_error:\n                    break\n                for detail_obj in dependency_ls:\n                    is_success, message = getattr(self, action)(detail_obj)\n                    if not is_success:\n                        self.is_error = True\n                        break\n                # ---- 非依赖项列表并发 ----\n                if self.is_error:\n                    break\n                _future_list = []\n                for detail_obj in no_dependency_ls:\n                    future_obj = executor.submit(\n                        getattr(self, action), detail_obj)\n                    _future_list.append(future_obj)\n                for future in as_completed(_future_list):\n                    is_success, message = future.result()\n                    if not is_success:\n                        self.is_error = True\n                        break\n            logger.info(\"End else install\")\n\n        if self.is_error:\n            # 步骤失败，主流程失败\n            main_obj.install_status = \\\n                MainInstallHistory.INSTALL_STATUS_FAILED\n            main_obj.save()\n            # 状态为 '待安装'/'安装中' 的记录，则记为 '失败'\n            queryset.filter(install_step_status__in=(\n                DetailInstallHistory.INSTALL_STATUS_READY,\n                DetailInstallHistory.INSTALL_STATUS_INSTALLING\n            )).update(install_step_status=DetailInstallHistory.INSTALL_STATUS_FAILED)\n            logger.info(f\"Main Install Failed, id[{self.main_id}]\")\n            return self.is_error\n\n        # 流程执行完整，主流程成功\n        main_obj.install_status = \\\n            MainInstallHistory.INSTALL_STATUS_SUCCESS\n        main_obj.save()\n        logger.info(f\"Main Install Success, id[{self.main_id}]\")\n        return self.is_error\n"
  },
  {
    "path": "omp_server/app_store/install_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: install_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-24 14:11\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装过程中页面显示数据解析工具\n\"\"\"\n\nimport os\nimport json\nimport uuid\nfrom copy import deepcopy\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom django.db import transaction\n\nfrom omp_server.settings import PROJECT_DIR\nfrom db_models.models import (\n    Env,\n    Host,\n    ApplicationHub,\n    ProductHub,\n    Product,\n    Service,\n    ClusterInfo,\n    ServiceConnectInfo,\n    MainInstallHistory,\n    DetailInstallHistory\n)\nfrom app_store.tasks import install_service\nfrom utils.common.exceptions import GeneralError\n# from utils.plugin import public_utils\nfrom utils.plugin.salt_client import SaltClient\n\nDIR_KEY = \"{data_path}\"\n\n\ndef make_lst_unique(lst, key_1, key_2):\n    \"\"\"\n    去重列表内的字典\n        lst = [{\"name\": \"1\", \"version\": \"1\"}, {\"name\": \"1\", \"version\": \"1\"}]\n        lst = make_lst_unique(lst, \"name\", \"version\")\n    :param lst: 被操作对象\n    :param key_1: 关键key1\n    :param key_2: 关键key2\n    :return:\n    \"\"\"\n    unique_dic = dict()\n    ret_lst = list()\n    for el in lst:\n        _unique = el.get(key_1, \"\") + \"_\" + el.get(key_2, \"\")\n        if _unique in unique_dic:\n            continue\n        ret_lst.append(el)\n        unique_dic[_unique] = True\n    return ret_lst\n\n\ndef make_editable(element):\n    \"\"\"\n    处理参数是否可编辑\n    :param element: 参数字典\n    :return:\n    \"\"\"\n    if element.get(\"editable\") is False or \\\n            str(element.get(\"editable\")).lower() == \"false\":\n        element[\"editable\"] = False\n    else:\n        element[\"editable\"] = True\n\n\ndef make_app_install_args(app_install_args):\n    \"\"\"\n    构建安装参数\n    :param app_install_args: 服务安装参数\n    :type app_install_args: list\n    :return:\n    \"\"\"\n    for el in app_install_args:\n        if isinstance(el.get(\"default\"), str) and \\\n                DIR_KEY in el.get(\"default\"):\n            el[\"default\"] = el[\"default\"].replace(DIR_KEY, \"\")\n            el[\"dir_key\"] = DIR_KEY\n        make_editable(el)\n    return app_install_args\n\n\nclass DataJson(object):\n    \"\"\" 生成data.json数据 \"\"\"\n\n    def __init__(self, operation_uuid):\n        \"\"\"\n        data.json数据生成方法\n        :param operation_uuid: 唯一操作uuid\n        :type operation_uuid: str\n        \"\"\"\n        self.operation_uuid = operation_uuid\n\n    def get_ser_install_args(self, obj):    # NOQA\n        \"\"\"\n        获取服务的安装参数\n        :param obj: Service\n        :type obj: Service\n        :return:\n        \"\"\"\n        deploy_detail = DetailInstallHistory.objects.get(service=obj)\n        install_args = \\\n            deploy_detail.install_detail_args.get(\"app_install_args\")\n        deploy_mode = \\\n            deploy_detail.install_detail_args.get(\"deploy_mode\")\n        return {\n            \"install_args\": install_args,\n            \"deploy_mode\": deploy_mode\n        }\n\n    def parse_single_service(self, obj):\n        \"\"\"\n        解析单个服务数据\n        :param obj: Service\n        :type obj: Service\n        :return:\n        \"\"\"\n        _ser_dic = {\n            \"ip\": obj.ip,\n            \"name\": obj.service.app_name,\n            \"instance_name\": obj.service_instance_name,\n            \"cluster_name\": obj.cluster.cluster_name if obj.cluster else None,\n            \"ports\": json.loads(obj.service_port) if obj.service_port else [],\n        }\n        _others = self.get_ser_install_args(obj)\n        _ser_dic.update(_others)\n        return _ser_dic\n\n    def make_data_json(self, json_lst):\n        \"\"\"\n        创建data.json数据文件\n        :param json_lst: 服务及分布信息组成的列表\n        :type json_lst: list\n        :return:\n        \"\"\"\n        _path = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/data_files\",\n            f\"{self.operation_uuid}.json\"\n        )\n        if not os.path.exists(os.path.dirname(_path)):\n            os.makedirs(os.path.dirname(_path))\n        with open(_path, \"w\", encoding=\"utf8\") as fp:\n            fp.write(json.dumps(json_lst, indent=2, ensure_ascii=False))\n\n    def run(self):\n        \"\"\"\n        生成data.json方法入口\n        :return:\n        \"\"\"\n        # step1: 获取所有的服务列表\n        all_ser_lst = Service.objects.all()\n        json_lst = list()\n        for item in all_ser_lst:\n            json_lst.append(self.parse_single_service(obj=item))\n        # step2: 生成data.json\n        self.make_data_json(json_lst=json_lst)\n\n\nclass SerDependenceParseUtils(object):\n    \"\"\"\n    依赖解决工具类\n    \"\"\"\n\n    def __init__(self, parse_name, parse_version):\n        \"\"\"\n        初始化对象, 服务级别的解析，包含自研服务和基础组件服务\n        :param parse_name: 要解析的名称，服务\n        :type parse_name: str\n        :param parse_version: 要解析的版本\n        :type parse_version: str\n        \"\"\"\n        self.parse_name = parse_name\n        self.parse_version = parse_version\n        self.unique_key = self.parse_name + self.parse_version\n\n    def get_newest_ser(self):\n        \"\"\"\n        获取最新的服务对象，同服务，同版本\n        :return: ApplicationHub\n        \"\"\"\n        return ApplicationHub.objects.filter(\n            app_name=self.parse_name,\n            app_version=self.parse_version,\n            is_release=True\n        ).last()\n\n    def get_ser_instances(self, obj):  # NOQA\n        \"\"\"\n        查看服务是否已经被安装以及应用商店内是否具备安装条件\n        返回值含义：\n            cluster_info：当前服务的集群信息\n            instance_info：当前服务的实例对象信息\n            is_pack_exist：当前服务的安装包是否存在\n        :param obj: ApplicationHub实例\n        :type obj: ApplicationHub\n        :return: cluster_info, instance_info, is_pack_exist\n        \"\"\"\n        # 判断当前服务的集群\n        cluster_info = list(ClusterInfo.objects.filter(\n            cluster_service_name=obj.app_name\n        ).values(\"id\", \"cluster_name\"))\n        # 判断当前服务的实例信息\n        instance_info = list(Service.objects.filter(\n            service__app_name=obj.app_name,\n            service__app_version=obj.app_version\n        ).values(\"ip\", \"service_instance_name\", \"id\"))\n        is_pack_exist = False\n        # 判断当前应用商店内是否包含该服务以及该服务的安装包条件\n        if obj.app_package:\n            path = os.path.join(\n                PROJECT_DIR,\n                \"package_hub\",\n                obj.app_package.package_path,\n                obj.app_package.package_name\n            )\n            if os.path.exists(path):\n                is_pack_exist = True\n        return cluster_info, instance_info, is_pack_exist\n\n    def get_deploy_mode(self, obj):     # NOQA\n        \"\"\"\n        解析服务的部署模式信息\n        [\n          {\n            \"key\": \"single\",\n            \"name\": \"单实例\"\n          }\n        ]\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: list()\n        \"\"\"\n        if not obj.extend_fields or not obj.extend_fields.get(\"deploy\", {}):\n            return [{\"key\": \"single\", \"name\": \"单实例\"}]\n        deploy_info = obj.extend_fields.get(\"deploy\", {})\n        ret_lst = list()\n        if \"single\" in deploy_info:\n            ret_lst.extend(deploy_info[\"single\"])\n        if \"complex\" in deploy_info:\n            ret_lst.extend(deploy_info[\"complex\"])\n        return ret_lst\n\n    def get_is_base_env(self, obj):     # NOQA\n        \"\"\"\n        确定当前服务是否为基础环境：如 jdk 等\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        if not obj.extend_fields:\n            return False\n        return obj.extend_fields.get(\"base_env\", False)\n\n    def get_dependence(self, lst, dep):\n        \"\"\"\n        解决服务依赖关系核心方法\n        :param lst: 存储结果的列表\n        :param dep: 服务依赖关系列表\n        :return: list()\n        \"\"\"\n        unique_key_lst = list()\n        for inner in dep:\n            _name, _version = inner.get(\"name\"), inner.get(\"version\")\n            _app = ApplicationHub.objects.filter(\n                app_name=_name,\n                app_version=_version,\n                is_release=True\n            ).order_by(\"created\").last()\n            # 定义服务&版本唯一标准，防止递归错误\n            unique_key = str(_name) + str(_version)\n            # 如果当前服务和需要被解析的源服务重叠，那么则跳过\n            # 如果当前服务的依赖关系已经被解决，那么则跳过\n            if unique_key == self.unique_key or unique_key in unique_key_lst:\n                continue\n            unique_key_lst.append(unique_key)\n            # 判断当前被依赖服务是否存在，如果不存在就直接返回，不再处理深层依赖\n            if not _app:\n                inner[\"cluster_info\"] = list()\n                inner[\"instance_info\"] = list()\n                inner[\"app_port\"] = list()\n                inner[\"app_install_args\"] = list()\n                inner[\"is_in_hub\"] = False\n                inner[\"is_base_env\"] = False\n                inner[\"is_pack_exist\"] = False\n                inner[\"deploy_mode\"] = list()\n                inner[\"process_continue\"] = False\n                inner[\"process_message\"] = f\"应用商店内未包含{_name}服务\"\n                lst.append(inner)\n                continue\n            # 查看当前服务是否被安装\n            cluster_info, instance_info, is_pack_exist = \\\n                self.get_ser_instances(obj=_app)\n            # 获取依赖服务的相关信息\n            inner[\"cluster_info\"] = cluster_info\n            inner[\"instance_info\"] = instance_info\n            # 获取依赖服务端口及其他参数信息\n            inner[\"app_port\"] = \\\n                json.loads(_app.app_port) if _app.app_port else list()\n            if _app.app_install_args:\n                _app_install_args = json.loads(_app.app_install_args)\n                inner[\"app_install_args\"] = \\\n                    make_app_install_args(_app_install_args)\n            else:\n                inner[\"app_install_args\"] = list()\n            inner[\"is_in_hub\"] = True\n            inner[\"is_base_env\"] = self.get_is_base_env(obj=_app)\n            inner[\"is_pack_exist\"] = is_pack_exist\n            inner[\"deploy_mode\"] = self.get_deploy_mode(obj=_app)\n            if cluster_info or instance_info or is_pack_exist:\n                inner[\"process_continue\"] = True\n            else:\n                inner[\"process_continue\"] = False\n                inner[\"process_message\"] = f\"服务{_name}未安装且无法找到安装包\"\n            lst.append(inner)\n            if not _app.app_dependence:\n                continue\n            _app_dependence = json.loads(_app.app_dependence)\n            self.get_dependence(\n                lst, dep=_app_dependence\n            )\n\n    def run_ser(self):\n        \"\"\"\n        解析服务的依赖关系入口\n        :return: 服务依赖关系列表\n        \"\"\"\n        _ser = self.get_newest_ser()\n        if not _ser or not _ser.app_dependence:\n            return list()\n        app_dep_lst = json.loads(_ser.app_dependence)\n        ret_lst = list()\n        self.get_dependence(lst=ret_lst, dep=app_dep_lst)\n        ret_lst = make_lst_unique(ret_lst, \"name\", \"version\")\n        return ret_lst\n\n\nclass ProDependenceParseUtils(object):\n    \"\"\"\n    依赖解决工具类\n    \"\"\"\n\n    def __init__(self, parse_name, parse_version):\n        \"\"\"\n        初始化对象, 产品级别的解析\n        :param parse_name: 要解析的产品、应用名称\n        :param parse_version: 要解析的版本\n        \"\"\"\n        self.parse_name = parse_name\n        self.parse_version = parse_version\n        self.unique_key = self.parse_name + self.parse_version\n\n    def get_pro_instances(self, obj):   # NOQA\n        \"\"\"\n        获取产品的实例信息，被依赖产品是否已经被安装\n        :param obj: 应用实例对象\n        :type obj: ProductHub\n        :return:\n        \"\"\"\n        ret_lst = Product.objects.filter(\n            product__pro_name=obj.pro_name\n        ).values(\"id\", \"product_instance_name\")\n        return list(ret_lst)\n\n    def get_dependence(self, lst, dep):\n        \"\"\"\n        解决产品依赖关系核心方法\n        :param lst: 存储结果的列表\n        :type lst: list\n        :param dep: 服务依赖关系列表\n        :type dep: list\n        :return: list()\n        \"\"\"\n        unique_key_lst = list()\n        for inner in dep:\n            _name, _version = inner.get(\"name\"), inner.get(\"version\")\n            _pro = ProductHub.objects.filter(\n                pro_name=_name,\n                pro_version=_version,\n                is_release=True\n            ).order_by(\"created\").last()\n            # 定义服务&版本唯一标准，防止递归错误\n            unique_key = str(_name) + str(_version)\n            # 如果当前产品和需要被解析的源产品重叠，那么则跳过\n            # 如果当前产品的依赖关系已经被解决，那么则跳过\n            if unique_key == self.unique_key or unique_key in unique_key_lst:\n                continue\n            unique_key_lst.append(unique_key)\n            # 判断当前被依赖服务是否存在，如果不存在就直接返回，不再处理深层依赖\n            if not _pro:\n                inner[\"instance_info\"] = list()\n                inner[\"is_in_hub\"] = False\n                inner[\"process_continue\"] = False\n                inner[\"process_message\"] = f\"应用商店内未包含{_name}应用\"\n                lst.append(inner)\n                continue\n            # 查看当前服务是否被安装\n            instance_info = self.get_pro_instances(obj=_pro)\n            # 获取依赖服务的相关信息\n            inner[\"instance_info\"] = instance_info\n            inner[\"is_in_hub\"] = True\n            inner[\"process_continue\"] = True\n            lst.append(inner)\n            if not _pro.pro_dependence:\n                continue\n            _pro_dependence = json.loads(_pro.pro_dependence)\n            self.get_dependence(\n                lst, dep=_pro_dependence\n            )\n\n    def get_newest_pro(self):\n        \"\"\"\n        获取最新的产品对象，同产品，同版本\n        :return: ProductHub\n        \"\"\"\n        return ProductHub.objects.filter(\n            pro_name=self.parse_name,\n            pro_version=self.parse_version,\n            is_release=True\n        ).last()\n\n    def run_pro(self):\n        \"\"\"\n        解析产品的依赖关系入口\n        :return: 产品关系依赖列表\n        \"\"\"\n        _pro = self.get_newest_pro()\n        if not _pro or not _pro.pro_dependence:\n            return list()\n        ret_lst = list()\n        self.get_dependence(ret_lst, json.loads(_pro.pro_dependence))\n        ret_lst = make_lst_unique(ret_lst, \"name\", \"version\")\n        return ret_lst\n\n\nclass ServiceArgsSerializer(object):\n    \"\"\" 服务安装过程中参数解析类 \"\"\"\n\n    def get_app_dependence(self, obj):  # NOQA\n        \"\"\"\n        解析服务级别的依赖关系\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        ser = SerDependenceParseUtils(obj.app_name, obj.app_version)\n        return ser.run_ser()\n\n    def get_app_port(self, obj):    # NOQA\n        \"\"\"\n        获取app的端口\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: list()\n        \"\"\"\n        if not obj.app_port:\n            return []\n        port_lst = json.loads(obj.app_port)\n        for item in port_lst:\n            make_editable(item)\n        return port_lst\n\n    def get_app_install_args(self, obj):  # NOQA\n        \"\"\"\n        解析安装参数信息\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: list()\n        \"\"\"\n        # 标记安装过程中涉及到的数据目录，通过此标记给前端\n        # 给与前端提示信息，此标记对应于主机中的数据目录 data_folder\n        # 在后续前端提供出安装参数后，我们应该检查其准确性\n        if not obj.app_install_args:\n            return list()\n        return make_app_install_args(json.loads(obj.app_install_args))\n\n    def get_deploy_mode(self, obj):  # NOQA\n        \"\"\"\n        解析部署模式信息\n        [\n          {\n            \"key\": \"single\",\n            \"name\": \"单实例\"\n          }\n        ]\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        # 如果服务未配置部署模式相关信息，那么默认为单实例模式\n        ser = SerDependenceParseUtils(obj.app_name, obj.app_version)\n        return ser.get_deploy_mode(obj)\n\n    def _process_continue_parse(self, obj):  # NOQA\n        \"\"\"\n        解析是否能够进行的核心接口\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: (bool, str)\n        \"\"\"\n        if not obj.app_package:\n            return False, f\"服务{obj.app_name}无安装包\"\n        # 服务级别的安装包均存在于 verified 目录内，使用 package_name 即可拼接完成\n        _path = os.path.join(\n            PROJECT_DIR,\n            \"package_hub\",\n            obj.app_package.package_path,\n            obj.app_package.package_name\n        )\n        if not os.path.exists(_path):\n            return False, f\"服务{obj.app_name}的安装包无法找到\"\n        return True, \"success\"\n\n    def get_process_continue(self, obj):    # NOQA\n        \"\"\"\n        解析能否继续进行的标志接口\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: bool\n        \"\"\"\n        flag, _ = self._process_continue_parse(obj)\n        return flag\n\n    def get_process_message(self, obj):    # NOQA\n        \"\"\"\n        解析能否继续进行的信息接口\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        _, msg = self._process_continue_parse(obj)\n        return msg\n\n\nclass ValidateExistService(object):\n    \"\"\" 检查已存在服务信息是否准确 \"\"\"\n\n    def __init__(self, data=None):\n        \"\"\"\n        初始化方法\n        :param data: 要被检验的服务信息\n        :type data: list\n        \"\"\"\n        if not data or not isinstance(data, list):\n            raise GeneralError(\n                \"ValidateExistService __init__ arg error: data\")\n        self.data = data\n\n    def check_cluster(self, dic):   # NOQA\n        \"\"\"\n        校验集群信息是否准确\n        :param dic: 集群信息字典\n        :type dic: dict\n        :return:\n        \"\"\"\n        if ClusterInfo.objects.filter(id=dic.get(\"id\")).exists():\n            dic[\"check_flag\"] = True\n            dic[\"check_msg\"] = \"success\"\n            return dic\n        dic[\"check_flag\"] = False\n        dic[\"check_msg\"] = f\"复用已存在集群{dic.get('id', 'UNKNOWN')}不存在\"\n        return dic\n\n    def check_single(self, dic):    # NOQA\n        \"\"\"\n        检查服务的合法性\n        :param dic: 服务信息字典\n        :type dic: dict\n        :return:\n        \"\"\"\n        if Service.objects.filter(id=dic.get(\"id\")).exists():\n            dic[\"check_flag\"] = True\n            dic[\"check_msg\"] = \"success\"\n            return dic\n        dic[\"check_flag\"] = False\n        dic[\"check_msg\"] = f\"复用已存在实例{dic.get('id', 'UNKNOWN')}不存在\"\n        return dic\n\n    def run(self):\n        \"\"\"\n        运行入口\n        :return:\n        \"\"\"\n        for item in self.data:\n            _type = item.get(\"type\")\n            if _type not in (\"cluster\", \"single\"):\n                item[\"check_flag\"] = False\n                item[\"check_msg\"] = \"已存在的服务信息必须在single和cluster内\"\n                continue\n            _recheck_item = getattr(self, f\"check_{_type}\")(item)\n            item.update(_recheck_item)\n        return self.data\n\n\nclass ValidateInstallService(object):\n    \"\"\" 检查要安装的服务信息是否准确 \"\"\"\n\n    def __init__(self, data=None):\n        \"\"\"\n        初始化方法\n        :param data: 要被检验的服务信息\n        :type data: list\n        \"\"\"\n        if not data or not isinstance(data, list):\n            raise GeneralError(\n                \"ValidateInstallService __init__ arg error: data\"\n            )\n        self.data = data\n\n    def check_service_port(self, app_port, ip):     # NOQA\n        \"\"\"\n        检查服务端口\n        :param app_port: 服务端口列表\n        :type app_port: list\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        salt_obj = SaltClient()\n        for el in app_port:\n            _port = el.get(\"default\", \"\")\n            if not _port or not str(_port).isnumeric():\n                el[\"check_flag\"] = False\n                el[\"check_msg\"] = f\"端口 {_port} 必须为数字\"\n                continue\n            # method1: 从OMP本机查看端口是否已被占用\n            # _flag, _msg = public_utils.check_ip_port(ip=ip, port=int(_port))\n            # method2: 从目标服务器查看端口是否被占用\n            _flag, _msg = salt_obj.cmd(\n                target=ip,\n                command=f\"</dev/tcp/{ip}/{_port}\",\n                timeout=10\n            )\n            if _flag:\n                el[\"check_flag\"] = False\n                el[\"check_msg\"] = f\"主机 {ip} 上的端口 {_port} 已被占用\"\n        return app_port\n\n    def check_service_args(self, app_install_args, data_path, ip):  # NOQA\n        \"\"\"\n        检查服务的安装参数，路径检查\n        :param app_install_args: 服务安装参数\n        :type app_install_args: list\n        :param data_path: 主机数据目录\n        :type data_path: str\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        _salt_obj = SaltClient()\n        for el in app_install_args:\n            if \"dir_key\" not in el:\n                continue\n            _tobe_check_path = os.path.join(\n                data_path, el.get(\"default\", \"\").lstrip(\"/\"))\n            # 直接封装部署数据到数据库中\n            el[\"default\"] = _tobe_check_path\n            _cmd = \\\n                f\"test -d {_tobe_check_path} && echo 'EXISTS' || echo 'OK'\"\n            _flag, _msg = _salt_obj.cmd(\n                target=ip,\n                command=_cmd,\n                timeout=10\n            )\n            if not _flag:\n                el[\"check_flag\"] = False\n                el[\"check_msg\"] = \\\n                    f\"无法确定该路径状态: {_tobe_check_path}; \" \\\n                    f\"请检查主机及主机Agent状态是否正常\"\n                continue\n            if \"OK\" in _msg:\n                el[\"check_flag\"] = True\n                el[\"check_msg\"] = \"success\"\n            else:\n                el[\"check_flag\"] = False\n                el[\"check_msg\"] = f\"{_tobe_check_path} 在目标主机 {ip} 上已存在\"\n        return app_install_args\n\n    def check_single_service(self, dic):    # NOQA\n        \"\"\"\n        检查单个服务的安装信息\n        :param dic: 服务安装信息\n        :type dic: dict\n        :return:\n        \"\"\"\n        _dic = deepcopy(dic)\n        _ip = _dic.get(\"ip\")\n        _host_obj = Host.objects.filter(ip=_ip).last()\n        if not _host_obj:\n            _dic[\"check_flag\"] = False\n            _dic[\"check_msg\"] = f\"主机 {_ip} 不存在\"\n            return _dic\n        _data_path = _host_obj.data_folder\n        service_name = _dic.get(\"name\")\n        # 检查服务是否已在该主机上安装\n        if Service.objects.filter(\n                service__app_name=service_name, ip=_ip).exists():\n            _dic[\"check_flag\"] = False\n            _dic[\"check_msg\"] = f\"该主机 {_ip} 已安装过 {service_name}, 请勿重复安装\"\n            return _dic\n        # 检查实例名称是否重复\n        service_instance_name = _dic.get(\"service_instance_name\")\n        if Service.objects.filter(\n                service_instance_name=service_instance_name).exists():\n            _dic[\"check_flag\"] = False\n            _dic[\"check_msg\"] = \"实例名称重复\"\n            return _dic\n        if \"is_pack_exist\" in _dic and not _dic[\"is_pack_exist\"]:\n            _dic[\"check_flag\"] = False\n            _dic[\"check_msg\"] = f\"服务 {service_name} 的安装包不存在\"\n            return _dic\n        # 检查端口是否被占用\n        app_port = self.check_service_port(\n            app_port=_dic.get(\"app_port\", []),\n            ip=_ip\n        )\n        # 校验安装参数\n        app_install_args = self.check_service_args(\n            app_install_args=_dic.get(\"app_install_args\", []),\n            data_path=_data_path,\n            ip=_ip\n        )\n        _dic[\"app_port\"] = app_port\n        _dic[\"app_install_args\"] = app_install_args\n        return _dic\n\n    def run(self):\n        \"\"\"\n        运行检查入口函数\n        :return:\n        \"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=10, thread_name_prefix=\"check_install_service_\"\n        )\n        # futures_list:[(item, future)]\n        futures_list = list()\n        for item in self.data:\n            future = thread_p.submit(self.check_single_service, item)\n            futures_list.append((item.get(\"service_instance_name\"), future))\n        # result_list:[{}, ...]\n        result_list = list()\n        for f in futures_list:\n            result_list.append(f[1].result())\n        thread_p.shutdown(wait=True)\n        # TODO 整体服务间的关键校验\n        return result_list\n\n\nclass CreateInstallPlan(object):\n    \"\"\" 生成部署计划相关数据 \"\"\"\n\n    def __init__(self, install_data):\n        \"\"\"\n        解析安装数据入库方法\n        {\n            \"install_type\": 0,\n            \"use_exist_services\": [\n                {\n                    \"name\": \"a\",\n                    \"id\": 1,\n                    \"type\": \"cluster\",\n                    \"check_flag\": false,\n                    \"check_msg\": \"此集群不存在\"\n                }\n            ],\n            \"install_services\": [\n                {\n                    \"name\": \"jdk\",\n                    \"version\": \"8u211\",\n                    \"ip\": \"10.0.9.175\",\n                    \"cluster_name\": \"test_cluster_1\",\n                    \"product_instance_name\": \"aa\",\n                    \"app_install_args\": [\n                        {\n                            \"name\": \"安装目录\",\n                            \"key\": \"base_dir\",\n                            \"default\": \"/jdk\",\n                            \"dir_key\": \"{data_path}\",\n                            \"check_flag\": false,\n                            \"check_msg\": \"无法确定该路径状态: /data/jdk\"\n                        }\n                    ],\n                    \"app_port\": [\n                        {\n                            \"name\": \"服务端口\",\n                            \"protocol\": \"TCP\",\n                            \"key\": \"service_port\",\n                            \"default\": 19001,\n                            \"check_flag\": false,\n                            \"check_msg\": \"主机 10.0.9.175 上的端口 19001 已被占用\"\n                        }\n                    ],\n                    \"service_instance_name\": \"jdk-1-1\"\n                }\n            ],\n            \"is_valid_flag\": false,\n            \"is_valid_msg\": \"数据校验出错\"\n        }\n        :param install_data: 安装解析数据\n        :type install_data: dict\n        \"\"\"\n        self.install_data = install_data\n        self.install_type = install_data[\"install_type\"]\n        self.install_services = install_data[\"install_services\"]\n\n    def get_app_obj_for_service(self, dic):     # NOQA\n        \"\"\"\n        获取服务实例表中关联的app对象\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        return ApplicationHub.objects.filter(\n            app_name=dic[\"name\"], app_version=dic[\"version\"]\n        ).last()\n\n    def get_app_port_for_service(self, dic):    # NOQA\n        \"\"\"\n        获取服务实例上设置的端口信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        return json.dumps(dic[\"app_port\"]) if dic[\"app_port\"] else None\n\n    def get_controllers_for_service(self, dic):  # NOQA\n        \"\"\"\n        获取服务控制脚本信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        # 获取关联application对象\n        _app = self.get_app_obj_for_service(dic)\n        # TODO 确定application表内的app_controllers字段存储类型\n        _app_controllers = json.loads(_app.app_controllers)\n        # 获取服务家目录\n        install_args = dic[\"app_install_args\"]\n        _home = \"\"\n        data_folder = Host.objects.filter(ip=dic[\"ip\"]).last().data_folder\n        for el in install_args:\n            if \"dir_key\" in el and el[\"key\"] == \"base_dir\":\n                _home = el[\"default\"]\n        real_home = os.path.join(data_folder, _home.rstrip(\"/\"))\n        _new_controller = dict()\n        # 更改服务控制脚本、拼接相对路径\n        for key, value in _app_controllers.items():\n            if not value:\n                continue\n            _new_controller[key] = os.path.join(real_home, value)\n        # 如果该服务需要在整体安装完成后有一些操作，那么需要重新构建post_action\n        # 在每次安装完所有服务后，需要搜索出相应的post_action并统一执行\n        if \"post_action\" in _app.extend_fields and \\\n                _app.extend_fields[\"post_action\"]:\n            _new_controller[\"post_action\"] = os.path.join(\n                real_home, _app.extend_fields[\"post_action\"]\n            )\n        return _new_controller\n\n    def get_env_for_service(self):  # NOQA\n        \"\"\"\n        获取当前环境\n        :return:\n        \"\"\"\n        # TODO 暂时使用默认环境\n        return Env.objects.last()\n\n    def create_connect_info(self, dic):     # NOQA\n        \"\"\"\n        创建或获取服务的用户名、密码信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        username = password = username_enc = password_enc = \"\"\n        for item in dic[\"app_install_args\"]:\n            if not item[\"default\"]:\n                continue\n            if item[\"key\"] == \"username\":\n                username = item[\"default\"]\n            if item[\"key\"] == \"password\":\n                password = item[\"default\"]\n            if item[\"key\"] == \"username_enc\":\n                username_enc = item[\"default\"]\n            if item[\"key\"] == \"password_enc\":\n                password_enc = item[\"default\"]\n        if username or password or username_enc or password_enc:\n            _ser_conn_obj, _ = ServiceConnectInfo.objects.get_or_create(\n                service_name=dic[\"name\"],\n                service_username=username,\n                service_password=password,\n                service_username_enc=username_enc,\n                service_password_enc=password_enc\n            )\n            return _ser_conn_obj\n        return None\n\n    def create_cluster(self, dic):  # NOQA\n        \"\"\"\n        创建集群信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        if \"cluster_name\" not in dic or not dic[\"cluster_name\"]:\n            return None\n        _app_obj = self.get_app_obj_for_service(dic)\n        # 根据要安装的服务是组件还是应用，这里仅做组件级别的集群\n        if _app_obj.app_type != 0:\n            return None\n        # 如果存在则获取、如果不存在则创建\n        cluster_obj, _ = ClusterInfo.objects.get_or_create(\n            cluster_service_name=dic[\"name\"],\n            cluster_name=dic[\"cluster_name\"],\n            service_connect_info=self.create_connect_info(dic)\n        )\n        return cluster_obj\n\n    def create_service(self, dic):\n        \"\"\"\n        创建服务实例\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        # 创建服务实例对象，默认从安装来的服务的状态为 安装中\n        _ser_obj = Service(\n            ip=dic[\"ip\"],\n            service_instance_name=dic[\"service_instance_name\"],\n            service=self.get_app_obj_for_service(dic),\n            service_port=self.get_app_port_for_service(dic),\n            service_controllers=self.get_controllers_for_service(dic),\n            cluster=self.create_cluster(dic),\n            env=self.get_env_for_service(),\n            service_status=6,\n            service_connect_info=self.create_connect_info(dic)\n        )\n        _ser_obj.save()\n        return _ser_obj\n\n    def create_product_instance(self, dic):     # NOQA\n        \"\"\"\n        创建产品实例\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        if \"product_instance_name\" not in dic or \\\n                not self.get_app_obj_for_service(dic):\n            return\n        product_instance_name = dic[\"product_instance_name\"]\n        Product.objects.get_or_create(\n            product_instance_name=product_instance_name,\n            product=self.get_app_obj_for_service(dic).product\n        )\n\n    def check_if_has_post_action(self, ser):    # NOQA\n        \"\"\"\n        检测是否需要执行安装后的动作\n        :param ser: 服务对象\n        :type ser Service\n        :return:\n        \"\"\"\n        if \"post_action\" in ser.service_controllers and \\\n                ser.service_controllers.get(\"post_action\"):\n            return True\n        return False\n\n    def run(self):\n        \"\"\"\n        服务部署信息入库操作\n        :return:\n        \"\"\"\n        with transaction.atomic():\n            try:\n                # step0: 生成\n                # step1: 生成操作唯一uuid，创建主安装记录\n                operation_uuid = str(uuid.uuid4())\n                main_obj = MainInstallHistory(\n                    operation_uuid=operation_uuid,\n                    install_status=0,\n                    install_args=self.install_data\n                )\n                main_obj.save()\n                # step2: 创建安装细节表\n                for item in self.install_services:\n                    # 创建服务实例对象\n                    ser_obj = self.create_service(item)\n                    # 创建产品实例对象\n                    self.create_product_instance(item)\n                    post_action_flag = 0 if self.check_if_has_post_action(\n                        ser_obj) else 4\n                    DetailInstallHistory(\n                        service=ser_obj,\n                        main_install_history=main_obj,\n                        install_detail_args=item,\n                        post_action_flag=post_action_flag\n                    ).save()\n                _json_obj = DataJson(operation_uuid=operation_uuid)\n                _json_obj.run()\n                # 调用安装异步任务，并回写异步任务到\n                task_id = install_service.delay(main_obj.id)\n                MainInstallHistory.objects.filter(id=main_obj.id).update(\n                    task_id=task_id\n                )\n            except Exception as e:\n                return False, f\"生成部署计划失败: {str(e)}\"\n        return True, operation_uuid\n"
  },
  {
    "path": "omp_server/app_store/new_install_serializers.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: new_install_serializers\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-12 09:23\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装过程中使用的各种序列化类\n\"\"\"\n\nimport uuid\nimport json\nimport logging\n\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.serializers import Serializer\n\nfrom db_models.models import (\n    ProductHub,\n    Service,\n    ClusterInfo,\n    Product,\n    Host,\n    ApplicationHub,\n    MainInstallHistory\n)\n\nfrom app_store.new_install_utils import (\n    ProductServiceParse,\n    SerDependenceParseUtils,\n    make_lst_unique,\n    SerWithUtils,\n    ServiceArgsPortUtils,\n    BaseEnvServiceUtils,\n    RedisDB,\n    BaseRedisData,\n    CreateInstallPlan,\n    MakeServiceOrder,\n    ValidateInstallServicePortArgs,\n    WithServiceUtils,\n    ComponentServiceParse,\n    SerRoleUtils,\n    SerVipUtils\n)\nfrom app_store.tasks import install_service as install_service_task\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger(\"server\")\n\nUNIQUE_KEY_ERROR = \"后台无法追踪此流程,请重新进行安装操作!\"\n\n\nclass BaseInstallSerializer(Serializer):\n    \"\"\" 安装过程中使用的基础序列化类 \"\"\"\n    unique_key = serializers.CharField(\n        help_text=\"操作唯一值\",\n        required=True,\n        error_messages={\"required\": \"必须包含[unique_key]字段\"}\n    )\n    data = serializers.DictField(help_text=\"详细安装数据可选范围\")\n\n    def validate_unique_key(self, unique_key):  # NOQA\n        \"\"\"\n        校验整个流程标志是否符合当前流程要求\n        :param unique_key: 操作唯一标识\n        :type unique_key: str\n        :return:\n        \"\"\"\n        return BaseRedisData(unique_key).get_unique_key()\n\n\nclass CreateInstallInfoSerializer(BaseInstallSerializer):\n    \"\"\"\n    请求格式：\n    {\n        \"high_availability\": true,\n        \"install_product\": [\n            {\n                \"name\": \"jenkinsNB\",\n                \"version\": \"2.303.2\"\n            }\n        ],\n        \"unique_key\": \"abf7d622-6fc8-4a04-ad4c-49b57298ecdf\"\n    }\n    \"\"\"\n    high_availability = serializers.BooleanField(\n        write_only=True, required=True,\n        help_text=\"是否选择高可用模式\",\n        error_messages={\"required\": \"必须包含[high_availability]字段\"}\n    )\n    install_product = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"产品列表，eg: [{'name': 'ser1', 'version': '1'}]\",\n        write_only=True, required=True,\n        error_messages={\"required\": \"必须包含[install_product]字段\"}\n    )\n    data = serializers.DictField(\n        help_text=\"详细安装数据\",\n        read_only=True\n    )\n\n    def check_product_dependence(self, pro_obj, all_dic):  # NOQA\n        \"\"\"\n\n        :param pro_obj: 应用对象\n        :type pro_obj: ProductHub\n        :param all_dic: 即将安装应用与版本的对应关系\n        :type all_dic: dict\n        :return:\n        \"\"\"\n        if not pro_obj.pro_dependence or \\\n                not json.loads(pro_obj.pro_dependence):\n            return\n        for el in json.loads(pro_obj.pro_dependence):\n            _el_name = el[\"name\"]\n            _el_version = el[\"version\"]\n            # 如果当次安装已包含了依赖应用则跳过\n            if _el_name in all_dic and \\\n                    _el_version == all_dic[_el_name]:\n                continue\n            # 如果当前系统中存在已安装应用则跳过\n            if Product.objects.filter(\n                    product__pro_name=_el_name,\n                    product__pro_version=_el_version\n            ).exists():\n                continue\n            # 以上都不满足那么需要阻断安装程序\n            raise ValidationError(\n                f\"应用 [{pro_obj.pro_name}] [{pro_obj.pro_version}] \"\n                f\"缺少依赖: [{_el_name}] [{_el_version}]\"\n            )\n\n    def validate_install_product(self, install_product):  # NOQA\n        \"\"\"\n        校验即将安装的产品、应用是否在可支持范围内\n        :param install_product: 要安装的应用\n        :type install_product: list\n        :return:\n        \"\"\"\n        # 获取所有要安装产品的name: version映射字典\n        _all_dic = {el[\"name\"]: el[\"version\"] for el in install_product}\n        for item in install_product:\n            _name = item.get(\"name\")\n            _version = item.get(\"version\")\n            pro_obj = ProductHub.objects.filter(\n                pro_name=_name,\n                pro_version=_version,\n                is_release=True\n            ).last()\n            if not pro_obj:\n                raise ValidationError(f\"应用 [{_name}] [{_version}] 不存在\")\n            self.check_product_dependence(pro_obj, _all_dic)\n        return install_product\n\n    def create(self, validated_data):\n        \"\"\"\n        构建前端可选安装数据\n        :param validated_data:\n        :return:\n        \"\"\"\n        logger.info(\n            f\"CreateInstallInfoSerializer.validated_data: {validated_data}\")\n        install_product = validated_data[\"install_product\"]\n        high_availability = validated_data[\"high_availability\"]\n        unique_key = validated_data[\"unique_key\"]\n\n        _basic = list()\n        # 遍历所有需要安装的产品，将产品下的服务信息收集并存储至_basic变量内\n        for item in install_product:\n            _pro_info = ProductServiceParse(\n                pro_name=item.get(\"name\"),\n                pro_version=item.get(\"version\"),\n                high_availability=high_availability,\n                unique_key=unique_key\n            ).run()\n            _basic.append(_pro_info)\n\n        # 设置_recheck_service用于过滤服务、组件间重复性数据\n        _recheck_service = dict()\n        # 遍历所有需要安装的自研服务信息，确定服务的依赖关系并存储至_dependence变量内\n        _dependence = list()\n        # 将带有with标识的服务进行处理\n        with_ser_lst = list()\n        for _pro in _basic:\n            _re_services_list = list()\n            for item in _pro.get(\"services_list\"):\n                # 版本严格校验\n                _recheck_service.update({\n                    item.get(\"name\", \"\") + \"-\" + item.get(\"version\", \"\"): True\n                })\n                _dep = SerDependenceParseUtils(\n                    parse_name=item.get(\"name\"),\n                    parse_version=item.get(\"version\"),\n                    high_availability=high_availability\n                ).run_ser()\n                _dependence.extend(_dep)\n                if \"with_flag\" in item:\n                    with_ser_lst.append(item)\n                else:\n                    _re_services_list.append(item)\n            _pro[\"services_list\"] = _re_services_list\n\n        # 将带有with标识的服务存放至redis数据中\n        BaseRedisData(\n            unique_key=unique_key).step_set_with_ser(data=with_ser_lst)\n\n        # 使用make_lst_unique方法将服务依赖列表去重处理\n        _dependence = make_lst_unique(\n            lst=_dependence, key_1=\"name\", key_2=\"version\")\n        # 自研服务与基础组件重复处理\n        _recheck_dependence = list()\n        for item in _dependence:\n            # 版本严格校验\n            _key = item.get(\"name\", \"\") + \"-\" + item.get(\"version\", \"\")\n            if _key in _recheck_service:\n                continue\n            _recheck_dependence.append(item)\n\n        # 判断最终用户可否进行下一步的标记\n        is_continue = True\n        for item in _basic:\n            if \"error_msg\" in item and item[\"error_msg\"]:\n                is_continue = False\n                break\n        for item in _recheck_dependence:\n            if \"error_msg\" in item and item[\"error_msg\"]:\n                is_continue = False\n        validated_data[\"data\"] = {\n            \"basic\": _basic,\n            \"dependence\": _recheck_dependence,\n            \"is_continue\": is_continue\n        }\n        # 存储基础信息到redis\n        if is_continue:\n            BaseRedisData(\n                validated_data[\"unique_key\"]\n            ).step_2_set_origin_install_data_args(data=validated_data)\n        return validated_data\n\n\nclass CheckInstallInfoSerializer(BaseInstallSerializer):\n    \"\"\" 安装基础信息校验接口 \"\"\"\n\n    def get_product_instance_name_lst(self):  # NOQA\n        \"\"\"\n        获取集群名称列表\n        :return:\n        \"\"\"\n        product_ins_name_lst = Product.objects.values(\"product_instance_name\")\n        return [el[\"product_instance_name\"] for el in product_ins_name_lst]\n\n    def get_cluster_name_lst(self):  # NOQA\n        \"\"\"\n        获取集群名称列表\n        :return:\n        \"\"\"\n        cluster_name_lst = ClusterInfo.objects.values(\"cluster_name\")\n        return [el[\"cluster_name\"] for el in cluster_name_lst]\n\n    def check_basic_product_instance_name_unique(self, lst):\n        \"\"\"\n        检查产品集群名称实例是否存在重复，如果安装的是产品，那么必定有产品集群名称\n        cluster_name必须存在\n        :param lst:\n        :return:\n        \"\"\"\n        product_instance_name_lst = self.get_product_instance_name_lst()\n        is_repeat = False\n        for item in lst:\n            _name = item.get(\"name\")\n            if \"cluster_name\" not in item:\n                is_repeat = True\n                item[\"error_msg\"] = f\"{_name}实例名称[cluster_name]必须填写\"\n                continue\n            if item[\"cluster_name\"] in product_instance_name_lst:\n                is_repeat = True\n                item[\"error_msg\"] = \\\n                    f\"产品实例名称: {item['cluster_name']} 不允许重复\"\n        return is_repeat, lst\n\n    def check_dependence_cluster_name_unique(self, lst):\n        \"\"\"\n        检查服务实例名称、集群实例名称是否重复\n        :param lst:\n        :return:\n        \"\"\"\n        cluster_name_lst = self.get_cluster_name_lst()\n        is_repeat = False\n        for item in lst:\n            _name = item.get('name')\n            if item.get(\"deploy_mode\") == \"single\":\n                continue\n            if item.get(\"is_base_env\") or \\\n                    item.get(\"is_use_exist\") or \\\n                    item.get(\"deploy_mode\", 0) == 1:\n                continue\n            if isinstance(item.get(\"deploy_mode\"), int) and \\\n                    item.get(\"deploy_mode\", 0) == 1:\n                continue\n            if (isinstance(item.get(\"deploy_mode\"), int) and item.get(\n                    \"deploy_mode\",\n                    0) > 1 and \"cluster_name\" not in item) or \"cluster_name\" not in item:\n                item[\"error_msg\"] = f\"{_name}应用集群实例名称[cluster_name]必须填写\"\n                is_repeat = True\n                continue\n            if item[\"cluster_name\"] in cluster_name_lst:\n                is_repeat = True\n                item[\"error_msg\"] = \\\n                    f\"{_name}应用集群实例名称: {item['cluster_name']} 不允许重复\"\n        return is_repeat, lst\n\n    def check_deploy_mode_num(self):\n        pass\n\n    def validate_data(self, data):\n        \"\"\"\n        校验请求数据、返回校验结果\n        data:\n        {\n            \"basic\": [],\n            \"dependence\": []\n        }\n        :param data:\n        :return:\n        \"\"\"\n        basic = data[\"basic\"]\n        dependence = data[\"dependence\"]\n\n        basic_repeat, basic = \\\n            self.check_basic_product_instance_name_unique(lst=basic)\n        dependence_repeat, dependence = \\\n            self.check_dependence_cluster_name_unique(lst=dependence)\n        _data = {\n            \"basic\": basic,\n            \"dependence\": dependence\n        }\n        # TODO 开源组件部署模式校验\n        #  使用已存在服务校验\n        if basic_repeat or dependence_repeat:\n            _data[\"is_continue\"] = False\n        else:\n            _data[\"is_continue\"] = True\n        return _data\n\n    def check_service(self, validated_data, use_exist, install):  # NOQA\n        is_continue = True\n        for item in validated_data[\"data\"].get(\"basic\", []):\n            for el in item.get(\"services_list\"):\n                if el.get(\"name\") not in install or \\\n                    el.get(\"version\") != install[el.get(\"name\")][\n                        \"version\"]:\n                    el[\"error_msg\"] = f\"无法追踪此服务: {el.get('name')}\"\n                    is_continue = False\n        for item in validated_data[\"data\"].get(\"use_exist\", []):\n            if item.get(\"is_use_exist\") and \\\n                    not use_exist.get(item.get(\"name\")):\n                item[\"error_msg\"] = f\"此服务不存在: {item.get('name')}, 无法复用\"\n                is_continue = False\n        return is_continue\n\n    def create(self, validated_data):\n        \"\"\"\n        :param validated_data:\n        :return:\n        \"\"\"\n        _re_obj = RedisDB()\n        _flag, _data = _re_obj.get(\n            name=validated_data[\"unique_key\"] + \"_step_2_origin_data\"\n        )\n        if not _flag:\n            raise ValidationError(UNIQUE_KEY_ERROR)\n        is_continue = validated_data[\"data\"].get(\"is_continue\")\n        if is_continue:\n            install = _data[\"install\"]\n            use_exist = _data[\"use_exist\"]\n            is_continue = self.check_service(\n                validated_data=validated_data,\n                use_exist=use_exist,\n                install=install\n            )\n        if not is_continue:\n            validated_data[\"data\"][\"is_continue\"] = False\n        else:\n            # 存储安装数据到redis\n            BaseRedisData(\n                validated_data[\"unique_key\"]\n            ).step_3_set_checked_data(data=validated_data)\n        return validated_data\n\n\nclass CreateServiceDistributionSerializer(BaseInstallSerializer):\n    \"\"\" 生成服务分布数据 \"\"\"\n    data = serializers.DictField(\n        read_only=True,\n        help_text=\"详细安装数据\"\n    )\n\n    def get_host_info(self):  # NOQA\n        \"\"\"\n        获取主机信息\n        :return:\n        \"\"\"\n        host_queryset = Host.objects.all().values(\n            \"ip\", \"service_num\").order_by(\"-created\")\n        return [\n            {\"ip\": el[\"ip\"], \"num\": el[\"service_num\"]} for el in host_queryset\n        ]\n\n    def get_basic_data(self, data, all_data, check_data):  # NOQA\n        \"\"\"\n        获取产品应用中的服务数量信息\n        :param data: 产品basic列表\n        :type data: list\n        :param all_data: 全部数据字典\n        :type all_data: dict\n        :param check_data: 已校验过的需要安装的服务的名称及版本信息\n        :type check_data: dict\n        :return:\n        \"\"\"\n        for item in data:\n            services_list = item.get(\"services_list\", [])\n            for el in services_list:\n                _with_ser = SerWithUtils(\n                    ser_name=el.get(\"name\"),\n                    ser_version=check_data[el[\"name\"]][\"version\"]\n                ).run()\n                all_data[el.get(\"name\")] = {\n                    \"num\": el.get(\"deploy_mode\"),\n                    \"with\": _with_ser\n                }\n        return all_data\n\n    def get_denpendence_data(self, data, all_data, check_data):  # NOQA\n        \"\"\"\n        获取依赖信息的服务数量\n        :param data: 依赖信息列表\n        :type data: list\n        :param all_data: 所有服务及数量关系字典\n        :type all_data: dict\n        :param check_data: 已校验过的需要安装的服务的名称及版本信息\n        :type check_data: dict\n        :return:\n        \"\"\"\n        for item in data:\n            if item.get(\"is_use_exist\") or item.get(\"is_base_env\"):\n                continue\n            if isinstance(item.get(\"deploy_mode\"), int):\n                all_data[item.get(\"name\")] = {\n                    \"num\": item.get(\"deploy_mode\"),\n                    \"with\": SerWithUtils(\n                        ser_name=item.get(\"name\"),\n                        ser_version=check_data[item[\"name\"]][\"version\"]\n                    ).run()\n                }\n            else:\n                # TODO 提取支持 VIP 的服务\n                if item.get(\"name\") in (\"mysql\", \"tengine\"):\n                    deploy_num = \\\n                        1 if item.get(\"deploy_mode\") == \"single\" else 2\n                else:\n                    deploy_num = 1\n                all_data[item.get(\"name\")] = {\n                    \"num\": deploy_num,\n                    \"with\": SerWithUtils(\n                        ser_name=item.get(\"name\"),\n                        ser_version=check_data[item[\"name\"]][\"version\"]\n                    ).run()\n                }\n        return all_data\n\n    def get_product_info(self, data, lst):  # NOQA\n        \"\"\"\n        获取应用产品与服务的关系，为后续使用级联选择做准备\n        :param data: 产品信息列表\n        :type data: list\n        :param lst: 返回信息列表\n        :type lst: list\n        :return:\n        \"\"\"\n        for item in data:\n            lst.append({\n                \"name\": item.get(\"name\"),\n                \"child\": [\n                    el.get(\"name\") for el in item.get(\"services_list\", [])\n                ]\n            })\n        return lst\n\n    def get_basic_info(self, data, lst):  # NOQA\n        \"\"\"\n        :param data: 基础组件列表\n        :type data: list\n        :param lst: 返回信息列表\n        :type lst: list\n        :return:\n        \"\"\"\n        lst.append({\n            \"name\": \"基础组件\",\n            \"child\": [\n                el.get(\"name\") for el in data\n                if not el.get(\"is_base_env\") and not el.get(\"is_use_exist\")\n            ]\n        })\n        return lst\n\n    def create(self, validated_data):\n        \"\"\"\n        校验\n        :param validated_data:\n        :return:\n        \"\"\"\n        validated_data[\"data\"] = dict()\n        validated_data[\"data\"][\"host\"] = self.get_host_info()\n\n        _data = BaseRedisData(\n            unique_key=validated_data[\"unique_key\"]\n        ).get_step_3_checked_data()\n\n        check_data = BaseRedisData(\n            unique_key=validated_data[\"unique_key\"]\n        ).get_step_2_origin_data()\n\n        basic = _data.get(\"data\", {}).get(\"basic\", [])\n        dependence = _data.get(\"data\", {}).get(\"dependence\", [])\n        all_data = dict()\n        self.get_basic_data(\n            data=basic,\n            all_data=all_data,\n            check_data=check_data.get(\"install\", {})\n        )\n        self.get_denpendence_data(\n            data=dependence,\n            all_data=all_data,\n            check_data=check_data.get(\"install\", {})\n        )\n        # 如果all_data的变量为空，那么可能安装的是base_env为True的服务，如jdk\n        # 这些服务都应划归到基础组件级别\n        is_base_env_flag = False\n        base_env_ser_name = None\n        if not all_data:\n            is_base_env_flag = True\n            for key in check_data.get(\"install\", {}).keys():\n                # base_env不存在集群，数量直接设置为1\n                base_env_ser_name = key\n                all_data[key] = {\"num\": 1, \"with\": None}\n        validated_data[\"data\"][\"all\"] = all_data\n        product_lst = list()\n        self.get_product_info(data=basic, lst=product_lst)\n        self.get_basic_info(data=dependence, lst=product_lst)\n        if is_base_env_flag:\n            for item in product_lst:\n                if item.get(\"name\") == \"基础组件\" and not item.get(\"child\"):\n                    item[\"child\"] = [base_env_ser_name]\n        validated_data[\"data\"][\"product\"] = product_lst\n        BaseRedisData(\n            validated_data['unique_key']\n        ).step_4_set_service_distribution(data=all_data)\n        return validated_data\n\n\nclass CheckServiceDistributionSerializer(BaseInstallSerializer):\n    \"\"\" 检查服务分布 \"\"\"\n    is_continue = serializers.BooleanField(\n        help_text=\"可否继续下一步骤操作\",\n        read_only=True\n    )\n    error_lst = serializers.ListField(\n        help_text=\"错误信息列表\",\n        read_only=True\n    )\n\n    def check_agent_status(self, ip):\n        \"\"\"\n        校验主机状态\n        :param ip:\n        :return:\n        \"\"\"\n\n    def validate_data(self, data):  # NOQA\n        \"\"\"\n        校验安装数据分布的合法性\n        {'10.0.14.234': ['doucApi', 'doucSso']}\n        :param data: 服务分布字典\n        :type data: dict\n        :return:\n        \"\"\"\n        logger.info(\n            f\"CheckServiceDistributionSerializer.validate_data: \"\n            f\"data: {data}\")\n        # 校验主机及主机上的服务是否存在\n        ip_lst = [el[\"ip\"] for el in Host.objects.values(\"ip\")]\n        error_lst = list()\n        _salt = SaltClient()\n        for key, value in data.items():\n            if key not in ip_lst:\n                error_lst.append({\"ip\": key, \"error_msg\": f\"无法找到主机{key}\"})\n                continue\n            _flag, _ = _salt.fun(target=key, fun=\"test.ping\")\n            if not _flag:\n                error_lst.append({\n                    \"ip\": key,\n                    \"error_msg\": f\"主机 [{key}] Agent当前不在线，无法使用该主机\"\n                })\n                continue\n            exist_services = Service.split_objects.filter(\n                ip=key, service__app_name__in=value\n            )\n            if exist_services.exists():\n                _msg = ','.join([el.service.app_name for el in exist_services])\n                error_lst.append(\n                    {\n                        \"ip\": key,\n                        \"error_msg\": f\"主机{key}上存在重复服务: {_msg}\"\n                    }\n                )\n        if error_lst:\n            data[\"error_lst\"] = error_lst\n        return data\n\n    def create(self, validated_data):\n        \"\"\"\n        校验\n        {\n            'unique_key': '886e8fc4-8e77-4de0-8123-9f3aec31ed73',\n            'data': {\n                '10.0.14.234': ['doucApi', 'doucSso']\n            }\n        }\n        :param validated_data:\n        :return:\n        \"\"\"\n        logger.info(\n            f\"CheckServiceDistributionSerializer.create: \"\n            f\"validated_data: {validated_data}\")\n        if \"error_lst\" in validated_data[\"data\"] and \\\n                validated_data[\"data\"][\"error_lst\"]:\n            validated_data[\"error_lst\"] = \\\n                validated_data[\"data\"].pop(\"error_lst\")\n            validated_data[\"is_continue\"] = False\n            return validated_data\n        # 校验服务数量准确性\n        all_install_service = dict()\n        for _, value in validated_data[\"data\"].items():\n            for item in value:\n                if item not in all_install_service:\n                    all_install_service[item] = 0\n                all_install_service[item] += 1\n        _re_obj = RedisDB()\n        _flag, _data = _re_obj.get(\n            name=f\"{validated_data['unique_key']}_step_4_service_distribution\")\n        if not _flag:\n            raise ValidationError(UNIQUE_KEY_ERROR)\n        for key, value in _data.items():\n            if key not in all_install_service:\n                raise ValidationError(f\"缺少必须部署的服务{key}\")\n            if value[\"num\"] != all_install_service[key]:\n                raise ValidationError(\n                    f\"服务{key}应部署{value['num']}个实例，\"\n                    f\"实际部署{all_install_service[key]}个\")\n        # TODO 服务绑定的准确性校验\n        # 临时数据存储至redis\n        BaseRedisData(\n            validated_data['unique_key']).step_5_set_host_and_service_map(\n            host_list=list(validated_data[\"data\"].keys()),\n            host_service_map=validated_data[\"data\"]\n        )\n        validated_data[\"is_continue\"] = True\n        validated_data[\"error_lst\"] = list()\n        return validated_data\n\n\nclass CreateInstallPlanSerializer(BaseInstallSerializer):\n    \"\"\" 创建安装计划序列化类 \"\"\"\n    is_continue = serializers.BooleanField(\n        help_text=\"可否继续下一步骤操作\",\n        read_only=True\n    )\n    error_lst = serializers.ListField(\n        help_text=\"错误信息列表\",\n        read_only=True\n    )\n    run_user = serializers.CharField(\n        help_text=\"服务运行用户\",\n        required=True, allow_null=True, allow_blank=True\n    )\n    error_msg = serializers.CharField(\n        help_text=\"错误信息\",\n        required=False, allow_null=True, allow_blank=True\n    )\n\n    def check_service_dis(self, host_service_map, install_data):  # NOQA\n        \"\"\"\n        校验服务分布是否正确\n        :param host_service_map: 存储在redis中的服务与主机的对应关系\n        :type host_service_map: dict\n        :param install_data: 提交上来的服务与主机的对应关系\n        :type install_data: dict\n        :return:\n        \"\"\"\n        error_lst = list()\n        for key, value in install_data.items():\n            if not host_service_map.get(key):\n                error_lst.append({key: \"此主机未被选中，请重新查看服务分布策略\"})\n                continue\n            if len(value) == 0:\n                continue\n            if len(value) != len(host_service_map.get(key)):\n                error_lst.append({\n                    key: f\"此主机服务数量不准确，\"\n                         f\"应选{len(host_service_map.get(key))}; \"\n                         f\"实际为：{len(value)}\"\n                })\n                continue\n            for el in value:\n                if el.get(\"name\") not in host_service_map.get(key):\n                    error_lst.append({key: f\"此服务{el.get('name')}不在安装范围内\"})\n        return error_lst\n\n    def make_final_install_data(  # NOQA\n            self, install_data, valid_data,\n            run_user, host_ser_map, cluster_name_map, host_user_map\n    ):\n        \"\"\"\n        构建最终的安装数据\n        :param install_data:\n        :param valid_data:\n        :param run_user:\n        :param host_ser_map:\n        :param cluster_name_map:\n        :param host_user_map:\n        :return:\n        \"\"\"\n        all_install_service_lst = list()\n        for ip, ser_lst in install_data.items():\n            data_folder = Host.objects.filter(ip=ip).last().data_folder\n            if len(ser_lst) != 0:\n                for item in ser_lst:\n                    instance_name = None\n                    for el in item[\"install_args\"]:\n                        if el.get(\"key\") == \"instance_name\":\n                            instance_name = el.get(\"default\")\n                    item[\"ip\"] = ip\n                    item[\"version\"] = valid_data[item[\"name\"]][\"version\"]\n                    item[\"install_args\"] = ServiceArgsPortUtils(\n                        ip=ip, data_folder=data_folder, run_user=run_user,\n                        host_user_map=host_user_map\n                    ).reformat_install_args(item[\"install_args\"])\n                    item[\"data_folder\"] = data_folder\n                    item[\"run_user\"] = run_user\n                    item[\"instance_name\"] = instance_name\n                    item[\"cluster_name\"] = cluster_name_map.get(item[\"name\"])\n                all_install_service_lst.extend(ser_lst)\n            else:\n                for item in host_ser_map[ip]:\n                    _dic = {\n                        \"name\": item,\n                        \"version\": valid_data[item][\"version\"],\n                        \"ip\": ip\n                    }\n                    _app = ApplicationHub.objects.filter(\n                        app_name=item,\n                        app_version=valid_data[item][\"version\"]\n                    ).last()\n                    _dic[\"data_folder\"] = data_folder\n                    _dic[\"run_user\"] = run_user\n                    _dic[\"install_args\"] = \\\n                        ServiceArgsPortUtils(\n                            ip=ip, data_folder=data_folder, run_user=run_user,\n                            host_user_map=host_user_map\n                    ).remake_install_args(obj=_app)\n                    _dic[\"ports\"] = \\\n                        ServiceArgsPortUtils(\n                            ip=ip, data_folder=data_folder, run_user=run_user,\n                            host_user_map=host_user_map\n                    ).get_app_port(obj=_app)\n                    _dic[\"instance_name\"] = \\\n                        item + \"-\" + \"-\".join(ip.split(\".\")[-2:])\n                    _dic[\"cluster_name\"] = cluster_name_map.get(item)\n                    all_install_service_lst.append(_dic)\n        return all_install_service_lst\n\n    def check_error_msg(self, all_install_service_lst):  # NOQA\n        \"\"\"\n        检查安装参数和端口的联通性是否存在错误，数据过滤\n        :param all_install_service_lst:\n        :return:\n        \"\"\"\n        for item in all_install_service_lst:\n            lst = item.get(\"install_args\", [])\n            lst.extend(item.get(\"ports\"))\n            for el in lst:\n                if \"error_msg\" in el and el[\"error_msg\"]:\n                    return False, el[\"error_msg\"]\n        return True, \"\"\n\n    def create(self, validated_data):\n        \"\"\"\n        创建部署计划\n        :param validated_data:\n        :return:\n        \"\"\"\n        logger.info(\n            f\"CreateInstallPlanSerializer.create: \"\n            f\"validated_data: {validated_data}\")\n        run_user = validated_data[\"run_user\"]\n        unique_key = validated_data[\"unique_key\"]\n        # 添加主机与ssh连接用户的映射关系\n        BaseRedisData(unique_key).set_host_user_map()\n        # 获取存储在redis中的服务与主机的映射关系\n        _host_ser_map = BaseRedisData(\n            unique_key).get_step_5_host_service_map()\n        install_data = validated_data[\"data\"]\n        # 查看前端发送的ip地址范围和后端存储的是否能够对应\n        _set_1 = set(_host_ser_map.keys()) - set(install_data.keys())\n        if len(_set_1) != 0:\n            raise ValidationError(\n                f\"主机 [{','.join(list(_set_1))}] 缺失\")\n        _set_2 = set(install_data.keys()) - set(_host_ser_map.keys())\n        if len(_set_2) != 0:\n            raise ValidationError(\n                f\"主机 [{','.join(list(_set_2))}] 不在已选范围内\")\n        # 校验服务数量及合法性 TODO 后期可针对服务数量准确性进行详细校验\n        error_lst = self.check_service_dis(\n            host_service_map=_host_ser_map, install_data=install_data)\n        if error_lst:\n            validated_data[\"error_lst\"] = error_lst\n            validated_data[\"is_continue\"] = False\n            return validated_data\n        cluster_name_map = BaseRedisData(\n            unique_key).get_step_3_cluster_name_map()\n        # 组装服务数据\n        _valid_data = BaseRedisData(\n            unique_key).get_step_2_origin_data()\n        _install_data = _valid_data[\"install\"]\n        # 获取主机与用户映射关系\n        host_user_map = BaseRedisData(unique_key).get_host_user_map()\n        # _use_exist_data = _valid_data[\"use_exist\"]\n        all_install_service_lst = self.make_final_install_data(\n            install_data=install_data,\n            valid_data=_install_data,\n            run_user=run_user,\n            host_ser_map=_host_ser_map,\n            cluster_name_map=cluster_name_map,\n            host_user_map=host_user_map\n        )\n\n        # 解决base_env服务的安装\n        base_env_ser_lst = BaseEnvServiceUtils(\n            all_install_service_lst=all_install_service_lst,\n            host_user_map=host_user_map\n        ).run()\n        all_install_service_lst.extend(base_env_ser_lst)\n        # 解决带有with标识服务的解析方法\n        with_ser_lst = WithServiceUtils(\n            all_install_service_lst=all_install_service_lst,\n            unique_key=unique_key,\n            run_user=run_user,\n            host_user_map=host_user_map\n        ).run()\n        all_install_service_lst.extend(with_ser_lst)\n        # 划分vip处理\n        service_vip_map = BaseRedisData(\n            unique_key=unique_key).get_step3_service_vip_map()\n        if service_vip_map:\n            _keep_alive_lst = SerVipUtils(\n                install_services=all_install_service_lst,\n                service_vip_map=service_vip_map,\n                host_user_map=host_user_map,\n                run_user=run_user\n            ).run()\n            all_install_service_lst.extend(_keep_alive_lst)\n        # TODO 依赖关系绑定\n        all_install_service_lst = ValidateInstallServicePortArgs(\n            data=all_install_service_lst\n        ).run()\n        is_continue, error_msg = self.check_error_msg(all_install_service_lst)\n        logger.info(f\"Final check_error_msg: {is_continue}\")\n        logger.info(f\"Final check service: {all_install_service_lst}\")\n        if not is_continue:\n            _re_data = {\n                el: [\n                    ser for ser in all_install_service_lst\n                    if ser.get(\"ip\") == el\n                ] for el in validated_data[\"data\"]\n            }\n            validated_data[\"data\"] = _re_data\n            validated_data[\"is_continue\"] = is_continue\n            validated_data[\"error_msg\"] = error_msg\n            return validated_data\n        # 分配role\n        all_install_service_lst = SerRoleUtils.get(all_install_service_lst)\n        # 服务排序处理\n        all_install_service_lst = MakeServiceOrder(\n            all_service=all_install_service_lst\n        ).run()\n        _flag, _res = CreateInstallPlan(\n            all_install_service_lst=all_install_service_lst,\n            unique_key=unique_key\n        ).run()\n        if not _flag:\n            logger.error(f\"Failed CreateInstallPlan: {_res}\")\n            raise _res\n        validated_data[\"is_continue\"] = is_continue\n        validated_data[\"error_msg\"] = error_msg\n        return validated_data\n\n\nclass MainInstallHistorySerializer(serializers.ModelSerializer):\n    \"\"\" 历史安装记录 \"\"\"\n    operator = serializers.CharField(max_length=32, help_text=\"操作用户\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = MainInstallHistory\n        fields = [\n            \"operation_uuid\", \"task_id\",\n            \"install_status\", \"created\", \"modified\", \"operator\"\n        ]\n\n\nclass CreateComponentInstallInfoSerializer(Serializer):\n    \"\"\"\n    请求格式：\n    {\n        \"high_availability\": true,\n        \"install_component\": [\n            {\n                \"name\": \"jenkinsNB\",\n                \"version\": \"2.303.2\"\n            }\n        ],\n        \"unique_key\": \"abf7d622-6fc8-4a04-ad4c-49b57298ecdf\"\n    }\n    \"\"\"\n    unique_key = serializers.CharField(\n        help_text=\"操作唯一值\", read_only=True\n    )\n    high_availability = serializers.BooleanField(\n        write_only=True, required=True,\n        help_text=\"是否选择高可用模式\",\n        error_messages={\"required\": \"必须包含[high_availability]字段\"}\n    )\n    install_component = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"产品列表，eg: [{'name': 'ser1', 'version': '1'}]\",\n        write_only=True, required=True,\n        error_messages={\"required\": \"必须包含[install_component]字段\"}\n    )\n    data = serializers.DictField(\n        help_text=\"详细安装数据\",\n        read_only=True\n    )\n\n    def validate_install_component(self, install_component):  # NOQA\n        \"\"\"\n        校验即将安装的产品、应用是否在可支持范围内\n        :param install_component: 要安装的应用\n        :type install_component: list\n        :return:\n        \"\"\"\n        for item in install_component:\n            _name = item.get(\"name\")\n            _version = item.get(\"version\")\n            component_obj = ApplicationHub.objects.filter(\n                app_name=_name,\n                app_version=_version,\n                is_release=True\n            ).last()\n            if not component_obj:\n                raise ValidationError(f\"组件 [{_name}] [{_version}] 不存在\")\n        return install_component\n\n    def create(self, validated_data):\n        \"\"\"\n        构建前端可选安装数据\n        :param validated_data:\n        :return:\n        \"\"\"\n        logger.info(\n            f\"CreateComponentInstallInfoSerializer.validated_data: \"\n            f\"{validated_data}\")\n        install_component = validated_data[\"install_component\"]\n        high_availability = validated_data[\"high_availability\"]\n        unique_key = str(uuid.uuid4())\n\n        _basic = list()\n        # 遍历所有需要安装的自研服务信息，确定服务的依赖关系并存储至_dependence变量内\n        _dependence = list()\n        for item in install_component:\n            _info = ComponentServiceParse(\n                ser_name=item[\"name\"], ser_version=item[\"version\"],\n                high_availability=high_availability, unique_key=unique_key\n            ).run()\n            _dependence.append(_info)\n        # 将带有with标识的服务进行处理\n        with_ser_lst = list()\n        for item in install_component:\n            _dep = SerDependenceParseUtils(\n                parse_name=item.get(\"name\"),\n                parse_version=item.get(\"version\"),\n                high_availability=high_availability\n            ).run_ser()\n            _dependence.extend(_dep)\n            if \"with_flag\" in item:\n                with_ser_lst.append(item)\n\n        # 将带有with标识的服务存放至redis数据中\n        BaseRedisData(\n            unique_key=unique_key).step_set_with_ser(data=with_ser_lst)\n\n        # 使用make_lst_unique方法将服务依赖列表去重处理\n        _dependence = make_lst_unique(\n            lst=_dependence, key_1=\"name\", key_2=\"version\")\n\n        # 判断最终用户可否进行下一步的标记\n        is_continue = True\n        for item in _basic:\n            if \"error_msg\" in item and item[\"error_msg\"]:\n                is_continue = False\n                break\n        for item in _dependence:\n            if \"error_msg\" in item and item[\"error_msg\"]:\n                is_continue = False\n        validated_data[\"data\"] = {\n            \"basic\": _basic,\n            \"dependence\": _dependence,\n            \"is_continue\": is_continue\n        }\n        # 存储基础信息到redis\n        if is_continue:\n            BaseRedisData(\n                unique_key=unique_key\n            ).step_1_set_unique_key(data=validated_data)\n            BaseRedisData(\n                unique_key=unique_key\n            ).step_2_set_origin_install_data_args(data=validated_data)\n        validated_data[\"unique_key\"] = unique_key\n        return validated_data\n\n\nclass RetryInstallSerializer(Serializer):\n    \"\"\" 重试安装序列化 \"\"\"\n    unique_key = serializers.CharField(\n        help_text=\"操作唯一值\",\n        required=True,\n        error_messages={\"required\": \"必须包含[unique_key]字段\"}\n    )\n\n    def validate_unique_key(self, unique_key):  # NOQA\n        \"\"\"\n        校验unique_key\n        :param unique_key:\n        :return:\n        \"\"\"\n        if not MainInstallHistory.objects.filter(\n                operation_uuid=unique_key\n        ).exists():\n            raise ValidationError(f\"无法找到[unique_key: {unique_key}]\")\n        return unique_key\n\n    def create(self, validated_data):\n        \"\"\"\n        :param validated_data:\n        :return:\n        \"\"\"\n        unique_key = validated_data[\"unique_key\"]\n        main_install_history = MainInstallHistory.objects.filter(\n            operation_uuid=unique_key\n        ).last()\n        # 调用异步任务，存储异步任务执行id\n        task_id = install_service_task.delay(main_install_history.id)\n        MainInstallHistory.objects.filter(\n            id=main_install_history.id\n        ).update(task_id=task_id)\n        return validated_data\n"
  },
  {
    "path": "omp_server/app_store/new_install_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: new_install_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-12 14:01\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport os\nimport json\nimport pickle\nimport logging\nimport traceback\nfrom copy import deepcopy\nfrom concurrent.futures import ThreadPoolExecutor\n\nimport redis\nfrom ruamel import yaml\nfrom django.db import transaction\nfrom django.db.models import F\nfrom rest_framework.exceptions import ValidationError\n\nfrom omp_server.settings import PROJECT_DIR\nfrom db_models.models import (\n    ProductHub, ApplicationHub, ClusterInfo, Service, Host,\n    Env, ServiceConnectInfo, Product, MainInstallHistory,\n    DetailInstallHistory, PreInstallHistory, PostInstallHistory\n)\nfrom app_store.tasks import install_service as install_service_task\nfrom app_store.deploy_mode_utils import SERVICE_MAP\nfrom utils.common.exceptions import GeneralError\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.parse_config import (\n    BASIC_ORDER,\n    OMP_REDIS_HOST,\n    OMP_REDIS_PORT,\n    OMP_REDIS_PASSWORD\n)\nfrom app_store.deploy_role_utils import DEPLOY_ROLE_UTILS\n\nlogger = logging.getLogger(\"server\")\n\nDIR_KEY = \"{data_path}\"\nUNIQUE_KEY_ERROR = \"后台无法追踪此流程或安装流程已开始,请查看安装记录或重新进入部署流程!\"\nWEB_CONTAINERS = [\"tengine\"]\n\n\nclass RedisDB(object):\n    \"\"\"\n    redis数据库管理工具\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"\n        获取redis连接对象\n        \"\"\"\n        self.conn = redis.Redis(\n            host=OMP_REDIS_HOST,\n            port=OMP_REDIS_PORT,\n            db=15,\n            password=OMP_REDIS_PASSWORD\n        )\n\n    def delete_keys(self, keyword):\n        \"\"\"\n        删除以某个关键字开头的key\n        :param keyword: 关键字\n        :return:\n        \"\"\"\n        for key in self.conn.scan_iter(f\"{keyword}*\"):\n            self.conn.delete(key)\n\n    def set(self, name, data, timeout=60 * 60 * 8):\n        \"\"\"\n        设置redis键值对\n        :param name: redis键名称\n        :param data: redis中要存储的值\n        :param timeout: 超时时间\n        :return:\n        \"\"\"\n        self.conn.set(name, pickle.dumps(data), ex=timeout)\n        logger.info(f\"Insert data to redis:\\nname:{name}\\ndata:{data}\")\n\n    def update(self, name, data, timeout=60 * 60 * 8):\n        \"\"\"\n        更新redis键值对,此时过期时间不可刷新\n        :param name: redis键名称\n        :param data: redis中要存储的值\n        :param timeout: 超时时间 -2是过期 -1是无过期\n        :return:\n        \"\"\"\n        _obj = self.conn.ttl(name)\n        if _obj == -2:\n            _obj = timeout\n        return self.set(name, data, timeout=_obj)\n\n    def get(self, name):\n        \"\"\"\n        获取存储在redis中的值\n        :param name: redis键名称\n        :return:\n        \"\"\"\n        try:\n            logger.info(f\"Try get data from redis by name: {name}\")\n            _obj = self.conn.get(name=name)\n            if not _obj:\n                logger.error(\n                    f\"Failed get data from redis by name: {name}, res is None\")\n                return False, None\n            data = pickle.loads(_obj)\n            logger.info(f\"Get data from redis by name: {name}, res is {data}\")\n            return True, data\n        except Exception as e:\n            logger.error(\n                f\"Error while get {name} from redis: {str(e)}\\n\"\n                f\"{traceback.format_exc()}\")\n            return False, None\n\n\nclass BaseRedisData(object):\n    \"\"\" redis中存储信息配置类 \"\"\"\n\n    def __init__(self, unique_key):\n        self.unique_key = unique_key\n        self.redis = RedisDB()\n\n    def delete_all_keys(self):\n        \"\"\"\n        删除unique_key相关数据\n        :return:\n        \"\"\"\n        self.redis.delete_keys(keyword=self.unique_key)\n\n    def _get(self, key):\n        \"\"\"\n        根据redis的key获取值\n        :param key: redis的key\n        :type key: str\n        :return:\n        \"\"\"\n        _flag, _data = self.redis.get(name=key)\n        if not _flag:\n            raise ValidationError(UNIQUE_KEY_ERROR)\n        return _data\n\n    def step_set_with_ser(self, data):\n        \"\"\"\n        设置with服务范围，临时存储，在最终部署的时候添加回来\n        [\n            {\"name\": \"xx\", \"version\": \"xxx\", \"with\": \"xxx\"}\n        ]\n        :param data:\n        :return:\n        \"\"\"\n        self.redis.set(\n            name=self.unique_key + \"_with_ser\",\n            data=data\n        )\n\n    def get_with_ser(self):\n        \"\"\"\n        获取 with ser服务的列表\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_with_ser\"\n        return self._get(key=key)\n\n    def step_1_set_unique_key(self, data):\n        \"\"\"\n        第一步 存储安装流程标志位到redis中\n        data数据格式为\n        {\n            'data': [{'name': 'douc', 'version': ['5.3.0']}],\n            'unique_key': '60649454-0149-44e8-a813-7309ffbd96ca'\n        }\n        :param data: 安装入口数据\n        :type data: dict\n        :return:\n        \"\"\"\n        self.redis.set(name=self.unique_key, data=data)\n\n    def get_unique_key(self):\n        \"\"\"\n        获取安装流程标记\n        :return:\n        \"\"\"\n        _flag, _ = self.redis.get(name=self.unique_key)\n        if not _flag:\n            raise ValidationError(UNIQUE_KEY_ERROR)\n        return self.unique_key\n\n    def step_2_set_origin_install_data_args(self, data):  # NOQA\n        \"\"\"\n        存储要安装的数据到redis，存储的是将要安装的服务的名称，版本以及所属产品\n        入参data如下：\n        {\n            \"unique_key\": \"60649454-0149-44e8-a813-7309ffbd96ca\",\n            \"high_availability\": false,\n            \"install_product\": [\n                {\n                    \"name\": \"douc\",\n                    \"version\": \"5.3.0\"\n                }\n            ],\n            \"data\": {\n                \"basic\": [\n                    {\n                        \"name\": \"douc\",\n                        \"version\": \"5.3.0\",\n                        \"services_list\": [\n                            {\n                                \"name\": \"doucApi\",\n                                \"version\": \"2.3.0\",\n                                \"deploy_mode\": {\n                                    \"default\": 1,\n                                    \"step\": 0\n                                }\n                            },\n                            {\n                                \"name\": \"doucWeb\",\n                                \"version\": \"2.3.0\",\n                                \"deploy_mode\": {\n                                    \"default\": 1,\n                                    \"step\": 0\n                                }\n                            }\n                        ],\n                        \"error_msg\": \"\"\n                    }\n                ],\n                \"dependence\": [\n                    {\n                        \"name\": \"kafka\",\n                        \"version\": \"2.2.2\",\n                        \"exist_instance\": [],\n                        \"is_use_exist\": false,\n                        \"is_base_env\": false,\n                        \"deploy_mode\": {\n                            \"default\": 1,\n                            \"step\": 1\n                        }\n                    },\n                    {\n                        \"name\": \"jdk\",\n                        \"version\": \"1.8.0\",\n                        \"exist_instance\": [],\n                        \"is_use_exist\": true,\n                        \"is_base_env\": false,\n                        \"deploy_mode\": {\n                            \"default\": 1,\n                            \"step\": 1\n                        }\n                    },\n                    {\n                        \"name\": \"mysql\",\n                        \"version\": \"5.7.34\",\n                        \"exist_instance\": [],\n                        \"is_use_exist\": false,\n                        \"is_base_env\": false,\n                        \"deploy_mode\": [\n                            {\n                                \"key\": \"master-slave\",\n                                \"name\": \"主从\"\n                            },\n                            {\n                                \"key\": \"master-master\",\n                                \"name\": \"主主(vip)\"\n                            }\n                        ]\n                    }\n                ],\n                \"is_continue\": true\n            }\n        }\n        存储到redis中的数据如下：\n        {\n            \"install\": {\n                \"doucApi\": {\n                    \"version\": \"2.3.0\",\n                    \"product\": \"douc\"\n                },\n                \"kafka\": {\n                    \"version\": \"2.2.2\",\n                    \"product\": null\n                }\n            },\n            \"use_exist\": {}\n        }\n\n        :param data: 根据要安装的产品生成的安装参数\n        :type data: dict\n        :return:\n        \"\"\"\n        basic = data.get(\"data\", {}).get(\"basic\")\n        dependence = data.get(\"data\", {}).get(\"dependence\")\n        _data = {\n            \"install\": dict(),\n            \"use_exist\": dict()\n        }\n        for item in basic:\n            for el in item.get(\"services_list\"):\n                _data[\"install\"][el[\"name\"]] = {\n                    \"version\": el[\"version\"],\n                    \"product\": item[\"name\"]\n                }\n        for item in dependence:\n            if item.get(\"is_use_exist\"):\n                _data[\"use_exist\"][item[\"name\"]] = item.get(\n                    \"exist_instance\", [])\n                continue\n            # TODO 优化版本间若依赖选择\n            _data[\"install\"][item[\"name\"]] = {\n                # \"version\": item[\"version\"],\n                \"version\": ApplicationHub.objects.filter(\n                    app_name=item[\"name\"],\n                    app_version__startswith=item[\"version\"]\n                ).last().app_version,\n                \"product\": None\n            }\n        self.redis.set(\n            name=self.unique_key + \"_step_2_origin_data\",\n            data=_data\n        )\n        return _data\n\n    def get_step_2_origin_data(self):\n        \"\"\"\n        获取安装原始数据\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_2_origin_data\"\n        return self._get(key=key)\n\n    def step_3_set_checked_data(self, data):\n        \"\"\"\n        存储已被校验过的安装数据\n        {\n            \"unique_key\": \"60649454-0149-44e8-a813-7309ffbd96ca\",\n            \"data\": {\n                \"basic\": [\n                    {\n                        \"name\": \"douc\",\n                        \"version\": \"5.3.0\",\n                        \"services_list\": [\n                            {\n                                \"name\": \"doucApi\",\n                                \"version\": \"2.3.0\",\n                                \"deploy_mode\": 1\n                            }\n                        ],\n                        \"cluster_name\": \"douc-cluster-1\"\n                    }\n                ],\n                \"dependence\": [\n                    {\n                        \"name\": \"kafka\",\n                        \"version\": \"2.2.2\",\n                        \"exist_instance\": [],\n                        \"deploy_mode\": 1,\n                        \"is_use_exist\": false,\n                        \"is_base_env\": false,\n                        \"cluster_name\": \"kafka-cluster-1\"\n                    }\n                ],\n                \"is_continue\": true\n            }\n        }\n        :param data: 存储点击下一步校验的数据\n        :type data: dict\n        :return:\n        \"\"\"\n        # 覆盖第一步选择的安装数据\n        self.step_2_set_origin_install_data_args(data=data)\n        self.redis.set(\n            name=self.unique_key + \"_step_3_checked_data\",\n            data=data\n        )\n        cluster_name_map = dict()\n        service_vip_map = dict()\n        basic = data.get(\"data\", {}).get(\"basic\", [])\n        for item in basic:\n            cluster_name_map[item[\"name\"]] = \\\n                item.get(\"cluster_name\")\n            # 存储服务对应产品的集群名称\n            # for el in item.get(\"services_list\"):\n            #     cluster_name_map[el[\"name\"]] = item.get(\"cluster_name\")\n        dependence = data.get(\"data\", {}).get(\"dependence\", [])\n        for item in dependence:\n            if item.get(\"is_use_exist\"):\n                continue\n            if item.get(\"vip\"):\n                service_vip_map[item.get(\"name\")] = item.get(\"vip\")\n            cluster_name_map[item[\"name\"]] = \\\n                item.get(\"cluster_name\")\n        self.redis.set(\n            name=self.unique_key + \"_step_3_cluster_name_map\",\n            data=cluster_name_map\n        )\n        self.redis.set(\n            name=self.unique_key + \"_step3_service_vip_map\",\n            data=service_vip_map\n        )\n\n    def get_step_3_checked_data(self):\n        \"\"\"\n        获取校验过的安装数据值\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_3_checked_data\"\n        return self._get(key=key)\n\n    def get_step_3_cluster_name_map(self):\n        \"\"\"\n        获取将要安装的产品的实例名称及组件的集群名称映射关系\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_3_cluster_name_map\"\n        return self._get(key=key)\n\n    def get_step3_service_vip_map(self):\n        \"\"\"\n        获取要是用的 vip 名\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step3_service_vip_map\"\n        return self._get(key=key)\n\n    def step_4_set_service_distribution(self, data):\n        \"\"\"\n        存储生成的服务部署数量以及服务间的绑定关系，用于前端选择服务分布\n        {\n            \"doucApi\": {\n                \"num\": 1,\n                \"with\": null\n            },\n            \"doucWeb\": {\n                \"num\": 1,\n                \"with\": \"portalWeb\"\n            },\n            \"mysql\": {\n                \"num\": 1,\n                \"with\": null\n            },\n            \"tengine\": {\n                \"num\": 1,\n                \"with\": null\n            }\n        }\n        :param data: 要安装的服务及数量以及服务间with绑定关系\n        :type data: dict\n        :return:\n        \"\"\"\n        self.redis.set(\n            name=self.unique_key + \"_step_4_service_distribution\",\n            data=data\n        )\n\n    def get_step_4_service_distribution(self):\n        \"\"\"\n        获取要部署的服务的数量以及服务绑定关系\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_4_service_distribution\"\n        return self._get(key=key)\n\n    def step_5_set_host_and_service_map(self, host_list, host_service_map):\n        \"\"\"\n        存储本次安装所需要的主机列表及主机与服务的分布关系字典\n        host_list:\n        [\n            \"10.0.14.234\",\n            \"10.0.14.231\"\n        ]\n        host_service_map:\n        {\n            \"10.0.14.234\": [\n                \"doucApi\",\n                \"doucSso\",\n            ],\n            \"10.0.14.231\": [\n                \"portalServer\",\n                \"kafka\"\n            ]\n        }\n\n        :param host_list: 本次安装涉及到的主机列表\n        :type host_list: list\n        :param host_service_map: 本次安装主机与服务的映射关系\n        :type host_service_map: dict\n        :return:\n        \"\"\"\n        self.redis.set(\n            name=self.unique_key + \"_step_5_host_list\",\n            data=host_list\n        )\n        self.redis.set(\n            name=self.unique_key + \"_step_5_host_service_map\",\n            data=host_service_map\n        )\n\n    def get_step_5_host_list(self):\n        \"\"\"\n        获取本次服务部署涉及到的主机列表\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_5_host_list\"\n        return self._get(key=key)\n\n    def get_step_5_host_service_map(self):\n        \"\"\"\n        获取本次服务部署涉及到主机与服务的映射关系\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_5_host_service_map\"\n        return self._get(key=key)\n\n    def step_6_set_final_data(self, data):\n        \"\"\"\n        设置最终要安装的服务列表\n        :param data:\n        :return:\n        \"\"\"\n        self.redis.set(\n            name=self.unique_key + \"_step_6_set_final_data\",\n            data=data\n        )\n\n    def get_step_6_set_final_data(self):\n        \"\"\"\n        获取最终要安装的服务列表\n        :return:\n        \"\"\"\n        key = self.unique_key + \"_step_6_set_final_data\"\n        return self._get(key=key)\n\n    def set_host_user_map(self):\n        \"\"\"\n        设置主机与用户之间的关系\n        :return:\n        \"\"\"\n        host_user_lst = Host.objects.all().values(\"ip\", \"username\")\n        host_user_dic = dict()\n        for item in host_user_lst:\n            host_user_dic[item[\"ip\"]] = item[\"username\"]\n        self.redis.set(self.unique_key + \"_host_user_map\", data=host_user_dic)\n\n    def get_host_user_map(self):\n        \"\"\"\n        获取主机与用户映射关系\n        :return:\n        \"\"\"\n        return self._get(self.unique_key + \"_host_user_map\")\n\n\ndef check_package_exists(app_obj):\n    \"\"\"\n    检查安装包是否存在\n    :param app_obj: 服务对象\n    :type app_obj: ApplicationHub\n    :return:\n    \"\"\"\n    try:\n        path = os.path.join(\n            PROJECT_DIR,\n            \"package_hub\",\n            app_obj.app_package.package_path,\n            app_obj.app_package.package_name\n        )\n        if not os.path.exists(path):\n            return False, path\n        return True, path\n    except Exception as e:\n        logger.error(\n            f\"check_package_exists: Exception: {str(e)}; \"\n            f\"{traceback.format_exc()}\")\n        return False, f\"程序错误: {str(e)}\"\n\n\nclass ProductServiceParse(object):\n    \"\"\" 解析要安装的应用服务的基础信息 \"\"\"\n\n    def __init__(\n            self, pro_name, pro_version,\n            high_availability=False, unique_key=None\n    ):\n        \"\"\"\n        产品、应用解析服务信息\n        :param pro_name: 产品、应用名称\n        :param pro_version: 产品、应用版本\n        :param high_availability: 是否使用高可用\n        \"\"\"\n        self.pro_name = pro_name\n        self.pro_version = pro_version\n        self.high_availability = high_availability\n        self.unique_key = unique_key\n\n    def get_default_and_step(self, app_obj):\n        \"\"\"\n        获取服务默认数量及步长\n        :param app_obj: 服务对象\n        :type app_obj ApplicationHub\n        :return:\n        \"\"\"\n        if not self.high_availability:\n            return 1, 1\n        # 如果服务是和tengine进行强绑定的，那么需要限制其数量为1\n        # TODO 当前tengine不支持高可用模式\n        if \"affinity\" in app_obj.extend_fields and \\\n                app_obj.extend_fields.get(\"affinity\") == \"tengine\":\n            return 1, 0\n        return 1, 1\n\n    def parse_single_service(self, service_dic):\n        \"\"\"\n        解析单个服务的数据\n        :param service_dic: 服务名称、字典\n        :type service_dic: dict\n        :return:\n        \"\"\"\n        _name = service_dic.get(\"name\")\n        _version = service_dic.get(\"version\")\n        app_obj = ApplicationHub.objects.filter(\n            app_type=ApplicationHub.APP_TYPE_SERVICE,\n            app_name=_name,\n            app_version=_version\n        ).last()\n        # 解决服务强绑定依赖问题\n        if \"affinity\" in app_obj.extend_fields and \\\n                app_obj.extend_fields[\"affinity\"] in WEB_CONTAINERS:\n            return {\n                \"name\": _name,\n                \"version\": _version,\n                \"with\": app_obj.extend_fields[\"affinity\"],\n                \"with_flag\": True,\n                \"deploy_mode\": {\n                    \"default\": 1,\n                    \"step\": 0\n                }\n            }\n        _default_dic = {\n            \"name\": _name,\n            \"version\": _version,\n            \"deploy_mode\": {\n                \"default\": 1,\n                \"step\": 0\n            },\n            \"error_msg\": \"\"\n        }\n        # 如果产品下的服务不存在应该返回错误信息\n        if not app_obj:\n            _default_dic[\"error_msg\"] = \\\n                f\"应用商店内无此服务 [{_name}({_version})]\"\n            return _default_dic\n        # 如果产品下的服务的安装包对象不存在应该返回错误信息\n        if not app_obj.app_package:\n            _default_dic[\"error_msg\"] = \\\n                f\"此服务 [{_name}({_version})] 无法找到安装包\"\n            return _default_dic\n        # 如果安装包实际不存在应该返回错误消息\n        _flag, _msg = check_package_exists(app_obj=app_obj)\n        if not _flag:\n            _default_dic[\"error_msg\"] = \\\n                f\"{_name}安装包不存在，请查看 {_msg}\"\n            return _default_dic\n        # 如果是非高可用模式，直接返回服务数据\n        # 如果是高可用模式，那么前端服务步长为0，后端服务步长为1\n        default, step = self.get_default_and_step(app_obj=app_obj)\n        return {\n            \"name\": _name,\n            \"version\": _version,\n            \"deploy_mode\": {\n                \"default\": default,\n                \"step\": step\n            }\n        }\n\n    def get_services_list(self):\n        \"\"\"\n        获取产品下的服务信息列表以及其默认数量及步长\n        :return:\n        \"\"\"\n        pro_obj = ProductHub.objects.filter(\n            pro_name=self.pro_name,\n            pro_version=self.pro_version\n        ).last()\n        pro_services = json.loads(pro_obj.pro_services)\n        return [self.parse_single_service(el) for el in pro_services]\n\n    def run(self):\n        \"\"\"\n        解析入口\n        :return:\n        \"\"\"\n        error_msg_lst = list()\n        services_list = self.get_services_list()\n        for item in services_list:\n            if \"error_msg\" in item and item[\"error_msg\"]:\n                error_msg_lst.append(item[\"error_msg\"])\n                item.pop(\"error_msg\")\n        _dic = {\n            \"name\": self.pro_name,\n            \"version\": self.pro_version,\n            \"services_list\": services_list,\n            \"error_msg\": \"\\n\".join(error_msg_lst)\n        }\n        return _dic\n\n\nclass ComponentServiceParse(object):\n    \"\"\"组件服务安祖昂解析\"\"\"\n\n    def __init__(\n            self, ser_name, ser_version,\n            high_availability=False, unique_key=None\n    ):\n        \"\"\"\n        产品、应用解析服务信息\n        :param ser_name: 组件名称\n        :param ser_version: 组件版本\n        :param high_availability: 是否使用高可用\n        \"\"\"\n        self.ser_name = ser_name\n        self.ser_version = ser_version\n        self.high_availability = high_availability\n        self.unique_key = unique_key\n\n    def get_deploy_mode(self, app_obj):\n        \"\"\"\n        获取服务默认数量及步长\n        :param app_obj: 服务对象\n        :type app_obj ApplicationHub\n        :return:\n        \"\"\"\n        pass\n\n    def parse_single_service(self):\n        \"\"\"\n        解析单个服务的数据\n        :return:\n        \"\"\"\n        app_obj = ApplicationHub.objects.filter(\n            # app_type=ApplicationHub.APP_TYPE_COMPONENT,\n            app_name=self.ser_name,\n            app_version=self.ser_version\n        ).last()\n        # 解决服务强绑定依赖问题\n        _default_dic = {\n            \"name\": self.ser_name,\n            \"version\": self.ser_version,\n            \"deploy_mode\": self.get_deploy_mode(app_obj=app_obj),\n        }\n        # 如果产品下的服务不存在应该返回错误信息\n        if not app_obj:\n            _default_dic[\"error_msg\"] = \\\n                f\"应用商店内无此服务 [{self.ser_name}({self.ser_version})]\"\n            return _default_dic\n        _, is_pack_exist, msg = SerDependenceParseUtils(\n            parse_name=self.ser_name, parse_version=self.ser_version,\n            high_availability=self.high_availability\n        ).get_ser_instances(obj=app_obj)\n        if not is_pack_exist:\n            _default_dic[\"error_msg\"] = \\\n                f\"{self.ser_name}安装包不存在，请查看 {msg}\"\n        _default_dic[\"is_pack_exist\"] = is_pack_exist\n        _default_dic[\"is_use_exist\"] = False\n        _default_dic[\"exist_instance\"] = list()\n        _default_dic[\"is_base_env\"] = SerDependenceParseUtils(\n            parse_name=self.ser_name, parse_version=self.ser_version,\n            high_availability=self.high_availability\n        ).get_is_base_env(obj=app_obj)\n        _default_dic[\"deploy_mode\"] = SerDeployModeUtils(\n            ser_name=self.ser_name,\n            high_availability=self.high_availability\n        ).get()\n        return _default_dic\n\n    def run(self):\n        \"\"\"\n        解析入口\n        :return:\n        \"\"\"\n        return self.parse_single_service()\n\n\ndef make_lst_unique(lst, key_1, key_2):\n    \"\"\"\n    去重列表内的字典\n        lst = [{\"name\": \"1\", \"version\": \"1\"}, {\"name\": \"1\", \"version\": \"1\"}]\n        lst = make_lst_unique(lst, \"name\", \"version\")\n    :param lst: 被操作对象\n    :param key_1: 关键key1\n    :param key_2: 关键key2\n    :return:\n    \"\"\"\n    unique_dic = dict()\n    ret_lst = list()\n    for el in lst:\n        _unique = el.get(key_1, \"\") + \"_\" + el.get(key_2, \"\")\n        if _unique in unique_dic:\n            continue\n        # 服务版本模糊判断逻辑 jon.liu 20211225\n        _app = ApplicationHub.objects.filter(\n            app_name=el.get(key_1),\n            app_version__startswith=el.get(key_2)\n        ).last()\n        if not _app or el.get(key_1) + \"_\" + el.get(key_2) not in unique_dic:\n            unique_dic[el.get(key_1) + \"_\" + el.get(key_2)] = True\n            ret_lst.append(el)\n            continue\n        _unique = _app.app_name + \"_\" + _app.app_version\n        if _unique in unique_dic:\n            continue\n        # 更新弱校验的服务版本\n        el[\"version\"] = _app.app_version\n        ret_lst.append(el)\n        unique_dic[_unique] = True\n    return ret_lst\n\n\nclass SerDependenceParseUtils(object):\n    \"\"\"\n    依赖解决工具类\n    \"\"\"\n\n    def __init__(self, parse_name, parse_version, high_availability=False):\n        \"\"\"\n        初始化对象, 服务级别的解析，包含自研服务和基础组件服务\n        :param parse_name: 要解析的名称，服务\n        :type parse_name: str\n        :param parse_version: 要解析的版本\n        :type parse_version: str\n        :param high_availability: 是否使用高可用模式\n        :type high_availability: bool\n        \"\"\"\n        self.high_availability = high_availability\n        self.parse_name = parse_name\n        self.parse_version = parse_version\n        self.unique_key = self.parse_name + self.parse_version\n        self.host_num = Host.objects.all().count()\n\n    def get_newest_ser(self):\n        \"\"\"\n        获取最新的服务对象，同服务，同版本\n        :return: ApplicationHub\n        \"\"\"\n        return ApplicationHub.objects.filter(\n            app_name=self.parse_name,\n            app_version=self.parse_version,\n            is_release=True\n        ).last()\n\n    def get_ser_instances(self, obj):  # NOQA\n        \"\"\"\n        查看服务是否已经被安装以及应用商店内是否具备安装条件\n        返回值含义：\n            cluster_info：当前服务的集群信息\n            instance_info：当前服务的实例对象信息\n            is_pack_exist：当前服务的安装包是否存在\n        :param obj: ApplicationHub实例\n        :type obj: ApplicationHub\n        :return: cluster_info, instance_info, is_pack_exist\n        \"\"\"\n        is_pack_exist, msg = check_package_exists(app_obj=obj)\n        if self.get_is_base_env(obj=obj):\n            return list(), is_pack_exist, msg\n        exist_instance = list()\n        # 判断当前服务的集群\n        cluster_info = list(ClusterInfo.objects.filter(\n            cluster_service_name=obj.app_name\n        ).values(\"id\", \"cluster_name\"))\n        exist_instance.extend(\n            [\n                {\n                    \"id\": el.get(\"id\"),\n                    \"name\": el.get(\"cluster_name\"),\n                    \"type\": \"cluster\"\n                } for el in cluster_info\n            ]\n        )\n        # 判断当前服务的实例信息\n        instance_info = list(Service.split_objects.filter(\n            service__app_name=obj.app_name,\n            service__app_version=obj.app_version,\n            cluster__isnull=True\n        ).values(\"service_instance_name\", \"id\"))\n        exist_instance.extend(\n            [\n                {\n                    \"id\": el.get(\"id\"),\n                    \"name\": el.get(\"service_instance_name\"),\n                    \"type\": \"single\"\n                } for el in instance_info\n            ]\n        )\n        return exist_instance, is_pack_exist, msg\n\n    def get_is_base_env(self, obj):  # NOQA\n        \"\"\"\n        确定当前服务是否为基础环境：如 jdk 等\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        if obj.is_base_env:\n            return True\n        if not obj.extend_fields:\n            return False\n        is_base_env = obj.extend_fields.get(\"base_env\", False)\n        if is_base_env:\n            if isinstance(is_base_env, bool):\n                return is_base_env\n            if isinstance(is_base_env, str) and \\\n                    is_base_env.upper().strip() == \"TRUE\":\n                return True\n        return False\n\n    def get_dependence(self, lst, dep):\n        \"\"\"\n        解决服务依赖关系核心方法\n        :param lst: 存储结果的列表\n        :param dep: 服务依赖关系列表\n        :return: list()\n        \"\"\"\n        unique_key_lst = list()\n        for inner in dep:\n            _name, _version = inner.get(\"name\"), inner.get(\"version\")\n            # 服务版本弱依赖逻辑 jon.liu 20211225\n            _app = ApplicationHub.objects.filter(\n                app_name=_name,\n                app_version__startswith=_version,\n                is_release=True\n            ).order_by(\"created\").last()\n            if _app:\n                inner[\"version\"] = _app.app_version\n                _version = _app.app_version\n            # 定义服务&版本唯一标准，防止递归错误\n            unique_key = str(_name) + str(_version)\n            # 如果当前服务和需要被解析的源服务重叠，那么则跳过\n            # 如果当前服务的依赖关系已经被解决，那么则跳过\n            if unique_key == self.unique_key or unique_key in unique_key_lst:\n                continue\n            unique_key_lst.append(unique_key)\n            # 判断当前被依赖服务是否存在，如果不存在就直接返回，不再处理深层依赖\n            if not _app:\n                inner[\"exist_instance\"] = list()\n                inner[\"deploy_mode\"] = list()\n                inner[\"error_msg\"] = f\"应用商店内无此服务 [{_name}({_version})]\"\n                inner[\"is_base_env\"] = False\n                lst.append(inner)\n                continue\n            # 查看当前服务是否被安装\n            exist_instance, is_pack_exist, msg = \\\n                self.get_ser_instances(obj=_app)\n            # 获取依赖服务的相关信息\n            inner[\"exist_instance\"] = exist_instance\n            if inner[\"exist_instance\"]:\n                inner[\"is_use_exist\"] = True\n            else:\n                inner[\"is_use_exist\"] = False\n            if not is_pack_exist:\n                inner[\"error_msg\"] = f\"{_name}安装包不存在，请查看 {msg}\"\n            inner[\"is_base_env\"] = self.get_is_base_env(obj=_app)\n            # TODO 获取当前服务的部署模式\n            inner[\"deploy_mode\"] = SerDeployModeUtils(\n                ser_name=_name,\n                high_availability=self.high_availability,\n                host_num=self.host_num\n            ).get()\n            lst.append(inner)\n            if not _app.app_dependence:\n                continue\n            _app_dependence = json.loads(_app.app_dependence)\n            self.get_dependence(\n                lst, dep=_app_dependence\n            )\n\n    def run_ser(self):\n        \"\"\"\n        解析服务的依赖关系入口\n        :return: 服务依赖关系列表\n        \"\"\"\n        _ser = self.get_newest_ser()\n        if not _ser or not _ser.app_dependence:\n            return list()\n        app_dep_lst = json.loads(_ser.app_dependence)\n        ret_lst = list()\n        self.get_dependence(lst=ret_lst, dep=app_dep_lst)\n        ret_lst = make_lst_unique(ret_lst, \"name\", \"version\")\n        return ret_lst\n\n\nclass SerDeployModeUtils(object):\n    \"\"\" 针对开源组件服务部署模式的获取及校验工具 \"\"\"\n\n    def __init__(self, ser_name, high_availability=False, host_num=0):\n        self.ser_name = ser_name\n        self.high_availability = high_availability\n        self.host_num = Host.objects.all().count()\n\n    def get(self):\n        \"\"\"\n        获取服务的部署模式\n        :return:\n        \"\"\"\n        if self.ser_name in SERVICE_MAP:\n            return SERVICE_MAP[self.ser_name](\n                host_num=self.host_num,\n                high_availability=self.high_availability\n            ).get()\n        # 如果服务不在SERVICE_MAP中，说明omp本身还未支持该服务，给出默认值\n        return {\n            \"default\": 1,\n            \"step\": 1\n        }\n\n\nclass SerRoleUtils(object):\n    \"\"\" 针对开源组件角色分配类 \"\"\"\n\n    @staticmethod\n    def get(install_services):\n        \"\"\"\n        获取服务的部署模式\n        :return:\n        \"\"\"\n        tmp_dict = {}\n        cp_service = deepcopy(install_services)\n        for item in cp_service:\n            if item['name'] in DEPLOY_ROLE_UTILS.keys():\n                tmp_dict[item['name']] = tmp_dict.get(\n                    item['name'], []) + [item]\n                if item[\"name\"] != \"mysql\":\n                    install_services.remove(item)\n        for name, obj in tmp_dict.items():\n            # 发现有需要分role的实例\n            if name == \"mysql\":\n                install_services = DEPLOY_ROLE_UTILS[name]().update_service(\n                    install_services\n                )\n            else:\n                install_services.extend(\n                    DEPLOY_ROLE_UTILS[name]().update_service(obj))\n        return install_services\n\n\nclass SerVipUtils(object):\n    \"\"\" 针对开源组件进行vip绑定处理 \"\"\"\n\n    def __init__(\n            self,\n            install_services, service_vip_map, host_user_map, run_user):\n        self.install_services = install_services\n        self.service_vip_map = service_vip_map\n        self.host_user_map = host_user_map\n        self.run_user = run_user\n\n    def get_keep_alive(self, ip, data_folder, name):\n        _tmp_ser = dict()\n        _app = ApplicationHub.objects.filter(\n            app_name=\"keepalived\",\n        ).last()\n        install_args = ServiceArgsPortUtils(\n            ip=ip,\n            data_folder=data_folder,\n            run_user=self.run_user,\n            host_user_map=self.host_user_map\n        ).remake_install_args(obj=_app)\n        ports = ServiceArgsPortUtils(\n            ip=ip,\n            data_folder=data_folder,\n            run_user=self.run_user,\n            host_user_map=self.host_user_map\n        ).get_app_port(obj=_app)\n        _tmp_ser[\"name\"] = _app.app_name\n        _tmp_ser[\"version\"] = _app.app_version\n        instance_name = \\\n            \"keepalived\" + \"-\" + \"-\".join(ip.split(\".\")[-2:])\n        _tmp_ser[\"ip\"] = ip\n        _tmp_ser[\"data_folder\"] = data_folder\n        _tmp_ser[\"run_user\"] = self.run_user\n        _tmp_ser[\"install_args\"] = install_args\n        _tmp_ser[\"ports\"] = ports\n        _tmp_ser[\"instance_name\"] = instance_name\n        _tmp_ser[\"cluster_name\"] = None\n        _tmp_ser[\"roles\"] = name\n        return _tmp_ser\n\n    def run(self):\n        \"\"\"\n        处理服务的 vip 模式\n        :return:\n        \"\"\"\n        keep_alive_lst = list()\n        for item in self.install_services:\n            if item.get(\"name\") in self.service_vip_map:\n                _ser = self.get_keep_alive(\n                    ip=item.get(\"ip\"),\n                    data_folder=item.get(\"data_folder\"),\n                    name=item.get(\"name\")\n                )\n                _ser[\"vip\"] = self.service_vip_map[item[\"name\"]]\n                keep_alive_lst.append(_ser)\n        return keep_alive_lst\n\n\nclass SerWithUtils(object):\n    \"\"\" 服务强绑定关系解析类 \"\"\"\n\n    def __init__(self, ser_name, ser_version):\n        \"\"\"\n\n        :param ser_name: 服务名称\n        :type ser_name: str\n        \"\"\"\n        self.ser_name = ser_name\n        self.ser_version = ser_version\n\n    def run(self):\n        \"\"\"\n        获取服务绑定关系\n        :return:\n        \"\"\"\n        app_obj = ApplicationHub.objects.filter(\n            app_name=self.ser_name,\n            app_version=self.ser_version\n        ).last()\n        if not app_obj or not app_obj.extend_fields:\n            return None\n        if \"affinity\" in app_obj.extend_fields and \\\n                app_obj.extend_fields[\"affinity\"]:\n            return app_obj.extend_fields[\"affinity\"]\n        return None\n\n\nclass ServiceArgsPortUtils(object):\n    \"\"\" 服务安装过程中参数解析类 \"\"\"\n\n    def __init__(self, ip=None, data_folder=None,\n                 run_user=None, host_user_map=None):\n        self.ip = ip\n        self.data_folder = data_folder\n        self.run_user = run_user\n        self.host_user_map = host_user_map\n\n    @staticmethod\n    def get_product_config():\n        \"\"\"\n        获取产品配置信息，读取 config/product.yaml 配置文件\n        :return:\n        \"\"\"\n        config_path = os.path.join(PROJECT_DIR, \"config/product.yaml\")\n        if not os.path.exists(config_path):\n            return dict(), dict()\n        with open(config_path, \"r\") as fp:\n            product_config = yaml.load(fp, Loader=yaml.SafeLoader)\n        return product_config.get(\"install\", dict()), \\\n               product_config.get(\"ports\", dict())\n\n    @staticmethod\n    def inner_replace_args(target_lst, config_lst):\n        \"\"\"\n        更新配置参数方法\n        :param target_lst: 源配置列表\n        :param config_lst: 需要更新的配置列表\n        :return:\n        \"\"\"\n        try:\n            config_dic = {el[\"key\"]: el for el in config_lst}\n            new_lst = list()\n            for item in target_lst:\n                if item.get(\"key\") in config_dic:\n                    new_lst.append(config_dic[item.get(\"key\")])\n                else:\n                    new_lst.append(item)\n            return new_lst\n        except Exception as e:\n            raise GeneralError(\n                f\"更新安装参数错误, \"\n                f\"请检查 {os.path.join(PROJECT_DIR, 'config/product.yaml')} \"\n                f\"数据格式是否符合规则, 错误信息: {str(e)}\"\n            )\n\n    def make_product_config_overwrite(self, app_name, rep_type, lst):\n        \"\"\"\n        覆盖原有数据库中的yaml，利用 config/product.yaml 中的配置文件覆盖相关参数\n        :param app_name: 服务名称\n        :param rep_type: 替换类型, app_install_args | app_ports\n        :param lst: 替换参数列表\n        :return:\n        \"\"\"\n        app_install_args_dic, app_ports_dic = self.get_product_config()\n        if rep_type == \"app_install_args\" and app_name in app_install_args_dic:\n            return self.inner_replace_args(\n                target_lst=lst, config_lst=app_install_args_dic[app_name]\n            )\n        elif rep_type == \"app_ports\" and app_name in app_ports_dic:\n            return self.inner_replace_args(\n                target_lst=lst, config_lst=app_ports_dic[app_name]\n            )\n        return lst\n\n    @staticmethod\n    def make_editable(element):\n        \"\"\"\n        处理参数是否可编辑\n        :param element: 参数字典\n        :return:\n        \"\"\"\n        if element.get(\"editable\") is False or \\\n                str(element.get(\"editable\")).lower() == \"false\":\n            element[\"editable\"] = False\n        else:\n            element[\"editable\"] = True\n\n    def get_app_dependence(self, obj):  # NOQA\n        \"\"\"\n        解析服务级别的依赖关系\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        ser = SerDependenceParseUtils(obj.app_name, obj.app_version)\n        return ser.run_ser()\n\n    def get_app_port(self, obj):  # NOQA\n        \"\"\"\n        获取app的端口\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: list()\n        \"\"\"\n        if not obj.app_port:\n            return []\n        origin_ports = json.loads(obj.app_port)\n        final_ports = self.make_product_config_overwrite(\n            app_name=obj.app_name,\n            rep_type=\"app_ports\",\n            lst=origin_ports\n        )\n        for item in final_ports:\n            self.make_editable(item)\n        return final_ports\n\n    def format_app_install_args(self, app_install_args):  # NOQA\n        \"\"\"\n        构建安装参数\n        :param app_install_args: 服务安装参数\n        :type app_install_args: list\n        :return:\n        \"\"\"\n        for el in app_install_args:\n            if isinstance(el.get(\"default\"), str) and \\\n                    DIR_KEY in el.get(\"default\"):\n                el[\"default\"] = el[\"default\"].replace(DIR_KEY, \"\")\n                el[\"dir_key\"] = DIR_KEY\n        return app_install_args\n\n    def get_app_install_args(self, obj):  # NOQA\n        \"\"\"\n        解析安装参数信息\n        :param obj: 服务对象\n        :type obj: ApplicationHub\n        :return: list()\n        \"\"\"\n        # 标记安装过程中涉及到的数据目录，通过此标记给前端\n        # 给与前端提示信息，此标记对应于主机中的数据目录 data_folder\n        # 在后续前端提供出安装参数后，我们应该检查其准确性\n        if not obj.app_install_args:\n            return list()\n        origin_args = json.loads(obj.app_install_args)\n        final_args = self.make_product_config_overwrite(\n            app_name=obj.app_name,\n            rep_type=\"app_install_args\",\n            lst=origin_args\n        )\n        for item in final_args:\n            self.make_editable(item)\n        return self.format_app_install_args(final_args)\n\n    def reformat_install_args(self, install_args):\n        \"\"\"\n        重新格式化前端已经修改过了的install_args参数\n        :param install_args: 前端修改过的安装参数\n        :type install_args: list\n        :return:\n        \"\"\"\n        return self._parse(app_install_args=install_args)\n\n    def remake_install_args(self, obj):\n        \"\"\"\n        重新生成部署时需要使用的参数，相当于直接从数据库中解析完整的安装参数\n        :param obj: ApplicationHub对象\n        :type obj: ApplicationHub\n        :return:\n        \"\"\"\n        if not obj.app_install_args:\n            return list()\n        app_install_args = self.make_product_config_overwrite(\n            app_name=obj.app_name,\n            rep_type=\"app_install_args\",\n            lst=json.loads(obj.app_install_args)\n        )\n        return self._parse(app_install_args)\n\n    def _parse(self, app_install_args):\n        \"\"\"\n        格式化解析安装参数数据\n        :param app_install_args: 安装参数\n        :type app_install_args: list\n        :return:\n        \"\"\"\n        for el in app_install_args:\n            if DIR_KEY in el.get(\"default\") or \"dir_key\" in el:\n                el[\"default\"] = os.path.join(\n                    self.data_folder,\n                    el[\"default\"].replace(DIR_KEY, \"\").lstrip(\"/\")\n                )\n                el[\"dir_key\"] = DIR_KEY\n            if el.get(\"key\") == \"run_user\":\n                if self.host_user_map[self.ip] != \"root\":\n                    el[\"default\"] = self.host_user_map[self.ip]\n                    continue\n                if self.run_user:\n                    el[\"default\"] = self.run_user\n        return app_install_args\n\n\nclass ValidateInstallService(object):\n    \"\"\" 检查要安装的服务信息是否准确 \"\"\"\n\n    def __init__(self, data=None):\n        \"\"\"\n        初始化方法\n        :param data: 要被检验的服务信息\n        :type data: list\n        \"\"\"\n        if not data or not isinstance(data, list):\n            raise GeneralError(\n                \"ValidateInstallService __init__ arg error: data\"\n            )\n        self.data = data\n\n    def check_service_port(self, app_port, ip):  # NOQA\n        \"\"\"\n        检查服务端口\n        :param app_port: 服务端口列表\n        :type app_port: list\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        salt_obj = SaltClient()\n        for el in app_port:\n            _port = el.get(\"default\", \"\")\n            if not _port or not str(_port).isnumeric():\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"端口 {_port} 必须为数字\"\n                continue\n            # method1: 从OMP本机查看端口是否已被占用\n            # _flag, _msg = public_utils.check_ip_port(ip=ip, port=int(_port))\n            # method2: 从目标服务器查看端口是否被占用\n            _flag, _msg = salt_obj.cmd(\n                target=ip,\n                command=f\"</dev/tcp/{ip}/{_port}\",\n                timeout=10\n            )\n            if _flag:\n                el[\"error_msg\"] = f\"主机 {ip} 上的端口 {_port} 已被占用\"\n        return app_port\n\n    def check_service_args(self, app_install_args, data_path, ip):  # NOQA\n        \"\"\"\n        检查服务的安装参数，路径检查\n        :param app_install_args: 服务安装参数\n        :type app_install_args: list\n        :param data_path: 主机数据目录\n        :type data_path: str\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        _salt_obj = SaltClient()\n        for el in app_install_args:\n            if \"dir_key\" not in el:\n                continue\n            # 直接封装部署数据到数据库中\n            _tobe_check_path = el[\"default\"]\n            _cmd = \\\n                f\"test -d {_tobe_check_path} && echo 'EXISTS' || echo 'OK'\"\n            _flag, _msg = _salt_obj.cmd(\n                target=ip,\n                command=_cmd,\n                timeout=10\n            )\n            if not _flag:\n                el[\"error_msg\"] = \\\n                    f\"无法确定该路径状态: {_tobe_check_path}; \" \\\n                    f\"请检查主机及主机Agent状态是否正常\"\n                continue\n            if \"OK\" not in _msg:\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"{_tobe_check_path} 在目标主机 {ip} 上已存在\"\n        return app_install_args\n\n    def check_single_service(self, dic):  # NOQA\n        \"\"\"\n        检查单个服务的安装信息\n        :param dic: 服务安装信息\n        :type dic: dict\n        :return:\n        \"\"\"\n        _dic = deepcopy(dic)\n        _ip = _dic.get(\"ip\")\n        _host_obj = Host.objects.filter(ip=_ip).last()\n        if not _host_obj:\n            _dic[\"error_msg\"] = f\"主机 {_ip} 不存在\"\n            return _dic\n        _data_path = _host_obj.data_folder\n        # 检查端口是否被占用\n        app_port = self.check_service_port(\n            app_port=_dic.get(\"app_port\", []),\n            ip=_ip\n        )\n        # 校验安装参数\n        app_install_args = self.check_service_args(\n            app_install_args=_dic.get(\"app_install_args\", []),\n            data_path=_data_path,\n            ip=_ip\n        )\n        _dic[\"app_port\"] = app_port\n        _dic[\"app_install_args\"] = app_install_args\n        return _dic\n\n    def run(self):\n        \"\"\"\n        运行检查入口函数\n        :return:\n        \"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=10, thread_name_prefix=\"check_install_service_\"\n        )\n        # futures_list:[(item, future)]\n        futures_list = list()\n        for item in self.data:\n            future = thread_p.submit(self.check_single_service, item)\n            futures_list.append((item.get(\"service_instance_name\"), future))\n        # result_list:[{}, ...]\n        result_list = list()\n        for f in futures_list:\n            result_list.append(f[1].result())\n        thread_p.shutdown(wait=True)\n        return result_list\n\n\nclass BaseEnvServiceUtils(object):\n    \"\"\" 解决base_env服务的安装事宜 \"\"\"\n\n    def __init__(self, all_install_service_lst, host_user_map):\n        \"\"\"\n        解决依赖于base_env服务的安装操作\n        :param all_install_service_lst: 所有需要安装的服务\n        :type all_install_service_lst: list\n        \"\"\"\n        self.all = all_install_service_lst\n        self.host_user_map = host_user_map\n\n    def _dep_parse(self, dep_lst, base_env_dic, base_env_ser_lst, item):  # NOQA\n        \"\"\"\n        解析依赖关系\n        :param dep_lst: 服务依赖列表\n        :param base_env_dic: 基础依赖字典，去重作用\n        :param base_env_ser_lst: 所有基础依赖服务数据\n        :param item: 要安装的某个服务的信息\n        :return:\n        \"\"\"\n        for el in dep_lst:\n            # 服务版本弱依赖逻辑 jon.liu 20211225\n            _dep_obj = ApplicationHub.objects.filter(\n                app_name=el[\"name\"],\n                app_version__startswith=el[\"version\"]\n            ).last()\n            if not _dep_obj.is_base_env:\n                continue\n            # 当被依赖的基础服务已经被安装时，使用如下方法进行过滤处理\n            # 服务版本弱依赖逻辑 jon.liu 20211225\n            if Service.split_objects.filter(\n                    ip=item[\"ip\"],\n                    service__app_name=el[\"name\"],\n                    service__app_version__startswith=el[\"version\"]\n            ).count() > 0:\n                continue\n            if item[\"ip\"] in base_env_dic and \\\n                    el[\"name\"] in base_env_dic[item[\"ip\"]]:\n                continue\n            _ins_name = \\\n                el[\"name\"] + \"-\" + \"-\".join(item[\"ip\"].split(\".\")[-2:])\n            base_env_ser_lst.append({\n                \"name\": el[\"name\"],\n                \"version\": el[\"version\"],\n                \"ip\": item[\"ip\"],\n                \"install_args\": ServiceArgsPortUtils(\n                    ip=item[\"ip\"],\n                    data_folder=item[\"data_folder\"],\n                    run_user=item[\"run_user\"],\n                    host_user_map=self.host_user_map\n                ).remake_install_args(obj=_dep_obj),\n                \"ports\": ServiceArgsPortUtils(\n                    ip=item[\"ip\"],\n                    data_folder=item[\"data_folder\"],\n                    run_user=item[\"run_user\"],\n                    host_user_map=self.host_user_map\n                ).get_app_port(obj=_dep_obj),\n                \"instance_name\": _ins_name\n            })\n            if item[\"ip\"] not in base_env_dic:\n                base_env_dic[item[\"ip\"]] = list()\n            base_env_dic[item[\"ip\"]].append(el[\"name\"])\n\n    def run(self):\n        \"\"\"\n        解析base_env数据入口\n        :return:\n        \"\"\"\n        base_env_dic = dict()\n        base_env_ser_lst = list()\n        for item in self.all:\n            app_obj = ApplicationHub.objects.filter(\n                app_name=item[\"name\"],\n                app_version=item[\"version\"]\n            ).last()\n            # 当前服务没有依赖的情况\n            if not app_obj.app_dependence or \\\n                    not json.loads(app_obj.app_dependence):\n                continue\n            _dep_lst = json.loads(app_obj.app_dependence)\n            self._dep_parse(\n                dep_lst=_dep_lst,\n                base_env_dic=base_env_dic,\n                base_env_ser_lst=base_env_ser_lst,\n                item=item\n            )\n        return base_env_ser_lst\n\n\nclass WithServiceUtils(object):\n    \"\"\" 解决带有with的服务场景 \"\"\"\n\n    def __init__(self, all_install_service_lst,\n                 unique_key=None, run_user=None, host_user_map=None):\n        \"\"\"\n        解决依赖于base_env服务的安装操作\n        :param all_install_service_lst: 所有需要安装的服务\n        :type all_install_service_lst: list\n        :param unique_key: 安装标识\n        :type unique_key: str\n        :param run_user: 运行用户\n        :type run_user: str\n        :param host_user_map: 主机与运行用户关系\n        :type host_user_map: str\n        \"\"\"\n        self.all = all_install_service_lst\n        self.unique_key = unique_key\n        self.run_user = run_user\n        self.host_user_map = host_user_map\n\n    def parse_single_service(self, ser_dic):\n        \"\"\"\n        解析单个服务的with场景，并拼接为最终可安装的数据格式\n        :param ser_dic: 服务信息\n        :type ser_dic: dict\n        :return:\n        \"\"\"\n        _ser = {\n            \"name\": ser_dic.get(\"name\"),\n            \"version\": ser_dic.get(\"version\")\n        }\n        _ser_lst = list()\n        with_ser = ser_dic.get(\"with\")\n        is_found_flag = False  # 是否已经找到with服务的标识\n        for item in self.all:\n            _tmp_ser = deepcopy(_ser)\n            # 当需要被with的服务在需要安装的服务列表中时\n            if item.get(\"name\") == with_ser:\n                ip = item.get(\"ip\")\n                data_folder = item.get(\"data_folder\")\n                run_user = item.get(\"run_user\")\n                _app = ApplicationHub.objects.filter(\n                    app_name=_tmp_ser[\"name\"],\n                    app_version=_tmp_ser[\"version\"]\n                ).last()\n                install_args = ServiceArgsPortUtils(\n                    ip=ip,\n                    data_folder=data_folder,\n                    run_user=run_user,\n                    host_user_map=self.host_user_map\n                ).remake_install_args(obj=_app)\n                ports = ServiceArgsPortUtils(\n                    ip=ip,\n                    data_folder=data_folder,\n                    run_user=run_user,\n                    host_user_map=self.host_user_map\n                ).get_app_port(obj=_app)\n                instance_name = \\\n                    _tmp_ser[\"name\"] + \"-\" + \"-\".join(ip.split(\".\")[-2:])\n                _tmp_ser[\"ip\"] = ip\n                _tmp_ser[\"data_folder\"] = data_folder\n                _tmp_ser[\"run_user\"] = run_user\n                _tmp_ser[\"install_args\"] = install_args\n                _tmp_ser[\"ports\"] = ports\n                _tmp_ser[\"instance_name\"] = instance_name\n                _tmp_ser[\"cluster_name\"] = None\n                _ser_lst.append(_tmp_ser)\n                is_found_flag = True\n        # 如果没有找到，则证明被with的服务是在复用的列表内的，需要查看当前系统内的该服务信息\n        if not is_found_flag:\n            with_ser_ips = Service.split_objects.filter(\n                service__app_name=with_ser).values(\"ip\")\n            with_ser_ips = [el[\"ip\"] for el in with_ser_ips]\n            for _ip in with_ser_ips:\n                _tmp_ser = deepcopy(_ser)\n                data_folder = Host.objects.filter(ip=_ip).last().data_folder\n                run_user = self.run_user\n                _app = ApplicationHub.objects.filter(\n                    app_name=_tmp_ser[\"name\"],\n                    app_version=_tmp_ser[\"version\"]\n                ).last()\n                install_args = ServiceArgsPortUtils(\n                    ip=_ip,\n                    data_folder=data_folder,\n                    run_user=run_user,\n                    host_user_map=self.host_user_map\n                ).remake_install_args(obj=_app)\n                ports = ServiceArgsPortUtils(\n                    ip=_ip,\n                    data_folder=data_folder,\n                    run_user=run_user,\n                    host_user_map=self.host_user_map\n                ).get_app_port(obj=_app)\n                instance_name = \\\n                    _tmp_ser[\"name\"] + \"-\" + \"-\".join(_ip.split(\".\")[-2:])\n                _tmp_ser[\"ip\"] = _ip\n                _tmp_ser[\"data_folder\"] = data_folder\n                _tmp_ser[\"run_user\"] = run_user\n                _tmp_ser[\"install_args\"] = install_args\n                _tmp_ser[\"ports\"] = ports\n                _tmp_ser[\"instance_name\"] = instance_name\n                _tmp_ser[\"cluster_name\"] = None\n                _ser_lst.append(_tmp_ser)\n        return _ser_lst\n\n    def run(self):\n        \"\"\"\n        运行方法\n        :return:\n        \"\"\"\n        with_ser_lst = BaseRedisData(\n            unique_key=self.unique_key).get_with_ser()\n        ret_lst = list()\n        for item in with_ser_lst:\n            ret_lst.extend(self.parse_single_service(ser_dic=item))\n        return ret_lst\n\n\nclass DataJson(object):\n    \"\"\" 生成data.json数据 \"\"\"\n\n    def __init__(self, operation_uuid, service_obj=None):\n        \"\"\"\n        data.json数据生成方法\n        :param operation_uuid: 唯一操作uuid\n        :param service_obj: service 操作对象，可为空\n        :type operation_uuid: str\n        \"\"\"\n        self.operation_uuid = operation_uuid\n        self.service_obj = service_obj\n\n    def get_ser_install_args(self, obj):  # NOQA\n        \"\"\"\n        获取服务的安装参数\n        :param obj: Service\n        :type obj: Service\n        :return:\n        \"\"\"\n        deploy_detail = DetailInstallHistory.objects.get(service=obj)\n        install_args = \\\n            deploy_detail.install_detail_args.get(\"install_args\")\n        deploy_mode = \\\n            deploy_detail.install_detail_args.get(\"deploy_mode\")\n        return {\n            \"install_args\": install_args,\n            \"deploy_mode\": deploy_mode\n        }\n\n    def parse_single_service(self, obj):\n        \"\"\"\n        解析单个服务数据\n        :param obj: Service\n        :type obj: Service\n        :return:\n        \"\"\"\n        _ser_dic = {\n            \"ip\": obj.ip,\n            \"name\": obj.service.app_name,\n            \"role\": obj.service_role if obj.service_role else \"master\",\n            \"instance_name\": obj.service_instance_name,\n            \"cluster_name\": obj.cluster.cluster_name if obj.cluster else None,\n            \"ports\": json.loads(obj.service_port) if obj.service_port else [],\n            \"dependence\": json.loads(obj.service_dependence) if\n            obj.service_dependence else [],\n            \"vip\": obj.vip\n        }\n        _others = self.get_ser_install_args(obj)\n        _ser_dic.update(_others)\n        return _ser_dic\n\n    def make_data_json(self, json_lst):\n        \"\"\"\n        创建data.json数据文件\n        :param json_lst: 服务及分布信息组成的列表\n        :type json_lst: list\n        :return:\n        \"\"\"\n        _path = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/data_files\",\n            f\"{self.operation_uuid}.json\"\n        )\n        if not os.path.exists(os.path.dirname(_path)):\n            os.makedirs(os.path.dirname(_path))\n        with open(_path, \"w\", encoding=\"utf8\") as fp:\n            fp.write(json.dumps(json_lst, indent=2, ensure_ascii=False))\n\n    def run(self):\n        \"\"\"\n        生成data.json方法入口\n        :return:\n        \"\"\"\n        # step1: 获取所有的服务列表\n        # edit by vum:\n        # 服务中表中会有残留 base_env 服务的情况，类似 jdk，此时不宜获取所有服务\n        if self.service_obj:\n            all_ser_lst = self.service_obj\n        else:\n            all_ser_lst = Service.split_objects.all()\n        json_lst = list()\n        for item in all_ser_lst:\n            json_lst.append(self.parse_single_service(obj=item))\n        # 在json文件中标记该服务所在主机上的agent的地址\n        ip_agent_dir_dir = {\n            el[\"ip\"]: el[\"agent_dir\"] for el in\n            Host.objects.values(\"ip\", \"agent_dir\")\n        }\n        for item in json_lst:\n            item[\"agent_dir\"] = ip_agent_dir_dir.get(item.get(\"ip\"))\n        # step2: 生成data.json\n        self.make_data_json(json_lst=json_lst)\n\n\nclass CreateInstallPlan(object):\n    \"\"\" 生成部署计划相关数据 \"\"\"\n\n    def __init__(self, all_install_service_lst, unique_key=None):\n        \"\"\"\n\n        :param all_install_service_lst:\n        \"\"\"\n        logger.info(f\"CreateInstallPlan.__init__: {all_install_service_lst}\")\n        self.install_services = all_install_service_lst\n        self.unique_key = unique_key\n\n    def get_app_obj_for_service(self, dic):  # NOQA\n        \"\"\"\n        获取服务实例表中关联的app对象\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        return ApplicationHub.objects.filter(\n            app_name=dic[\"name\"], app_version__startswith=dic[\"version\"]\n        ).last()\n\n    def get_controllers_for_service(self, dic):  # NOQA\n        \"\"\"\n        获取服务控制脚本信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        # 获取关联application对象\n        _app = self.get_app_obj_for_service(dic)\n        # TODO 确定application表内的app_controllers字段存储类型\n        _app_controllers = json.loads(_app.app_controllers)\n        # 获取服务家目录\n        install_args = dic[\"install_args\"]\n        _home = \"\"\n        data_folder = Host.objects.filter(ip=dic[\"ip\"]).last().data_folder\n        for el in install_args:\n            if \"dir_key\" in el and el[\"key\"] == \"base_dir\":\n                _home = el[\"default\"]\n        real_home = os.path.join(data_folder, _home.rstrip(\"/\"))\n        _new_controller = dict()\n        # 更改服务控制脚本、拼接相对路径\n        for key, value in _app_controllers.items():\n            if not value:\n                continue\n            _new_controller[key] = os.path.join(real_home, value)\n        # 如果该服务需要在整体安装完成后有一些操作，那么需要重新构建post_action\n        # 在每次安装完所有服务后，需要搜索出相应的post_action并统一执行\n        if \"post_action\" in _app.extend_fields and \\\n                _app.extend_fields[\"post_action\"]:\n            _new_controller[\"post_action\"] = os.path.join(\n                real_home, _app.extend_fields[\"post_action\"]\n            )\n        return _new_controller\n\n    def get_env_for_service(self):  # NOQA\n        \"\"\"\n        获取当前环境\n        :return:\n        \"\"\"\n        # TODO 暂时使用默认环境\n        return Env.objects.last()\n\n    def create_connect_info(self, dic):  # NOQA\n        \"\"\"\n        创建或获取服务的用户名、密码信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        username = password = username_enc = password_enc = \"\"\n        for item in dic[\"install_args\"]:\n            if not item[\"default\"]:\n                continue\n            if item[\"key\"] == \"username\":\n                username = item[\"default\"]\n            if item[\"key\"] == \"password\":\n                password = item[\"default\"]\n            if item[\"key\"] == \"username_enc\":\n                username_enc = item[\"default\"]\n            if item[\"key\"] == \"password_enc\":\n                password_enc = item[\"default\"]\n        if username or password or username_enc or password_enc:\n            _ser_conn_obj, _ = ServiceConnectInfo.objects.get_or_create(\n                service_name=dic[\"name\"],\n                service_username=username,\n                service_password=password,\n                service_username_enc=username_enc,\n                service_password_enc=password_enc\n            )\n            return _ser_conn_obj\n        return None\n\n    def create_cluster(self, dic):  # NOQA\n        \"\"\"\n        创建集群信息\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        if \"cluster_name\" not in dic or not dic[\"cluster_name\"]:\n            return None\n        _app_obj = self.get_app_obj_for_service(dic)\n        # 根据要安装的服务是组件还是应用，这里仅做组件级别的集群\n        if _app_obj.app_type != 0:\n            return None\n        # 如果存在则获取、如果不存在则创建\n        cluster_obj, _ = ClusterInfo.objects.get_or_create(\n            cluster_service_name=dic[\"name\"],\n            cluster_name=dic[\"cluster_name\"],\n            service_connect_info=self.create_connect_info(dic)\n        )\n        return cluster_obj\n\n    def _get_exist_dep(self, inner):\n        \"\"\"\n        获取已存在依赖信息\n        :param inner:\n        :type inner: dict\n        :return:\n        \"\"\"\n        if inner.get(\"type\") == \"cluster\":\n            cl_obj = ClusterInfo.objects.filter(id=inner[\"id\"]).last()\n            return {\n                \"name\": cl_obj.cluster_service_name,\n                \"cluster_name\": cl_obj.cluster_name,\n                \"instance_name\": None\n            }\n        ser_obj = Service.split_objects.filter(id=inner[\"id\"]).last()\n        return {\n            \"name\": ser_obj.service.app_name,\n            \"cluster_name\": None,\n            \"instance_name\": ser_obj.service_instance_name\n        }\n\n    def _get_install_dep(self, inner):\n        \"\"\"\n        获取将要安装服务中的被依赖项\n        :param inner:\n        :return:\n        \"\"\"\n        _ser_name = inner[\"name\"]\n        _ser_version = inner[\"version\"]\n        for el in self.install_services:\n            if el.get(\"name\") == _ser_name and \\\n                    el.get(\"version\").startswith(_ser_version):\n                cluster_name = el.get(\"cluster_name\")\n                instance_name = el.get(\"instance_name\")\n                if cluster_name:\n                    instance_name = None\n                else:\n                    cluster_name = None\n                return {\n                    \"name\": el.get(\"name\"),\n                    \"cluster_name\": cluster_name,\n                    \"instance_name\": instance_name\n                }\n        raise ValidationError(\n            f\"无法找到被依赖的服务[{_ser_name}({_ser_version})]\")\n\n    def get_dependence(self, dic):\n        \"\"\"\n        获取服务依赖的实例信息\n        :param dic:\n        :return:\n        \"\"\"\n        _obj = self.get_app_obj_for_service(dic)\n        _dep_lst = list()\n        if not _obj.app_dependence or not json.loads(_obj.app_dependence):\n            return []\n        lst = json.loads(_obj.app_dependence)\n\n        exist_data = BaseRedisData(\n            unique_key=self.unique_key\n        ).get_step_2_origin_data().get(\"use_exist\")\n        for item in lst:\n            # 已存在的base_env服务依赖\n            _dep_obj = ApplicationHub.objects.filter(\n                app_name=item.get(\"name\"),\n                app_version__startswith=item.get(\"version\")\n            ).last()\n            if _dep_obj.is_base_env:\n                _ser_obj = Service.split_objects.filter(\n                    service=_dep_obj, ip=dic.get(\"ip\")\n                ).last()\n                if _ser_obj:\n                    _dep_lst.append({\n                        \"name\": item.get(\"name\"),\n                        \"cluster_name\": None,\n                        \"instance_name\": _ser_obj.service_instance_name\n                    })\n                    continue\n            if item.get(\"name\") in exist_data:\n                _de = exist_data[item[\"name\"]]\n                _dep_lst.append(self._get_exist_dep(_de))\n            else:\n                _dep_lst.append(self._get_install_dep(item))\n        return _dep_lst\n\n    def create_service(self, dic):\n        \"\"\"\n        创建服务实例\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        # 创建服务实例对象，默认从安装来的服务的状态为 安装中\n        _ser_obj = Service(\n            ip=dic[\"ip\"],\n            service_instance_name=dic[\"instance_name\"],\n            service=self.get_app_obj_for_service(dic),\n            service_port=json.dumps(dic.get(\"ports\")),\n            service_role=dic.get(\"roles\", \"\"),\n            service_controllers=self.get_controllers_for_service(dic),\n            cluster=self.create_cluster(dic),\n            env=self.get_env_for_service(),\n            service_status=6,\n            service_connect_info=self.create_connect_info(dic),\n            service_dependence=json.dumps(self.get_dependence(dic)),\n            vip=dic.get(\"vip\")\n        )\n        _ser_obj.save()\n        return _ser_obj\n\n    def create_product_instance(self, dic):  # NOQA\n        \"\"\"\n        创建产品实例\n        :param dic: 服务数据\n        :type dic: dict\n        :return:\n        \"\"\"\n        _obj = self.get_app_obj_for_service(dic)\n        if not _obj or _obj.app_type != ApplicationHub.APP_TYPE_SERVICE:\n            return\n        _data = BaseRedisData(\n            unique_key=self.unique_key).get_step_3_checked_data()\n        for item in _data.get(\"data\", {}).get(\"basic\", []):\n            if _obj.product.pro_name == item.get(\"name\") and \\\n                    _obj.product.pro_version == item.get(\"version\"):\n                product_instance_name = item.get(\"cluster_name\")\n                Product.objects.get_or_create(\n                    product_instance_name=product_instance_name,\n                    product=_obj.product\n                )\n\n    def check_if_has_post_action(self, ser):  # NOQA\n        \"\"\"\n        检测是否需要执行安装后的动作\n        :param ser: 服务对象\n        :type ser Service\n        :return:\n        \"\"\"\n        if \"post_action\" in ser.service_controllers and \\\n                ser.service_controllers.get(\"post_action\"):\n            return True\n        return False\n\n    def create_pre_install_history(self, main_obj):\n        \"\"\"\n        创建安装计划，在执行安装计划时，优先执行此处的操作记录\n        :param main_obj:\n        :return:\n        \"\"\"\n        _data = list(DetailInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).values_list(\"service__ip\", flat=True))\n\n        for item in set(_data):\n            PreInstallHistory(\n                main_install_history=main_obj,\n                ip=item\n            ).save()\n\n    def create_post_install_history(self, main_obj):\n        \"\"\"\n        创建安装后的行为规范\n        :param main_obj:\n        :return:\n        \"\"\"\n        # 在安装完成后，需要执行一些操作\n        # 在这里进行定制化处理，所有的安装操作都需要执行下重新加载nacos和tengine的配置\n        # 此处专为 云智慧 进行定制\n        PostInstallHistory(main_install_history=main_obj).save()\n        # post_action_queryset = DetailInstallHistory.objects.select_related(\n        #     \"service\", \"service__service\", \"service__service__app_package\"\n        # ).filter(main_install_history=main_obj).exclude(\n        #     post_action_flag__in=[2, 4]\n        # )\n        # if post_action_queryset.exists():\n        #     PostInstallHistory(main_install_history=main_obj).save()\n        #     return\n        # logger.info(f\"Do execute_post_install for {main_obj.operation_uuid}\")\n        # if DetailInstallHistory.objects.filter(\n        #         main_install_history=main_obj,\n        #         service__service__app_type=ApplicationHub.APP_TYPE_SERVICE\n        # ).exists():\n        #     PostInstallHistory(main_install_history=main_obj).save()\n\n    def run(self):\n        \"\"\"\n        服务部署信息入库操作\n        :return:\n        \"\"\"\n        try:\n            logger.info(\"start CreateInstallPlan.run!\")\n            with transaction.atomic():\n                # step0: 生成\n                # step1: 生成操作唯一uuid，创建主安装记录\n                operation_uuid = self.unique_key\n                main_obj = MainInstallHistory(\n                    operation_uuid=operation_uuid,\n                    install_status=0,\n                    install_args=self.install_services\n                )\n                main_obj.save()\n                # step2: 创建安装细节表\n                for item in self.install_services:\n                    # 创建服务实例对象\n                    ser_obj = self.create_service(item)\n                    # 创建产品实例对象\n                    self.create_product_instance(item)\n                    post_action_flag = 0 if self.check_if_has_post_action(\n                        ser_obj) else 4\n                    DetailInstallHistory(\n                        service=ser_obj,\n                        main_install_history=main_obj,\n                        install_detail_args=item,\n                        post_action_flag=post_action_flag\n                    ).save()\n                # 创建安装前的操作记录\n                self.create_pre_install_history(main_obj)\n                # 创建安装后操作日志\n                self.create_post_install_history(main_obj)\n                _json_obj = DataJson(operation_uuid=operation_uuid)\n                _json_obj.run()\n                # 调用安装异步任务，并回写异步任务到\n                task_id = install_service_task.delay(main_obj.id)\n                MainInstallHistory.objects.filter(id=main_obj.id).update(\n                    task_id=task_id\n                )\n                # 删除redis中缓存的安装临时数据\n                BaseRedisData(unique_key=self.unique_key).delete_all_keys()\n                # 更新主机上的服务数量\n                _ser_ip_lst = DetailInstallHistory.objects.filter(\n                    main_install_history=main_obj,\n                    service__service__is_base_env=False\n                ).values(\"service__ip\")\n                _tmp_dic = dict()\n                for item in _ser_ip_lst:\n                    if item[\"service__ip\"] not in _tmp_dic:\n                        _tmp_dic[item[\"service__ip\"]] = 0\n                    _tmp_dic[item[\"service__ip\"]] += 1\n                for key, value in _tmp_dic.items():\n                    Host.objects.filter(ip=key).update(\n                        service_num=F(\"service_num\") + value)\n        except Exception as e:\n            logger.error(\n                f\"CreateInstallPlan.run failed with error: \\n\"\n                f\"{traceback.format_exc()}\")\n            return False, e\n        return True, operation_uuid\n\n\nclass MakeServiceOrder(object):\n    def __init__(self, all_service):\n        self.all_service = all_service\n\n    def run(self):\n        return self.make_install_order()\n\n    def make_install_order(self):\n        \"\"\"\n        :return:\n        \"\"\"\n        final_lst = list()\n        # 对基础组件进行排序处理，其中基础配置中的 BASIC_ORDER 为基础组件的排序等级\n        # 如果有其他组件需要安装，怎需要在配置中进行额外的配置\n        for i in range(10):\n            if i not in BASIC_ORDER:\n                break\n            _lst = [\n                el for el in self.all_service\n                if el.get(\"name\") in BASIC_ORDER[i]\n            ]\n            final_lst.extend(_lst)\n        all_self_app = ApplicationHub.objects.filter(\n            app_type=ApplicationHub.APP_TYPE_SERVICE\n        ).values(\"app_name\", \"app_version\", \"extend_fields\")\n        all_ser_dic = {\n            el[\"app_name\"] + \"_\" + el[\"app_version\"]: el for el in all_self_app\n        }\n        level_0_lst = list()\n        level_1_lst = list()\n        for item in self.all_service:\n            _key = item[\"name\"] + \"_\" + item[\"version\"]\n            if _key not in all_ser_dic:\n                continue\n            if str(all_ser_dic[_key][\"extend_fields\"].get(\"level\")) == \"0\":\n                level_0_lst.append(item)\n            else:\n                level_1_lst.append(item)\n        final_lst.extend(level_0_lst)\n        final_lst.extend(level_1_lst)\n        return final_lst\n\n\nclass ValidateInstallServicePortArgs(object):\n    \"\"\" 检查要安装的服务信息是否准确 \"\"\"\n\n    def __init__(self, data=None):\n        \"\"\"\n        初始化方法\n        :param data: 要被检验的服务信息\n        :type data: list\n        \"\"\"\n        if not data or not isinstance(data, list):\n            raise GeneralError(\n                \"ValidateInstallService __init__ arg error: data\"\n            )\n        self.data = data\n\n    def check_service_port(self, app_port, ip):  # NOQA\n        \"\"\"\n        检查服务端口\n        :param app_port: 服务端口列表\n        :type app_port: list\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        salt_obj = SaltClient()\n        for el in app_port:\n            _port = el.get(\"default\", \"\")\n            if not _port or not str(_port).isnumeric():\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"端口 {_port} 必须为数字\"\n                continue\n            # method1: 从OMP本机查看端口是否已被占用\n            # _flag, _msg = public_utils.check_ip_port(ip=ip, port=int(_port))\n            # method2: 从目标服务器查看端口是否被占用\n            _flag, _msg = salt_obj.cmd(\n                target=ip,\n                command=f\"</dev/tcp/{ip}/{_port}\",\n                timeout=10\n            )\n            if _flag:\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"主机 {ip} 上的端口 {_port} 已被占用\"\n        return app_port\n\n    def check_service_args(self, app_install_args, data_path, ip):  # NOQA\n        \"\"\"\n        检查服务的安装参数，路径检查\n        :param app_install_args: 服务安装参数\n        :type app_install_args: list\n        :param data_path: 主机数据目录\n        :type data_path: str\n        :param ip: 主机ip地址\n        :type ip: str\n        :return:\n        \"\"\"\n        _salt_obj = SaltClient()\n        for el in app_install_args:\n            if el.get(\"key\") == \"instance_name\" and Service.split_objects.filter(\n                    service_instance_name=el.get(\"default\")\n            ).count() != 0:\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"实例名称 {el.get('default')} 已存在\"\n                continue\n            if \"dir_key\" not in el:\n                continue\n            _tobe_check_path = el.get(\"default\", \"\")\n            _cmd = \\\n                f\"test -d {_tobe_check_path} && echo 'EXISTS' || echo 'OK'\"\n            _flag, _msg = _salt_obj.cmd(\n                target=ip,\n                command=_cmd,\n                timeout=10\n            )\n            if not _flag:\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = \\\n                    f\"无法确定该路径状态: {_tobe_check_path}; \" \\\n                    f\"请检查主机及主机Agent状态是否正常\"\n                continue\n            if \"OK\" not in _msg:\n                el[\"check_flag\"] = False\n                el[\"error_msg\"] = f\"{_tobe_check_path} 在目标主机 {ip} 上已存在\"\n        return app_install_args\n\n    def check_single_service(self, dic):  # NOQA\n        \"\"\"\n        检查单个服务的安装信息\n        :param dic: 服务安装信息\n        :type dic: dict\n        :return:\n        \"\"\"\n        _dic = deepcopy(dic)\n        _ip = _dic.get(\"ip\")\n        _host_obj = Host.objects.filter(ip=_ip).last()\n        if not _host_obj:\n            _dic[\"check_flag\"] = False\n            _dic[\"error_msg\"] = f\"主机 {_ip} 不存在\"\n            return _dic\n        _data_path = _host_obj.data_folder\n        # 检查端口是否被占用\n        app_port = self.check_service_port(\n            app_port=_dic.get(\"ports\", []),\n            ip=_ip\n        )\n        # 校验安装参数\n        app_install_args = self.check_service_args(\n            app_install_args=_dic.get(\"install_args\", []),\n            data_path=_data_path,\n            ip=_ip\n        )\n        _dic[\"ports\"] = app_port\n        _dic[\"install_args\"] = app_install_args\n        return _dic\n\n    def run(self):\n        \"\"\"\n        运行检查入口函数\n        :return:\n        \"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=10, thread_name_prefix=\"check_install_service_\"\n        )\n        # futures_list:[(item, future)]\n        futures_list = list()\n        for item in self.data:\n            future = thread_p.submit(self.check_single_service, item)\n            futures_list.append((item.get(\"instance_name\"), future))\n        # result_list:[{}, ...]\n        result_list = list()\n        for f in futures_list:\n            result_list.append(f[1].result())\n        thread_p.shutdown(wait=True)\n        # TODO 整体服务间的关键校验\n        return result_list\n"
  },
  {
    "path": "omp_server/app_store/new_install_view.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: new_install_view\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-12 09:19\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport uuid\nimport logging\nfrom collections import OrderedDict\n\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin\n)\n\nfrom db_models.models import (\n    ApplicationHub, ProductHub, Product, Service,\n    MainInstallHistory, DetailInstallHistory, Host,\n    PreInstallHistory, PostInstallHistory\n)\nfrom utils.common.exceptions import ValidationError\nfrom utils.common.paginations import PageNumberPager\n# from app_store.install_utils import ServiceArgsSerializer\nfrom app_store.new_install_utils import ServiceArgsPortUtils\nfrom app_store.new_install_utils import BaseRedisData\n\nfrom app_store.new_install_serializers import (\n    CreateInstallInfoSerializer,\n    CheckInstallInfoSerializer,\n    CreateServiceDistributionSerializer,\n    CheckServiceDistributionSerializer,\n    CreateInstallPlanSerializer,\n    MainInstallHistorySerializer,\n    CreateComponentInstallInfoSerializer,\n    RetryInstallSerializer\n)\n\nlogger = logging.getLogger(\"server\")\nUNIQUE_KEY_ERROR = \"后台无法追踪此流程,请重新进行安装操作!\"\n\n\nclass BatchInstallEntranceView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    批量安装入口\n        List:\n        获取批量安装参数范围\n    \"\"\"\n    get_description = \"获取批量安装数据\"\n\n    @staticmethod\n    def check_product_instance(pro_name, pro_version_lst):\n        \"\"\"\n        检查应用产品是否安装过\n        :param pro_name: 应用名称\n        :param pro_version_lst: 应用版本\n        :return:\n        \"\"\"\n        queryset = Product.objects.filter(\n            product__pro_name=pro_name,\n            product__pro_version__in=pro_version_lst\n        )\n        if queryset.exists():\n            # 判断产品下是否还有服务，如果没有服务，那么\n            if Service.objects.filter(\n                service__product_id__in=queryset.values_list(\n                    \"product_id\", flat=True\n                )\n            ).exists():\n                return True\n            queryset.delete()\n        return False\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取批量安装参数范围\n        :param request:\n        :type request: Request\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        if Host.objects.all().count() == 0:\n            raise ValidationError(\n                \"当前系统内无可用主机，请先纳管主机后再执行安装操作！\")\n        product_name = request.query_params.get(\"product_name\")\n        if not product_name:\n            product_queryset = ProductHub.objects.filter(\n                is_release=True).values(\n                \"pro_name\", \"pro_version\"\n            )\n        else:\n            product_queryset = ProductHub.objects.filter(\n                is_release=True, pro_name=product_name).values(\n                \"pro_name\", \"pro_version\"\n            )\n        tmp_dic = dict()\n        for item in product_queryset:\n            if item.get(\"pro_name\") not in tmp_dic:\n                tmp_dic[item[\"pro_name\"]] = list()\n            tmp_dic[item[\"pro_name\"]].append(item[\"pro_version\"])\n        _data = [\n            {\n                \"name\": key,\n                \"version\": value,\n                \"is_continue\": not self.check_product_instance(key, value)\n            }\n            for key, value in tmp_dic.items()\n        ]\n        unique_key = str(uuid.uuid4())\n        # unique_key = str(\"21e041a9-c9a5-4734-9673-7ed932625d21\")\n        data = {\n            \"data\": _data,\n            \"unique_key\": unique_key\n        }\n        # 添加安装唯一标识到redis内\n        BaseRedisData(unique_key=unique_key).step_1_set_unique_key(data=data)\n        return Response(data=data)\n\n\nclass CreateInstallInfoView(GenericViewSet, CreateModelMixin):\n    serializer_class = CreateInstallInfoSerializer\n    post_description = \"创建基础安装数据\"\n\n\nclass CheckInstallInfoView(GenericViewSet, CreateModelMixin):\n    serializer_class = CheckInstallInfoSerializer\n    post_description = \"校验基础安装数据\"\n\n\nclass CreateServiceDistributionView(GenericViewSet, CreateModelMixin):\n    serializer_class = CreateServiceDistributionSerializer\n    post_description = \"生成部署服务分布源数据\"\n\n\nclass CheckServiceDistributionView(GenericViewSet, CreateModelMixin):\n    serializer_class = CheckServiceDistributionSerializer\n    post_description = \"校验服务分布\"\n\n\nclass GetInstallHostRangeView(GenericViewSet, ListModelMixin):\n    get_description = \"获取安装主机范围\"\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取某次安装涉及到的主机范围接口\n        :param request: 请求\n        :type request: Request\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        unique_key = request.query_params.get(\"unique_key\")\n        if not unique_key:\n            return Response(\n                data={\"error_msg\": \"请求参数必须包含[unique_key]字段\"})\n        _data = BaseRedisData(unique_key).get_step_5_host_list()\n        return Response(data={\n            \"unique_key\": unique_key,\n            \"data\": _data\n        })\n\n\nclass GetInstallArgsByIpView(GenericViewSet, ListModelMixin):\n    get_description = \"获取某主机上的服务的安装参数\"\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取某次安装涉及到的主机范围接口\n        :param request: 请求\n        :type request: Request\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        unique_key = request.query_params.get(\"unique_key\")\n        ip = request.query_params.get(\"ip\")\n        if not unique_key or not ip:\n            return Response(\n                data={\"error_msg\": \"请求参数必须包含[unique_key]和[ip]\"})\n        _data = BaseRedisData(unique_key).get_step_5_host_service_map()\n        check_data = BaseRedisData(unique_key).get_step_2_origin_data()\n        install_ser = check_data.get(\"install\")\n        services_lst = _data.get(ip, [])\n        app_lst = ApplicationHub.objects.filter(app_name__in=services_lst)\n        _ret_data = list()\n        for item in app_lst:\n            if item.app_name not in install_ser or \\\n                    item.app_version != install_ser[item.app_name].get(\"version\"):\n                continue\n            install_args = ServiceArgsPortUtils().get_app_install_args(item)\n            install_args.insert(\n                0, {\n                    \"key\": \"instance_name\",\n                    \"name\": \"实例名称\",\n                    \"default\":\n                        item.app_name + \"-\" + \"-\".join(ip.split(\".\")[-2:]),\n                    \"editable\": True\n                }\n            )\n            _app = {\n                \"name\": item.app_name,\n                \"instance_name\":\n                    item.app_name + \"-\" + \"-\".join(ip.split(\".\")[-2:]),\n                \"install_args\": install_args,\n                \"ports\": ServiceArgsPortUtils().get_app_port(item)\n            }\n            _ret_data.append(_app)\n        if len(_ret_data) != len(services_lst):\n            logger.info(f\"GetInstallArgsByIpView ERROR: \\n\"\n                        f\"ip: {ip}\\n\"\n                        f\"services_lst: {services_lst}\\n\"\n                        f\"_ret_data: {_ret_data}\")\n            return Response(\n                data={\n                    \"error_msg\": \"存储数据出现错误，\"\n                                 \"请检查应用商店内已纳管服务是否有变化！\"\n                }\n            )\n        return Response(data={\n            \"unique_key\": unique_key,\n            \"data\": _ret_data\n        })\n\n\nclass CreateInstallPlanView(GenericViewSet, CreateModelMixin):\n    serializer_class = CreateInstallPlanSerializer\n    post_description = \"校验并生成部署计划\"\n\n\nclass ListServiceByIpView(GenericViewSet, ListModelMixin):\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        根据ip显示主机上安装的服务\n        :param request:\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        # ip = request.query_params.get(\"ip\")\n        _data = Service.objects.filter().exclude(\n            service__is_base_env=True\n        ).values(\n            \"ip\", \"service__app_name\", \"service_instance_name\"\n        )\n        data = dict()\n        for item in _data:\n            if item[\"ip\"] not in data:\n                data[item[\"ip\"]] = list()\n            data[item[\"ip\"]].append(item)\n        return Response(data=data)\n\n\nclass ShowInstallProcessView(GenericViewSet, ListModelMixin):\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        显示安装的进度信息\n        :param request:\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        unique_key = request.query_params.get(\"unique_key\")\n        main_obj = MainInstallHistory.objects.filter(\n            operation_uuid=unique_key).last()\n        if not main_obj:\n            raise ValidationError(f\"{unique_key}不存在!\")\n        main_status = main_obj.install_status\n        install_detail_queryset = DetailInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).exclude(service=None).values(\n            \"service__service__app_name\",\n            \"service__ip\",\n            \"install_step_status\"\n        )\n        pre_queryset = PreInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).values(\"ip\", \"install_flag\", \"install_log\", \"name\")\n        detail_dic = OrderedDict()\n        install_success_num = total_num = 0\n        for item in pre_queryset:\n            app_name = item.get(\"name\")\n            if app_name not in detail_dic:\n                detail_dic[app_name] = list()\n            detail_dic[app_name].append({\n                \"ip\": item[\"ip\"],\n                \"status\": item[\"install_flag\"]\n            })\n            if item[\"install_flag\"] == 2:\n                install_success_num += 1\n            total_num += 1\n        for item in install_detail_queryset:\n            app_name = item[\"service__service__app_name\"]\n            if app_name not in detail_dic:\n                detail_dic[app_name] = list()\n            detail_dic[app_name].append({\n                \"ip\": item[\"service__ip\"],\n                \"status\": item[\"install_step_status\"]\n            })\n            if item[\"install_step_status\"] == \\\n                    DetailInstallHistory.INSTALL_STATUS_SUCCESS:\n                install_success_num += 1\n            total_num += 1\n        post_queryset = PostInstallHistory.objects.filter(\n            main_install_history=main_obj\n        ).values(\"ip\", \"install_flag\", \"install_log\", \"name\")\n        for item in post_queryset:\n            app_name = item.get(\"name\")\n            if app_name not in detail_dic:\n                detail_dic[app_name] = list()\n            detail_dic[app_name].append({\n                \"ip\": item[\"ip\"],\n                \"status\": item[\"install_flag\"]\n            })\n            if item[\"install_flag\"] == 2:\n                install_success_num += 1\n            total_num += 1\n        if total_num != 0:\n            percentage = int((install_success_num / total_num) * 100)\n            if main_status != MainInstallHistory.INSTALL_STATUS_SUCCESS and \\\n                    percentage == 100:\n                percentage = 95\n        else:\n            if main_status == MainInstallHistory.INSTALL_STATUS_SUCCESS:\n                percentage = 100\n            else:\n                percentage = 5\n        data = {\n            \"status\": main_status,\n            \"detail\": detail_dic,\n            \"percentage\": percentage\n        }\n        return Response(data=data)\n\n\nclass ShowSingleServiceInstallLogView(GenericViewSet, ListModelMixin):\n\n    def get_pre_install_log(self, main_obj, ip):\n        \"\"\"\n        获取日志\n        :param main_obj:\n        :param ip:\n        :return:\n        \"\"\"\n        _pre_obj = PreInstallHistory.objects.filter(\n            main_install_history=main_obj, ip=ip).last()\n        return _pre_obj.install_log\n\n    def get_post_install_log(self, main_obj, ip):\n        \"\"\"\n        获取日志\n        :param main_obj:\n        :param ip:\n        :return:\n        \"\"\"\n        _post_obj = PostInstallHistory.objects.filter(\n            main_install_history=main_obj, ip=ip).last()\n        return _post_obj.install_log\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        查找某服务的安装日志\n        :param request:\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        ip = request.query_params.get(\"ip\")\n        app_name = request.query_params.get(\"app_name\")\n        unique_key = request.query_params.get(\"unique_key\")\n        lst = [\n            \"send_msg\", \"unzip_msg\", \"install_msg\",\n            \"init_msg\", \"start_msg\", \"post_action_msg\"\n        ]\n        main_obj = MainInstallHistory.objects.filter(\n            operation_uuid=unique_key\n        ).last()\n        if app_name == \"初始化安装流程\":\n            log = self.get_pre_install_log(main_obj=main_obj, ip=ip)\n            return Response(data={\"log\": log})\n        if app_name == \"安装后续任务\":\n            log = self.get_post_install_log(main_obj=main_obj, ip=ip)\n            return Response(data={\"log\": log})\n        detail = DetailInstallHistory.objects.filter(\n            main_install_history=main_obj,\n            service__service__app_name=app_name,\n            service__ip=ip\n        ).last()\n\n        def get_log(detail):\n            _log = \"\"\n            for item in lst:\n                _log += getattr(detail, item, \"\")\n            return _log\n\n        log = get_log(detail)\n        return Response(data={\"log\": log})\n\n\nclass MainInstallHistoryView(GenericViewSet, ListModelMixin):\n    queryset = MainInstallHistory.objects.all().order_by(\"-id\")\n    serializer_class = MainInstallHistorySerializer\n    pagination_class = PageNumberPager\n\n\nclass CreateComponentInstallInfoView(GenericViewSet, CreateModelMixin):\n    serializer_class = CreateComponentInstallInfoSerializer\n    post_description = \"创建基础组件安装数据\"\n\n\nclass RetryInstallView(GenericViewSet, CreateModelMixin):\n    serializer_class = RetryInstallSerializer\n    post_description = \"重试安装\"\n"
  },
  {
    "path": "omp_server/app_store/post_install_utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-12-06 11:47\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom app_store.post_install_utils.nacos import Nacos\nfrom app_store.post_install_utils.tengine import Tengine\n\nPOST_INSTALL_SERVICE = {\n    \"nacos\": Nacos,\n    \"tengine\": Tengine\n}\n"
  },
  {
    "path": "omp_server/app_store/post_install_utils/base.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: base\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-12-06 13:58\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport os\n\nfrom utils.plugin.salt_client import SaltClient\nfrom db_models.models import Host\n\n\nclass BasePostInstallUtils(object):\n    \"\"\" 执行安装后动作的基础类 \"\"\"\n\n    def __init__(self, main_obj):\n        self.main_obj = main_obj\n        self.json_path = os.path.join(\n            \"data_files\", f\"{self.main_obj.operation_uuid}.json\"\n        )\n\n    def send_json(self, detail_obj):\n        \"\"\"\n        发送json文件\n        :param detail_obj:\n        :return:\n        \"\"\"\n        target_ip = detail_obj.service.ip\n        data_folder = Host.objects.filter(ip=target_ip).last().data_folder\n        target_path = os.path.join(\n            data_folder, \"omp_packages\",\n            f\"{self.main_obj.operation_uuid}.json\")\n        salt_obj = SaltClient()\n        return salt_obj.cp_file(\n            target=target_ip,\n            source_path=self.json_path,\n            target_path=target_path\n        )\n\n    def execute_install(self, detail_obj):\n        \"\"\"\n        执行安装脚本\n        :param detail_obj:\n        :return:\n        \"\"\"\n        target_ip = detail_obj.service.ip\n        install_path = self.get_install_path(detail_obj=detail_obj)\n        if not install_path:\n            return True, \"no need execute install!\"\n        data_folder = Host.objects.filter(ip=target_ip).last().data_folder\n        target_json_path = os.path.join(\n            data_folder, \"omp_packages\",\n            f\"{self.main_obj.operation_uuid}.json\")\n        salt_obj = SaltClient()\n        return salt_obj.cmd(\n            target=target_ip,\n            command=f\"python {install_path} --local_ip={target_ip} \"\n                    f\"--data_json={target_json_path}\",\n            timeout=60\n        )\n\n    def execute_init(self, detail_obj):\n        \"\"\"\n        执行安装脚本\n        :param detail_obj:\n        :return:\n        \"\"\"\n        target_ip = detail_obj.service.ip\n        init_path = self.get_init_path(detail_obj=detail_obj)\n        if not init_path:\n            return True, \"no need execute init!\"\n        data_folder = Host.objects.filter(ip=target_ip).last().data_folder\n        target_json_path = os.path.join(\n            data_folder, \"omp_packages\",\n            f\"{self.main_obj.operation_uuid}.json\")\n        salt_obj = SaltClient()\n        return salt_obj.cmd(\n            target=target_ip,\n            command=f\"python {init_path} --local_ip={target_ip} \"\n                    f\"--data_json={target_json_path}\",\n            timeout=60\n        )\n\n    def execute_restart(self, detail_obj):\n        \"\"\"\n        执行安装脚本\n        :param detail_obj:\n        :return:\n        \"\"\"\n        restart_path = self.get_restart_path(detail_obj=detail_obj)\n        if not restart_path:\n            return True, \"no need execute restart\"\n        salt_obj = SaltClient()\n        return salt_obj.cmd(\n            target=detail_obj.service.ip,\n            command=f\"bash {restart_path}\",\n            timeout=60\n        )\n\n    def get_install_path(self, detail_obj):\n        \"\"\"\n        获取安装脚本路径\n        :param detail_obj:\n        :return:\n        \"\"\"\n        return detail_obj.service.service_controllers.get(\"install\")\n\n    def get_init_path(self, detail_obj):\n        \"\"\"\n        获取安装脚本路径\n        :param detail_obj:\n        :return:\n        \"\"\"\n        return detail_obj.service.service_controllers.get(\"init\")\n\n    def get_restart_path(self, detail_obj):\n        \"\"\"\n        获取安装脚本路径\n        :param detail_obj:\n        :return:\n        \"\"\"\n        return detail_obj.service.service_controllers.get(\"restart\")\n"
  },
  {
    "path": "omp_server/app_store/post_install_utils/nacos.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: nacos\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-12-06 11:48\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport logging\n\nfrom db_models.models import (\n    Service, DetailInstallHistory\n)\nfrom app_store.post_install_utils.base import BasePostInstallUtils\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Nacos(BasePostInstallUtils):\n    \"\"\" nacos更新类 \"\"\"\n\n    def run(self):\n        \"\"\"\n        运行\n        :return:\n        \"\"\"\n        logger.info(\"execute Nacos post install action!\")\n        # nacos仅取一个节点即可\n        nacos_obj = Service.objects.filter(\n            service__app_name=\"nacos\"\n        ).last()\n        if not nacos_obj:\n            return True, \"success\"\n        detail_obj = DetailInstallHistory.objects.filter(\n            service=nacos_obj\n        ).last()\n        if not detail_obj:\n            return True, \"success\"\n        self.send_json(detail_obj=detail_obj)\n        # 执行 install.py\n        install_flag, install_msg = self.execute_install(\n            detail_obj=detail_obj\n        )\n        logger.info(f\"execute nacos install: {install_flag};{install_msg}\")\n        if not install_flag:\n            return False, f\"execute install failed: {install_msg}\"\n        # 执行 init.py\n        init_flag, init_msg = self.execute_init(\n            detail_obj=detail_obj\n        )\n        logger.info(f\"execute nacos install: {init_flag};{init_msg}\")\n        if not init_flag:\n            return False, f\"execute init failed: {init_msg}\"\n        # 执行 restart\n        # restart_flag, restart_msg = self.execute_restart(\n        #     detail_obj=detail_obj\n        # )\n        # logger.info(f\"execute nacos install: {restart_flag};{restart_msg}\")\n        # if not restart_flag:\n        #     return False, f\"restart nacos failed: {restart_msg}\"\n        logger.info(\"Execute post install action for nacos success!\")\n        return True, \"success\"\n"
  },
  {
    "path": "omp_server/app_store/post_install_utils/tengine.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: tengine\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-12-06 11:47\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport logging\n\nfrom db_models.models import (\n    Service, DetailInstallHistory\n)\nfrom app_store.post_install_utils.base import BasePostInstallUtils\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Tengine(BasePostInstallUtils):\n    \"\"\" tengine更新类 \"\"\"\n\n    def run(self):\n        \"\"\"\n        运行\n        :return:\n        \"\"\"\n        logger.info(\"execute Tengine post install action!\")\n        # nacos仅取一个节点即可\n        tengine_obj_queryset = Service.objects.filter(\n            service__app_name=\"tengine\"\n        )\n        if not tengine_obj_queryset.exists():\n            return True, \"success\"\n        detail_obj_queryset = DetailInstallHistory.objects.filter(\n            service__in=list(tengine_obj_queryset)\n        )\n        if not detail_obj_queryset.exists():\n            return True, \"success\"\n        for item in detail_obj_queryset:\n            # 发送json文件\n            self.send_json(detail_obj=item)\n            # 执行 install.py\n            install_flag, install_msg = self.execute_install(\n                detail_obj=item\n            )\n            logger.info(\n                f\"execute tengine install: {install_flag};{install_msg}\")\n            if not install_flag:\n                return False, f\"execute install failed: {install_msg}\"\n            # 执行 restart\n            restart_flag, restart_msg = self.execute_restart(\n                detail_obj=item\n            )\n            logger.info(\n                f\"execute tengine install: {restart_flag};{restart_msg}\")\n            if not restart_flag:\n                return False, f\"restart tengine failed: {restart_msg}\"\n            logger.info(\"Execute post install action for tengine success!\")\n            return True, \"success\"\n"
  },
  {
    "path": "omp_server/app_store/service_splitting.py",
    "content": "import json\nimport logging\n\nfrom django.db.models import F\n\nfrom db_models.models import Service, DetailInstallHistory, Host\nfrom utils.plugin.salt_client import SaltClient\n\n\nlogger = logging.getLogger('server')\n\n\ndef service_log_splitting(obj_id, split_service_ids):\n    install_logs = list(\n        DetailInstallHistory.objects.filter(\n            service_id=obj_id\n        ).values()\n    )\n    if not install_logs:\n        return False\n    install_log = install_logs[0]\n    install_log.pop(\"id\")\n    install_log.pop(\"service_id\")\n    for _id in split_service_ids:\n        try:\n            DetailInstallHistory.objects.create(\n                **install_log,\n                service_id=_id\n            )\n        except Exception as e:\n            logger.error('omp_detail_install_history 输入写入失败 {}'.format(e))\n            return False\n    return True\n\n\n# todo:抽出hadoop、doim等存在子服务或进程的服务，增加服务下属子服务进程, the end\ndef service_splitting(doim_ids):\n    name = 'doim'\n    obj_name = 'all'\n    add_service_list = ['ProcessWatcher', 'TSManager', 'bluesky']\n    base_name = 'bluesky'\n    restart_services = ['portalServer', 'gatewayServer']\n    service_restart = True\n    queryset_info = list(\n        Service.objects.filter(\n            service_instance_name__contains=name,\n            id__in=doim_ids\n        ).values()\n    )\n    for _obj_info in queryset_info:\n        _obj_id = _obj_info.pop(\"id\")\n        ip = _obj_info.get(\"ip\")\n        sub_name = \"-\" + \"-\".join(ip.split(\".\")[-2:])\n        _obj_info.pop(\"service_instance_name\")\n        service_port = _obj_info.pop(\"service_port\")\n        service_controllers_ = json.dumps(_obj_info.pop(\"service_controllers\"))\n        service_port_ = json.loads(service_port)\n        split_service_ids = []\n        for port_info in service_port_:\n            service_instance_name_ = port_info.get('name')\n            if service_instance_name_ != base_name:\n                service_controllers_obj = service_controllers_.replace(\n                    obj_name, service_instance_name_\n                )\n                try:\n                    service = Service.objects.create(\n                        service_instance_name=port_info.get('name') + sub_name,\n                        service_port=json.dumps([port_info]),\n                        service_controllers=eval(service_controllers_obj),\n                        **_obj_info\n                    )\n                    split_service_ids.append(service.id)\n                except Exception as e:\n                    logger.error('有端口服务拆分入库报错 {}'.format(e))\n                    return False\n            else:\n                for add_service in add_service_list:\n                    service_controllers_obj = service_controllers_.replace(\n                        obj_name, add_service)\n                    try:\n                        service = Service.objects.create(\n                            service_instance_name=add_service + sub_name,\n                            service_controllers=eval(service_controllers_obj),\n                            service_port=json.dumps([]),\n                            **_obj_info\n                        )\n                        split_service_ids.append(service.id)\n                    except Exception as e:\n                        logger.error('无端口服务拆分入库报错1 {}'.format(e))\n                        return False\n        try:\n            Service.objects.filter(\n                service_instance_name=f\"{name}{sub_name}\"\n            ).update(service_instance_name=f\"{base_name}{sub_name}\")\n            Host.objects.filter(ip=ip).update(\n                service_num=F(\"service_num\") + len(split_service_ids)\n            )\n        except Exception as e:\n            logger.error('服务名称替换报错 {}'.format(e))\n            return False\n        # \"\"\" 获取表中数据\"\"\"\n        service_restart = service_log_splitting(_obj_id, split_service_ids)\n        logger.info('日志拆分返回值 {}'.format(service_restart))\n        logger.info('进入第二步')\n\n    if service_restart:\n        service_replace_list = []\n        res_queryset = Service.objects.filter(\n            service__app_name__in=restart_services\n        ).values('ip', 'service_controllers')\n        for restart_service in res_queryset:\n            logger.info('需要重启服务的对象 {}'.format(restart_service))\n            service_replace_list.append(\n                {\n                    restart_service[\"ip\"]:\n                        restart_service[\"service_controllers\"].get('start')\n                }\n            )\n        logger.info('需要重启服务集合 {}'.format(service_replace_list))\n        salt_client = SaltClient()\n        for service_info in service_replace_list:\n            for _ip, start_cmd in service_info.items():\n                cmd_flag, cmd_msg = salt_client.cmd(\n                    target=_ip,\n                    command=start_cmd,\n                    timeout=60\n                )\n                logger.info('服务返回值cmd_flag {}'.format(cmd_flag))\n                logger.info('服务返回值cmd_msg {}'.format(cmd_msg))\n                if not cmd_flag:\n                    logger.error('错误信息 {}'.format(cmd_msg))\n                    return False\n    else:\n        logger.error('日志写入失败 {}'.format(service_restart))\n        return False\n    return True\n"
  },
  {
    "path": "omp_server/app_store/tasks.py",
    "content": "\"\"\"\n主机相关异步任务\n\"\"\"\n\nimport os\nimport yaml\nimport time\nimport json\nimport redis\nimport logging\n\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\nfrom utils.plugin import public_utils\nfrom utils.parse_config import (\n    OMP_REDIS_PORT, OMP_REDIS_PASSWORD, OMP_REDIS_HOST\n)\n\nfrom db_models.models import (\n    UploadPackageHistory, ApplicationHub, ProductHub,\n    MainInstallHistory, DetailInstallHistory\n)\nfrom app_store.upload_task import CreateDatabase\nfrom app_store.install_exec import InstallServiceExecutor\n# from app_store.install_executor import InstallServiceExecutor\nfrom promemonitor.prometheus_utils import PrometheusUtils\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nproject_dir = os.path.dirname(os.path.dirname(current_dir))\npackage_hub = os.path.join(project_dir, \"package_hub\")\n\npackage_dir = {\"back_end_verified\": \"back_end_verified\",\n               \"front_end_verified\": \"front_end_verified\",\n               \"verified\": \"verified\"}\n\n\nclass PublicAction(object):\n    def __init__(self, md5):\n        self.md5_obj = UploadPackageHistory.objects.filter(package_md5=md5)\n\n    def update_package_status(self, status, msg=None):\n        self.md5_obj.update(package_status=status, error_msg=msg)\n        logger.info(msg)\n\n    def update_fail_status(self, msg=None):\n        self.md5_obj.update(package_status=1, error_msg=msg)\n\n\nclass FiledCheck(object):\n    \"\"\"\n     弱校验 只校验key\n     强校验 在弱校验key存在的情况下校验value值\n     params is_weak强校验前执行弱校验\n     ignore 强校验中去除一些必要字段的强校验\n     settings 待校验文本\n     field 需要校验的字段，如果为空则强校验文本所有字段是否value为空\n     is_weak 强校验一定包含弱校验。值为True，简化代码量。\n    \"\"\"\n\n    def __init__(self, yaml_dir, db_obj):\n        self.db_obj = db_obj\n        self.yaml_dir = yaml_dir\n\n    def strong_check(self, settings, field=None, is_weak=False, ignore=None, attention=\"\"):\n        try:\n            if is_weak:\n                if not self.weak_check(settings, field, attention=attention):\n                    return False\n            if ignore:\n                field = field - ignore\n            if isinstance(settings, dict):\n                if not field:\n                    field = set(settings.keys())\n                for i in field:\n                    if settings.get(i) is None:\n                        self.db_obj.update_package_status(\n                            1,\n                            f\"yml{i}缺乏值，检查yml文件{self.yaml_dir}\")\n                        return False\n                return True\n            elif isinstance(settings, list):\n                if not field:\n                    field = set(settings[0].keys())\n                for i in settings:\n                    for j in field:\n                        if i.get(j) is None:\n                            self.db_obj.update_package_status(\n                                1,\n                                f\"yml{i}缺乏值，检查yml文件{self.yaml_dir}\")\n                            return False\n                return True\n            else:\n                return False\n        except Exception as e:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml:{attention}异常，检查yml文件{self.yaml_dir}\")\n            return False\n\n    def weak_check(self, settings, field, attention=\"\"):\n        try:\n            if isinstance(settings, dict):\n                # 以field为基准,settings多出的也不会显示,只要满足field即可\n                status = field - set(settings.keys())\n                if status:\n                    self.db_obj.update_package_status(\n                        1,\n                        f\"yml{str(status)}字段和预期不符，检查yml文件{self.yaml_dir}\")\n                    return False\n                return True\n            elif isinstance(settings, list):\n                for i in settings:\n                    status = field - set(i.keys())\n                    if status:\n                        self.db_obj.update_package_status(\n                            1,\n                            f\"yml{str(status)}字段和预期不符，检查yml文件{self.yaml_dir}\")\n                        return False\n                return True\n            else:\n                return False\n        except Exception as e:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml:{attention}异常，检查yml文件{self.yaml_dir}\")\n            return False\n\n\n@shared_task\ndef front_end_verified(uuid, operation_user, package_name, random_str, ver_dir, upload_obj):\n    \"\"\"\n     前端发布界面校验逻辑及后端校验逻辑公共类\n     params\n     uuid 操作唯一id\n     operation_user 执行用户\n     package_name 上传的安装包\n     md5 md5值，暂时不用，现为后端自己生成\n     random_str 随机字符串，用于拼接临时校验目录\n     ver_dir 区分前后端校验临时存储路径\n     upload_obj 上传记录表的id\n    \"\"\"\n    upload_obj = UploadPackageHistory.objects.get(id=upload_obj)\n    package_path = os.path.join(package_hub, ver_dir)\n    file_name = os.path.join(package_path, package_name)\n    # md5校验生成\n    md5_out = public_utils.local_cmd(f'md5sum {file_name}')\n    if md5_out[2] != 0:\n        upload_obj.package_status = 1\n        upload_obj.error_msg = \"md5sum命令执行失败\"\n        upload_obj.save()\n        return None\n    md5sum = md5_out[0].split()[0]\n    md5 = md5sum\n    upload_obj.package_md5 = md5\n    upload_obj.save()\n    # 实例化状态更新公共类对象\n    public_action = PublicAction(md5)\n    touch_name = file_name[:-7] if \\\n        file_name[-7:] == \".tar.gz\" else file_name[:-3]\n    tmp_dir = os.path.join(package_path, touch_name + random_str)\n    # 创建临时校验路径\n    os.mkdir(tmp_dir)\n    tar_out = public_utils.local_cmd(f'tar -xmf {file_name} -C {tmp_dir}')\n    if tar_out[2] != 0:\n        return public_action.update_package_status(\n            1,\n            f\"安装包{package_name}解压失败或者压缩包格式不合规\")\n    app_name = package_name.split('-', 1)[0]\n    tmp_dir = os.path.join(tmp_dir, app_name)\n    # 查询临时路径下符合规范的yaml\n    check_file = os.path.join(tmp_dir, f'{app_name}.yaml')\n    if not os.path.exists(check_file):\n        return public_action.update_package_status(\n            1,\n            f\"安装包{package_name}:{app_name}.yaml文件不存在\")\n    # yaml内容进行标准校验\n    explain_yml = ExplainYml(public_action, check_file).explain_yml()\n    # 这个校验可能用不到\n    if isinstance(explain_yml, bool):\n        return None\n    kind = explain_yml[1].get(\"kind\")\n    versions = explain_yml[1].get(\"version\")\n    name = explain_yml[1].get(\"name\")\n    pro_name = f\"{name}-{versions}\"\n    # 校验图片\n    image = None\n    if kind == 'product' or kind == 'component':\n        try:\n            image_dir = os.path.join(tmp_dir, f'{app_name}.svg')\n            if os.path.exists(image_dir):\n                with open(image_dir, 'r') as fp:\n                    image = fp.read()\n        except UnicodeDecodeError as e:\n            logger.error(f'{package_name}:图片格式异常{e}')\n            return public_action.update_package_status(\n                1,\n                f\"{package_name}图片格式异常\")\n    # yaml分为产品，组建，和服务逻辑。产品必须包含服务。因此需对产品类型做子级服务校验\n    if kind == 'product':\n        service = explain_yml[1].get(\"service\")\n        count = ProductHub.objects.filter(pro_version=versions,\n                                          pro_name=name).count()\n        if count != 0:\n            return public_action.update_package_status(\n                1,\n                f\"安装包{package_name}已存在:请确保name联合version唯一\")\n        explain_service_list = []\n        yml_dirs = os.path.join(tmp_dir, app_name)\n        # 查找产品包路径下符合规则的tar与产品字段内service字段进行比对，\n        # 成功的将会入库，未匹配到的则跳过逻辑。\n        service_name = [os.path.join(tmp_dir, i) for i in os.listdir(tmp_dir)]\n        service_packages_value = [\n            p for p in service_name if\n            os.path.isfile(p) and 'tar' in p]\n        service_packages_key = [service_package.rsplit(\"/\", 1)[1].split(\"-\")[0]\n                                for service_package in\n                                service_packages_value]\n        service_package = dict(\n            zip(service_packages_key, service_packages_value))\n        # 对匹配到的yaml进行yaml校验，此时逻辑产品下服务包没有合法，\n        # 但产品内service字段存在的service必须有对应的yaml文件。\n        name_version = []\n        for i in service:\n            service_dir = os.path.join(yml_dirs, f\"{i.get('name')}.yaml\")\n            if not os.path.exists(service_dir):\n                return public_action.update_package_status(\n                    1,\n                    f\"安装包{package_name}:{i.get('name')}.yaml文件不存在\")\n            # 子集服务进行yaml标准校验\n            explain_service_yml = ExplainYml(public_action,\n                                             service_dir).explain_yml()\n            if isinstance(explain_service_yml, bool):\n                return None\n            ser_name = i.get('name')\n            name_version.append(\n                {'name': ser_name, 'version': explain_service_yml[1].get('version')})\n            service_pk = service_package.get(ser_name)\n            if not service_pk:\n                continue\n            ser_kind = explain_service_yml[1].get(\"kind\", \"\")\n            if ser_kind != \"service\":\n                return public_action.update_package_status(\n                    1,\n                    f\"安装包{package_name}类型错误，请解压后将服务单独发布服务\")\n            # 校验服务是否唯一,无安装包跳过逻辑后\n            count = ApplicationHub.objects.filter(app_version=ser_name,\n                                                  app_name=i.get(\"version\")).count()\n            if count != 0:\n                return public_action.update_package_status(\n                    1,\n                    f\"安装包{package_name}服务{ser_name}已存在:请确保name联合version唯一\")\n            # 校验md5\n            service_pk_name = service_pk.rsplit(\"/\", 1)[1]\n            md5_ser = public_utils.local_cmd(f'md5sum {service_pk}')\n            if md5_ser[2] != 0:\n                return public_action.update_package_status(\n                    1, \"md5sum命令执行失败\")\n            md5_service = md5_ser[0].split()[0]\n            # 对合法服务的记录进行创建操作，\n            # 信息会追加入\"product_service\"字段并归入所属产品yaml，组件则不会有此值。\n            UploadPackageHistory.objects.create(\n                operation_uuid=uuid,\n                operation_user=operation_user,\n                package_name=service_pk_name,\n                package_md5=md5_service,\n                package_path=os.path.join(\n                    package_dir.get(\n                        \"verified\"), pro_name\n                ),\n                package_status=0,\n                package_parent=upload_obj\n            )\n            explain_service_yml[1]['package_name'] = service_pk_name\n            explain_service_list.append(explain_service_yml[1])\n        explain_yml[1]['product_service'] = explain_service_list\n        explain_yml[1]['service'] = name_version\n        tmp_dir = [tmp_dir, versions]\n    elif kind == 'service':\n        dependence_product = explain_yml[1].get(\n            \"extend_fields\", {}).get(\"product\")\n        if not dependence_product:\n            return public_action.update_package_status(\n                1,\n                f\"安装包{package_name}不能无依赖产品上传\")\n        product_version = dependence_product.get(\"version\", \"\")\n        if not product_version:\n            product_obj = ProductHub.objects.filter(pro_name=dependence_product.get(\"name\")\n                                                    ).last()\n\n        else:\n            product_obj = ProductHub.objects.filter(pro_name=dependence_product.get(\"name\"),\n                                                    pro_version=product_version).last()\n        if not product_obj:\n            return public_action.update_package_status(\n                1,\n                f\"安装包{package_name}依赖的产品包不存在\")\n        # 将版本追加至最新版本\n        dependence_product[\"version\"] = product_obj.pro_version\n        app_obj = ApplicationHub.objects.filter(product=product_obj)\n        for obj in app_obj:\n            if obj.app_name == explain_yml[1]['name'] and \\\n                    obj.app_version == explain_yml[1]['version']:\n                return public_action.update_package_status(\n                    1,\n                    f\"安装包{package_name}依赖的产品包存在同服务同版本的服务包\")\n        upload_obj.package_status = 0\n        upload_obj.package_path = os.path.join(\n            package_dir.get(\"verified\"),\n            f\"{product_obj.pro_name}-{product_obj.pro_version}\"\n        )\n        upload_obj.save()\n        tmp_dir = [file_name, versions, tmp_dir]\n    else:\n        count = ApplicationHub.objects.filter(app_version=versions,\n                                              app_name=name).count()\n        if count != 0:\n            return public_action.update_package_status(\n                1,\n                f\"安装包{package_name}已存在:请确保name联合version唯一\")\n        tmp_dir = [file_name, versions, tmp_dir]\n    explain_yml[1]['image'] = image\n    explain_yml[1]['package_name'] = package_name\n    explain_yml[1]['tmp_dir'] = tmp_dir\n    # 开启写入中间结果，包含发布入库所有的信息\n    middle_data = os.path.join(project_dir, 'data', f'middle_data-{uuid}.json')\n    with open(middle_data, mode='a', encoding='utf-8') as f:\n        f.write(json.dumps(explain_yml[1], ensure_ascii=False) + '\\n')\n    # 提示存在逻辑注释，改存在校验失败逻辑\n    # name = explain_yml[1]['name']\n    # version = explain_yml[1]['version']\n    # if explain_yml[1]['kind'] == 'product':\n    #    count = ProductHub.objects.filter(pro_version=version,\n    #                                      pro_name=name).count()\n    # else:\n    #    count = ApplicationHub.objects.filter(app_version=version,\n    #                                          app_name=name).count()\n    # if count:\n    #    count = \"已存在,将覆盖\"\n    # else:\n    #    count = None\n    # 无校验失败则会更新数据库状态为校验成功\n    return public_action.update_package_status(0)\n\n\nclass ExplainYml:\n    \"\"\"\n    校验yml文件总类\n    params:\n    db_obj 更新记录表obj\n    yaml_dir yaml文件路径\n    \"\"\"\n\n    def __init__(self, db_obj, yaml_dir):\n        # md5 的\n        self.db_obj = db_obj\n        self.yaml_exc = yaml_dir\n        self.yaml_dir = yaml_dir.rsplit(\"/\", 1)[1]\n        self.check_obj = FiledCheck(self.yaml_dir, self.db_obj)\n\n    def check_book_tools(self, key, value):\n        \"\"\"\n        校验值bool类型，前置条件，需强校验通过\n        后期根据需求进行扩展\n        params:\n        key输出错误日志的key\n        value需要判断的值\n        \"\"\"\n        if value.lower() == \"false\" or value.lower() == \"true\":\n            return False\n        self.db_obj.update_package_status(\n            1,\n            f\"yml校验{key}非bool值，检查yml文件{self.yaml_dir}\")\n        return True\n\n    def explain_yml(self):\n        \"\"\"\n        各种kind类型公共字段\n        \"\"\"\n        kinds = ['product', 'service', 'upgrade', 'component']\n        try:\n            with open(self.yaml_exc, \"r\", encoding=\"utf8\") as fp:\n                settings = yaml.load(fp, Loader=yaml.BaseLoader)\n            if not settings:\n                self.db_obj.update_package_status(\n                    1,\n                    f\"yml文件为空，检查yml文件{self.yaml_dir}\")\n        except Exception as e:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml包格式错误，检查yml文件{self.yaml_dir}:{e}\")\n            return False\n        # 将公共字段抽出校验，生成中间结果\n        kind = settings.pop('kind', None)\n        name = settings.pop('name', None)\n        version = settings.pop('version', None)\n        dependencies = settings.pop('dependencies', \"-1\")\n        if dependencies == \"-1\":\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验dependencies校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        if dependencies:\n            for i in dependencies:\n                if not i.get(\"name\") or not i.get(\"version\"):\n                    self.db_obj.update_package_status(\n                        1,\n                        f\"yml校验dependencies校验失败，检查yml文件{self.yaml_dir}\")\n                    return False\n        if kind not in kinds:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验kind校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        if not name or not version:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验name或version校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        # 对剩余字段进行自定义校验\n        yml = getattr(self, kind)(settings)\n        if isinstance(yml, bool):\n            return False\n        db_filed = {\n            \"kind\": kind,\n            \"name\": name,\n            \"version\": version,\n            \"dependencies\": dependencies,\n            \"extend_fields\": settings\n        }\n        db_filed.update(yml[1])\n        return True, db_filed\n\n    def product(self, settings):\n        \"\"\"产品级校验\"\"\"\n        db_filed = {}\n        service = settings.pop('service', None)\n        if not service:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验service校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        for i in service:\n            if not i.get(\"name\"):\n                self.db_obj.update_package_status(\n                    1,\n                    f\"yml校验service校验失败，检查yml文件{self.yaml_dir}\")\n                return False\n        db_filed['service'] = service\n        label = settings.pop('labels', None)\n        if not label:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验labels失败，检查yml文件{self.yaml_dir}\")\n            return False\n        db_filed['labels'] = label\n        description = settings.pop('description', \"-1\")\n        if description == \"-1\":\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验description校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        if description is not None and len(description) > 200:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验description长度过长，检查yml文件{self.yaml_dir}\")\n            return False\n        db_filed['description'] = description\n        return True, db_filed\n\n    def service_component(self, settings):\n        \"\"\"校验kind为service\"\"\"\n        # service骨架弱校验\n        db_filed = {}\n        # post_action affinity base_env auto_launch不再校验\n        # level 默认0 monitor不做校验\n        first_check = {\"ports\", \"install\",\n                       \"control\"}\n        if not self.check_obj.weak_check(settings, first_check, attention=str(first_check)):\n            return False\n        # auto_launch 校验 不填写默认给true\n        settings[\"auto_launch\"] = settings.get(\"auto_launch\", \"true\")\n        # base_env 校验 不填写True true 全部按照false处理\n        db_filed['base_env'] = settings.pop('base_env', \"\")\n        # ports 校验\n        ports = settings.pop('ports')\n        ports_strong_check = {\"name\", \"protocol\", \"default\", \"key\"}\n        port = self.check_obj.strong_check(\n            ports, ports_strong_check,\n            is_weak=True,\n            ignore={\"key\"},\n            attention=\"ports\") if ports else 1\n        if not port:\n            return False\n        db_filed['ports'] = ports\n        #  control校验\n        control = settings.pop('control')\n        control_weak_check = {\"start\", \"stop\", \"restart\", \"install\",\n                              \"init\"}\n        control_check = self.check_obj.weak_check(\n            control,\n            control_weak_check,\n            attention=str(control_weak_check)) if control else 1\n        if not control_check:\n            return False\n        control_strong_check = self.check_obj.strong_check(\n            control,\n            {\"install\"},\n            attention=\"control\")\n        if not control_strong_check:\n            return False\n        db_filed['control'] = control\n        # install 校验\n        install = settings.pop('install')\n        single_strong_install = {\"name\", \"key\", \"default\"}\n        install_check = self.check_obj.strong_check(\n            install,\n            single_strong_install,\n            is_weak=True,\n            attention=\"install\") if install else 1\n        if not install_check:\n            return False\n        db_filed['install'] = install\n        # monitor 校验\n        db_filed['monitor'] = settings.pop('monitor', None)\n        return True, db_filed\n\n    def service(self, settings):\n        \"\"\"\n        创建服务校验类，原服务类变为基类\n        \"\"\"\n        level = settings.pop('level', -1)\n        if level == -1:\n            level = 0\n        result = self.service_component(settings)\n        if isinstance(result, bool):\n            return False\n        settings['level'] = level\n        return True, result[1]\n\n    def upgrade(self, settings):\n        return self.service_component(settings)\n\n    def component(self, settings):\n        # 校验label,继承service\n        db_filed = {}\n        label = settings.pop('labels', None)\n        if not label:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验labels失败，检查yml文件{self.yaml_dir}\")\n            return False\n        db_filed['labels'] = label\n        description = settings.pop('description', \"-1\")\n        if description == \"-1\":\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验description校验失败，检查yml文件{self.yaml_dir}\")\n            return False\n        if description is not None and len(description) > 200:\n            self.db_obj.update_package_status(\n                1,\n                f\"yml校验description长度过长，检查yml文件{self.yaml_dir}\")\n            return False\n        db_filed['description'] = description\n        result = self.service_component(settings)\n        if isinstance(result, bool):\n            return False\n        db_filed.update(result[1])\n        return True, db_filed\n\n\ndef exec_clear(clear_dir):\n    # 清理逻辑，当发布完成时对临时路径进行清空处理，\n    # 此逻辑会考虑状态为正在校验和正在发布状态的情况\n    # 存在清理跳过，后期会考虑只选择一段时间内状态非中间态不做清理逻辑。，\n    online = UploadPackageHistory.objects.filter(\n        is_deleted=False,\n        package_status__in=[2,\n                            5]).count()\n    if len(clear_dir) <= 28:\n        logger.error(f'{clear_dir}路径异常')\n        return None\n    if online == 0 or 'back_end_verified' in clear_dir:\n        clear_out = public_utils.local_cmd(\n            f'rm -rf {clear_dir}')\n        logger.info(clear_dir)\n        if clear_out[2] != 0:\n            logger.error('清理环境失败')\n\n\ndef clear_check(need_rm):\n    \"\"\"\n    梳理要删除的包\n    \"\"\"\n    result = []\n    for tmp_dir in need_rm:\n        if \".tar\" in tmp_dir[0]:\n            result.append(tmp_dir[0])\n            result.append(tmp_dir[2].rsplit('/', 1)[0])\n        else:\n            rm_dir = tmp_dir[0].rsplit('/', 1)[0]\n            result.append(rm_dir)\n            result.append(f\"{rm_dir[:-10]}*\")\n    logger.info(\"需要删除的目录:\" + \" \".join(result))\n    return \" \".join(result)\n\n\n@shared_task\ndef publish_bak_end(uuid, exc_len):\n    \"\"\"\n    后台扫描同步等待发布函数\n    params:\n    uuid 当前唯一操作id\n    exc_len 合法安装包个数\n    \"\"\"\n\n    # 增加try，并增加超时机制释放锁\n    exc_task = True\n    time_count = 0\n    try:\n        while exc_task and time_count <= 600:\n            # 当所有安装包的状态均不为正在校验，\n            # 并和扫描出得包的个数相同且不为0，进行发布逻辑。\n            valid_uuids = UploadPackageHistory.objects.filter(\n                operation_uuid=uuid,\n                package_parent__isnull=True,\n            ).exclude(\n                package_status=2)\n            valid_success = valid_uuids.exclude(\n                package_status=1).count()\n            if valid_uuids.count() != exc_len:\n                time_count += 1\n                time.sleep(5)\n            else:\n                if valid_uuids.count() != 0 and valid_success != 0:\n                    publish_entry(uuid)\n                else:\n                    exec_clear(\"{0}/*\".format(os.path.join(\n                        package_hub, package_dir.get('back_end_verified'))))\n                exc_task = False\n    finally:\n        re = redis.Redis(host=OMP_REDIS_HOST, port=OMP_REDIS_PORT, db=9,\n                         password=OMP_REDIS_PASSWORD)\n        re.delete('back_end_verified')\n\n\n@shared_task\ndef publish_entry(uuid):\n    \"\"\"\n    前台发扫描台发布函数公共类\n    params:\n    uuid 当前唯一操作id\n    注：此异步任务的调用的前提必须是校验已完成状态\n    \"\"\"\n    # 修改校验无误的安装包的状态为正在发布状态。\n    valid_uuids = UploadPackageHistory.objects.filter(\n        is_deleted=False,\n        operation_uuid=uuid,\n        package_parent__isnull=True,\n        package_status=0)\n    valid_uuids.update(package_status=5)\n    valid_uuids = UploadPackageHistory.objects.filter(\n        is_deleted=False,\n        operation_uuid=uuid,\n        package_parent__isnull=True,\n        package_status=5)\n    valid_packages = {}\n    # 从库获取校验合法的安装包名称\n    if valid_uuids:\n        for j in valid_uuids:\n            valid_packages[j.package_name] = j\n\n    json_data = os.path.join(project_dir, 'data', f'middle_data-{uuid}.json')\n    with open(json_data, \"r\", encoding=\"utf8\") as fp:\n        lines = fp.readlines()\n    valid_info = []\n    # 将匹配到的安装包和中间结果的安装包比对，将安装包名替换成历史记录表的model对象\n    for line in lines:\n        json_line = json.loads(line)\n        valid_obj = valid_packages.get(json_line.get('package_name'))\n        if json_line.get(\"extend_fields\").get(\"product\"):\n            valid_info.append(json_line)\n        # 升级包单独逻辑\n        elif valid_obj:\n            json_line['package_name'] = valid_obj\n            valid_info.append(json_line)\n    valid_packages_obj = []\n    valid_dir = None\n    # 中间结果转入创表函数\n    product_obj = None\n    tmp_dir = []\n    front_dir = []\n    for line in valid_info:\n        if line.get('kind') == 'product':\n            CreateDatabase(line).create_product()\n        elif line.get('kind') == 'service':\n            product = line.get(\"extend_fields\").get(\"product\")\n            product_obj = ProductHub.objects.filter(pro_name=product.get(\"name\"),\n                                                    pro_version=product.get(\n                                                        \"version\")\n                                                    ).last()\n            upload_history_obj = None\n            for j in valid_uuids:\n                if j.package_name == line.get('package_name'):\n                    upload_history_obj = j\n            # 更新服务的upload历史状态和已有服务关联\n            upload_history_obj.package_parent = product_obj.pro_package\n            upload_history_obj.save()\n            CreateDatabase(line).create_service([line], product_obj)\n            line['package_name'] = upload_history_obj\n        else:\n            CreateDatabase(line).create_component()\n        tmp_dir = line.get('tmp_dir')\n        if len(tmp_dir[0]) <= 28:\n            line['package_name'].package_status = 4\n            line['package_name'].save()\n            logger.error(f'{tmp_dir[0]}路径异常')\n            return None\n        # 匹配到的安装包移动至对应路径\n        # 产品包路径 package_hub/xxx/random/product_name/xxx\n        # 组件路径   package_hub/xxx/app.tar.gz\n        # 产品目标路径   verified/product_name-version/xxx\n        # 组件目标路径   verified/app.tar.gz\n\n        valid_name = tmp_dir[0].rsplit('/', 1)\n        valid_pk = f\"{valid_name[1]}-{tmp_dir[1]}\" if len(\n            tmp_dir) == 2 else valid_name[1]\n        if product_obj and line.get('kind') == 'service':\n            valid_pk = f\"{product_obj.pro_name}-{product_obj.pro_version}/{valid_name[1]}\"\n\n        valid_dir = os.path.join(project_dir, 'package_hub',\n                                 'verified', valid_pk)\n        move_tmp = \"/\".join(valid_name)\n        move_out = public_utils.local_cmd(\n            f'rm -rf {valid_dir} && mv {move_tmp} {valid_dir}')\n        if move_out[2] != 0:\n            line['package_name'].package_status = 4\n            line['package_name'].save()\n            logger.error('移动或删除失败')\n        valid_packages_obj.append(line['package_name'].id)\n        if \"front_end_verified\" in tmp_dir[0]:\n            front_dir.append(tmp_dir)\n    clear_dir = os.path.dirname(tmp_dir[0]) if os.path.isfile(valid_dir) else \\\n        os.path.dirname(os.path.dirname(tmp_dir[0]))\n    UploadPackageHistory.objects.filter(id__in=valid_packages_obj).update(\n        package_status=3)\n    need_rm = clear_check(front_dir)\n    if not need_rm:\n        need_rm = f\"{clear_dir}/*\"\n    exec_clear(need_rm)\n\n\ndef check_monitor_data(detail_obj):\n    \"\"\"\n    根据部署详情信息，确认该服务的要监控的方式，并返回监控处理结果\n    {\n        \"type\": \"JavaSpringBoot\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    }\n    :param detail_obj: 部署详情对象\n    :type detail_obj: DetailInstallHistory\n    :return: (bool, _ret_dic)\n    \"\"\"\n\n    def get_port(keyword):\n        \"\"\"\n        根据关键字获取端口值\n        :param keyword:\n        :return:\n        \"\"\"\n        service_port_ls = json.loads(detail_obj.service.service_port)\n        for item in service_port_ls:\n            if item.get(\"key\") == keyword:\n                return item.get(\"default\")\n        return None\n\n    _ret_dic = {\n        \"listen_port\": get_port(\"service_port\"),\n        \"metric_port\": None,\n        \"run_port\": [],\n        \"only_process\": False,\n        \"process_key_word\": None,\n        \"type\": None\n    }\n    run_port_key_list = list()\n    run_port_value_list = list()\n    app_monitor = detail_obj.service.service.app_monitor\n    if not app_monitor:\n        return False, _ret_dic\n    run_port_key_list = app_monitor.get(\"run_port\", [])\n    if len(run_port_key_list) > 0:\n        run_port_value_list = [get_port(key) for key in run_port_key_list]\n        if None not in run_port_value_list:\n            _ret_dic[\"run_port\"] = run_port_value_list\n    _ret_dic[\"type\"] = app_monitor.get(\"type\")\n    _ret_dic[\"process_key_word\"] = app_monitor.get(\"process_name\")\n\n    if isinstance(app_monitor.get(\"metric_port\"), str):\n        _metric_port_key = app_monitor.get(\n            \"metric_port\", \"\").replace(\"{\", \"\").replace(\"}\", \"\")\n    elif isinstance(app_monitor.get(\"metric_port\"), dict):\n        _metric_port_key = list(app_monitor.get(\"metric_port\", {}).keys())[0]\n    else:\n        _metric_port_key = None\n    if detail_obj.service.service_port is not None:\n        _ret_dic[\"metric_port\"] = get_port(_metric_port_key)\n    if _ret_dic[\"metric_port\"]:\n        return True, _ret_dic\n    if _ret_dic[\"process_key_word\"]:\n        _ret_dic[\"only_process\"] = True\n        return True, _ret_dic\n    return False, _ret_dic\n\n\ndef add_prometheus(main_history_id, queryset=None):\n    \"\"\" 添加服务到 Prometheus \"\"\"\n    logger.info(\"Add Prometheus Begin\")\n    prometheus = PrometheusUtils()\n    # TODO 不同类型服务添加监控方式不同，后续版本优化\n    # 仅更新已经安装完成的最新服务\n    # 给monitor_agent刷新使用，提供detail_obj list。\n    queryset = queryset if queryset else DetailInstallHistory.objects.filter(\n        main_install_history_id=main_history_id,\n        install_step_status=DetailInstallHistory.INSTALL_STATUS_SUCCESS\n    )\n    for detail_obj in queryset:\n        try:\n            _flag, _monitor_dic = check_monitor_data(detail_obj=detail_obj)\n            logger.info(\n                f\"Add Prometheus get monitor_dic for \"\n                f\"{detail_obj}: {_monitor_dic}\")\n        except Exception as e:\n            logger.info(\n                f\"Add Prometheus get monitor_dic Failed: \"\n                f\"{detail_obj}; {str(e)}\")\n            continue\n        if not _flag:\n            continue\n        # TODO 已是否具有端口作为是否需要添加监控的依据，后续版本优化\n        instance_name = detail_obj.service.service_instance_name\n        service_port = _monitor_dic.get(\"listen_port\")\n        run_port = _monitor_dic.get(\"run_port\", [])\n        # 获取数据目录、日志目录\n        app_install_args = detail_obj.install_detail_args.get(\n            \"install_args\", [])\n        data_dir = log_dir = \"\"\n        username = password = \"\"\n        for info in app_install_args:\n            if info.get(\"key\", \"\") == \"data_dir\":\n                data_dir = info.get(\"default\", \"\")\n            if info.get(\"key\", \"\") == \"log_dir\":\n                log_dir = info.get(\"default\", \"\")\n            if info.get(\"key\", \"\") == \"username\":\n                username = info.get(\"default\", \"\")\n            if info.get(\"key\", \"\") == \"password\":\n                password = info.get(\"default\", \"\")\n        # TODO 后期优化\n        ser_name = detail_obj.service.service.app_name\n        if ser_name == \"hadoop\":\n            ser_name = instance_name.split(\"_\", 1)[0]\n        # 添加服务到 prometheus\n        is_success, message = prometheus.add_service({\n            \"service_name\": ser_name,\n            \"instance_name\": instance_name,\n            \"data_path\": data_dir,\n            \"log_path\": log_dir,\n            \"env\": \"default\",\n            \"ip\": detail_obj.service.ip,\n            \"listen_port\": service_port,\n            \"run_port\": run_port,\n            \"metric_port\": _monitor_dic.get(\"metric_port\"),\n            \"only_process\": _monitor_dic.get(\"only_process\"),\n            \"process_key_word\": _monitor_dic.get(\"process_key_word\"),\n            \"username\": username,\n            \"password\": password,\n        })\n        if not is_success:\n            logger.error(\n                f\"Add Prometheus Failed {instance_name}, error: {message}\")\n            continue\n        logger.info(f\"Add Prometheus Success {instance_name}\")\n    logger.info(\"Add Prometheus End\")\n\n\ndef make_inspection(username):\n    \"\"\"\n    触发巡检任务\n    :param username:\n    :return:\n    \"\"\"\n    logger.info(\"安装成功后触发巡检任务\")\n    from rest_framework.test import APIClient\n    from rest_framework.reverse import reverse\n    from db_models.models import UserProfile\n    from db_models.models import Env\n    data = {\n        \"inspection_name\": \"mock\", \"inspection_type\": \"deep\",\n        \"inspection_status\": \"1\", \"execute_type\": \"man\",\n        \"inspection_operator\": username, \"env\": Env.objects.last().id\n    }\n    user = UserProfile.objects.filter(username=username).last()\n    client = APIClient()\n    client.force_authenticate(user)\n    res = client.post(\n        path=reverse(\"history-list\"),\n        data=json.dumps(data),\n        content_type=\"application/json\"\n    ).json()\n    logger.info(f\"安装成功后触发巡检任务的结果为: {res}\")\n    return res\n\n\n@shared_task\ndef install_service(main_history_id, username=\"admin\"):\n    \"\"\"\n    安装服务\n    :param main_history_id: MainInstallHistory 主表 id\n    :param username: 执行用户\n    :return:\n    \"\"\"\n    try:\n        # 为防止批量安装时数据库写入数据过多，这里采用循环的方式判断main_history_id\n        try_times = 0\n        while try_times < 3:\n            if MainInstallHistory.objects.filter(id=main_history_id).exists():\n                break\n            time.sleep(5)\n        else:\n            logger.error(\n                \"Install Service Task Failed: can not find {main_history_id}\")\n        executor = InstallServiceExecutor(main_history_id, username)\n        executor.main()\n        logger.error(f\"Install Service Task Success [{main_history_id}]\")\n    except Exception as err:\n        import traceback\n        logger.error(f\"Install Service Task Failed [{main_history_id}], \"\n                     f\"err: {err}\")\n        logger.error(traceback.format_exc())\n        # 更新主表记录为失败\n        MainInstallHistory.objects.filter(\n            id=main_history_id).update(\n            install_status=MainInstallHistory.INSTALL_STATUS_FAILED)\n\n    # 安装成功，则注册服务至监控\n    add_prometheus(main_history_id)\n\n    # 安装成功，触发巡检任务\n    if MainInstallHistory.objects.filter(\n            id=main_history_id,\n            install_status=MainInstallHistory.INSTALL_STATUS_SUCCESS\n    ).exists():\n        make_inspection(username=username)\n"
  },
  {
    "path": "omp_server/app_store/tmp_exec_back_task.py",
    "content": "import os\nfrom utils.parse_config import (\n    OMP_REDIS_PORT, OMP_REDIS_PASSWORD, OMP_REDIS_HOST\n)\nimport time\nfrom app_store.tasks import front_end_verified, publish_bak_end\nimport redis\nfrom db_models.models import UploadPackageHistory\nimport random\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nproject_dir = os.path.dirname(os.path.dirname(current_dir))\npackage_hub = os.path.join(project_dir, \"package_hub\")\n\npackage_dir = {\n    \"back_end_verified\": \"back_end_verified\",\n    \"front_end_verified\": \"front_end_verified\",\n    \"verified\": \"verified\"\n}\n\n\nclass RedisLock(object):\n    def __init__(self, host='127.0.0.1', port='6379', db=9, **kwargs):\n        self.rdcon = self.args_init(host, port, db, kwargs)\n\n    @staticmethod\n    def args_init(host, port, db, kwargs):\n        if kwargs and kwargs.get('password'):\n            return redis.Redis(host, port, db, kwargs['password'])\n        return redis.Redis(host, port, db)\n\n    def get_lock(self, key='back_end_verified'):\n        if self.rdcon.exists(key) == 0:\n            return False, self.rdcon\n        return True, self.rdcon\n\n\ndef back_end_verified_init(operation_user):\n    \"\"\"\n    后台扫描接口\n    :param operation_user: 操作用户\n    :return:\n    \"\"\"\n    # uuid 自己生成，上redis锁，如果存在则返回当前锁及包名\n    uuid = str(round(time.time() * 1000))\n    redis_obj = RedisLock(\n        host=OMP_REDIS_HOST, port=OMP_REDIS_PORT,\n        password=OMP_REDIS_PASSWORD)\n    lock, redis_key = redis_obj.get_lock()\n    if lock:\n        return redis_key.lindex(\"back_end_verified\", 1).decode(\"utf-8\"), redis_key. \\\n            lindex(\"back_end_verified\", 0).decode(\"utf-8\").split(\",\")\n    back_verified = os.path.join(\n        package_hub, package_dir.get('back_end_verified'))\n    service_name = os.listdir(back_verified)\n    exec_name = [\n        p for p in service_name\n        if os.path.isfile(os.path.join(back_verified, p)) and (p.endswith('.tar') or p.endswith('.tar.gz'))\n    ]\n    redis_key.lpush(\"back_end_verified\", uuid, \",\".join(exec_name))\n    # 设置过期时间，同时创建异步校验任务及发布任务\n    redis_key.expire(\"back_end_verified\", 3600)\n    for j in exec_name:\n        upload_obj = UploadPackageHistory(\n            operation_uuid=uuid,\n            operation_user=operation_user,\n            package_name=j,\n            package_md5='1',\n            package_path=\"verified\")\n        upload_obj.save()\n        front_end_verified_init(uuid, operation_user, j, upload_obj.id)\n    publish_bak_end.delay(uuid, len(exec_name))\n    return uuid, exec_name\n\n\ndef front_end_verified_init(uuid, operation_user, package_name, obj_id, md5=None):\n    # 前端发布校验接口\n    random_str = ''.join(\n        random.sample('abcdefghijklmnopqrstuvwxyz1234567890', 10))\n    if md5:\n        ver_dir = package_dir.get(\"front_end_verified\")\n    else:\n        ver_dir = package_dir.get(\"back_end_verified\")\n    front_end_verified.delay(uuid, operation_user, package_name,\n                             random_str, ver_dir, obj_id)\n"
  },
  {
    "path": "omp_server/app_store/upload_task.py",
    "content": "import os\nfrom db_models.models import ApplicationHub, ProductHub, UploadPackageHistory, Labels\nimport logging\nfrom celery.utils.log import get_task_logger\nimport json\n\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\n\n\nclass CreateDatabase(object):\n    \"\"\"\n     创建产品表，服务表，标签表公共类\n     params json_data创建表所需要的json\n     label_type json归属类型\n     eg: 1 产品类型\n    \"\"\"\n\n    def __init__(self, json_data):\n        self.json_data = json_data\n        self.label_type = None\n\n    def str_to_bool(self, value):\n        \"\"\"\n        str转换bool值\n        \"\"\"\n        str_bool = self.json_data.get(value)\n        result = \"true\" if str_bool.lower() == \"true\" else None\n        return bool(result)\n\n    def explain(self, data, default=None):\n        \"\"\"\n        将dict list转换成 json\n        \"\"\"\n        data_info = self.json_data.get(data)\n        if data_info:\n            if isinstance(data_info, dict) or isinstance(data_info, list):\n                data_info = json.dumps(data_info, ensure_ascii=False)\n        else:\n            data_info = default\n        return data_info\n\n    def explain_dependence(self):\n        # 不符合常规逻辑，慎用\n        data_info = self.json_data.get(\"dependencies\")\n        if isinstance(data_info, list):\n            for key in data_info:\n                if isinstance(key, dict):\n                    key[\"version\"] = key.get(\"version\", \"\").split(\".\")[0]\n            data_info = json.dumps(data_info, ensure_ascii=False)\n        return data_info\n\n    def create_product(self):\n        \"\"\"\n        创建产品表\n        \"\"\"\n        self.label_type = 1\n        self.create_lab()\n        _dic = {\n            \"is_release\": True,\n            \"pro_name\": self.json_data.get('name'),\n            \"pro_version\": self.json_data.get('version'),\n            \"pro_description\": self.json_data.get('description'),\n            \"pro_dependence\": self.explain('dependencies'),\n            \"pro_services\": self.explain('service', []),\n            \"pro_package\": self.json_data.get('package_name'),\n            \"pro_logo\": self.json_data.get('image'),\n            \"extend_fields\": self.json_data.get(\"extend_fields\")\n        }\n        # app_obj = ProductHub(\n        #     is_release=True, pro_name=self.json_data.get('name'),\n        #     pro_version=self.json_data.get('version'),\n        #     pro_description=self.json_data.get('description'),\n        #     pro_dependence=self.explain('dependencies'),\n        #     pro_services=self.explain('service'),\n        #     pro_package=self.json_data.get('package_name'),\n        #     pro_logo=self.json_data.get('image'),\n        #     extend_fields=self.json_data.get(\"extend_fields\")\n        # )\n        pro_queryset = ProductHub.objects.filter(\n            pro_name=self.json_data.get('name'),\n            pro_version=self.json_data.get('version')\n        )\n        if pro_queryset.exists():\n            pro_queryset.update(**_dic)\n            app_obj = pro_queryset.first()\n        else:\n            app_obj = ProductHub.objects.create(**_dic)\n        app_obj.save()\n        # 创建lab表\n        self.create_pro_app_lab(app_obj)\n        service = self.json_data.pop('product_service')\n        # 创建服务表\n        self.create_service(service, app_obj)\n\n    def create_service(self, service, app_obj):\n        \"\"\"\n        创建服务表\n        params service service的json字段，格式同json_data一致\n        app_obj 需要关联产品表的对象\n        \"\"\"\n        pro_history = app_obj.pro_package\n        service_obj = UploadPackageHistory.objects.filter(\n            package_parent=pro_history)\n        valid_packages = {}\n        for j in service_obj:\n            valid_packages[j.package_name] = j\n        valid_info = []\n        for line in service:\n            valid_obj = valid_packages.get(line.get('package_name'))\n            if valid_obj:\n                line['package_name'] = valid_obj\n                valid_info.append(line)\n        # 实例化变量添加label标签，创建产品名称标签，类型组件\n        self.json_data['labels'] = [app_obj.pro_name]\n        self.label_type = 0\n        self.create_lab()\n        pro_services = json.loads(app_obj.pro_services)\n        name_ls = [name.get('name') for name in pro_services]\n        version_ls = [version.get('version') for version in pro_services]\n        for info in valid_info:\n            self.json_data = info\n            # 服务json添加labels字段，给create_pro_app_lab做筛选\n            self.json_data['labels'] = [app_obj.pro_name]\n            # 按照服务名和版本进行划分 如果存在则覆盖，否则创建\n            monitor = self.json_data.get(\n                \"monitor\") if self.json_data.get(\"monitor\") else None\n            _dic = {\n                \"is_release\": True,\n                \"app_type\": 1,\n                \"app_name\": self.json_data.get(\"name\", \"\"),\n                \"app_version\": self.json_data.get(\"version\", \"\"),\n                \"app_port\": self.explain(\"ports\", json.dumps([])),\n                \"app_dependence\": self.explain_dependence(),\n                \"app_install_args\": self.explain(\"install\"),\n                \"app_controllers\": self.explain(\"control\"),\n                \"app_package\": self.json_data.get(\"package_name\"),\n                \"product\": app_obj,\n                \"extend_fields\": self.json_data.get(\"extend_fields\"),\n                \"is_base_env\": self.str_to_bool(\"base_env\"),\n                \"app_monitor\": monitor\n            }\n            app_queryset = ApplicationHub.objects.filter(\n                app_name=self.json_data.get(\"name\"),\n                app_version=self.json_data.get(\"version\")\n            )\n            if _dic[\"app_name\"] not in name_ls \\\n                    or _dic[\"app_version\"] not in version_ls:\n                pro_services.append({\"name\": _dic[\"app_name\"], \"version\": _dic[\"app_version\"]})\n            if app_queryset.exists():\n                app_queryset.update(**_dic)\n            else:\n                ser_obj = ApplicationHub.objects.create(**_dic)\n                # 做多对多关联\n                self.create_pro_app_lab(ser_obj)\n        app_obj.pro_services = json.dumps(pro_services, ensure_ascii=False)\n        app_obj.save()\n        # ApplicationHub.objects.create(\n        #     is_release=True, app_type=1,\n        #     app_name=self.json_data.get(\"name\"),\n        #     app_version=self.json_data.get(\"version\"),\n        #     app_description=self.json_data.get(\"description\"),\n        #     app_port=self.explain(\"ports\"),\n        #     app_dependence=self.explain(\"dependencies\"),\n        #     app_install_args=self.explain(\"install\"),\n        #     app_controllers=self.explain(\"control\"),\n        #     app_package=self.json_data.get(\"package_name\"),\n        #     product=app_obj,\n        #     extend_fields=self.json_data.get(\"extend_fields\")\n        # )\n\n    def create_component(self):\n        \"\"\"\n        创建组件表 逻辑同创建产品表一致\n        \"\"\"\n        self.label_type = 0\n        self.create_lab()\n        monitor = self.json_data.get(\n            \"monitor\") if self.json_data.get(\"monitor\") else None\n        _dic = {\n            \"is_release\": True,\n            \"app_type\": 0,\n            \"app_name\": self.json_data.get(\"name\"),\n            \"app_version\": self.json_data.get(\"version\"),\n            \"app_description\": self.json_data.get(\"description\"),\n            \"app_port\": self.explain(\"ports\"),\n            \"app_dependence\": self.explain_dependence(),\n            \"app_install_args\": self.explain(\"install\"),\n            \"app_controllers\": self.explain(\"control\"),\n            \"app_package\": self.json_data.get(\"package_name\"),\n            \"extend_fields\": self.json_data.get(\"extend_fields\"),\n            \"app_logo\": self.json_data.get(\"image\"),\n            \"is_base_env\": self.str_to_bool(\"base_env\"),\n            \"app_monitor\": monitor\n        }\n        app_queryset = ApplicationHub.objects.filter(\n            app_name=self.json_data.get(\"name\"),\n            app_version=self.json_data.get(\"version\")\n        )\n        if app_queryset.exists():\n            app_queryset.update(**_dic)\n            app_obj = app_queryset.first()\n        else:\n            app_obj = ApplicationHub.objects.create(**_dic)\n        # app_obj = ApplicationHub.objects.create(\n        #     is_release=True, app_type=0,\n        #     app_name=self.json_data.get(\"name\"),\n        #     app_version=self.json_data.get(\"version\"),\n        #     app_description=self.json_data.get(\"description\"),\n        #     app_port=self.explain(\"ports\"),\n        #     app_dependence=self.explain(\"dependencies\"),\n        #     app_install_args=self.explain(\"install\"),\n        #     app_controllers=self.explain(\"control\"),\n        #     app_package=self.json_data.get(\"package_name\"),\n        #     extend_fields=self.json_data.get(\"extend_fields\"),\n        #     app_logo=self.json_data.get(\"image\")\n        # )\n        # app_obj.save()\n        self.create_pro_app_lab(app_obj)\n\n    def create_pro_app_lab(self, obj):\n        \"\"\"\n        创建lab表和服务表应用表做多对多关联\n        \"\"\"\n        labels = self.json_data.get('labels')\n        for i in labels:\n            label_obj = Labels.objects.get(\n                label_name=i, label_type=self.label_type)\n            if self.label_type == 1:\n                obj.pro_labels.add(label_obj)\n            else:\n                obj.app_labels.add(label_obj)\n\n    def create_lab(self):\n        \"\"\"\n        创建lab表，未存在的名称做创建，已存在的跳过处理\n        \"\"\"\n        labels_obj = Labels.objects.filter(label_type=self.label_type)\n        labels = [i.label_name for i in labels_obj]\n        compare_labels = set(self.json_data.get('labels')) - set(labels)\n        compare_list = []\n        if compare_labels:\n            for compare_label in compare_labels:\n                label_obj = Labels(label_name=compare_label,\n                                   label_type=self.label_type)\n                compare_list.append(label_obj)\n            Labels.objects.bulk_create(compare_list)\n"
  },
  {
    "path": "omp_server/app_store/urls.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: urls\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-08 15:54\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\nfrom rest_framework.routers import DefaultRouter\nfrom app_store.views import (\n    LabelListView, ComponentListView, ServiceListView,\n    UploadPackageView, RemovePackageView,\n    ComponentDetailView, ServiceDetailView,\n    ServicePackPageVerificationView, PublishViewSet,\n    ExecuteLocalPackageScanView, LocalPackageScanResultView,\n    ApplicationTemplateView, DeploymentPlanValidateView,\n    DeploymentPlanImportView, DeploymentPlanListView,\n    DeploymentOperableView, DeploymentTemplateView,\n    ExecutionRecordAPIView, DeleteAppStorePackageView,\n    ProductCompositionView)\nfrom app_store.views_for_install import (\n    ComponentEntranceView,\n    ProductEntranceView,\n    ExecuteInstallView,\n    InstallHistoryView,\n    ServiceInstallHistoryDetailView\n)\n\nfrom app_store.new_install_view import (\n    BatchInstallEntranceView,\n    CreateInstallInfoView,\n    CheckInstallInfoView,\n    CreateServiceDistributionView,\n    CheckServiceDistributionView,\n    GetInstallHostRangeView,\n    GetInstallArgsByIpView,\n    CreateInstallPlanView,\n    ListServiceByIpView,\n    ShowInstallProcessView,\n    ShowSingleServiceInstallLogView,\n    MainInstallHistoryView,\n    CreateComponentInstallInfoView,\n    RetryInstallView\n)\n\nrouter = DefaultRouter()\nrouter.register(\"labels\", LabelListView, basename=\"labels\")\nrouter.register(\"components\", ComponentListView, basename=\"components\")\nrouter.register(\"services\", ServiceListView, basename=\"appServices\")\nrouter.register(\"upload\", UploadPackageView, basename=\"upload\")\nrouter.register(\"remove\", RemovePackageView, basename=\"remove\")\nrouter.register(\"delete\", DeleteAppStorePackageView, basename=\"delete\")\nrouter.register(\"componentDetail\", ComponentDetailView,\n                basename=\"componentDetail\")\nrouter.register(\"serviceDetail\", ServiceDetailView,\n                basename=\"appServiceDetail\")\nrouter.register(\"pack_verification_results\",\n                ServicePackPageVerificationView, basename=\"pack_verification_results\")\nrouter.register(\"publish\", PublishViewSet, basename=\"publish\")\nrouter.register(\"executeLocalPackageScan\", ExecuteLocalPackageScanView,\n                basename=\"executeLocalPackageScan\")\nrouter.register(\"localPackageScanResult\", LocalPackageScanResultView,\n                basename=\"localPackageScanResult\")\nrouter.register(\n    \"executeLocalPackageScan\", ExecuteLocalPackageScanView,\n    basename='executeLocalPackageScan')\nrouter.register(\n    \"localPackageScanResult\", LocalPackageScanResultView,\n    basename='localPackageScanResult')\nrouter.register(\n    \"applicationTemplate\", ApplicationTemplateView,\n    basename='applicationTemplate')\nrouter.register(\n    \"deploymentPlanValidate\", DeploymentPlanValidateView,\n    basename=\"deploymentPlanValidate\"\n)\nrouter.register(\n    \"deploymentPlanImport\", DeploymentPlanImportView,\n    basename=\"deploymentPlanImport\"\n)\nrouter.register(\n    \"deploymentPlanList\", DeploymentPlanListView,\n    basename=\"deploymentPlanList\"\n)\nrouter.register(\n    \"deploymentOperable\", DeploymentOperableView,\n    basename=\"deploymentOperable\"\n)\nrouter.register(\n    \"deploymentTemplate\", DeploymentTemplateView,\n    basename=\"deploymentTemplate\"\n)\n\n# 安装部分使用路由\nrouter.register(\n    \"componentEntrance\", ComponentEntranceView,\n    basename='componentEntrance')\nrouter.register(\n    \"productEntrance\", ProductEntranceView,\n    basename='productEntrance')\nrouter.register(\n    \"executeInstall\", ExecuteInstallView,\n    basename='executeInstall')\nrouter.register(\n    \"installHistory\", InstallHistoryView,\n    basename='installHistory')\nrouter.register(\n    \"serviceInstallHistoryDetail\", ServiceInstallHistoryDetailView,\n    basename='serviceInstallHistoryDetail')\n\n# 新版安装逻辑\nrouter.register(\n    \"batchInstallEntrance\",\n    BatchInstallEntranceView,\n    basename=\"batchInstallEntrance\"\n)\nrouter.register(\n    \"createInstallInfo\",\n    CreateInstallInfoView,\n    basename=\"createInstallInfo\"\n)\nrouter.register(\n    \"checkInstallInfo\",\n    CheckInstallInfoView,\n    basename=\"checkInstallInfo\"\n)\nrouter.register(\n    \"createServiceDistribution\",\n    CreateServiceDistributionView,\n    basename=\"createServiceDistribution\"\n)\nrouter.register(\n    \"checkServiceDistribution\",\n    CheckServiceDistributionView,\n    basename=\"checkServiceDistribution\"\n)\nrouter.register(\n    \"getInstallHostRange\",\n    GetInstallHostRangeView,\n    basename=\"getInstallHostRange\"\n)\nrouter.register(\n    \"getInstallArgsByIp\",\n    GetInstallArgsByIpView,\n    basename=\"getInstallArgsByIp\"\n)\nrouter.register(\n    \"createInstallPlan\",\n    CreateInstallPlanView,\n    basename=\"createInstallPlan\"\n)\nrouter.register(\n    \"listServiceByIp\",\n    ListServiceByIpView,\n    basename=\"listServiceByIp\"\n)\nrouter.register(\n    \"showInstallProcess\",\n    ShowInstallProcessView,\n    basename=\"showInstallProcess\"\n)\nrouter.register(\n    \"showSingleServiceInstallLog\",\n    ShowSingleServiceInstallLogView,\n    basename=\"showSingleServiceInstallLog\"\n)\nrouter.register(\n    \"mainInstallHistory\",\n    MainInstallHistoryView,\n    basename=\"mainInstallHistory\"\n)\nrouter.register(\n    \"createComponentInstallInfo\",\n    CreateComponentInstallInfoView,\n    basename=\"createComponentInstallInfo\"\n)\nrouter.register(\n    \"retryInstall\",\n    RetryInstallView,\n    basename=\"retryInstall\"\n)\n\nrouter.register(\n    \"executionRecord\",\n    ExecutionRecordAPIView,\n    basename=\"ExecutionRecord\"\n)\n\n# 产品类型接口查看及修改\nrouter.register(\n    \"productComposition\",\n    ProductCompositionView,\n    basename=\"productComposition\"\n)\n"
  },
  {
    "path": "omp_server/app_store/views.py",
    "content": "\"\"\"\n应用商店相关视图\n\"\"\"\nimport os\nimport uuid\nimport json\nimport time\nimport string\nimport random\nimport logging\n\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin\n)\nfrom rest_framework.response import Response\nfrom rest_framework.exceptions import ValidationError\nfrom django.db import transaction\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\n\nfrom db_models.models import (\n    Labels, ApplicationHub, Product, ProductHub,\n    Env, Host, Service, ServiceConnectInfo,\n    DeploymentPlan, ClusterInfo,\n    MainInstallHistory, DetailInstallHistory,\n    PreInstallHistory, PostInstallHistory,\n    UploadPackageHistory, ExecutionRecord)\nfrom utils.common.paginations import PageNumberPager\nfrom app_store.app_store_filters import (\n    LabelFilter, ComponentFilter, ServiceFilter, UploadPackageHistoryFilter,\n    PublishPackageHistoryFilter\n)\nfrom app_store.app_store_serializers import (\n    ComponentListSerializer, ServiceListSerializer,\n    UploadPackageSerializer, RemovePackageSerializer,\n    UploadPackageHistorySerializer, ExecuteLocalPackageScanSerializer,\n    PublishPackageHistorySerializer, DeploymentPlanValidateSerializer,\n    DeploymentImportSerializer, DeploymentPlanListSerializer,\n    ExecutionRecordSerializer, DeleteComponentSerializer,\n    DeleteProDuctSerializer, ProductCompositionSerializer\n)\nfrom backups.backups_utils import cmd\nfrom omp_server.settings import PROJECT_DIR\n\nfrom app_store.app_store_serializers import (\n    ProductDetailSerializer, ApplicationDetailSerializer\n)\nfrom app_store import tmp_exec_back_task\n\nfrom utils.common.exceptions import OperateError\nfrom utils.common.views import BaseDownLoadTemplateView\nfrom app_store.tasks import publish_entry\nfrom rest_framework.filters import OrderingFilter, SearchFilter\nfrom utils.parse_config import (\n    BASIC_ORDER, AFFINITY_FIELD\n)\nfrom app_store.new_install_utils import DataJson\nfrom app_store.new_install_utils import ServiceArgsPortUtils\n\nlogger = logging.getLogger(\"server\")\n\n\nclass AppStoreListView(GenericViewSet, ListModelMixin):\n    \"\"\" 应用商店 list 视图类 \"\"\"\n\n    def list(self, request, *args, **kwargs):\n        queryset = self.filter_queryset(self.get_queryset())\n        name_field = kwargs.get(\"name_field\")\n        # 根据名称进行去重\n        result_ls, name_set = [], set()\n        for obj in queryset:\n            name = getattr(obj, name_field)\n            if name not in name_set:\n                name_set.add(name)\n                result_ls.append(obj)\n\n        serializer = self.get_serializer(\n            self.paginate_queryset(result_ls), many=True)\n\n        return self.get_paginated_response(serializer.data)\n\n\nclass LabelListView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查询所有标签列表\n    \"\"\"\n    queryset = Labels.objects.all()\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = LabelFilter\n    # 操作信息描述\n    get_description = \"查询所有标签列表\"\n\n    def list(self, request, *args, **kwargs):\n        query_set = self.get_queryset()\n        # 过滤掉子项为 null 的 label\n        label_type = request.query_params.get(\"label_type\", -1)\n        if int(label_type) == Labels.LABEL_TYPE_COMPONENT:\n            query_set = Labels.objects.filter(\n                applicationhub__app_type=ApplicationHub.APP_TYPE_COMPONENT)\n        if int(label_type) == Labels.LABEL_TYPE_APPLICATION:\n            query_set = Labels.objects.exclude(\n                producthub__isnull=True)\n        query_set = query_set.order_by(\n            \"id\").values_list(\"label_name\", flat=True).distinct()\n        queryset = self.filter_queryset(query_set)\n        return Response(list(queryset))\n\n\nclass ComponentListView(AppStoreListView):\n    \"\"\"\n        list:\n        查询所有基础组件列表\n    \"\"\"\n    queryset = ApplicationHub.objects.filter(\n        app_type=ApplicationHub.APP_TYPE_COMPONENT,\n        is_release=True,\n    ).order_by(\"-created\")\n    serializer_class = ComponentListSerializer\n    pagination_class = PageNumberPager\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = ComponentFilter\n    # 操作信息描述\n    get_description = \"查询所有基础组件列表\"\n\n    def list(self, request, *args, **kwargs):\n        return super(ComponentListView, self).list(\n            request, name_field=\"app_name\", *args, **kwargs)\n\n\nclass ServiceListView(AppStoreListView):\n    \"\"\"\n        list:\n        查询所有应用服务列表\n    \"\"\"\n    queryset = ProductHub.objects.filter(\n        is_release=True).order_by(\"-created\")\n    serializer_class = ServiceListSerializer\n    pagination_class = PageNumberPager\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = ServiceFilter\n    # 操作信息描述\n    get_description = \"查询所有应用服务列表\"\n\n    def list(self, request, *args, **kwargs):\n        return super(ServiceListView, self).list(\n            request, name_field=\"pro_name\", *args, **kwargs)\n\n\nclass UploadPackageView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        上传安装包\n    \"\"\"\n    queryset = UploadPackageHistory.objects.all()\n    serializer_class = UploadPackageSerializer\n    # 操作信息描述\n    post_description = \"上传安装包\"\n\n\nclass RemovePackageView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        post:\n        批量移除安装包\n    \"\"\"\n    queryset = UploadPackageHistory.objects.all()\n    serializer_class = RemovePackageSerializer\n    # 操作信息描述\n    post_description = \"移除安装包\"\n\n\nclass ComponentDetailView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    查询组件详情\n    \"\"\"\n    serializer_class = ApplicationDetailSerializer\n\n    # 操作信息描述\n    get_description = \"查询组件详情\"\n\n    def list(self, request, *args, **kwargs):\n        arg_app_name = request.GET.get('app_name')\n\n        queryset = ApplicationHub.objects.filter(\n            app_name=arg_app_name).order_by(\"created\")\n        serializer = self.get_serializer(queryset, many=True)\n        result = dict()\n        result.update(\n            {\n                \"app_name\": arg_app_name,\n                \"versions\": list(serializer.data)\n            }\n        )\n        return Response(result)\n\n\nclass ServiceDetailView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    查询服务详情\n    \"\"\"\n    serializer_class = ProductDetailSerializer\n\n    # 操作信息描述\n    get_description = \"查询服务详情\"\n\n    def list(self, request, *args, **kwargs):\n        arg_pro_name = request.GET.get('pro_name')\n\n        queryset = ProductHub.objects.filter(\n            pro_name=arg_pro_name).order_by(\"created\")\n        serializer = self.get_serializer(queryset, many=True)\n        result = dict()\n        result.update(\n            {\n                \"pro_name\": arg_pro_name,\n                \"versions\": list(serializer.data)\n            }\n        )\n        return Response(result)\n\n\nclass ServicePackPageVerificationView(GenericViewSet, ListModelMixin):\n    queryset = UploadPackageHistory.objects.filter(\n        is_deleted=False, package_parent__isnull=True)\n    serializer_class = UploadPackageHistorySerializer\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = UploadPackageHistoryFilter\n\n\nclass PublishViewSet(ListModelMixin, CreateModelMixin, GenericViewSet):\n    \"\"\"\n        create:\n        发布接口\n    \"\"\"\n\n    queryset = UploadPackageHistory.objects.filter(is_deleted=False,\n                                                   package_parent__isnull=True,\n                                                   package_status__in=[3, 4, 5])\n    serializer_class = PublishPackageHistorySerializer\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = PublishPackageHistoryFilter\n    post_description = \"上传应用商店安装包发布\"\n\n    def create(self, request, *args, **kwargs):\n        params = request.data\n        uuid = params.pop('uuid', None)\n        if not uuid:\n            raise OperateError(\"请传入uuid\")\n        publish_entry.delay(uuid)\n        return Response({\"status\": \"发布任务下发成功\"})\n\n\nclass ExecuteLocalPackageScanView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        post:\n        扫描服务端执行按钮\n    \"\"\"\n    serializer_class = ExecuteLocalPackageScanSerializer\n    # 操作信息描述\n    post_description = \"扫描本地安装包\"\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n            post:\n            扫描服务端执行按钮\n        \"\"\"\n        _uuid, _package_name_lst = tmp_exec_back_task.back_end_verified_init(\n            operation_user=request.user.username\n        )\n        ret_data = {\n            \"uuid\": _uuid,\n            \"package_names\": _package_name_lst\n        }\n\n        return Response(ret_data)\n\n\nclass LocalPackageScanResultView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        扫描服务端执行结果查询接口\n        参数:\n            uuid: 操作唯一uuid\n            package_names: 安装包名称组成的字符串，英文逗号分隔\n    \"\"\"\n\n    @staticmethod\n    def get_res_data(operation_uuid, package_names):\n        \"\"\"\n        获取安装包扫描状态\n        :param operation_uuid: 唯一操作uuid\n        :param package_names: 安装包名称组成的字符串\n        :return:\n        \"\"\"\n        package_names_lst = package_names.split(\",\")\n        # 确定当前安装包状态\n        if UploadPackageHistory.objects.filter(\n                operation_uuid=operation_uuid,\n                package_name__in=package_names_lst,\n                package_status=1\n        ).count() == len(package_names_lst):\n            # 当全部安装包的状态为 1 - 校验失败 时，整个流程结束\n            stage_status = \"check_all_failed\"\n        elif UploadPackageHistory.objects.filter(\n                operation_uuid=operation_uuid,\n                package_name__in=package_names_lst,\n                package_status__gt=2\n        ).exists():\n            # 当有安装包进入到分布流程时，整个流程进入到发布流程\n            if UploadPackageHistory.objects.filter(\n                    operation_uuid=operation_uuid,\n                    package_name__in=package_names_lst,\n                    package_status=5\n            ).exists():\n                # 如果有安装包处于发布中装太，那么整个流程处于发布中状态\n                stage_status = \"publishing\"\n            else:\n                stage_status = \"published\"\n        else:\n            # 校验中\n            stage_status = \"checking\"\n        package_info_dic = {\n            el: {\n                \"status\": 2, \"message\": \"\"\n            } for el in package_names_lst\n        }\n        queryset = UploadPackageHistory.objects.filter(\n            operation_uuid=operation_uuid,\n            package_name__in=package_names_lst,\n        )\n        # 发布安装包状态及error信息提取\n        for item in queryset:\n            package_info_dic[item.package_name][\"status\"] = item.package_status\n            package_info_dic[item.package_name][\"message\"] = item.error_msg\n        if stage_status == \"checking\":\n            count = UploadPackageHistory.objects.filter(\n                operation_uuid=operation_uuid,\n                package_name__in=package_names_lst,\n                package_status=UploadPackageHistory.PACKAGE_STATUS_PARSING\n            ).count()\n            message = f\"共扫描到 {len(package_names_lst)} 个安装包，\" \\\n                      f\"正在校验中...\" \\\n                      f\"({len(package_names_lst) - count}/{len(package_names_lst)})\"\n        elif stage_status == \"check_all_failed\":\n            message = f\"共计 {len(package_names_lst)} 个安装包校验失败!\"\n        elif stage_status == \"publishing\":\n            _count = UploadPackageHistory.objects.filter(\n                operation_uuid=operation_uuid,\n                package_name__in=package_names_lst,\n                package_status__gt=2\n            ).count()\n            message = f\"本次共发布 {_count} 个安装包，正在发布中...\"\n        elif stage_status == \"published\":\n            _count = UploadPackageHistory.objects.filter(\n                operation_uuid=operation_uuid,\n                package_name__in=package_names_lst,\n                package_status=3\n            ).count()\n            message = \\\n                f\"本次共发布成功 {_count} 个安装包，\" \\\n                f\"发布失败 {len(package_names_lst) - _count} 个安装包!\"\n        else:\n            message = \"\"\n        package_detail_lst = list()\n        for item in package_names_lst:\n            package_detail_lst.append(package_info_dic[item])\n        ret_dic = {\n            \"uuid\": operation_uuid,\n            \"package_names_lst\": package_names_lst,\n            \"package_detail\": package_detail_lst,\n            \"message\": message,\n            \"stage_status\": stage_status\n        }\n        return ret_dic\n\n    @staticmethod\n    def check_request_param(operation_uuid, package_names):\n        \"\"\"\n        校验参数\n        :param operation_uuid: 唯一uuid\n        :param package_names: 安装包名称\n        :return:\n        \"\"\"\n        if not operation_uuid:\n            raise ValidationError({\"uuid\": \"请求参数中必须包含 [uuid] 字段\"})\n        if not package_names:\n            raise ValidationError(\n                {\"package_names\": \"请求参数中必须包含 [package_names] 字段\"})\n\n    def list(self, request, *args, **kwargs):\n        operation_uuid = request.query_params.get(\"uuid\", \"\")\n        package_names = request.query_params.get(\"package_names\", \"\")\n        self.check_request_param(operation_uuid, package_names)\n        res = self.get_res_data(operation_uuid, package_names)\n        return Response(res)\n\n\nclass ApplicationTemplateView(BaseDownLoadTemplateView):\n    \"\"\"\n        list:\n        获取应用商店下载模板\n    \"\"\"\n    # 操作描述信息\n    get_description = \"应用商店下载组件模板\"\n\n    def list(self, request, *args, **kwargs):\n        return super(ApplicationTemplateView, self).list(\n            request, template_file_name=\"app_publish_readme.md\",\n            *args, **kwargs)\n\n\nclass DeploymentOperableView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        部署计划是否可操作\n    \"\"\"\n    queryset = Service.objects.filter(\n        service__is_base_env=False)\n    # 操作描述信息\n    get_description = \"查看部署计划\"\n\n    def list(self, request, *args, **kwargs):\n        return Response(not self.get_queryset().exists())\n\n\nclass DeploymentTemplateView(BaseDownLoadTemplateView):\n    \"\"\"\n          list:\n          获取部署计划模板\n      \"\"\"\n    # 操作描述信息\n    get_description = \"获取部署计划模板\"\n\n    def list(self, request, *args, **kwargs):\n        return super(DeploymentTemplateView, self).list(\n            request, template_file_name=\"deployment.xlsx\",\n            *args, **kwargs)\n\n\nclass DeploymentPlanListView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查看部署计划\n    \"\"\"\n    queryset = DeploymentPlan.objects.all().order_by(\"-created\")\n    serializer_class = DeploymentPlanListSerializer\n    pagination_class = PageNumberPager\n    # 操作描述信息\n    get_description = \"查看部署计划\"\n\n\nclass DeploymentPlanValidateView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        校验部署计划服务数据\n    \"\"\"\n    serializer_class = DeploymentPlanValidateSerializer\n    # 操作描述信息\n    post_description = \"校验部署计划服务数据\"\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        if not serializer.is_valid():\n            logger.error(f\"deployment plan validate failed:{request.data}\")\n            raise ValidationError(\"数据格式错误\")\n        return Response(serializer.validated_data.get(\"result_dict\"))\n\n\nclass DeploymentPlanImportView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        部署计划导入，服务数据入库\n    \"\"\"\n    serializer_class = DeploymentImportSerializer\n    # 操作描述信息\n    post_description = \"部署计划导入，服务数据入库\"\n\n    @staticmethod\n    def _get_app_pro_queryset(service_name_ls):\n        \"\"\" 获取 app、pro 最新 queryset \"\"\"\n        # 查询所有 application 信息\n        _queryset = ApplicationHub.objects.filter(\n            app_name__in=service_name_ls, is_release=True)\n        # 所有 application 默认取最新\n        new_app_id_list = []\n        for app in _queryset:\n            new_version = _queryset.filter(\n                app_name=app.app_name\n            ).order_by(\"-created\").first().app_version\n            if new_version == app.app_version:\n                new_app_id_list.append(app.id)\n        app_queryset = _queryset.filter(\n            id__in=new_app_id_list, is_release=True\n        ).select_related(\"product\")\n\n        # 获取 application 对应的 product 信息\n        app_now = app_queryset.exclude(product__isnull=True)\n        pro_id_list = app_now.values_list(\"product_id\", flat=True).distinct()\n        # 验证 product 的依赖项均已包含\n        pro_queryset = ProductHub.objects.filter(id__in=pro_id_list)\n        return app_queryset, pro_queryset\n\n    @staticmethod\n    def _add_service(service_obj_ls, host_obj, app_obj, env_obj, only_dict,\n                     cluster_dict, product_dict, service_set,\n                     is_base_env=False, vip=None, role=None):\n        \"\"\" 添加服务 \"\"\"\n        # 切分 ip 字段，构建服务实例名\n        ip_split_ls = host_obj.ip.split(\".\")\n        service_name = app_obj.app_name\n        service_instance_name = f\"{service_name}-{ip_split_ls[-2]}-{ip_split_ls[-1]}\"\n\n        # 当服务实例已经存在，则跳过\n        if service_instance_name in service_set:\n            return\n        service_set.add(service_instance_name)\n\n        # 服务端口\n        service_port = json.dumps(ServiceArgsPortUtils().get_app_port(app_obj))\n        if not service_port:\n            service_port = json.dumps([])\n\n        # 获取服务的基础目录、用户名、密码、密文\n        base_dir = \"/data\"\n        username, password, password_enc = \"\", \"\", \"\"\n        app_install_args = ServiceArgsPortUtils().get_app_install_args(app_obj)\n        for item in app_install_args:\n            if item.get(\"key\") == \"base_dir\":\n                base_dir = os.path.join(\n                    host_obj.data_folder,\n                    item.get(\"default\", \"\").lstrip(\"/\")\n                )\n            elif item.get(\"key\") == \"username\":\n                username = item.get(\"default\")\n            elif item.get(\"key\") == \"password\":\n                password = item.get(\"default\")\n            elif item.get(\"key\") == \"password_enc\":\n                password_enc = item.get(\"default\")\n            else:\n                pass\n\n        # 拼接服务的控制脚本\n        service_controllers = {}\n        app_controllers = json.loads(app_obj.app_controllers)\n        for k, v in app_controllers.items():\n            if v != \"\":\n                service_controllers[k] = f\"{base_dir}/{v}\"\n        if \"post_action\" in app_obj.extend_fields:\n            service_controllers[\"post_action\"] = os.path.join(\n                base_dir, app_obj.extend_fields.get(\"post_action\")\n            )\n\n        # 创建服务连接信息表\n        connection_obj = None\n        if any([username, password, password_enc]):\n            connection_obj, _ = ServiceConnectInfo.objects.get_or_create(\n                service_name=service_name,\n                service_username=username,\n                service_password=password,\n                service_password_enc=password_enc,\n                service_username_enc=\"\",\n            )\n\n        # 集群信息\n        cluster_id = None\n        if not is_base_env:\n            if service_name in only_dict:\n                # 存在于单实例字典中，删除单实例字典中数据，创建集群\n                only_dict.pop(service_name)\n                upper_key = ''.join(random.choice(\n                    string.ascii_uppercase) for _ in range(7))\n                cluster_obj = ClusterInfo.objects.create(\n                    cluster_service_name=service_name,\n                    cluster_name=f\"{service_name}-cluster-{upper_key}\",\n                    service_connect_info=connection_obj,\n                )\n                # 写入集群字典，记录 id\n                cluster_dict[service_name] = (\n                    cluster_obj.id, cluster_obj.cluster_name)\n            elif service_name in cluster_dict:\n                # 存在于集群字典中\n                pass\n            else:\n                # 尚未记录，加入单实例字典\n                only_dict[service_name] = service_instance_name\n\n        # 如果产品信息不再字典中，将其添加至字典\n        if app_obj.product and \\\n                app_obj.product.pro_name not in product_dict:\n            product_dict[app_obj.product.pro_name] = app_obj.product\n\n        # 添加服务到列表中\n        service_obj_ls.append(Service(\n            ip=host_obj.ip,\n            service_instance_name=service_instance_name,\n            service=app_obj,\n            service_port=service_port,\n            service_controllers=service_controllers,\n            service_status=Service.SERVICE_STATUS_READY,\n            env=env_obj,\n            service_connect_info=connection_obj,\n            cluster_id=cluster_id,\n            vip=vip,\n            service_role=role,\n        ))\n\n    def create(self, request, *args, **kwargs):\n        # 信任数据，只进行格式校验\n        serializer = self.get_serializer(data=request.data)\n        if not serializer.is_valid():\n            logger.error(f\"host batch import failed:{request.data}\")\n            raise ValidationError(\"数据格式错误\")\n\n        # env 环境对象\n        default_env = Env.objects.filter(id=1).first()\n\n        # 实例名称、服务数据、服务名称列表\n        instance_info_ls = serializer.data.get(\"instance_info_ls\")\n        instance_name_ls = list(\n            map(lambda x: x.get(\"instance_name\"), instance_info_ls))\n        service_data_ls = serializer.data.get(\"service_data_ls\")\n        service_name_ls = list(\n            map(lambda x: x.get(\"service_name\"), service_data_ls))\n\n        # 亲和力 tengine 字段\n        tengine_name = AFFINITY_FIELD.get(\"tengine\", \"tengine\")\n\n        # 主机、基础环境 queryset\n        host_queryset = Host.objects.filter(instance_name__in=instance_name_ls)\n        base_env_queryset = ApplicationHub.objects.filter(is_base_env=True)\n        # 应用、产品 queryset\n        app_queryset, pro_queryset = self._get_app_pro_queryset(\n            service_name_ls)\n        # 如果主机不存在\n        if not host_queryset.exists():\n            raise OperateError(\"导入失败，主机未纳管\")\n        # 构建 uuid\n        operation_uuid = serializer.data.get(\"operation_uuid\") if \\\n            serializer.data.get(\"operation_uuid\") else uuid.uuid4()\n\n        # 考虑到录入主机可能大于分配服务主机，故此记录真实使用的主机实例数量\n        service_instance_set = set(\n            map(lambda x: x.get(\"instance_name\"), service_data_ls))\n        use_host_queryset = host_queryset.filter(\n            instance_name__in=service_instance_set)\n\n        try:\n            # 服务对象列表、基础环境字典\n            service_obj_ls = []\n            base_env_dict = {}\n            # 产品信息字典\n            product_dict = {}\n            # 单实例、集群服务字典\n            only_dict, cluster_dict = {}, {}\n            # tengine 所在主机对象字典\n            tengine_host_obj_dict = {}\n\n            # 服务实例唯一集合，用于限制重复服务\n            service_set = set()\n\n            # 遍历获取所有需要安装的服务\n            for service_data in service_data_ls:\n                instance_name = service_data.get(\"instance_name\")\n                service_name = service_data.get(\"service_name\")\n                # 服务的角色、虚拟IP\n                vip = service_data.get(\"vip\")\n                role = service_data.get(\"role\")\n                # 主机、应用对象\n                host_obj = use_host_queryset.filter(\n                    instance_name=instance_name).first()\n                app_obj = app_queryset.filter(app_name=service_name).first()\n\n                # 亲和力为 tengine 字段 (Web 服务) 跳过，后续按照 product 维度补充\n                if app_obj.extend_fields.get(\"affinity\") == tengine_name:\n                    continue\n                # 如果服务为 tengine 时，记录其所在节点\n                if app_obj.app_name == tengine_name:\n                    tengine_host_obj_dict[host_obj.ip] = host_obj\n\n                # 检查服务依赖\n                if app_obj.app_dependence:\n                    dependence_list = json.loads(app_obj.app_dependence)\n                    for dependence in dependence_list:\n                        app_name = dependence.get(\"name\")\n                        # version = dependence.get(\"version\")\n                        # base_env_obj = base_env_queryset.filter(\n                        #     app_name=app_name, app_version__startswith=version\n                        # ).order_by(\"-created\").first()\n                        base_env_obj = base_env_queryset.filter(\n                            app_name=app_name).order_by(\"-created\").first()\n                        # 如果服务的依赖中有 base_env，并且对应 ip 上不存在则写入\n                        if base_env_obj and \\\n                                app_name not in base_env_dict.get(host_obj.ip, []):\n                            # base_env 一定为单实例\n                            self._add_service(\n                                service_obj_ls, host_obj, base_env_obj, default_env,\n                                only_dict, cluster_dict, product_dict, service_set,\n                                is_base_env=True)\n                            # 以 ip 为维度记录，避免重复\n                            if host_obj.ip not in base_env_dict:\n                                base_env_dict[host_obj.ip] = []\n                            base_env_dict[host_obj.ip].append(app_name)\n\n                # 添加服务\n                self._add_service(\n                    service_obj_ls, host_obj, app_obj, default_env,\n                    only_dict, cluster_dict, product_dict, service_set,\n                    vip=vip, role=role)\n\n            # 亲和力为 tengine 字段 (Web 服务) 列表\n            app_target = ApplicationHub.objects.filter(\n                product__in=pro_queryset)\n            tengine_app_list = list(filter(\n                lambda x: x.extend_fields.get(\"affinity\") == tengine_name, app_target))\n\n            # 为所有 tengine 节点添加亲和力服务\n            for tengine_ip, host_obj in tengine_host_obj_dict.items():\n                for app_obj in tengine_app_list:\n                    self._add_service(\n                        service_obj_ls, host_obj, app_obj, default_env,\n                        only_dict, cluster_dict, product_dict, service_set)\n\n            service_instance_name_ls = list(map(\n                lambda x: x.service_instance_name, service_obj_ls))\n            # run_user 字典\n            run_user_dict = {}\n            for instance_info in instance_info_ls:\n                if instance_info.get(\"run_user\", \"\") != \"\":\n                    run_user_dict[\n                        instance_info.get(\"instance_name\")\n                    ] = instance_info.get(\"run_user\")\n\n            # 服务 memory 字典\n            service_memory_dict = {}\n            for service_data in service_data_ls:\n                if service_data.get(\"memory\", \"\") != \"\":\n                    service_memory_dict[\n                        service_data.get(\"service_name\")\n                    ] = service_data.get(\"memory\")\n\n            # 数据库入库\n            with transaction.atomic():\n\n                # 已安装产品信息\n                product_obj_ls = []\n                for pro_name, pro_obj in product_dict.items():\n                    upper_key = ''.join(random.choice(\n                        string.ascii_uppercase) for _ in range(7))\n                    product_obj_ls.append(Product(\n                        product_instance_name=f\"{pro_name}-{upper_key}\",\n                        product=pro_obj\n                    ))\n                Product.objects.bulk_create(product_obj_ls)\n\n                # 批量创建 service，return 无 id，需重查获取\n                Service.objects.bulk_create(service_obj_ls)\n\n                # 为所有服务统一补充集群信息\n                for k, v in cluster_dict.items():\n                    Service.objects.filter(\n                        service__app_name=k\n                    ).update(cluster_id=v[0])\n\n                # 获取所有服务对象\n                service_queryset = Service.objects.filter(\n                    service_instance_name__in=service_instance_name_ls\n                ).select_related(\"service\")\n\n                # 遍历服务，如果存在依赖信息则补充\n                for service_obj in service_queryset:\n                    service_dependence_list = []\n                    if service_obj.service.app_dependence:\n                        dependence_list = json.loads(\n                            service_obj.service.app_dependence)\n                        for dependence in dependence_list:\n                            app_name = dependence.get(\"name\")\n                            item = {\n                                \"name\": app_name,\n                                \"cluster_name\": None,\n                                \"instance_name\": None,\n                            }\n\n                            if app_name in only_dict:\n                                item[\"instance_name\"] = only_dict.get(app_name)\n                            elif app_name in cluster_dict:\n                                item[\"cluster_name\"] = cluster_dict.get(\n                                    app_name)[1]\n                            else:\n                                # base_env 不在单实例和集群列表中\n                                ip_split_ls = service_obj.ip.split(\".\")\n                                service_instance_name = f\"{app_name}-{ip_split_ls[-2]}-{ip_split_ls[-1]}\"\n                                item[\"instance_name\"] = service_instance_name\n                            service_dependence_list.append(item)\n                    service_obj.service_dependence = json.dumps(\n                        service_dependence_list)\n                    service_obj.save()\n\n                # 更新主机非base_env服务数量\n                for host_obj in use_host_queryset:\n                    obj_service_num = service_queryset.filter(\n                        ip=host_obj.ip).exclude(service__is_base_env=True).count()\n                    Host.objects.filter(\n                        id=host_obj.id\n                    ).update(service_num=obj_service_num)\n\n                # 主安装记录表、后续任务记录表\n                main_history_obj = MainInstallHistory.objects.create(\n                    operator=request.user.username,\n                    operation_uuid=operation_uuid,\n                )\n                PostInstallHistory.objects.create(\n                    main_install_history=main_history_obj,\n                )\n\n                # 主机层安装记录表\n                pre_install_obj_ls = []\n                for host_obj in use_host_queryset:\n                    pre_install_obj_ls.append(PreInstallHistory(\n                        main_install_history=main_history_obj,\n                        ip=host_obj.ip,\n                    ))\n                PreInstallHistory.objects.bulk_create(pre_install_obj_ls)\n\n                # 构建基础组件多维列表\n                component_order_ls = [[] for _ in range(len(BASIC_ORDER))]\n                for k, v in BASIC_ORDER.items():\n                    for i in range(len(v)):\n                        component_order_ls[k].append([])\n\n                # 用于详情表排序的列表\n                component_last_ls = []\n                service_order_ls = []\n                service_last_ls = []\n\n                # 安装详情表\n                for service_obj in service_queryset:\n                    # 获取主机对象\n                    host_obj = use_host_queryset.filter(\n                        ip=service_obj.ip).first()\n\n                    app_args = ServiceArgsPortUtils().get_app_install_args(\n                        service_obj.service\n                    )\n                    # 获取服务对应的 run_user 和 memory\n                    run_user = run_user_dict.get(host_obj.instance_name, None)\n                    memory = service_memory_dict.get(\n                        service_obj.service.app_name, None)\n                    # 如果用户自定义 run_user、memory 需覆盖写入 install_args\n                    if run_user:\n                        for i in app_args:\n                            if i.get(\"key\") == \"run_user\":\n                                i[\"default\"] = run_user\n                                break\n                        else:\n                            app_args.append({\n                                \"name\": \"安装用户\",\n                                \"key\": \"run_user\",\n                                \"default\": run_user,\n                            })\n                    if memory:\n                        for i in app_args:\n                            if i.get(\"key\") == \"memory\":\n                                i[\"default\"] = memory\n                                break\n                        else:\n                            app_args.append({\n                                \"name\": \"运行内存\",\n                                \"key\": \"memory\",\n                                \"default\": memory,\n                            })\n\n                    # {data_path} 占位符替换\n                    for i in app_args:\n                        if \"dir_key\" in i:\n                            i[\"default\"] = os.path.join(\n                                host_obj.data_folder,\n                                i.get(\"default\", \"\").lstrip(\"/\")\n                            )\n\n                    # 标记服务是否需要 post\n                    post_action_flag = 4\n                    if service_obj.service.extend_fields.get(\n                            \"post_action\", \"\") != \"\":\n                        post_action_flag = 0\n\n                    # 服务端口\n                    service_port = ServiceArgsPortUtils().get_app_port(\n                        service_obj.service\n                    )\n                    # 构建 detail_install_args\n                    detail_install_args = {\n                        \"ip\": service_obj.ip,\n                        \"name\": service_obj.service.app_name,\n                        \"ports\": service_port,\n                        \"version\": service_obj.service.app_version,\n                        \"run_user\": \"\",\n                        \"data_folder\": host_obj.data_folder,\n                        \"cluster_name\": None,\n                        \"install_args\": app_args,\n                        \"instance_name\": service_obj.service_instance_name\n                    }\n\n                    detail_obj = DetailInstallHistory(\n                        service=service_obj,\n                        main_install_history=main_history_obj,\n                        install_detail_args=detail_install_args,\n                        post_action_flag=post_action_flag,\n                    )\n\n                    # 安装详情表按顺序录入\n                    app_type = service_obj.service.app_type\n                    # 公共组件\n                    if app_type == ApplicationHub.APP_TYPE_COMPONENT:\n                        for k, v in BASIC_ORDER.items():\n                            if service_obj.service.app_name in v:\n                                # 动态根据层级插入数据\n                                target = v.index(service_obj.service.app_name)\n                                component_order_ls[k][target].append(\n                                    detail_obj)\n                                break\n                        else:\n                            component_last_ls.append(detail_obj)\n\n                    elif app_type == ApplicationHub.APP_TYPE_SERVICE:\n                        app_level_str = service_obj.service.extend_fields.get(\n                            \"level\", \"\")\n                        if app_level_str == \"\":\n                            service_last_ls.append(detail_obj)\n                        else:\n                            k = int(app_level_str)\n                            # 动态根据层级索引创建空列表\n                            if len(service_order_ls) <= k:\n                                for i in range(len(service_order_ls), k + 1):\n                                    service_order_ls.append([])\n                            service_order_ls[k].append(detail_obj)\n                    else:\n                        pass\n\n                # 合并多维列表和后置列表\n                detail_history_obj_ls = []\n                for child_ls in component_order_ls:\n                    for i in child_ls:\n                        if len(i) != 0:\n                            detail_history_obj_ls += i\n                detail_history_obj_ls += component_last_ls\n                for i in service_order_ls:\n                    detail_history_obj_ls += i\n                detail_history_obj_ls += service_last_ls\n\n                DetailInstallHistory.objects.bulk_create(detail_history_obj_ls)\n\n                # 部署计划表\n                DeploymentPlan.objects.create(\n                    plan_name=f\"快速部署-{str(int(round(time.time() * 1000)))}\",\n                    host_num=use_host_queryset.count(),\n                    product_num=pro_queryset.count(),\n                    service_num=len(service_data_ls),\n                    create_user=request.user.username,\n                    operation_uuid=operation_uuid,\n                )\n\n                # 生成 data.json\n                data_json = DataJson(\n                    operation_uuid=str(operation_uuid),\n                    service_obj=service_queryset)\n                data_json.run()\n\n        except Exception as err:\n            logger.error(f\"import deployment plan err: {err}\")\n            import traceback\n            logger.error(traceback.print_exc())\n            raise OperateError(f\"导入执行计划失败: {err}\")\n\n        return Response({\n            \"operation_uuid\": operation_uuid,\n            \"host_num\": host_queryset.count(),\n            \"product_num\": pro_queryset.count(),\n            \"service_num\": len(service_data_ls),\n        })\n\n\nclass ExecutionRecordAPIView(GenericViewSet, ListModelMixin):\n    queryset = ExecutionRecord.objects.exclude(count=0).all()\n    pagination_class = PageNumberPager\n    filter_backends = (OrderingFilter, SearchFilter)\n    search_fields = (\"module\",)\n    ordering_fields = (\"created\",)\n    ordering = ('-created',)\n    serializer_class = ExecutionRecordSerializer\n    # 操作信息描述\n    get_description = \"查询执行记录\"\n\n\nclass ProductCompositionView(GenericViewSet, ListModelMixin,\n                             CreateModelMixin):\n    serializer_class = ProductCompositionSerializer\n    # 关闭权限、认证设置\n    authentication_classes = ()\n    permission_classes = ()\n\n    get_description = \"查询产品信息\"\n    post_description = \"修改产品包含服务信息\"\n\n    def get_queryset(self):\n        return ProductHub.objects.filter(**self.request.query_params.dict())\n\n    def list(self, request, *args, **kwargs):\n        serializer = self.get_serializer(self.get_queryset(), many=True)\n        for data in serializer.data:\n            data[\"pro_services\"] = json.loads(data.get(\"pro_services\"))\n        return Response(serializer.data)\n\n    def create(self, request, *args, **kwargs):\n        pro_services = json.dumps(request.data.get(\"pro_services\", []), ensure_ascii=False)\n        request.data[\"pro_services\"] = pro_services\n\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        params = request.data\n        pro_obj = ProductHub.objects.filter(\n            pro_name=params.get('pro_name'), pro_version=params.get('pro_version')\n        ).first()\n        pro_obj.pro_services = pro_services\n        pro_obj.save()\n\n        return Response(\"修改成功\")\n\n\nclass DeleteAppStorePackageView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n        get:\n        查看应用商店\n    \"\"\"\n    get_description = \"查看应用商店\"\n    post_description = \"删除应用商店\"\n\n    def get_queryset(self):\n        if self.request.query_params.get('type') == \"component\":\n            return ApplicationHub.objects.filter(\n                app_type=ApplicationHub.APP_TYPE_COMPONENT).order_by(\"-created\")\n        else:\n            return ProductHub.objects.all().order_by(\"-created\")\n\n    def get_serializer_class(self):\n        if self.request.query_params.get('type') == \"component\":\n            return DeleteComponentSerializer\n        return DeleteProDuctSerializer\n\n    def list(self, request, *args, **kwargs):\n        app_type = request.GET.get(\"type\")\n        if not app_type or app_type not in [\"component\", \"product\"]:\n            raise OperateError(\"请传入type或合法type\")\n        queryset = self.filter_queryset(self.get_queryset())\n        serializer = self.get_serializer(queryset, many=True)\n        app_name_dc = {}\n        for _ in serializer.data:\n            app_name_dc.setdefault(_[\"name\"], []).extend(_[\"versions\"])\n        res_ls = []\n        for name, versions in app_name_dc.items():\n            res_ls.append({\"name\": name, \"versions\": versions})\n        return Response(\n            {\"data\": res_ls,\n             \"type\": app_type}\n        )\n\n    @staticmethod\n    def explain_info():\n        app_ls = ApplicationHub.objects.all().values_list(\n            \"id\", \"app_name\", \"app_version\", \"product\")\n        pro_ls = ProductHub.objects.all().values_list(\"id\", \"pro_name\", \"pro_version\")\n        app_dc = {}\n        pro_id_app_count = {}\n        for app in app_ls:\n            app_dc[f\"{app[1]}|{app[2]}\"] = app[0]\n            if app[3]:\n                pro_id_app_count[app[3]] = pro_id_app_count.get(app[3], 0) + 1\n        pro_id_count = {}\n        for pro in pro_ls:\n            if pro_id_app_count.get(pro[0]):\n                pro_id_count[f\"{pro[1]}|{pro[2]}\"] = [\n                    pro_id_app_count[pro[0]], pro[0]]\n            else:\n                pro_id_count[f\"{pro[1]}|{pro[2]}\"] = [0, pro[0]]\n        return pro_id_count, app_dc\n\n    def check_service(self, params):\n        pro_id_count, app_dc = self.explain_info()\n        ser_id = []\n        pro_dc = {}\n        for info in params[\"data\"]:\n            if params['type'] == \"component\":\n                for version in info[\"versions\"]:\n                    app_id = app_dc.get(f'{info[\"name\"]}|{version}')\n                    if app_id:\n                        ser_id.append(app_id)\n            else:\n                # 查询选择的产品是不是勾选全部\n                pro_info = pro_id_count[info[\"name\"]]\n                # 确定就是产品删除\n                if pro_info[0] == len(info[\"versions\"]):\n                    pro_dc[pro_info[1]] = info[\"name\"]\n                for _ in info[\"versions\"]:\n                    app_id = app_dc.get(_)\n                    if app_id:\n                        ser_id.append(app_id)\n        ser_name = Service.objects.filter(\n            service__in=ser_id).values_list('service_instance_name', flat=True)\n        if ser_name:\n            raise OperateError(f'存在已安装的服务{\",\".join(ser_name)}')\n        return ser_id, pro_dc\n\n    @staticmethod\n    def del_file(file_path):\n        logger.info(f\"应用包可能删除的路径 {file_path}\")\n        if file_path and len(file_path) > 28:\n            _out, _err, _code = cmd(f\"/bin/rm -rf {file_path}\")\n            if _code != 0:\n                raise OperateError(f'执行cmd异常,删除路径失败{file_path}:{_err},{_out}')\n\n    def del_database(self, ser_id, pro_dc):\n        app_objs = ApplicationHub.objects.filter(id__in=ser_id)\n        del_ser_file = []\n        for app in app_objs:\n            if app.app_package.package_name:\n                del_ser_file.append(os.path.join(\n                    PROJECT_DIR, \"package_hub\", app.app_package.package_path,\n                    app.app_package.package_name,\n                ))\n        self.del_file(\" \".join(del_ser_file))\n        app_objs.delete()\n        if pro_dc:\n            del_pro_file = []\n            for pro_id, pro_info in pro_dc.items():\n                pro_info = pro_info.replace(\"|\", \"-\")\n                if pro_info:\n                    del_pro_file.append(\n                        os.path.join(PROJECT_DIR, f\"package_hub/verified/{pro_info}\"))\n            self.del_file(\" \".join(del_pro_file))\n            ProductHub.objects.filter(id__in=list(pro_dc)).delete()\n\n    def create(self, request, *args, **kwargs):\n        params = request.data\n        app_type = params.get('type', None)\n        if not app_type:\n            raise OperateError(\"请传入类型\")\n        ser_id, pro_dc = self.check_service(params)\n        self.del_database(ser_id, pro_dc)\n        return Response({\"status\": \"删除成功\"})\n"
  },
  {
    "path": "omp_server/app_store/views_for_install.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: views_for_install\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-21 17:59\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom rest_framework import status\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin\n)\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\n\nfrom db_models.models import (\n    ApplicationHub, ProductHub,\n    MainInstallHistory, Service\n)\n\nfrom app_store.app_store_serializers import (\n    ComponentEntranceSerializer,\n    ProductEntranceSerializer,\n    ExecuteInstallSerializer,\n    InstallHistorySerializer,\n    ServiceInstallHistorySerializer\n)\n\nfrom app_store.app_store_filters import (\n    ComponentEntranceFilter,\n    ProductEntranceFilter,\n    InstallHistoryFilter,\n    ServiceInstallHistoryFilter\n)\n\n\nclass ComponentEntranceView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        组件安装入口\n    \"\"\"\n    queryset = ApplicationHub.objects.filter(\n        is_release=True,\n        app_type=ApplicationHub.APP_TYPE_COMPONENT\n    ).order_by(\"-created\")\n    serializer_class = ComponentEntranceSerializer\n    filter_backends = (DjangoFilterBackend, )\n    filter_class = ComponentEntranceFilter\n    get_description = \"获取组件安装数据入口\"\n\n    def list(self, request, *args, **kwargs):\n        queryset = self.filter_queryset(self.get_queryset())\n        serializer = self.get_serializer(queryset, many=True)\n        # 对返回数据重新进行处理，对process_continue字段进行提取\n        for el in serializer.data:\n            if len(el.get(\"app_dependence\", [])) != 0:\n                for item in el.get(\"app_dependence\", []):\n                    if \"process_continue\" in item and \\\n                            not item[\"process_continue\"] and \\\n                            el.get(\"process_continue\"):\n                        el[\"process_continue\"] = False\n                        el[\"process_message\"] = item.get(\"process_message\")\n                        break\n        return Response(serializer.data)\n\n\nclass ProductEntranceView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        产品、应用安装入口\n    \"\"\"\n    queryset = ProductHub.objects.filter(\n        is_release=True,\n    ).order_by(\"-created\")\n    serializer_class = ProductEntranceSerializer\n    filter_backends = (DjangoFilterBackend, )\n    filter_class = ProductEntranceFilter\n    get_description = \"获取产品应用安装数据入口\"\n\n\nclass ExecuteInstallView(GenericViewSet, CreateModelMixin):\n\n    serializer_class = ExecuteInstallSerializer\n    post_description = \"执行安装入口接口\"\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n            post:\n            执行安装按钮接口\n        \"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        headers = self.get_success_headers(serializer.data)\n        return Response(\n            serializer.data,\n            status=status.HTTP_201_CREATED,\n            headers=headers\n        )\n\n\nclass InstallHistoryView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        获取安装记录\n    \"\"\"\n    queryset = MainInstallHistory.objects.all().order_by(\"-created\")\n    serializer_class = InstallHistorySerializer\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = InstallHistoryFilter\n    get_description = \"获取安装历史数据入口\"\n\n\nclass ServiceInstallHistoryDetailView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        获取安装记录\n    \"\"\"\n    queryset = Service.objects.all().order_by(\"-created\")\n    serializer_class = ServiceInstallHistorySerializer\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = ServiceInstallHistoryFilter\n    get_description = \"获取单个服务安装记录\"\n"
  },
  {
    "path": "omp_server/backups/__init__.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n"
  },
  {
    "path": "omp_server/backups/admin.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n"
  },
  {
    "path": "omp_server/backups/apps.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n\nfrom django.apps import AppConfig\n\n\nclass BackupsConfig(AppConfig):\n    name = 'backups'\n"
  },
  {
    "path": "omp_server/backups/backup_service.py",
    "content": "# # -*- coding:utf-8 -*-\n# # Project: backup_service\nimport logging\nimport random\nimport time\nimport json\n\n\"\"\"\nif __name__ == '__main__':\n    import django\n\n    CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\n    PROJECT_DIR = os.path.dirname(CURRENT_DIR)\n    PYTHON_PATH = os.path.join(PROJECT_DIR, \"component/env/bin/python3\")\n    MANAGE_PATH = os.path.join(PROJECT_DIR, \"omp_server/manage.py\")\n    sys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\n\n    # 加载Django环境\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\n    django.setup()\n\n\nfrom db_models.models.install import DetailInstallHistory\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger(\"server\")\n\n\nclass BackupDB(object):\n    def __init__(self):\n        self.salt_client = SaltClient()\n        self.timeout = 300\n        self.service_names = []\n        self.upload_real_paths = []\n\n    @staticmethod\n    def get_backup_info(service_obj):\n        try:\n            service_dict = {\n                \"cw_o_app_name\": service_obj.service.app_name,\n                \"cw_o_ip\": service_obj.ip,\n                \"tmp\": time.strftime(\"%Y%m%d%H%M%S\", time.localtime()) + str(random.randint(1000, 9999)),\n            }\n            # 连接信息\n            service_connect_info = service_obj.service_connect_info\n            if service_connect_info:\n                service_dict.update(\n                    {\n                        \"cw_o_username\": service_connect_info.service_username,\n                        \"cw_o_password\": service_connect_info.service_password,\n                    }\n                )\n            # 端口\n            port_json = json.loads(service_obj.service_port)\n            for k_port in port_json:\n                service_dict.update({\n                    f\"cw_o_{k_port.get('key', '')}\": k_port.get(\"default\", \"\")\n                })\n\n            # 安装参数\n            obj = DetailInstallHistory.objects.filter(service_id=service_obj.id).first()\n            install_args = obj.install_detail_args.get(\"install_args\", {})\n            for args in install_args:\n                service_dict.update({\n                    f\"cw_o_{args.get('key', '')}\": args.get(\"default\", \"\")\n                })\n            return service_dict\n        except Exception as e:\n            logger.error(f\"获取信息失败请查看service或其账号密码端口是否存在{e}\")\n\n\n\n    def backup_service(self, back_id):\n        back_obj = BackupHistory.objects.filter(id=back_id).first()\n        service_instances = back_obj.content\n        omp_backup_dir = back_obj.retain_path\n        for si in service_instances:\n            service_obj = Service.objects.filter(\n                service_instance_name=si).first()\n\n            service_dict = self.get_backup_info(service_obj)\n            cmd_str = {}\n            if isinstance(service_dict, dict):\n                if service_obj.service.app_name == \"mysql\":\n                    cmd_str = \"test -d {backup_dir} || mkdir -p {backup_dir}&& \" \\\n                              \"chown -R {run_user}:{run_user} {backup_dir} &&\" \\\n                              \" {app_dir}/bin/mysqldump --single-transaction \" \\\n                              \"-P{service_port} -u{service_user} {service_pass} -h'127.0.0.1' --all-databases > \" \\\n                              \"{backup_dir}/{service_name}-{ip}-{tmp}.sql\".format(\n                        **service_dict)\n                    service_dict[\"file_name\"] = \"{service_name}-{ip}-{tmp}.sql\".format(\n                        **service_dict)\n\n                elif service_obj.service.app_name == \"arangodb\":\n                    cmd_str = \"test -d {backup_dir}/{service_name}-{ip}-{tmp} || \" \\\n                              \"mkdir -p {backup_dir}/{service_name}-{ip}-{tmp}&& \" \\\n                              \"chown -R {run_user}:{run_user} {backup_dir} \" \\\n                              \"&& {app_dir}/bin/arangodump --server.endpoint \" \\\n                              \"tcp://127.0.0.1:{service_port} --server.username {service_user}\" \\\n                              \" --server.password {arangodb_pwd} --all-databases true \" \\\n                              \"--output-directory {backup_dir}/{service_name}-{ip}-{tmp}\".format(\n                        **service_dict)\n                    service_dict[\"file_name\"] = \"{service_name}-{ip}-{tmp}\".format(\n                        **service_dict)\n                elif service_obj.service.app_name == \"postgreSql\":\n                    cmd_str = \"test -d {backup_dir} || mkdir -p {backup_dir}&& \" \\\n                              \"chown -R {run_user}:{run_user} {backup_dir} &&\" \\\n                              \" {app_dir}/bin/pg_dumpall -U {run_user} -h'127.0.0.1' -p{service_port} > \" \\\n                              \"{backup_dir}/{service_name}-{ip}-{tmp}.sql\".format(\n                        **service_dict)\n                    service_dict[\"file_name\"] = \"{service_name}-{ip}-{tmp}.sql\".format(\n                        **service_dict)\n                else:\n                    # TODO 应用不合法\n                    pass\n            backup_flag, backup_msg = self.salt_client.cmd(\n                target=service_dict.get(\"ip\"), command=(cmd_str,), timeout=self.timeout\n            )\n            if not backup_flag:\n                message = f'{service_dict.get(\"ip\")}上{service_dict.get(\"service_name\")}备份失败!'\n                logger.error(f\"{message}, 详情为：{backup_msg}\")\n                return False, message\n            # 同步数据\n            sync_flag, sync_msg = self.sync_data_with_omp(service_dict)\n            # print(cmd_str)\n            if not sync_flag:\n                message = f'{service_dict.get(\"ip\")}上{service_dict.get(\"service_name\")}备份失败!'\n                logger.error(f\"{message}, 详情为：{sync_msg}\")\n                return False, message\n\n        # 3.将主节点的备份数据同步到备份目录\n        file_name = back_obj.file_name\n        name = file_name.replace(\".tar.gz\", \"\")\n        template_dir = os.path.join(\n            omp_backup_dir, name)\n        upload_real_paths = \" \".join(self.upload_real_paths)\n        if len(template_dir) <= 5 or len(upload_real_paths) <= 5:\n            return False, \"目录异常，防止保护文件丢失触发熔断.\" \\\n                          \"template_dir:template_dir:{0},upload_real_paths:{1}\".format(\n                template_dir, upload_real_paths\n            )\n        cmd_str = \"test -d {0} || mkdir -p {0} && cp -rf {1} {0} && rm -rf {1} && cd {2}\" \\\n                  \" && tar -zcf {3} {4} && rm -rf {0}\".format(\n            template_dir, upload_real_paths, omp_backup_dir, file_name, name)\n        _out, _err, _code = cmd(\n            cmd_str\n        )\n        logger.info(f\"执行合并备份文件命令{cmd_str}\")\n        if int(_code) != 0:\n            return False, _out\n        logger.info(f\"{''.join(self.service_names)}备份完成\")\n        return True, \"Success\"\n\"\"\"\n"
  },
  {
    "path": "omp_server/backups/backups_serializers.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\nimport logging\nimport os\nfrom rest_framework.serializers import ModelSerializer\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom utils.common.validators import ReValidator\nfrom db_models.models import BackupHistory, BackupSetting, BackupCustom\n\nlogger = logging.getLogger(\"server\")\n\n\nclass BackupHistoryIdsSerializer(ModelSerializer):\n    ids = serializers.ListField(\n        child=serializers.IntegerField(),\n        help_text=\"主机id列表\",\n        required=True,\n        write_only=True,\n        source=\"id\",\n        error_messages={\"required\": \"必须包含[ids]字段\"}\n    )\n\n    def validate(self, attrs):\n        backup_obj = BackupHistory.objects.filter(id__in=attrs[\"id\"])\n        if len(backup_obj) != len(attrs[\"id\"]):\n            raise ValidationError(f\"id不存在\")\n        attrs[\"id\"] = backup_obj\n        return attrs\n\n    class Meta:\n        model = BackupHistory\n        fields = (\"ids\",)\n\n\nclass BackupHistorySerializer(ModelSerializer):\n    class Meta:\n        model = BackupHistory\n        fields = \"__all__\"\n        write_only_fields = (\"id\",)\n\n\nclass BackupCustomSerializer(ModelSerializer):\n    notes = serializers.CharField(\n        help_text=\"备注信息\",\n        required=False, default=\"\",\n        allow_null=True, allow_blank=True\n    )\n\n    def validate(self, attrs):\n        backup_obj = BackupCustom.objects.filter(\n            field_k=attrs[\"field_k\"], field_v=attrs[\"field_v\"]).count()\n        if backup_obj != 0:\n            raise ValidationError(f\"自定义参数不可重复\")\n        return attrs\n\n    class Meta:\n        model = BackupCustom\n        fields = \"__all__\"\n\n\nclass BackupCustomRepeatSerializer(ModelSerializer):\n    name = serializers.SerializerMethodField()\n\n    def get_name(self, obj):\n        instance_ls = []\n        for instance in obj.backupsetting_set.all():\n            instance_ls.extend(instance.backup_instances)\n        return \",\".join(set(instance_ls))\n\n    class Meta:\n        model = BackupCustom\n        fields = (\"name\",)\n\n\nclass BackupSettingSerializer(ModelSerializer):\n    backup_instances = serializers.ListField(\n        help_text=\"备份服务实例名称\", required=True,\n        error_messages={\"required\": \"备份服务需必填\"}\n    )\n    crontab_detail = serializers.DictField(\n        help_text=\"定时任务详情\", required=True,\n        error_messages={\"required\": \"请填写备份策略\"})\n    retain_day = serializers.IntegerField(help_text=\"文件保存天数\", default=1)\n    retain_path = serializers.CharField(\n        help_text=\"文件保存路径\", default=\"/data/omp/data/backup/\",\n        min_length=2,\n        error_messages={\n            \"required\": \"必须包含[retain_path]字段\",\n            \"min_length\": \"用户名长度需小于{min_length}\"},\n        validators=[\n            ReValidator(regex=r\"^/[-_/a-zA-Z0-9]+$\"),\n        ]\n    )\n    backup_custom = BackupCustomSerializer(many=True, required=False)\n\n    def validate_retain_path(self, retain_path):  # NOQA\n        try:\n            folder = os.path.exists(retain_path)\n            if not folder:\n                os.makedirs(retain_path)\n            file_path = os.path.join(retain_path, 'test.txt')\n            create_file = open(file_path, \"w\")\n            create_file.close()\n        except Exception as e:\n            logger.info(f\"校验文件夹权限失败：{str(e)}\")\n            raise ValidationError(f\"请确定程序对备份文件保存文件夹{retain_path}有读写权限！\")\n        return retain_path\n\n    def validate_crontab_detail(self, crontab_detail):  # NOQA\n        #\n        # ToDo暂时不校验但有校验的必要\n        return crontab_detail\n\n    class Meta:\n        model = BackupSetting\n        fields = (\n            \"id\", \"backup_instances\", \"crontab_detail\", \"retain_day\",\n            \"retain_path\", \"backup_custom\", \"is_on\"\n        )\n"
  },
  {
    "path": "omp_server/backups/backups_utils.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\nimport logging\nimport os\nimport subprocess\nimport tarfile\nimport json\nimport time\nimport random\n\nfrom db_models.models import Service, Host, BackupHistory, DetailInstallHistory\nfrom omp_server.settings import PROJECT_DIR\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.parse_config import \\\n    THREAD_POOL_MAX_WORKERS, LOCAL_IP, TENGINE_PORT\n\nlogger = logging.getLogger(\"server\")\n\n\ndef cmd(command):\n    \"\"\"执行本地shell命令\"\"\"\n    p = subprocess.Popen(\n        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n    stdout, stderr = p.communicate()\n    _out, _err, _code = \\\n        stdout.decode(\"utf8\"), stderr.decode(\"utf8\"), p.returncode\n    return _out, _err, _code\n\n\ndef tar_files(source_path, target_path):\n    \"\"\"\n    压缩文件\n    可以压缩多个文件或者单个目录\n    source_path：源文件路径 list [a.tar.gz, b.tar.gz]\n    target_path： 目标文件路径\n    \"\"\"\n    success = True\n    try:\n        with tarfile.open(target_path, \"w:gz\") as tar:\n            for file_path in source_path:\n                if not os.path.exists(file_path):\n                    success = False\n                tar.add(file_path)\n        logging.info(f\"{source_path}文件打包成功,压缩文件为{target_path}\")\n    except Exception as e:\n        logging.error(f\"{source_path}文件打包错误,error={e}\")\n        os.remove(target_path)\n        return False\n    if not success:\n        os.remove(target_path)\n        return False\n    for file_path in source_path:\n        os.remove(file_path)\n    return True\n\n\ndef transfer_week(day_of_week):\n    \"\"\"\n    适配前端day_of_week参数传递\n    \"\"\"\n    if day_of_week == '6':\n        day_of_week = '0'\n    elif day_of_week == '*':\n        pass\n    else:\n        day_of_week = str(int(day_of_week) + 1)\n\n    return day_of_week\n\n\ndef exec_scripts(exec_json, host_i_d, his_id):\n    # ToDo 考虑并发场景\n    tar_name = f\"{exec_json['cw_o_app_name']}_bak.sh\"\n    scripts_path = os.path.join(\n        PROJECT_DIR,\n        f\"package_hub/_modules/{tar_name}\"\n    )\n    try:\n        salt_client = SaltClient()\n        his_tmp = f\"{scripts_path}{his_id}\"\n        cmd(f\"cp {scripts_path}.tmp {his_tmp}\")\n        # 占位符替换\n        with open(his_tmp, 'r+') as f:\n            t = f.read()\n            for before, after in exec_json.items():\n                t = t.replace((\"${%s}\" % str(before)), str(after))\n            f.seek(0, 0)\n            f.write(t)\n            f.truncate()\n        # 发送脚本\n        tar_ip = exec_json['cw_o_ip']\n        tar_package = f\"{host_i_d[tar_ip]}/omp_packages/{tar_name}\"\n        is_success, message = salt_client.cp_file(\n            target=tar_ip,\n            source_path=f\"_modules/{tar_name}{his_id}\",\n            target_path=tar_package\n        )\n        if not is_success:\n            return False, message\n        # 执行脚本\n        cmd_str = f\"nohup bash {tar_package} >\" \\\n                  f\" {host_i_d[tar_ip]}/omp_packages/logs\" \\\n                  f\"/{exec_json['cw_o_app_name']}{exec_json['tmp']}.log  2>&1 &\"\n        return salt_client.cmd(tar_ip, cmd_str, timeout=30)\n    except Exception as e:\n        return False, f\"执行异常{e}\"\n\n\ndef change_status(obj, result, message=\"\"):\n    if result:\n        obj.result = BackupHistory.SUCCESS\n    else:\n        obj.result = BackupHistory.FAIL\n    obj.message = message\n    obj.save()\n\n\ndef check_result(future_list, his_ls):\n    for index, future in enumerate(as_completed(future_list)):\n        is_success, message = future.result()\n        if not is_success:\n            change_status(his_ls[index], False, message)\n\n\ndef get_backup_info(service_obj):\n    \"\"\"\n    获取备份配置所需变量,内置变量命名 cw_o_xxx\n    \"\"\"\n    try:\n        service_dict = {\n            \"cw_o_app_name\": service_obj.service.app_name,\n            \"cw_o_ip\": service_obj.ip,\n            \"tmp\": time.strftime(\"%Y%m%d%H%M%S\", time.localtime()) + str(random.randint(1000, 9999)),\n        }\n        # 连接信息\n        service_connect_info = service_obj.service_connect_info\n        if service_connect_info:\n            service_dict.update(\n                {\n                    \"cw_o_username\": service_connect_info.service_username,\n                    \"cw_o_password\": service_connect_info.service_password,\n                }\n            )\n        # 端口\n        port_json = json.loads(service_obj.service_port)\n        for k_port in port_json:\n            service_dict.update({\n                f\"cw_o_{k_port.get('key', '')}\": k_port.get(\"default\", \"\")\n            })\n\n        # 安装参数\n        obj = DetailInstallHistory.objects.filter(service_id=service_obj.id).first()\n        install_args = obj.install_detail_args.get(\"install_args\", {})\n        for args in install_args:\n            service_dict.update({\n                f\"cw_o_{args.get('key', '')}\": args.get(\"default\", \"\")\n            })\n        return service_dict\n    except Exception as e:\n        logger.error(f\"获取信息失败请查看service或其账号密码端口是否存在{e}\")\n\n\ndef backup_service_data(his_ls):\n    \"\"\"\n    备份服务\n    :param his_ls: his表列表\n    :return:\n    \"\"\"\n    master_url = f\"{LOCAL_IP}:{TENGINE_PORT.get('access_port', '19001')}\" \\\n                 f\"/api/backups/backupHistory\"\n\n    host_i_d = dict(Host.objects.all().values_list(\"ip\", \"data_folder\"))\n\n    with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n        check_ls = []\n        for his in his_ls:\n            service_obj = Service.objects.filter(\n                service_instance_name=his.content).first()\n            if not service_obj:\n                logger.info(f\"{his.content}服务已被卸载或不可用\")\n                continue\n            backup_script_args = get_backup_info(service_obj)\n            backup_script_args.update(his.extend_field)\n            backup_script_args.update({\n                \"cw_o_master_url\": f\"{master_url}/{his.id}/\"\n            })\n            future_obj = executor.submit(\n                exec_scripts,\n                backup_script_args, host_i_d, his.id)\n            check_ls.append(future_obj)\n        check_result(check_ls, his_ls)\n\n\ndef rm_backend_file(his_objs):\n    \"\"\"\n    删除过期文件\n    :param his_objs: BackupHistory his_objs\n    :return:\n    \"\"\"\n    fail_files = []\n    for history in his_objs:\n        if history.file_deleted or history.result == BackupHistory.FAIL:\n            history.delete()\n            continue\n        expire_file = os.path.join(history.retain_path, history.file_name)\n        try:\n            os.remove(expire_file)\n        except Exception as e:\n            logger.error(f\"删除备份文件{expire_file}失败: {str(e)}\")\n            fail_files.append(history.file_name)\n        history.delete()\n\n    return fail_files\n\n\ndef check_ing(obj):\n    if not obj:\n        return True\n    back_instance = BackupHistory.objects.filter(\n        result=BackupHistory.ING).values_list(\"content\", flat=True)\n    ing_instance = set(obj.backup_instances) & set(back_instance)\n    if ing_instance:\n        logger.info(f\"当前实例正在备份：{','.join(ing_instance)}\")\n        return True\n    return False\n"
  },
  {
    "path": "omp_server/backups/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/backups/tasks.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\nimport os\nimport time\nimport datetime\nimport logging\n\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\nfrom utils.plugin.salt_client import SaltClient\n\nfrom backups.backups_utils import rm_backend_file, backup_service_data, \\\n    cmd, change_status, check_ing\nfrom db_models.models import BackupSetting, BackupHistory\nfrom utils.plugin.crontab_utils import maintain\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\n@shared_task\n@maintain\ndef backup_service(**kwargs):\n    \"\"\"\n    定时备份函数\n    :**kwargs:\n    :return:\n    \"\"\"\n    # 判断setting是否存在，是否开启\n    backup_setting_id = kwargs.get(\"task_id\")\n    backup_setting = BackupSetting.objects.filter(\n        id=backup_setting_id).first()\n    if check_ing(backup_setting):\n        return False\n    if backup_setting.retain_day == -1:\n        expire_time = None\n    else:\n        expire_time = datetime.datetime.now() + datetime.timedelta(\n            days=backup_setting.retain_day)\n    extend_field = backup_setting.backup_custom\n    if extend_field:\n        extend_field = dict(extend_field.all().values_list(\"field_k\", \"field_v\"))\n\n    his_ls = []\n    for instance in backup_setting.backup_instances:\n        name = f\"backup-{str(int(time.time()))}-{instance}\"\n        his_ls.append(\n            BackupHistory.objects.create(\n                backup_name=name,\n                content=instance,\n                expire_time=expire_time,\n                message={},\n                retain_path=backup_setting.retain_path,\n                file_name=f\"{name}.tar.gz\",\n                extend_field=extend_field,\n            )\n\n        )\n    # 调备份\n    backup_service_data(his_ls)\n\n    # 如果有过期删过期, 更新file_deleted ToDo 考虑过期数据走向\n    histories = BackupHistory.objects.filter(expire_time__lte=datetime.datetime.now()).exclude(expire_time=None)\n    if histories:\n        rm_backend_file(histories)\n        histories.delete()\n\n\n@shared_task\ndef pull_back_file(his_id, remote_path, ip):\n    \"\"\"\n    同步节点备份数据到omp所在机器\n    \"\"\"\n    # 异步暂时想不到如何同步数据\n    his_obj = BackupHistory.objects.filter(id=his_id).first()\n    salt_client = SaltClient()\n    salt_data = salt_client.client.opts.get(\"root_dir\")\n    logger.info(f\"获取文件ip:{ip},远端目录:{remote_path},本地目录:{his_obj.file_name}\")\n    cp_flag, cp_msg = salt_client.cp_push(\n        target=ip, source_path=remote_path, upload_path=his_obj.file_name\n    )\n    if not cp_flag:\n        return change_status(his_obj, False, cp_msg)\n    package_dir = os.path.join(salt_data, f\"var/cache/salt/master/minions/{ip}/files/{his_obj.file_name}\")\n    size = os.path.getsize(package_dir) / 1024 / 1024\n    his_obj.file_size = \"%.3fM\" % size\n    his_obj.remote_path = \"\"\n    _out, _err, _code = cmd(f\"mv {package_dir} {his_obj.retain_path}\")\n    if _code == 0:\n        logger.info(f\"成功同步到omp {his_obj.file_name}\")\n        return change_status(his_obj, True, \"success\")\n    return change_status(his_obj, False, _err)\n"
  },
  {
    "path": "omp_server/backups/urls.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n\"\"\"\n备份相关的路由\n\"\"\"\nfrom rest_framework.routers import DefaultRouter\n\nfrom backups.views import BackupSettingView, BackupHistoryView, \\\n    CanBackupInstancesView, BackupCustomView, BackupCustomRepeatView\n\nrouter = DefaultRouter()\n# 获取可备份实例列表\nrouter.register(r'canBackupInstances', CanBackupInstancesView,\n                basename='canBackupInstances')\n# 备份设置获取/创建/更新/删除 单次测试\nrouter.register(r'backupSettings', BackupSettingView,\n                basename='backupSettings')\n# 自定义接口\nrouter.register(r'backupCustom', BackupCustomView, basename='backupCustom')\n# 自定义接口校验\nrouter.register(r'backupRepeatCustom', BackupCustomRepeatView, basename='backupRepeatCustom')\n\n# 备份历史记录、删除备份\nrouter.register(r'backupHistory', BackupHistoryView,\n                basename='backupHistory')\n\nurlpatterns = router.urls\n"
  },
  {
    "path": "omp_server/backups/views.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n\nimport logging\n\nfrom rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.response import Response\nfrom rest_framework.exceptions import ValidationError\n\nfrom backups.backups_serializers import BackupHistorySerializer, \\\n    BackupHistoryIdsSerializer, BackupSettingSerializer, \\\n    BackupCustomSerializer, BackupCustomRepeatSerializer\n\nfrom backups.backups_utils import rm_backend_file, \\\n    check_ing\nfrom utils.plugin.crontab_utils import change_task\nfrom backups.tasks import backup_service, pull_back_file\nfrom db_models.models import BackupSetting, BackupHistory, Service, \\\n    BackupCustom\nfrom utils.common.paginations import PageNumberPager\nfrom utils.parse_config import BACKUP_SERVICE\n\nlogger = logging.getLogger(\"server\")\n\n\nclass CanBackupInstancesView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    获取可备份实例列表\n    \"\"\"\n    get_description = \"读取可备份实例列表\"\n\n    def list(self, request, *args, **kwargs):\n        # ToDo 找不出更合适的继承\n        ser_data = Service.objects.filter(\n            service__app_name__in=BACKUP_SERVICE\n        ).filter(service_status=Service.SERVICE_STATUS_NORMAL).values_list(\"service_instance_name\", flat=True)\n        # back_instance = BackupSetting.objects.all().values_list(\"backup_instances\", flat=True)\n        # back_set = []\n        # for instance in back_instance:\n        #    back_set.extend(instance)\n        # ser_data = list(set(ser_data) - set(back_set))\n        return Response(data=list(ser_data))\n\n\nclass BackupSettingView(GenericViewSet, ListModelMixin,\n                        CreateModelMixin, UpdateModelMixin,\n                        DestroyModelMixin):\n    \"\"\"\n    操作备份策略\n    \"\"\"\n\n    get_description = \"读取备份策略\"\n    post_description = \"更新备份策略\"\n    serializer_class = BackupSettingSerializer\n    queryset = BackupSetting.objects.all().order_by(\"-id\")\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        更新备份策略\n        \"\"\"\n        set_id = request.data.pop(\"id\", 0)\n        extend_field = request.data.pop(\"backup_custom\", [])\n        if set_id:\n            # 任务策略制定后触发一次备份任务\n            if check_ing(BackupSetting.objects.filter(id=set_id).first()):\n                return Response(data={\"code\": 1, \"message\": f\"当前策略存在正在备份的实例或id{set_id}不存在\"})\n            backup_service.delay(task_id=set_id)\n            return Response(\"任务下发成功\")\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        obj = serializer.save()\n        # 多对多\n        for field in extend_field:\n            custom = BackupCustom.objects.filter(**field).first()\n            obj.backup_custom.add(custom)\n        code, msg = change_task(obj.id, request.data)\n        return Response(msg)\n\n    def update(self, request, *args, **kwargs):\n        instance = self.get_object()\n        extend_field = request.data.pop(\"backup_custom\", [])\n        extend_con = [field[\"id\"] for field in extend_field]\n        instance.backup_custom.set(extend_con)\n        instance.save()\n        super(BackupSettingView, self).update(\n            request, request, *args, **kwargs)\n        code, msg = change_task(instance.id, request.data)\n        return Response(msg)\n\n    def destroy(self, request, *args, **kwargs):\n        instance = self.get_object()\n        super(BackupSettingView, self).destroy(\n            request, request, *args, **kwargs)\n        code, msg = change_task(instance.id)\n        return Response(msg)\n\n\nclass BackupHistoryView(GenericViewSet, ListModelMixin,\n                        DestroyModelMixin, UpdateModelMixin\n                        ):\n    \"\"\"\n    备份记录相关视图\n    \"\"\"\n    queryset = BackupHistory.objects.all().order_by(\"-create_time\")\n    pagination_class = PageNumberPager\n    # 关闭权限、认证设置\n    authentication_classes = ()\n    permission_classes = ()\n\n    delete_description = \"删除备份记录文件\"\n    get_description = \"获取备份历史记录\"\n\n    def get_serializer_class(self):\n        if self.request is not None and self.request.method == \"POST\":\n            return BackupHistoryIdsSerializer\n        return BackupHistorySerializer\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        instance = serializer.validated_data[\"id\"]\n        fail_files = rm_backend_file(instance)\n        if fail_files:\n            return Response(data={\"code\": 0, \"message\": f\"删除{','.join(fail_files)}可能已不存在！\"})\n        return Response(\"删除记录成功\")\n\n    def update(self, request, *args, **kwargs):\n        # 转换code意义，考虑是否把含义变更\n        code = 0 if request.data.pop(\"result\") != \"0\" else 1\n        remote_path = request.data.get(\"remote_path\", \"\")\n        need_push = request.data.get(\"need_push\", False)\n        # 文件备份\n        request.data[\"file_deleted\"] = False if remote_path else True\n        # 成功 且 存在要拉取的文件\n        if code and remote_path and need_push:\n            if remote_path.startswith(\"/\") and remote_path.endswith(\".tar.gz\"):\n                instance = self.get_object()\n                pull_back_file.delay(\n                    instance.id, remote_path, request.data[\"ip\"])\n            else:\n                raise ValidationError(f\"文件不合法{remote_path}\")\n            request.data[\"remote_path\"] = \"\"\n        else:\n            request.data[\"retain_path\"] = \"\"\n            request.data[\"result\"] = code\n        return super(BackupHistoryView, self).update(\n            request, request, *args, **kwargs)\n\n\nclass BackupCustomView(GenericViewSet, ListModelMixin,\n                       CreateModelMixin, UpdateModelMixin,\n                       DestroyModelMixin):\n    \"\"\"\n    备份自定义字典视图\n    \"\"\"\n    serializer_class = BackupCustomSerializer\n    queryset = BackupCustom.objects.all()\n\n    delete_description = \"删除备份自定义字典\"\n    get_description = \"获取备份自定义字典\"\n    put_description = \"修改备份自定义字典\"\n    post_description = \"创建备份自定义字典\"\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n        BackupCustom.objects.create(**serializer.data)\n        return Response(\"创建成功\")\n\n\nclass BackupCustomRepeatView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查询备份字典是否有多对多关联\n    \"\"\"\n\n    serializer_class = BackupCustomRepeatSerializer\n\n    get_description = \"查询备份字典是否有多对多关联\"\n\n    def get_queryset(self):\n        bk_id = self.request.query_params.get('id')\n        if bk_id:\n            return BackupCustom.objects.filter(id=bk_id)\n        else:\n            raise ValidationError(\"需要填写id\")\n"
  },
  {
    "path": "omp_server/db_models/__init__.py",
    "content": "default_app_config = \"db_models.apps.DbModelsConfig\"\n"
  },
  {
    "path": "omp_server/db_models/admin.py",
    "content": "# from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/db_models/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass DbModelsConfig(AppConfig):\n    name = 'db_models'\n\n    def ready(self):\n        # signals are imported, so that they are defined and can be used\n        from db_models import receivers\n"
  },
  {
    "path": "omp_server/db_models/migrations/0001_initial.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-01 09:44\n\nimport django.contrib.auth.models\nimport django.contrib.auth.validators\nfrom django.db import migrations, models\nimport django.db.models.deletion\nimport django.utils.timezone\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('auth', '0012_alter_user_first_name_max_length'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='AlertSendWaySetting',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('used', models.BooleanField(default=False, verbose_name='是否启用')),\n                ('env_id', models.IntegerField(default=0, verbose_name='环境id')),\n                ('way_name', models.CharField(max_length=64, verbose_name='告警推送服务名称')),\n                ('server_url', models.TextField(default='', verbose_name='告警推送服务url')),\n                ('way_token', models.CharField(default='', max_length=255, verbose_name='服务token')),\n                ('extra_info', models.JSONField(default=dict, verbose_name='服务其他信息')),\n            ],\n            options={\n                'verbose_name': '告警推送通道设置',\n                'verbose_name_plural': '告警推送通道设置',\n                'db_table': 'omp_alert_send_way_setting',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationHub',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('is_release', models.BooleanField(default=False, help_text='是否发布', verbose_name='是否发布')),\n                ('app_type', models.IntegerField(choices=[(0, '组件'), (1, '服务')], default=0, help_text='应用类型', verbose_name='应用类型')),\n                ('app_name', models.CharField(help_text='应用名称', max_length=256, verbose_name='应用名称')),\n                ('app_version', models.CharField(help_text='应用版本', max_length=256, verbose_name='应用版本')),\n                ('app_description', models.CharField(blank=True, help_text='应用描述', max_length=2048, null=True, verbose_name='应用描述')),\n                ('app_port', models.TextField(blank=True, help_text='应用端口', null=True, verbose_name='应用端口')),\n                ('app_dependence', models.TextField(blank=True, help_text='应用依赖', null=True, verbose_name='应用依赖')),\n                ('app_install_args', models.TextField(blank=True, help_text='安装参数', null=True, verbose_name='安装参数')),\n                ('app_controllers', models.TextField(blank=True, help_text='应用控制脚本', null=True, verbose_name='应用控制脚本')),\n                ('app_logo', models.TextField(blank=True, help_text='应用图标', null=True, verbose_name='应用图标')),\n                ('extend_fields', models.JSONField(blank=True, help_text='冗余字段', null=True, verbose_name='冗余字段')),\n                ('is_base_env', models.BooleanField(default=False, help_text='是否为基础环境', verbose_name='是否为基础环境')),\n                ('app_monitor', models.JSONField(blank=True, help_text='监控使用字段', null=True, verbose_name='监控使用字段')),\n            ],\n            options={\n                'verbose_name': '应用商店服务',\n                'verbose_name_plural': '应用商店服务',\n                'db_table': 'omp_application',\n            },\n        ),\n        migrations.CreateModel(\n            name='ClusterInfo',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('is_deleted', models.BooleanField(default=False, help_text='软删除')),\n                ('cluster_service_name', models.CharField(blank=True, help_text='集群所属服务', max_length=36, null=True, verbose_name='集群所属服务')),\n                ('cluster_type', models.CharField(blank=True, help_text='集群类型', max_length=36, null=True, verbose_name='集群类型')),\n                ('cluster_name', models.CharField(help_text='集群名称', max_length=64, verbose_name='集群名称')),\n                ('connect_info', models.CharField(blank=True, help_text='集群连接信息', max_length=512, null=True, verbose_name='集群连接信息')),\n                ('connect_info_parse_type', models.CharField(blank=True, help_text='连接信息解析方式', max_length=32, null=True, verbose_name='连接信息解析方式')),\n            ],\n            options={\n                'verbose_name': '集群信息表',\n                'verbose_name_plural': '集群信息表',\n                'db_table': 'omp_cluster_info',\n            },\n        ),\n        migrations.CreateModel(\n            name='EmailSMTPSetting',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('email_host', models.EmailField(max_length=254, null=True, verbose_name='邮箱SMTP主机地址:smtp.163.com')),\n                ('email_port', models.IntegerField(null=True, verbose_name='邮箱SMTP端口:465')),\n                ('email_host_user', models.CharField(max_length=128, null=True, verbose_name='邮箱SMTP服务器用户名:a@163.com')),\n                ('email_host_password', models.CharField(max_length=128, null=True, verbose_name='邮箱SMTP服务器秘钥')),\n            ],\n            options={\n                'verbose_name': '平台邮件服务器配置',\n                'verbose_name_plural': '平台邮件服务器配置',\n                'db_table': 'omp_email_smtp_setting',\n            },\n        ),\n        migrations.CreateModel(\n            name='Env',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(help_text='环境名称', max_length=256, verbose_name='环境名称')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n            ],\n            options={\n                'verbose_name': '环境',\n                'verbose_name_plural': '环境',\n                'db_table': 'omp_env',\n            },\n        ),\n        migrations.CreateModel(\n            name='GrafanaMainPage',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('instance_name', models.CharField(help_text='信息面板实例名字', max_length=32, unique=True, verbose_name='实例名字')),\n                ('instance_url', models.CharField(help_text='实例文根地址', max_length=255, unique=True, verbose_name='实例地址')),\n            ],\n            options={\n                'verbose_name': 'grafana面板记录',\n                'verbose_name_plural': 'grafana面板记录',\n                'db_table': 'omp_grafana_url',\n            },\n        ),\n        migrations.CreateModel(\n            name='Host',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('is_deleted', models.BooleanField(default=False, help_text='软删除')),\n                ('instance_name', models.CharField(help_text='实例名', max_length=64, verbose_name='实例名')),\n                ('ip', models.GenericIPAddressField(help_text='IP地址', verbose_name='IP地址')),\n                ('port', models.IntegerField(default=22, help_text='SSH端口', verbose_name='SSH端口')),\n                ('username', models.CharField(help_text='SSH登录用户名', max_length=256, verbose_name='SSH登录用户名')),\n                ('password', models.CharField(help_text='SSH登录密码', max_length=256, verbose_name='SSH登录密码')),\n                ('data_folder', models.CharField(default='/data', help_text='数据分区', max_length=256, verbose_name='数据分区')),\n                ('service_num', models.IntegerField(default=0, help_text='服务个数', verbose_name='服务个数')),\n                ('alert_num', models.IntegerField(default=0, help_text='告警次数', verbose_name='告警次数')),\n                ('operate_system', models.CharField(help_text='操作系统', max_length=128, verbose_name='操作系统')),\n                ('host_name', models.CharField(blank=True, help_text='主机名', max_length=64, null=True, verbose_name='主机名')),\n                ('memory', models.IntegerField(blank=True, help_text='内存', null=True, verbose_name='内存')),\n                ('cpu', models.IntegerField(blank=True, help_text='CPU', null=True, verbose_name='CPU')),\n                ('disk', models.JSONField(blank=True, help_text='磁盘', null=True, verbose_name='磁盘')),\n                ('host_agent', models.CharField(choices=[(0, '正常'), (1, '重启中'), (2, '启动失败'), (3, '部署中'), (4, '部署失败')], default=3, help_text='主机Agent状态', max_length=16, verbose_name='主机Agent状态')),\n                ('monitor_agent', models.CharField(choices=[(0, '正常'), (1, '重启中'), (2, '启动失败'), (3, '部署中'), (4, '部署失败')], default=3, help_text='监控Agent状态', max_length=16, verbose_name='监控Agent状态')),\n                ('host_agent_error', models.CharField(blank=True, help_text='主机Agent异常信息', max_length=256, null=True, verbose_name='主机Agent异常信息')),\n                ('monitor_agent_error', models.CharField(blank=True, help_text='监控Agent异常信息', max_length=256, null=True, verbose_name='监控Agent异常信息')),\n                ('is_maintenance', models.BooleanField(default=False, help_text='维护模式', verbose_name='维护模式')),\n                ('agent_dir', models.CharField(default='/data', help_text='Agent安装目录', max_length=256, verbose_name='Agent安装目录')),\n                ('env', models.ForeignKey(help_text='环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='环境')),\n            ],\n            options={\n                'verbose_name': '主机',\n                'verbose_name_plural': '主机',\n                'db_table': 'omp_host',\n                'ordering': ('-created',),\n            },\n        ),\n        migrations.CreateModel(\n            name='HostThreshold',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('index_type', models.CharField(help_text='指标项名称', max_length=64)),\n                ('condition', models.CharField(help_text='阈值条件', max_length=32)),\n                ('condition_value', models.CharField(help_text='阈值数值，百分号格式', max_length=128)),\n                ('alert_level', models.CharField(help_text='告警级别', max_length=32)),\n                ('create_date', models.DateTimeField(auto_now_add=True)),\n                ('env_id', models.IntegerField(default=1, help_text='环境id')),\n            ],\n            options={\n                'db_table': 'omp_host_threshold',\n            },\n        ),\n        migrations.CreateModel(\n            name='InspectionHistory',\n            fields=[\n                ('id', models.AutoField(help_text='自增主键', primary_key=True, serialize=False, unique=True)),\n                ('inspection_name', models.CharField(help_text='报告名称:巡检类型名称', max_length=256)),\n                ('inspection_type', models.CharField(default='service', help_text='巡检类型，service、host、deep', max_length=32)),\n                ('inspection_status', models.IntegerField(default=0, help_text='巡检状态:1-进行中；2-成功；3-失败')),\n                ('execute_type', models.CharField(default='man', help_text='执行方式: 手动-man；定时：auto', max_length=32)),\n                ('inspection_operator', models.CharField(help_text='操作人员-当前登录人', max_length=16)),\n                ('hosts', models.JSONField(blank=True, help_text=\"巡检主机:['10.0.9.158']\", null=True)),\n                ('services', models.JSONField(blank=True, help_text='巡检组件: [8,9]', null=True)),\n                ('start_time', models.DateTimeField(auto_now_add=True, help_text='开始时间')),\n                ('end_time', models.DateTimeField(help_text='结束时间，后端后补', null=True)),\n                ('duration', models.IntegerField(default=0, help_text='巡检用时：单位s，后端后补')),\n                ('send_email_result', models.IntegerField(choices=[('发送成功', 1), ('发送中', 2), ('发送失败', 0), ('未发送', 3)], default=3, verbose_name='邮件推送状态')),\n                ('env', models.ForeignKey(help_text='当前环境id', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='当前环境id')),\n            ],\n            options={\n                'verbose_name': '巡检记录历史表',\n                'verbose_name_plural': '巡检记录历史表',\n                'db_table': 'inspection_history',\n                'ordering': ('-start_time',),\n            },\n        ),\n        migrations.CreateModel(\n            name='Labels',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('label_name', models.CharField(help_text='标签名称', max_length=16, verbose_name='标签名称')),\n                ('label_type', models.IntegerField(choices=[(0, '组件'), (1, '应用')], default=0, help_text='标签类型', verbose_name='标签类型')),\n            ],\n            options={\n                'verbose_name': '应用产品标签表',\n                'verbose_name_plural': '应用产品标签表',\n                'db_table': 'omp_labels',\n            },\n        ),\n        migrations.CreateModel(\n            name='MainInstallHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('operator', models.CharField(default='admin', help_text='用户', max_length=32, verbose_name='操作用户')),\n                ('operation_uuid', models.CharField(help_text='部署操作uuid', max_length=36, verbose_name='部署操作uuid')),\n                ('task_id', models.CharField(blank=True, help_text='异步任务id', max_length=36, null=True, verbose_name='异步任务id')),\n                ('install_status', models.IntegerField(choices=[(0, '待安装'), (1, '安装中'), (2, '安装成功'), (3, '安装失败')], default=0, help_text='安装状态', verbose_name='安装状态')),\n                ('install_args', models.JSONField(blank=True, help_text='主表安装信息', null=True, verbose_name='主表安装信息')),\n                ('install_log', models.TextField(help_text='MAIN安装日志', verbose_name='MAIN安装日志')),\n            ],\n            options={\n                'verbose_name': '主安装记录表',\n                'verbose_name_plural': '主安装记录表',\n                'db_table': 'omp_main_install_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='Maintain',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('matcher_name', models.CharField(help_text='匹配标签', max_length=1024, verbose_name='匹配标签')),\n                ('matcher_value', models.CharField(help_text='匹配值', max_length=1024, verbose_name='匹配值')),\n                ('maintain_id', models.CharField(help_text='维护唯一标识', max_length=1024, verbose_name='维护唯一标识')),\n            ],\n            options={\n                'verbose_name': '维护记录',\n                'verbose_name_plural': '维护记录',\n                'db_table': 'omp_maintain',\n            },\n        ),\n        migrations.CreateModel(\n            name='ModuleSendEmailSetting',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('module', models.CharField(max_length=64, verbose_name='功能模块:BackupSetting,JobSetting')),\n                ('send_email', models.BooleanField(default=False, verbose_name='是否开启邮件推送')),\n                ('to_users', models.TextField(default='', verbose_name='邮箱接收用户')),\n                ('env_id', models.IntegerField(default=1, verbose_name='环境id')),\n            ],\n            options={\n                'verbose_name': '平台邮件发送账号配置',\n                'verbose_name_plural': '平台邮件发送账号配置',\n                'db_table': 'omp_module_email_send_setting',\n            },\n        ),\n        migrations.CreateModel(\n            name='MonitorUrl',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(help_text='监控类别', max_length=32, unique=True, verbose_name='监控类别')),\n                ('monitor_url', models.CharField(help_text='请求地址', max_length=128, verbose_name='请求地址')),\n            ],\n            options={\n                'verbose_name': '监控地址记录',\n                'verbose_name_plural': '监控地址记录',\n                'db_table': 'omp_promemonitor_url',\n            },\n        ),\n        migrations.CreateModel(\n            name='OperateLog',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('username', models.CharField(help_text='操作用户', max_length=128, verbose_name='操作用户')),\n                ('request_method', models.CharField(help_text='请求方法', max_length=32, verbose_name='请求方法')),\n                ('request_ip', models.GenericIPAddressField(blank=True, help_text='请求源IP', null=True, verbose_name='请求源IP')),\n                ('request_url', models.CharField(help_text='用户目标URL', max_length=256, verbose_name='用户目标URL')),\n                ('description', models.CharField(help_text='用户行为描述', max_length=256, verbose_name='用户行为描述')),\n                ('response_code', models.IntegerField(default=0, help_text='响应状态码', verbose_name='响应状态码')),\n                ('request_result', models.TextField(default='success', help_text='请求结果', verbose_name='请求结果')),\n                ('create_time', models.DateTimeField(auto_now_add=True, help_text='操作发生时间', verbose_name='操作发生时间')),\n            ],\n            options={\n                'verbose_name': '用户操作记录',\n                'verbose_name_plural': '用户操作记录',\n                'db_table': 'omp_user_operate_log',\n            },\n        ),\n        migrations.CreateModel(\n            name='Service',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('ip', models.GenericIPAddressField(help_text='服务所在主机', verbose_name='服务所在主机')),\n                ('service_instance_name', models.CharField(help_text='服务实例名称', max_length=64, verbose_name='服务实例名称')),\n                ('service_port', models.TextField(blank=True, help_text='服务端口', null=True, verbose_name='服务端口')),\n                ('service_controllers', models.JSONField(blank=True, help_text='服务控制脚本', null=True, verbose_name='服务控制脚本')),\n                ('service_role', models.CharField(blank=True, help_text='服务角色', max_length=128, null=True, verbose_name='服务角色')),\n                ('service_status', models.IntegerField(choices=[(0, '正常'), (1, '启动中'), (2, '停止中'), (3, '重启中'), (4, '停止'), (5, '未知'), (6, '安装中'), (7, '安装失败')], default=5, help_text='服务状态', verbose_name='服务状态')),\n                ('alert_count', models.IntegerField(default=0, help_text='告警次数', verbose_name='告警次数')),\n                ('self_healing_count', models.IntegerField(default=0, help_text='服务自愈次数', verbose_name='服务自愈次数')),\n                ('service_dependence', models.TextField(blank=True, help_text='服务依赖关系', null=True, verbose_name='服务依赖关系')),\n                ('cluster', models.ForeignKey(blank=True, help_text='所属集群', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.clusterinfo')),\n                ('env', models.ForeignKey(blank=True, help_text='所属环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env')),\n                ('service', models.ForeignKey(blank=True, help_text='服务表外键', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.applicationhub')),\n            ],\n            options={\n                'verbose_name': '服务实例表',\n                'verbose_name_plural': '服务实例表',\n                'db_table': 'omp_service',\n                'ordering': ('-created',),\n            },\n        ),\n        migrations.CreateModel(\n            name='ServiceConnectInfo',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('service_name', models.CharField(help_text='服务名', max_length=32, verbose_name='服务名')),\n                ('service_username', models.CharField(blank=True, help_text='用户名', max_length=512, null=True, verbose_name='用户名')),\n                ('service_password', models.CharField(blank=True, help_text='密码', max_length=512, null=True, verbose_name='密码')),\n                ('service_username_enc', models.CharField(blank=True, help_text='加密用户名', max_length=512, null=True, verbose_name='加密用户名')),\n                ('service_password_enc', models.CharField(blank=True, help_text='加密密码', max_length=512, null=True, verbose_name='加密密码')),\n            ],\n            options={\n                'verbose_name': '用户名密码信息表',\n                'verbose_name_plural': '用户名密码信息表',\n                'db_table': 'omp_service_connect_info',\n            },\n        ),\n        migrations.CreateModel(\n            name='ServiceCustomThreshold',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('service_name', models.CharField(max_length=64, verbose_name='服务名称')),\n                ('index_type', models.CharField(max_length=64, verbose_name='指标项名称')),\n                ('condition', models.CharField(max_length=32, verbose_name='阈值条件')),\n                ('condition_value', models.CharField(max_length=128, verbose_name='阈值数值')),\n                ('alert_level', models.CharField(max_length=32, verbose_name='告警级别')),\n                ('create_date', models.DateTimeField(auto_now_add=True)),\n                ('env_id', models.IntegerField(default=1, verbose_name='环境id')),\n            ],\n            options={\n                'verbose_name': '服务定制指标阈值设置',\n                'verbose_name_plural': '服务定制指标阈值设置',\n                'db_table': 'omp_service_custom_threshold',\n            },\n        ),\n        migrations.CreateModel(\n            name='ServiceThreshold',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('index_type', models.CharField(max_length=64, verbose_name='指标项名称')),\n                ('condition', models.CharField(max_length=32, verbose_name='阈值条件')),\n                ('condition_value', models.CharField(max_length=128, verbose_name='阈值数值，百分号格式')),\n                ('alert_level', models.CharField(max_length=32, verbose_name='告警级别')),\n                ('create_date', models.DateTimeField(auto_now_add=True)),\n                ('env_id', models.IntegerField(default=1, verbose_name='环境id')),\n            ],\n            options={\n                'db_table': 'omp_service_threshold',\n            },\n        ),\n        migrations.CreateModel(\n            name='UploadPackageHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('is_deleted', models.BooleanField(default=False, help_text='软删除')),\n                ('operation_uuid', models.CharField(help_text='唯一操作uuid', max_length=64, verbose_name='唯一操作uuid')),\n                ('operation_user', models.CharField(blank=True, help_text='操作用户', max_length=64, null=True, verbose_name='操作用户')),\n                ('package_name', models.CharField(help_text='安装包名称', max_length=256, verbose_name='安装包名称')),\n                ('package_md5', models.CharField(help_text='安装包MD5值', max_length=64, verbose_name='安装包MD5值')),\n                ('package_path', models.CharField(help_text='安装包路径', max_length=512, verbose_name='安装包路径')),\n                ('package_status', models.IntegerField(choices=[(0, '成功'), (1, '失败'), (2, '解析中'), (3, '发布成功'), (4, '发布失败'), (5, '发布中')], default=2, help_text='安装包状态', verbose_name='安装包状态')),\n                ('error_msg', models.CharField(blank=True, help_text='错误消息', max_length=1024, null=True, verbose_name='错误消息')),\n                ('package_parent', models.ForeignKey(blank=True, help_text='父级包', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.uploadpackagehistory')),\n            ],\n            options={\n                'verbose_name': '上传安装包记录',\n                'verbose_name_plural': '上传安装包记录',\n                'db_table': 'omp_upload_package_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='ServiceHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('username', models.CharField(help_text='操作用户', max_length=128, verbose_name='操作用户')),\n                ('description', models.CharField(help_text='用户行为描述', max_length=1024, verbose_name='用户行为描述')),\n                ('result', models.CharField(default='success', help_text='操作结果', max_length=1024, verbose_name='操作结果')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='发生时间', null=True, verbose_name='发生时间')),\n                ('service', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.service', verbose_name='服务')),\n            ],\n            options={\n                'verbose_name': '服务操作记录',\n                'verbose_name_plural': '服务操作记录',\n                'db_table': 'omp_service_operate_log',\n                'ordering': ('-created',),\n            },\n        ),\n        migrations.AddField(\n            model_name='service',\n            name='service_connect_info',\n            field=models.ForeignKey(blank=True, help_text='用户名密码信息', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.serviceconnectinfo'),\n        ),\n        migrations.CreateModel(\n            name='ProductHub',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('is_release', models.BooleanField(default=False, help_text='是否发布', verbose_name='是否发布')),\n                ('pro_name', models.CharField(help_text='产品名称', max_length=256, verbose_name='产品名称')),\n                ('pro_version', models.CharField(help_text='产品版本', max_length=256, verbose_name='产品版本')),\n                ('pro_description', models.CharField(blank=True, help_text='产品描述', max_length=2048, null=True, verbose_name='产品描述')),\n                ('pro_dependence', models.TextField(blank=True, help_text='产品依赖', null=True, verbose_name='产品依赖')),\n                ('pro_services', models.TextField(blank=True, help_text='服务列表', null=True, verbose_name='服务列表')),\n                ('pro_logo', models.TextField(blank=True, help_text='产品图标', null=True, verbose_name='产品图标')),\n                ('extend_fields', models.JSONField(blank=True, help_text='冗余字段', null=True, verbose_name='冗余字段')),\n                ('pro_labels', models.ManyToManyField(help_text='所属标签', to='db_models.Labels')),\n                ('pro_package', models.ForeignKey(blank=True, help_text='安装包', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.uploadpackagehistory', verbose_name='安装包')),\n            ],\n            options={\n                'verbose_name': '应用商店产品',\n                'verbose_name_plural': '应用商店产品',\n                'db_table': 'omp_product',\n            },\n        ),\n        migrations.CreateModel(\n            name='Product',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('product_instance_name', models.CharField(blank=True, help_text='安装产品时输入的实例名称', max_length=64, null=True, verbose_name='产品实例名称')),\n                ('product', models.ForeignKey(blank=True, help_text='所属产品', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.producthub')),\n            ],\n            options={\n                'verbose_name': '产品实例表',\n                'verbose_name_plural': '产品实例表',\n                'db_table': 'omp_product_instance',\n                'ordering': ('-created',),\n            },\n        ),\n        migrations.CreateModel(\n            name='InspectionReport',\n            fields=[\n                ('id', models.AutoField(help_text='自增主键', primary_key=True, serialize=False, unique=True)),\n                ('file_name', models.CharField(blank=True, help_text='导出文件名', max_length=128, null=True)),\n                ('scan_info', models.JSONField(blank=True, help_text='扫描统计', null=True)),\n                ('scan_result', models.JSONField(blank=True, help_text='分析结果', null=True)),\n                ('risk_data', models.JSONField(blank=True, help_text='风险指标', null=True)),\n                ('host_data', models.JSONField(blank=True, help_text='主机列表', null=True)),\n                ('serv_plan', models.JSONField(blank=True, help_text='服务平面图', null=True)),\n                ('serv_data', models.JSONField(blank=True, help_text='服务列表', null=True)),\n                ('inst_id', models.OneToOneField(help_text='巡检记录历史表id', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.inspectionhistory', verbose_name='巡检记录历史表')),\n            ],\n            options={\n                'verbose_name': '巡检任务 报告数据',\n                'verbose_name_plural': '巡检任务 报告数据',\n                'db_table': 'inspection_report',\n                'ordering': ('id',),\n            },\n        ),\n        migrations.CreateModel(\n            name='InspectionCrontab',\n            fields=[\n                ('id', models.AutoField(help_text='自增主键', primary_key=True, serialize=False, unique=True)),\n                ('job_type', models.IntegerField(choices=[(0, '深度分析'), (1, '主机巡检'), (2, '组件巡检')], default=0, help_text='任务类型：0-深度分析 1-主机巡检 2-组建巡检')),\n                ('job_name', models.CharField(help_text='任务名称', max_length=128)),\n                ('is_start_crontab', models.IntegerField(default=0, help_text='是否开启定时任务：0-开启，1-关闭')),\n                ('crontab_detail', models.JSONField(help_text='定时任务详情')),\n                ('create_date', models.DateTimeField(auto_now_add=True, help_text='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间')),\n                ('env', models.ForeignKey(help_text='环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='环境')),\n            ],\n            options={\n                'verbose_name': '巡检任务 定时配置表',\n                'verbose_name_plural': '巡检任务 定时配置表',\n                'db_table': 'inspection_crontab',\n                'ordering': ('id',),\n            },\n        ),\n        migrations.CreateModel(\n            name='HostOperateLog',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('username', models.CharField(help_text='操作用户', max_length=128, verbose_name='操作用户')),\n                ('description', models.CharField(help_text='用户行为描述', max_length=1024, verbose_name='用户行为描述')),\n                ('result', models.CharField(default='success', help_text='操作结果', max_length=1024, verbose_name='操作结果')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='发生时间', null=True, verbose_name='发生时间')),\n                ('host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.host', verbose_name='主机')),\n            ],\n            options={\n                'verbose_name': '主机操作记录',\n                'verbose_name_plural': '主机操作记录',\n                'db_table': 'omp_host_operate_log',\n                'ordering': ('-created',),\n            },\n        ),\n        migrations.CreateModel(\n            name='DetailInstallHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('install_step_status', models.IntegerField(choices=[(0, '待安装'), (1, '安装中'), (2, '安装成功'), (3, '安装失败')], default=0, help_text='安装步骤状态', verbose_name='安装步骤状态')),\n                ('send_flag', models.IntegerField(default=0, help_text='0-待发送 1-发送中 2-发送成功 3-发送失败', verbose_name='发包状态')),\n                ('send_msg', models.TextField(help_text='发包日志', verbose_name='发包日志')),\n                ('unzip_flag', models.IntegerField(default=0, help_text='0-待解压 1-解压中 2-解压成功 3-解压失败', verbose_name='解压包状态')),\n                ('unzip_msg', models.TextField(help_text='解压日志', verbose_name='解压日志')),\n                ('install_flag', models.IntegerField(default=0, help_text='0-待安装 1-安装中 2-安装成功 3-安装失败', verbose_name='安装执行状态')),\n                ('install_msg', models.TextField(help_text='安装日志', verbose_name='安装日志')),\n                ('init_flag', models.IntegerField(default=0, help_text='0-待初始化 1-初始化中 2-初始化成功 3-初始化失败', verbose_name='初始化执行状态')),\n                ('init_msg', models.TextField(help_text='初始化日志', verbose_name='初始化日志')),\n                ('start_flag', models.IntegerField(default=0, help_text='0-待启动 1-启动中 2-启动成功 3-启动失败', verbose_name='启动执行状态')),\n                ('start_msg', models.TextField(help_text='启动日志', verbose_name='启动日志')),\n                ('install_detail_args', models.JSONField(blank=True, help_text='详情表安装信息', null=True, verbose_name='详情表安装信息')),\n                ('post_action_flag', models.IntegerField(default=0, help_text='0-待执行 1-执行中 2-执行成功 3-执行失败 4-无需执行', verbose_name='安装后执行动作标记')),\n                ('post_action_msg', models.TextField(default='', help_text='安装后执行动作日志', verbose_name='安装后执行动作日志')),\n                ('main_install_history', models.ForeignKey(blank=True, help_text='关联主安装记录', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.maininstallhistory')),\n                ('service', models.ForeignKey(blank=True, help_text='关联服务对象', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.service')),\n            ],\n            options={\n                'verbose_name': '安装记录详情表',\n                'verbose_name_plural': '安装记录详情表',\n                'db_table': 'omp_detail_install_history',\n            },\n        ),\n        migrations.AddField(\n            model_name='clusterinfo',\n            name='service_connect_info',\n            field=models.ForeignKey(blank=True, help_text='用户名密码信息', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.serviceconnectinfo'),\n        ),\n        migrations.AddField(\n            model_name='applicationhub',\n            name='app_labels',\n            field=models.ManyToManyField(help_text='所属标签', to='db_models.Labels'),\n        ),\n        migrations.AddField(\n            model_name='applicationhub',\n            name='app_package',\n            field=models.ForeignKey(blank=True, help_text='安装包', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.uploadpackagehistory', verbose_name='安装包'),\n        ),\n        migrations.AddField(\n            model_name='applicationhub',\n            name='product',\n            field=models.ForeignKey(blank=True, help_text='所属产品', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.producthub', verbose_name='所属产品'),\n        ),\n        migrations.CreateModel(\n            name='Alert',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('is_read', models.IntegerField(default=0, help_text='此消息是否已读，0-未读；1-已读', verbose_name='已读')),\n                ('alert_type', models.CharField(default='', help_text='告警类型，主机host，服务service', max_length=32, verbose_name='告警类型')),\n                ('alert_host_ip', models.CharField(default='', help_text='告警来源主机ip', max_length=64, verbose_name='告警主机ip')),\n                ('alert_service_name', models.CharField(default='', help_text='服务类告警中的服务名称', max_length=128, verbose_name='告警服务名称')),\n                ('alert_instance_name', models.CharField(default='', help_text='告警实例名称', max_length=128, verbose_name='告警实例名称')),\n                ('alert_service_type', models.CharField(default='', help_text='服务所属类型', max_length=128, verbose_name='告警服务类型')),\n                ('alert_level', models.CharField(default='', help_text='告警级别', max_length=1024, verbose_name='告警级别')),\n                ('alert_describe', models.CharField(default='', help_text='告警描述', max_length=1024, verbose_name='告警描述')),\n                ('alert_receiver', models.CharField(default='', help_text='告警接收人', max_length=256, verbose_name='告警接收人')),\n                ('alert_resolve', models.CharField(default='', help_text='告警解决方案', max_length=1024, verbose_name='告警解决方案')),\n                ('alert_time', models.DateTimeField(help_text='告警发生时间', verbose_name='告警发生时间')),\n                ('create_time', models.DateTimeField(auto_now_add=True, help_text='告警信息入库时间', verbose_name='告警信息入库时间')),\n                ('monitor_path', models.CharField(blank=True, help_text='跳转grafana路由', max_length=2048, null=True, verbose_name='跳转监控路径')),\n                ('monitor_log', models.CharField(blank=True, help_text='跳转grafana日志页面路由', max_length=2048, null=True, verbose_name='跳转监控日志路径')),\n                ('fingerprint', models.CharField(blank=True, help_text='告警的唯一标识', max_length=1024, null=True, verbose_name='告警的唯一标识')),\n                ('env', models.ForeignKey(help_text='环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='环境')),\n            ],\n            options={\n                'verbose_name': '告警记录',\n                'verbose_name_plural': '告警记录',\n                'db_table': 'omp_alert',\n            },\n        ),\n        migrations.CreateModel(\n            name='UserProfile',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('password', models.CharField(max_length=128, verbose_name='password')),\n                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),\n                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),\n                ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),\n                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),\n                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),\n                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),\n                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),\n                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),\n                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),\n                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),\n                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),\n            ],\n            options={\n                'verbose_name': '用户',\n                'verbose_name_plural': '用户',\n                'db_table': 'omp_user_profile',\n            },\n            managers=[\n                ('objects', django.contrib.auth.models.UserManager()),\n            ],\n        ),\n        migrations.AlterUniqueTogether(\n            name='applicationhub',\n            unique_together={('app_name', 'app_version')},\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0002_auto_20211202_1830.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-02 18:30\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='service',\n            name='service_status',\n            field=models.IntegerField(choices=[(0, '正常'), (1, '启动中'), (2, '停止中'), (3, '重启中'), (4, '停止'), (5, '未知'), (6, '安装中'), (7, '安装失败'), (8, '待安装')], default=5, help_text='服务状态', verbose_name='服务状态'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0003_host_init_status.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-02 19:38\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0002_auto_20211202_1830'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='host',\n            name='init_status',\n            field=models.CharField(choices=[(0, '未执行'), (1, '成功'), (2, '失败')], default=0, help_text='主机初始化状态', max_length=16, verbose_name='主机初始化状态'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0004_auto_20211203_1617.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-03 16:17\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0003_host_init_status'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='maininstallhistory',\n            name='install_status',\n            field=models.IntegerField(choices=[(0, '待安装'), (1, '安装中'), (2, '安装成功'), (3, '安装失败'), (4, '正在注册')], default=0, help_text='安装状态', verbose_name='安装状态'),\n        ),\n        migrations.CreateModel(\n            name='PreInstallHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('name', models.CharField(default='preInstall', help_text='名称', max_length=32, verbose_name='名称')),\n                ('ip', models.GenericIPAddressField(help_text='主机ip地址', verbose_name='主机ip地址')),\n                ('install_flag', models.IntegerField(default=0, help_text='0-待安装 1-安装中 2-安装成功 3-安装失败', verbose_name='安装标志')),\n                ('install_log', models.TextField(help_text='主机层安装日志', verbose_name='主机层安装日志')),\n                ('main_install_history', models.ForeignKey(blank=True, help_text='关联主安装记录', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.maininstallhistory')),\n            ],\n            options={\n                'verbose_name': '前置安装记录',\n                'verbose_name_plural': '前置安装记录',\n                'db_table': 'omp_pre_install_history',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0005_auto_20211206_1723.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-06 17:23\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0004_auto_20211203_1617'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='preinstallhistory',\n            name='name',\n            field=models.CharField(default='初始化安装流程', help_text='名称', max_length=32, verbose_name='名称'),\n        ),\n        migrations.AlterField(\n            model_name='service',\n            name='service_status',\n            field=models.IntegerField(choices=[(0, '正常'), (1, '启动中'), (2, '停止中'), (3, '重启中'), (4, '停止'), (5, '未知'), (6, '安装中'), (7, '安装失败'), (8, '待安装'), (9, '删除中')], default=5, help_text='服务状态', verbose_name='服务状态'),\n        ),\n        migrations.CreateModel(\n            name='PostInstallHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('name', models.CharField(default='安装后续任务', help_text='名称', max_length=32, verbose_name='名称')),\n                ('ip', models.CharField(default='postAction', help_text='主机ip地址', max_length=128, verbose_name='fake主机ip地址')),\n                ('install_flag', models.IntegerField(default=0, help_text='0-待执行 1-执行中 2-执行成功 3-执行失败', verbose_name='安装标志')),\n                ('install_log', models.TextField(help_text='安装后续任务日志', verbose_name='安装后续任务日志')),\n                ('main_install_history', models.ForeignKey(blank=True, help_text='关联主安装记录', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.maininstallhistory')),\n            ],\n            options={\n                'verbose_name': '后置安装记录',\n                'verbose_name_plural': '后置安装记录',\n                'db_table': 'omp_post_install_history',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0005_update_init_status.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-03 14:30\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0004_auto_20211203_1617'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='host',\n            name='init_status',\n            field=models.CharField(choices=[(0, '成功'), (1, '未执行'), (2, '执行中'), (3, '失败')], default=1, help_text='主机初始化状态', max_length=16, verbose_name='主机初始化状态'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0006_merge_20211206_1833.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-06 18:33\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0005_update_init_status'),\n        ('db_models', '0005_auto_20211206_1723'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0007_deploymentplan.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-13 17:14\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0006_merge_20211206_1833'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='DeploymentPlan',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('plan_name', models.CharField(help_text='部署计划名称', max_length=32, verbose_name='部署计划名称')),\n                ('host_num', models.IntegerField(default=0, help_text='主机数量', verbose_name='主机数量')),\n                ('product_num', models.IntegerField(default=0, help_text='产品数量', verbose_name='产品数量')),\n                ('service_num', models.IntegerField(default=0, help_text='服务数量', verbose_name='服务数量')),\n                ('create_user', models.CharField(help_text='创建用户', max_length=16, verbose_name='创建用户')),\n                ('operation_uuid', models.CharField(help_text='部署操作uuid', max_length=36, verbose_name='部署操作uuid')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n            ],\n            options={\n                'verbose_name': '部署计划',\n                'verbose_name_plural': '部署计划',\n                'db_table': 'omp_deployment_plan',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0008_service_vip.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-22 10:20\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0007_deploymentplan'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='service',\n            name='vip',\n            field=models.GenericIPAddressField(blank=True, default=None, help_text='vip地址', null=True, verbose_name='vip地址'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0009_auto_20211228_1603.py",
    "content": "# Generated by Django 3.1.4 on 2021-12-28 16:03\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0008_service_vip'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='host',\n            name='instance_name',\n            field=models.CharField(help_text='实例名', max_length=64, unique=True, verbose_name='实例名'),\n        ),\n        migrations.AlterField(\n            model_name='host',\n            name='ip',\n            field=models.GenericIPAddressField(help_text='IP地址', unique=True, verbose_name='IP地址'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0010_auto_20220114_1830.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-14 18:30\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0009_auto_20211228_1603'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='service',\n            name='service_status',\n            field=models.IntegerField(choices=[(0, '正常'), (1, '启动中'), (2, '停止中'), (3, '重启中'), (4, '停止'), (5, '未知'), (6, '安装中'), (7, '安装失败'), (8, '待安装'), (9, '删除中'), (10, '升级中'), (11, '回滚中')], default=5, help_text='服务状态', verbose_name='服务状态'),\n        ),\n        migrations.CreateModel(\n            name='UpgradeHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('upgrade_state', models.IntegerField(choices=[(0, '等待升级'), (1, '正在升级'), (2, '升级成功'), (3, '升级失败')], default=0, verbose_name='升级结果')),\n                ('env', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='所属环境')),\n                ('operator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='用户')),\n            ],\n            options={\n                'verbose_name': '升级历史记录',\n                'verbose_name_plural': '升级历史记录',\n                'db_table': 'omp_upgrade_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='UpgradeDetail',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('upgrade_state', models.IntegerField(choices=[(0, '等待升级'), (1, '正在升级'), (2, '升级成功'), (3, '升级失败')], default=0, verbose_name='升级结果')),\n                ('union_server', models.CharField(max_length=199, verbose_name='唯一服务实例:ip-app_name(适配hadoop，单服务裂开多个服务)')),\n                ('path_info', models.JSONField(default=dict, verbose_name='服务包备份路径信息')),\n                ('handler_info', models.JSONField(default=dict, verbose_name='升级步骤信息')),\n                ('message', models.TextField(default='', verbose_name='升级日志信息')),\n                ('has_rollback', models.BooleanField(default=False, verbose_name='是否已回滚')),\n                ('current_app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='current_app_set', to='db_models.applicationhub', verbose_name='升级前服务')),\n                ('history', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.upgradehistory', verbose_name='升级历史记录')),\n                ('service', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.service', verbose_name='服务')),\n                ('target_app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='target_app_set', to='db_models.applicationhub', verbose_name='升级目标服务')),\n            ],\n            options={\n                'verbose_name': '单个服务升级记录',\n                'verbose_name_plural': '单个服务升级记录',\n                'db_table': 'omp_upgrade_detail',\n            },\n        ),\n        migrations.CreateModel(\n            name='RollbackHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('rollback_state', models.IntegerField(choices=[(0, '等待回滚'), (1, '正在回滚'), (2, '回滚成功'), (3, '回滚失败')], default=0, verbose_name='回滚结果')),\n                ('env', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='所属环境')),\n                ('operator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='用户')),\n            ],\n            options={\n                'verbose_name': '回滚历史记录',\n                'verbose_name_plural': '回滚历史记录',\n                'db_table': 'omp_rollback_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='RollbackDetail',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('rollback_state', models.IntegerField(choices=[(0, '等待回滚'), (1, '正在回滚'), (2, '回滚成功'), (3, '回滚失败')], default=0, verbose_name='回滚结果')),\n                ('path_info', models.JSONField(default=dict, verbose_name='服务包备份路径信息')),\n                ('handler_info', models.JSONField(default=dict, verbose_name='回滚步骤信息')),\n                ('message', models.TextField(default='', verbose_name='回滚日志信息')),\n                ('history', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.rollbackhistory', verbose_name='回滚历史记录')),\n                ('upgrade', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.upgradedetail', verbose_name='升级记录')),\n            ],\n            options={\n                'verbose_name': '单个服务回滚记录',\n                'verbose_name_plural': '单个服务回滚记录',\n                'db_table': 'omp_rollback_detail',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0010_backuphistory_backupsetting.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-11 11:01\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0009_auto_20211228_1603'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='BackupHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('backup_name', models.CharField(max_length=128, verbose_name='备份任务名称')),\n                ('content', models.JSONField(verbose_name='备份内容(服务名):[\"mysql\",\"arangodb\"]')),\n                ('result', models.IntegerField(choices=[('成功', 1), ('备份中', 2), ('失败', 0)], default=2, verbose_name='结果')),\n                ('message', models.JSONField(default=dict, verbose_name='返回信息')),\n                ('file_name', models.CharField(default='', max_length=128, verbose_name='备份文件名')),\n                ('file_size', models.CharField(default='0', max_length=64, verbose_name='备份文件大小, MB')),\n                ('expire_time', models.DateTimeField(null=True, verbose_name='过期时间')),\n                ('file_deleted', models.BooleanField(default=False, verbose_name='文件是否被删除')),\n                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='记录生成时间')),\n                ('env_id', models.IntegerField(default=0, verbose_name='环境id')),\n                ('retain_path', models.TextField(default='/data/backups/', verbose_name='文件保存路径')),\n                ('operation', models.CharField(default='定时任务执行', max_length=32, verbose_name='操作方式')),\n                ('send_email_result', models.IntegerField(choices=[('发送成功', 1), ('发送中', 2), ('发送失败', 0), ('未发送', 3)], default=3, verbose_name='邮件推送状态')),\n                ('email_fail_reason', models.TextField(default='', verbose_name='邮件推送失败原因')),\n            ],\n            options={\n                'verbose_name': '备份历史记录',\n                'verbose_name_plural': '备份历史记录',\n                'db_table': 'omp_backup_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='BackupSetting',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('backup_service', models.JSONField(verbose_name='备份服务名称')),\n                ('is_on', models.BooleanField(default=False, verbose_name='是否开启')),\n                ('crontab_detail', models.JSONField(verbose_name='定时任务详情')),\n                ('retain_day', models.IntegerField(default=1, verbose_name='文件保存天数')),\n                ('retain_path', models.TextField(verbose_name='文件保存路径')),\n                ('env_id', models.IntegerField(default=0, verbose_name='环境id')),\n            ],\n            options={\n                'verbose_name': '备份设置',\n                'verbose_name_plural': '备份设置',\n                'db_table': 'omp_backup_setting',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0011_auto_20220112_1607.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-12 16:07\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0010_backuphistory_backupsetting'),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name='backupsetting',\n            name='backup_service',\n        ),\n        migrations.AddField(\n            model_name='backupsetting',\n            name='backup_instances',\n            field=models.JSONField(default=dict, verbose_name='备份服务实例名称'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0012_auto_20220112_1653.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-12 16:53\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0011_auto_20220112_1607'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='backuphistory',\n            name='created',\n            field=models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间'),\n        ),\n        migrations.AddField(\n            model_name='backuphistory',\n            name='modified',\n            field=models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0013_merge_20220114_1838.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-14 18:38\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0010_auto_20220114_1830'),\n        ('db_models', '0012_auto_20220112_1653'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0014_auto_20220121_1616.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-21 16:16\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0013_merge_20220114_1838'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='UserLoginLog',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('username', models.CharField(max_length=128, verbose_name='Username')),\n                ('login_time', models.DateTimeField(blank=True, null=True, verbose_name='Login time')),\n                ('ip', models.CharField(blank=True, max_length=32, null=True, verbose_name='Login ip')),\n                ('role', models.CharField(blank=True, max_length=128, null=True, verbose_name='role ')),\n            ],\n            options={\n                'verbose_name': '用户登陆记录',\n                'verbose_name_plural': '用户登陆记录',\n                'db_table': 'omp_login_log',\n            },\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='content',\n            field=models.JSONField(verbose_name='备份内容(实例名):[\"mysql1\",\"arangodb2\"]'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='retain_path',\n            field=models.TextField(default='/data/omp/data/backup/', verbose_name='文件保存路径'),\n        ),\n        migrations.AlterField(\n            model_name='backupsetting',\n            name='retain_path',\n            field=models.TextField(default='/data/omp/data/backup/', verbose_name='文件保存路径'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0015_executionrecord.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-24 17:50\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0014_auto_20220121_1616'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ExecutionRecord',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('module', models.CharField(choices=[('MainInstallHistory', '安装'), ('UpgradeHistory', '升级'), ('RollbackHistory', '回滚')], default='MainInstallHistory', max_length=32, verbose_name='执行记录的module名')),\n                ('module_id', models.IntegerField(default=0, verbose_name='执行记录的id')),\n                ('operator', models.CharField(default='admin', max_length=150, verbose_name='操作用户')),\n                ('state', models.CharField(choices=[('INSTALL_STATUS_READY', '等待安装'), ('INSTALL_STATUS_INSTALLING', '正在安装'), ('INSTALL_STATUS_SUCCESS', '安装成功'), ('INSTALL_STATUS_FAILED', '安装失败'), ('INSTALL_STATUS_REGISTER', '正在注册'), ('UPGRADE_WAIT', '等待升级'), ('UPGRADE_ING', '正在升级'), ('UPGRADE_SUCCESS', '升级成功'), ('UPGRADE_FAIL', '升级失败'), ('ROLLBACK_WAIT', '等待回滚'), ('ROLLBACK_ING', '正在回滚'), ('ROLLBACK_SUCCESS', '回滚成功'), ('ROLLBACK_FAIL', '回滚失败')], default='INSTALL_STATUS_READY', max_length=32, verbose_name='状态')),\n                ('count', models.IntegerField(default=0, verbose_name='服务数量')),\n                ('end_time', models.DateTimeField(null=True, verbose_name='更新时间')),\n            ],\n            options={\n                'verbose_name': '执行记录',\n                'verbose_name_plural': '执行记录',\n                'db_table': 'omp_execution_record',\n            },\n        )\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0016_auto_20220125_1800.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-25 18:00\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0015_executionrecord'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='executionrecord',\n            name='module_id',\n            field=models.CharField(default='0', max_length=36, verbose_name='执行记录的id'),\n        )\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0017_selfhealinghistory_selfhealingsetting.py",
    "content": "# Generated by Django 3.1.4 on 2022-01-28 14:50\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0016_auto_20220125_1800'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='SelfHealingSetting',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('used', models.BooleanField(default=False, verbose_name='是否启用')),\n                ('alert_count', models.IntegerField(default=1, verbose_name='触发自愈的告警次数')),\n                ('max_healing_count', models.IntegerField(default=5, verbose_name='最多自愈操作次数')),\n                ('env', models.ForeignKey(help_text='环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='环境')),\n            ],\n            options={\n                'verbose_name': '自愈设置',\n                'verbose_name_plural': '自愈设置',\n                'db_table': 'omp_self_healing_setting',\n            },\n        ),\n        migrations.CreateModel(\n            name='SelfHealingHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('is_read', models.IntegerField(default=0, verbose_name='此消息是否已读，0-未读；1-已读')),\n                ('host_ip', models.CharField(max_length=64, verbose_name='自愈主机ip')),\n                ('service_name', models.CharField(default='', max_length=64, verbose_name='自愈服务名称')),\n                ('instance_name', models.CharField(default='', help_text='自愈实例名称', max_length=128, verbose_name='自愈实例名称')),\n                ('state', models.IntegerField(choices=[(2, '自愈中'), (0, '自愈失败'), (1, '自愈成功')], default=2, verbose_name='自愈状态')),\n                ('healing_count', models.IntegerField(default=0, verbose_name='已运行自愈次数')),\n                ('start_time', models.DateTimeField(null=True, verbose_name='自愈开始时间')),\n                ('end_time', models.DateTimeField(null=True, verbose_name='自愈结束时间')),\n                ('healing_log', models.JSONField(default=dict, verbose_name='自愈日志')),\n                ('fingerprint', models.CharField(max_length=64, verbose_name='关联告警唯一值')),\n                ('alert_time', models.DateTimeField(null=True, verbose_name='关联告警时间，与fingerprint确定同一次告警')),\n                ('alert_content', models.TextField(default='', verbose_name='告警日志内容')),\n                ('monitor_log', models.TextField(default='', verbose_name='grafana日志url')),\n                ('service_en_type', models.CharField(default='', max_length=64, verbose_name='服务类型，self_dev&component&database')),\n                ('env', models.ForeignKey(help_text='环境', null=True, on_delete=django.db.models.deletion.SET_NULL, to='db_models.env', verbose_name='环境')),\n            ],\n            options={\n                'verbose_name': '自愈历史记录',\n                'verbose_name_plural': '自愈历史记录',\n                'db_table': 'omp_self_healing_history',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0018_userloginlog_request_result.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-08 15:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0017_selfhealinghistory_selfhealingsetting'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='userloginlog',\n            name='request_result',\n            field=models.CharField(blank=True, help_text='请求结果', max_length=512, null=True, verbose_name='请求结果'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0019_toolexecutedetailhistory_toolexecutemainhistory_toolinfo_uploadfilehistory.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-16 11:25\n\nfrom django.conf import settings\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0018_userloginlog_request_result'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ToolInfo',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('name', models.CharField(help_text='实用工具名称', max_length=128)),\n                ('kind', models.IntegerField(choices=[(0, '管理工具'), (1, '检查工具'), (2, '安全工具'), (3, '其他工具')], default=0, help_text='实用工具分类', verbose_name='实用工具分类')),\n                ('script_type', models.IntegerField(choices=[(0, 'python3'), (1, 'shell')], default=0, help_text='脚本类型', verbose_name='脚本类型')),\n                ('target_name', models.CharField(default='host', help_text='脚本执行的目标对象', max_length=128, verbose_name='脚本执行的目标对象')),\n                ('source_package_md5', models.CharField(blank=True, help_text='源码包md5值', max_length=32, null=True, verbose_name='源码包md5值')),\n                ('source_package_path', models.CharField(max_length=128, verbose_name='源码包相对路径')),\n                ('tool_folder_path', models.CharField(help_text='实用工具目录相对路径', max_length=128, verbose_name='实用工具目录相对路径')),\n                ('script_path', models.CharField(help_text='脚本相对路径', max_length=128, verbose_name='脚本相对路径')),\n                ('send_package', models.JSONField(default=list, help_text='需要发送的文件相对路径', max_length=128, verbose_name='需要发送的文件相对路径')),\n                ('readme_info', models.TextField(blank=True, help_text='readme信息', null=True, verbose_name='readme信息')),\n                ('template_filepath', models.JSONField(default=list, help_text='模板文件相对路径', verbose_name='模板文件相对路径')),\n                ('obj_connection_args', models.JSONField(default=list, verbose_name='目标对象连接信息')),\n                ('script_args', models.JSONField(default=list, verbose_name='脚本执行参数')),\n                ('output', models.IntegerField(choices=[(0, '终端输出'), (1, '文件输出')], default=0, help_text='脚本的输出类型', verbose_name='脚本的输出类型')),\n                ('description', models.TextField(help_text='描述信息', verbose_name='描述信息')),\n            ],\n            options={\n                'verbose_name': '实用工具基本信息表',\n                'verbose_name_plural': '实用工具基本信息表',\n                'db_table': 'omp_tool_info',\n            },\n        ),\n        migrations.CreateModel(\n            name='UploadFileHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('module', models.CharField(default='', max_length=32, verbose_name='需要上传文件的model')),\n                ('module_id', models.IntegerField(default=0, verbose_name='需要上传文件的model id')),\n                ('union_id', models.CharField(default='', max_length=64, verbose_name='文件md5值')),\n                ('storage_klass', models.CharField(default='location', max_length=64, verbose_name='存储方式')),\n                ('relative_path', models.TextField(default='', verbose_name='文件存储相对路径')),\n                ('file_name', models.CharField(default='', max_length=64, verbose_name='文件名称')),\n                ('file_size', models.CharField(default='0K', max_length=16, verbose_name='文件大小')),\n                ('file_url', models.TextField(default='', verbose_name='文件访问路径')),\n                ('deleted', models.BooleanField(default=False, verbose_name='删除')),\n                ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='用户')),\n            ],\n            options={\n                'verbose_name': '上传文件记录',\n                'verbose_name_plural': '上传文件记录',\n                'db_table': 'omp_upload_file',\n            },\n        ),\n        migrations.CreateModel(\n            name='ToolExecuteMainHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('task_name', models.CharField(help_text='任务标题', max_length=128, null=True, verbose_name='任务标题')),\n                ('operator', models.CharField(blank=True, help_text='操作人', max_length=128, null=True, verbose_name='操作人')),\n                ('status', models.IntegerField(choices=[(0, '待执行'), (1, '执行中'), (2, '执行成功'), (3, '执行失败')], default=0, help_text='main执行状态', verbose_name='main执行状态')),\n                ('start_time', models.DateTimeField(auto_now_add=True, help_text='开始时间', null=True, verbose_name='开始时间')),\n                ('end_time', models.DateTimeField(help_text='结束时间', null=True, verbose_name='结束时间')),\n                ('form_answer', models.JSONField(default=dict, verbose_name='任务表单提交结果')),\n                ('tool', models.ForeignKey(help_text='实用工具对象', on_delete=django.db.models.deletion.CASCADE, to='db_models.toolinfo')),\n            ],\n            options={\n                'verbose_name': '实用工具执行记录',\n                'verbose_name_plural': '实用工具执行记录',\n                'db_table': 'omp_tool_execute_main_history',\n            },\n        ),\n        migrations.CreateModel(\n            name='ToolExecuteDetailHistory',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('target_ip', models.CharField(help_text='目标IP地址', max_length=64, verbose_name='目标IP地址')),\n                ('time_out', models.IntegerField(default=60, verbose_name='超时时间')),\n                ('run_user', models.CharField(default='', max_length=64, verbose_name='执行用户')),\n                ('status', models.IntegerField(choices=[(0, '待执行'), (1, '执行中'), (2, '执行成功'), (3, '执行失败'), (4, '执行超时')], default=0, help_text='detail执行状态', verbose_name='detail执行状态')),\n                ('execute_args', models.JSONField(default=dict, help_text='执行参数信息', verbose_name='执行参数信息')),\n                ('execute_log', models.TextField(help_text='执行日志', verbose_name='执行日志')),\n                ('output', models.JSONField(default=dict, verbose_name='脚本输出内容')),\n                ('main_history', models.ForeignKey(help_text='实用工具对象', on_delete=django.db.models.deletion.CASCADE, to='db_models.toolexecutemainhistory')),\n            ],\n            options={\n                'verbose_name': '实用工具执行详情表',\n                'verbose_name_plural': '实用工具执行详情表',\n                'db_table': 'omp_tool_execute_detail_history',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0020_init_tools.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-22 16:59\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0019_toolexecutedetailhistory_toolexecutemainhistory_toolinfo_uploadfilehistory'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0021_customscript.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-23 15:36\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0020_init_tools'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='CustomScript',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('created', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),\n                ('modified', models.DateTimeField(auto_now=True, help_text='更新时间', null=True, verbose_name='更新时间')),\n                ('script_name', models.CharField(help_text='自定义脚本名称', max_length=32, verbose_name='脚本名称')),\n                ('script_content', models.TextField(help_text='脚本内容', max_length=10000, verbose_name='脚本内容')),\n                ('metrics', models.JSONField(help_text='指标列表', verbose_name='指标列表')),\n                ('metric_num', models.IntegerField(help_text='metric数量', null=True, verbose_name='metric数量')),\n                ('scrape_interval', models.IntegerField(default=60, help_text='prometheus探测周期', verbose_name='探测周期')),\n                ('enabled', models.BooleanField(default=1, help_text='1位启用，0为禁用', verbose_name='是否启用')),\n                ('description', models.TextField(help_text='脚本描述', max_length=1024, verbose_name='脚本描述')),\n                ('bound_hosts', models.JSONField(help_text='已下发主机列表', verbose_name='已下发主机列表')),\n            ],\n            options={\n                'verbose_name': '自定义脚本',\n                'verbose_name_plural': '自定义脚本',\n                'db_table': 'omp_custom_script',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0022_alertrule_rule.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-25 09:44\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0021_customscript'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='AlertRule',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('env_id', models.IntegerField(default=1, verbose_name='环境id')),\n                ('expr', models.TextField(verbose_name='监控指标表达式，报警语法')),\n                ('threshold_value', models.FloatField(verbose_name='阈值的数值')),\n                ('compare_str', models.CharField(max_length=64, verbose_name='比较符')),\n                ('for_time', models.CharField(max_length=64, verbose_name='持续一段时间获取不到信息就触发告警')),\n                ('severity', models.CharField(max_length=64, verbose_name='告警级别')),\n                ('alert', models.TextField(verbose_name='标题，自定义摘要')),\n                ('service', models.CharField(max_length=255, verbose_name='指标所属服务名称')),\n                ('status', models.IntegerField(default=0, verbose_name='启用状态')),\n                ('name', models.CharField(max_length=255, null=True, verbose_name='内置指标名称')),\n                ('quota_type', models.IntegerField(choices=[(0, 'builtins'), (1, 'custom'), (2, 'log')], default=0, verbose_name='指标的类型')),\n                ('labels', models.JSONField(null=True, verbose_name='额外指定标签')),\n                ('summary', models.TextField(null=True, verbose_name='描述, 告警指标描述')),\n                ('description', models.TextField(null=True, verbose_name='描述, 告警指标描述')),\n                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='告警规则入库时间')),\n                ('update_time', models.DateTimeField(auto_now_add=True, verbose_name='告警规则更新时间')),\n            ],\n            options={\n                'verbose_name': '自定义告警规则',\n                'verbose_name_plural': '自定义告警规则',\n                'db_table': 'omp_alert_ruler',\n            },\n        ),\n        migrations.CreateModel(\n            name='Rule',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('name', models.CharField(max_length=255, verbose_name='指标名称')),\n                ('expr', models.TextField(verbose_name='监控指标表达式，报警语法')),\n                ('service', models.CharField(max_length=255, verbose_name='服务名称')),\n                ('description', models.TextField(null=True, verbose_name='描述')),\n            ],\n            options={\n                'verbose_name': '规则表达式',\n                'verbose_name_plural': '规则表达式',\n                'db_table': 'omp_rule',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0023_auto_20220225_1747.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-25 17:47\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0022_alertrule_rule'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='host',\n            name='ntpd_server',\n            field=models.GenericIPAddressField(default='127.0.0.1', verbose_name='时间同步服务器地址'),\n        ),\n        migrations.AddField(\n            model_name='host',\n            name='ntpdate_install_status',\n            field=models.CharField(choices=[(0, '执行成功'), (1, '未执行'), (2, '执行中'), (3, '执行失败')], default=1, max_length=16, verbose_name='安装ntpdate状态'),\n        ),\n        migrations.AddField(\n            model_name='host',\n            name='use_ntpd',\n            field=models.BooleanField(default=False, verbose_name='是否开启时间同步服务'),\n        ),\n        migrations.AlterField(\n            model_name='host',\n            name='host_agent',\n            field=models.CharField(choices=[(0, '正常'), (1, '重启中'), (2, '启动失败'), (3, '部署中'), (4, '部署失败'), (5, '删除中')], default=3, help_text='主机Agent状态', max_length=16, verbose_name='主机Agent状态'),\n        ),\n        migrations.AlterField(\n            model_name='host',\n            name='monitor_agent',\n            field=models.CharField(choices=[(0, '正常'), (1, '重启中'), (2, '启动失败'), (3, '部署中'), (4, '部署失败'), (5, '删除中')], default=3, help_text='监控Agent状态', max_length=16, verbose_name='监控Agent状态'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0024_auto_20220226_1300.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-27 13:00\nimport os\n\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nfrom db_models.models import ToolInfo\nfrom tool.find_tools import find_tools_package\n\n\ndef update_tool_logo(apps, schema_editor):\n    tools = ToolInfo.objects.all()\n    for tool in tools:\n        folder_path = os.path.join(\n            settings.PROJECT_DIR, \"package_hub\", tool.tool_folder_path\n        )\n        if os.path.exists(os.path.join(folder_path, 'logo.svg')):\n            tool.logo = os.path.join(tool.tool_folder_path, 'logo.svg')\n            tool.save()\n    find_tools_package()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0023_auto_20220225_1747'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='toolinfo',\n            name='logo',\n            field=models.URLField(default='', verbose_name='logo url'),\n        ),\n        migrations.RunPython(update_tool_logo),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0025_alertrule_forbidden.py",
    "content": "# Generated by Django 3.1.4 on 2022-02-28 19:31\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0024_auto_20220226_1300'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='alertrule',\n            name='forbidden',\n            field=models.IntegerField(default=1, verbose_name='禁止删除'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0026_alertrule_hash_data.py",
    "content": "# Generated by Django 3.1.4 on 2022-03-03 13:28\n\nfrom django.db import migrations, models\nfrom uuid import uuid4\nfrom db_models.models import AlertRule\nimport hashlib\n\n\ndef get_hash_value(expr, severity):\n    data = expr + severity\n    hash_data = hashlib.md5(data.encode(encoding='UTF-8')).hexdigest()\n    return hash_data\n\n\ndef update_hash_data(apps, schema_editor):\n    alert_rulers = AlertRule.objects.all()\n    for alert in alert_rulers:\n        hash_data = get_hash_value(alert.expr, alert.severity)\n        alert.hash_data = hash_data\n        alert.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0025_alertrule_forbidden'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='alertrule',\n            name='hash_data',\n            field=models.CharField(null=True, blank=True, unique=True, verbose_name='唯一hash值', max_length=255),\n        ),\n        migrations.RunPython(update_hash_data),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0026_auto_20220303_1527.py",
    "content": "# Generated by Django 3.1.4 on 2022-03-03 15:27\n\nfrom django.db import migrations, models\nimport django.db.models.manager\nfrom db_models.models import Service\n\n\ndef combine_names(apps, schema_editor):\n    for service in Service.objects.all():\n        if service.service.app_name == \"hadoop\" and service.service_split == 0:\n            if service.service_instance_name.startswith(\"hadoop\"):\n                service.service_split = 1\n            else:\n                service.service_split = 2\n            service.save()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0025_alertrule_forbidden'),\n    ]\n\n    operations = [\n        migrations.AlterModelManagers(\n            name='service',\n            managers=[\n                ('split_objects', django.db.models.manager.Manager()),\n            ],\n        ),\n        migrations.AddField(\n            model_name='service',\n            name='service_split',\n            field=models.IntegerField(choices=[(1, '拆分前'), (2, '拆分后'), (0, '未拆分')], default=0, help_text='拆分服务前对象',\n                                      verbose_name='拆分服务前对象'),\n        ),\n        migrations.RunPython(combine_names)\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0027_merge_20220304_2000.py",
    "content": "# Generated by Django 3.1.4 on 2022-03-04 20:00\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0026_auto_20220303_1527'),\n        ('db_models', '0026_alertrule_hash_data'),\n    ]\n\n    operations = [\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0028_auto_20220304_2001.py",
    "content": "# Generated by Django 3.1.4 on 2022-03-04 20:01\n\nfrom django.db import migrations, models\n\nfrom db_models.models import MainInstallHistory, UpgradeHistory, \\\n    RollbackHistory\nfrom db_models.receivers.execution_record import create_execution_record\n\n\ndef update_execution_record(apps, schema_editor):\n    for model in [MainInstallHistory, UpgradeHistory, RollbackHistory]:\n        histories = model.objects.all()\n        for history in histories:\n            create_execution_record(history)\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0027_merge_20220304_2000'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='upgradehistory',\n            name='pre_upgrade_result',\n            field=models.JSONField(default=dict, verbose_name='升级前置信息'),\n        ),\n        migrations.AddField(\n            model_name='upgradehistory',\n            name='pre_upgrade_state',\n            field=models.IntegerField(choices=[(0, '等待升级'), (1, '正在升级'), (2, '升级成功'), (3, '升级失败')], default=0, verbose_name='升级前置结果'),\n        ),\n        migrations.RunPython(update_execution_record),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0029_auto_20230110_1739.py",
    "content": "# Generated by Django 3.1.4 on 2023-01-10 17:39\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        ('db_models', '0028_auto_20220304_2001'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='userprofile',\n            name='role',\n            field=models.CharField(blank=True, help_text='用户角色', max_length=128, null=True, verbose_name='用户角色'),\n        ),\n        migrations.AlterField(\n            model_name='alertrule',\n            name='hash_data',\n            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='唯一hash值'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0030_auto_20230711_1739.py",
    "content": "# Generated by Django 3.1.4 on 2023-05-11 09:33\n\nimport django.core.validators\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0029_auto_20230110_1739'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='WaitSelfHealing',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('service_name', models.CharField(default='', max_length=64, verbose_name='自愈服务名称')),\n                ('repair_ser', models.JSONField(default=dict, verbose_name='自愈缓存服务详情')),\n                ('repair_status', models.IntegerField(default=0, verbose_name='自愈缓存服务')),\n            ],\n            options={\n                'verbose_name': '自愈等待队列',\n                'verbose_name_plural': '自愈等待队列',\n                'db_table': 'omp_wait_self_healing',\n            },\n        ),\n        migrations.RemoveField(\n            model_name='selfhealinghistory',\n            name='env',\n        ),\n        migrations.RemoveField(\n            model_name='selfhealinghistory',\n            name='fingerprint',\n        ),\n        migrations.RemoveField(\n            model_name='selfhealinghistory',\n            name='service_en_type',\n        ),\n        migrations.RemoveField(\n            model_name='selfhealingsetting',\n            name='alert_count',\n        ),\n        migrations.RemoveField(\n            model_name='selfhealingsetting',\n            name='env',\n        ),\n        migrations.AddField(\n            model_name='selfhealingsetting',\n            name='fresh_rate',\n            field=models.IntegerField(default=10, validators=[django.core.validators.MaxValueValidator(60)], verbose_name='周期内采集告警消息频次'),\n        ),\n        migrations.AddField(\n            model_name='selfhealingsetting',\n            name='instance_tp',\n            field=models.IntegerField(choices=[(0, '启动'), (1, '重新启动')], default=0, verbose_name='实例类别'),\n        ),\n        migrations.AddField(\n            model_name='selfhealingsetting',\n            name='repair_instance',\n            field=models.JSONField(default=dict, verbose_name='修复实例'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='retain_path',\n            field=models.TextField(blank=True, default='/data/omp/data/backup/', max_length=256, null=True, verbose_name='文件保存路径'),\n        ),\n        migrations.AlterField(\n            model_name='selfhealinghistory',\n            name='healing_log',\n            field=models.TextField(default='', verbose_name='自愈日志'),\n        ),\n        migrations.AlterField(\n            model_name='selfhealingsetting',\n            name='max_healing_count',\n            field=models.IntegerField(default=5, validators=[django.core.validators.MaxValueValidator(20)], verbose_name='最多自愈操作次数'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/0031_auto_20230921_1128.py",
    "content": "# Generated by Django 3.1.4 on 2023-09-21 11:28\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('db_models', '0030_auto_20230711_1739'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='BackupCustom',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('field_k', models.CharField(max_length=64, verbose_name='自定义字段k')),\n                ('field_v', models.CharField(max_length=256, verbose_name='自定义字段v')),\n                ('notes', models.CharField(max_length=32, verbose_name='备注')),\n            ],\n            options={\n                'verbose_name': '自定义备份',\n                'verbose_name_plural': '自定义备份',\n                'db_table': 'omp_backup_custom',\n            },\n        ),\n        migrations.RemoveField(\n            model_name='backuphistory',\n            name='email_fail_reason',\n        ),\n        migrations.RemoveField(\n            model_name='backuphistory',\n            name='env_id',\n        ),\n        migrations.RemoveField(\n            model_name='backuphistory',\n            name='operation',\n        ),\n        migrations.RemoveField(\n            model_name='backuphistory',\n            name='send_email_result',\n        ),\n        migrations.RemoveField(\n            model_name='backupsetting',\n            name='env_id',\n        ),\n        migrations.AddField(\n            model_name='backuphistory',\n            name='extend_field',\n            field=models.JSONField(default=dict, verbose_name='冗余字段'),\n        ),\n        migrations.AddField(\n            model_name='backuphistory',\n            name='remote_path',\n            field=models.CharField(blank=True, max_length=256, null=True, verbose_name='远端备份路径'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='content',\n            field=models.CharField(default='', max_length=256, verbose_name='备份实例'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='message',\n            field=models.TextField(default='', max_length=512, verbose_name='返回信息'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='result',\n            field=models.IntegerField(choices=[(1, '成功'), (2, '备份中'), (0, '失败')], default=2, verbose_name='结果'),\n        ),\n        migrations.AlterField(\n            model_name='backuphistory',\n            name='retain_path',\n            field=models.TextField(default='/data/omp/data/backup/', max_length=256, verbose_name='文件保存路径'),\n        ),\n        migrations.AlterField(\n            model_name='backupsetting',\n            name='retain_path',\n            field=models.CharField(default='/data/omp/data/backup/', max_length=256, verbose_name='文件保存路径'),\n        ),\n        migrations.AddField(\n            model_name='backupsetting',\n            name='backup_custom',\n            field=models.ManyToManyField(to='db_models.BackupCustom'),\n        ),\n    ]\n"
  },
  {
    "path": "omp_server/db_models/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/db_models/mixins.py",
    "content": "\"\"\"\n模型混入类\n\"\"\"\nfrom django.db import models\n\n\nclass TimeStampMixin(models.Model):\n    \"\"\" 创建、更新时间混入类 \"\"\"\n\n    created = models.DateTimeField(\n        \"创建时间\", null=True, auto_now_add=True, help_text=\"创建时间\")\n    modified = models.DateTimeField(\n        \"更新时间\", null=True, auto_now=True, help_text=\"更新时间\")\n\n    class Meta:\n        abstract = True\n\n\nclass DeleteMixin(models.Model):\n    \"\"\" 软删除混入类 \"\"\"\n\n    is_deleted = models.BooleanField(default=False, help_text=\"软删除\")\n\n    def delete(self, using=None, soft=True, *args, **kwargs):\n        if soft:\n            self.is_deleted = True\n            self.save(using=using)\n        else:\n            return super(DeleteMixin, self).delete(using=using, *args, **kwargs)\n\n    class Meta:\n        abstract = True\n\n\nclass UpgradeStateChoices(models.IntegerChoices):\n\n    UPGRADE_WAIT = 0, \"等待升级\"\n    UPGRADE_ING = 1, \"正在升级\"\n    UPGRADE_SUCCESS = 2, \"升级成功\"\n    UPGRADE_FAIL = 3, \"升级失败\"\n\n\nclass UpgradeStateMixin(models.Model):\n\n    upgrade_state = models.IntegerField(\n        \"升级结果\",\n        choices=UpgradeStateChoices.choices,\n        default=UpgradeStateChoices.UPGRADE_WAIT\n    )\n\n    class Meta:\n        abstract = True\n\n\nclass RollbackStateChoices(models.IntegerChoices):\n    ROLLBACK_WAIT = 0, \"等待回滚\"\n    ROLLBACK_ING = 1, \"正在回滚\"\n    ROLLBACK_SUCCESS = 2, \"回滚成功\"\n    ROLLBACK_FAIL = 3, \"回滚失败\"\n\n\nclass RollBackStateMixin(models.Model):\n\n    rollback_state = models.IntegerField(\n        \"回滚结果\",\n        choices=RollbackStateChoices.choices,\n        default=RollbackStateChoices.ROLLBACK_WAIT\n    )\n\n    class Meta:\n        abstract = True\n"
  },
  {
    "path": "omp_server/db_models/models/__init__.py",
    "content": "from .backup import BackupSetting, BackupHistory, BackupCustom\nfrom .email import EmailSMTPSetting, ModuleSendEmailSetting\nfrom .env import Env\nfrom .execution import ExecutionRecord\nfrom .host import Host, HostOperateLog\nfrom .inspection import InspectionHistory, InspectionCrontab, InspectionReport\nfrom .install import MainInstallHistory, PreInstallHistory, \\\n    DetailInstallHistory, PostInstallHistory, DeploymentPlan\nfrom .monitor import MonitorUrl, Alert, Maintain, GrafanaMainPage, \\\n    AlertSendWaySetting\nfrom .product import Labels, UploadPackageHistory, ProductHub, \\\n    ApplicationHub, Product\nfrom .service import ServiceConnectInfo, ClusterInfo, Service, ServiceHistory\nfrom .threshold import HostThreshold, ServiceThreshold, ServiceCustomThreshold,AlertRule,Rule\nfrom .tool import ToolInfo, ToolExecuteMainHistory, ToolExecuteDetailHistory\nfrom .upload import UploadFileHistory\nfrom .user import UserProfile, OperateLog, UserLoginLog\nfrom .upgrade import UpgradeHistory, UpgradeDetail, RollbackHistory, \\\n    RollbackDetail\nfrom .self_heal import SelfHealingSetting, SelfHealingHistory, WaitSelfHealing\nfrom .custom_metric import CustomScript\n\n__all__ = [\n    # 邮箱设置\n    EmailSMTPSetting,\n    ModuleSendEmailSetting,\n    # 环境\n    Env,\n    # 主机\n    Host,\n    HostOperateLog,\n    # 巡检\n    InspectionHistory,\n    InspectionCrontab,\n    InspectionReport,\n    # 安装\n    MainInstallHistory,\n    PreInstallHistory,\n    PostInstallHistory,\n    DetailInstallHistory,\n    DeploymentPlan,\n    # 监控\n    MonitorUrl,\n    Alert,\n    Maintain,\n    GrafanaMainPage,\n    AlertSendWaySetting,\n    # 产品\n    Labels,\n    UploadPackageHistory,\n    ProductHub,\n    ApplicationHub,\n    Product,\n    # 服务\n    ServiceConnectInfo,\n    ClusterInfo,\n    Service,\n    ServiceHistory,\n    # 阈值\n    HostThreshold,\n    ServiceThreshold,\n    ServiceCustomThreshold,\n    # 用户\n    UserProfile,\n    OperateLog,\n    UserLoginLog,\n    # 升级\n    UpgradeHistory,\n    UpgradeDetail,\n    # 回滚\n    RollbackHistory,\n    RollbackDetail,\n    # 备份\n    BackupSetting,\n    BackupHistory,\n    # 自愈\n    SelfHealingHistory,\n    SelfHealingSetting,\n    WaitSelfHealing,\n    # 执行记录\n    ExecutionRecord,\n    # 小工具\n    ToolInfo,\n    ToolExecuteMainHistory,\n    ToolExecuteDetailHistory,\n    # 上传文件公共表\n    UploadFileHistory,\n    Alert,\n    AlertRule,\n    # 自定义脚本\n    CustomScript,\n    Alert,\n    AlertRule\n\n]\n"
  },
  {
    "path": "omp_server/db_models/models/backup.py",
    "content": "import os\n\nfrom django.db import models\n\nfrom db_models.mixins import TimeStampMixin\n\n\nclass BackupCustom(models.Model):\n    field_k = models.CharField(\"自定义字段k\", max_length=64, null=False)\n    field_v = models.CharField(\"自定义字段v\", max_length=256, null=False)\n    notes = models.CharField(\"备注\", max_length=32, default=\"\")\n\n    class Meta:\n        db_table = 'omp_backup_custom'\n        verbose_name = verbose_name_plural = '自定义备份'\n\n\nclass BackupSetting(models.Model):\n    # 校验是否安装该服务，支持的服务\n    backup_instances = models.JSONField(\"备份服务实例名称\", default=dict)\n    is_on = models.BooleanField(\"是否开启\", default=False)\n    crontab_detail = models.JSONField(\"定时任务详情\")\n    retain_day = models.IntegerField(\"文件保存天数\", default=1)\n    retain_path = models.CharField(\"文件保存路径\", default=\"/data/omp/data/backup/\",\n                                   null=False, max_length=256)\n    backup_custom = models.ManyToManyField(BackupCustom)\n\n    class Meta:\n        db_table = 'omp_backup_setting'\n        verbose_name = verbose_name_plural = '备份设置'\n\n\nclass BackupHistory(TimeStampMixin):\n    backup_name = models.CharField(\"备份任务名称\", max_length=128)\n    content = models.CharField('备份实例', max_length=256, default=\"\")\n    SUCCESS = 1\n    ING = 2\n    FAIL = 0\n    RESULT_CHOICES = (\n        (SUCCESS, \"成功\"),\n        (ING, \"备份中\"),\n        (FAIL, \"失败\")\n    )\n    result = models.IntegerField(\"结果\", choices=RESULT_CHOICES, default=ING)\n    message = models.TextField(\"返回信息\", default=\"\", max_length=512)\n    file_name = models.CharField(\"备份文件名\", max_length=128, default=\"\")\n    file_size = models.CharField(\"备份文件大小, MB\", default=\"0\", max_length=64)\n    expire_time = models.DateTimeField(\"过期时间\", null=True)\n    file_deleted = models.BooleanField(\"文件是否被删除\", default=False)\n    create_time = models.DateTimeField(\"记录生成时间\", auto_now_add=True)\n    retain_path = models.TextField(\n        \"文件保存路径\", default=\"/data/omp/data/backup/\", max_length=256, null=True, blank=True\n    )\n    remote_path = models.CharField(\"远端备份路径\", max_length=256, null=True, blank=True)\n    extend_field = models.JSONField(\"冗余字段\", default=dict)\n\n    class Meta:\n        db_table = 'omp_backup_history'\n        verbose_name = verbose_name_plural = '备份历史记录'\n\n    def fetch_file_kwargs(self):\n        file_path = os.path.join(self.retain_path, self.file_name)\n        return {\"path\": file_path}\n"
  },
  {
    "path": "omp_server/db_models/models/custom_metric.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author:' Lingyang.guo'\n# CreateDate: 14:08\nimport os\n\nfrom django.db import models\n\nfrom db_models.mixins import TimeStampMixin\n\n\nclass CustomScript(TimeStampMixin):\n    \"\"\"\n    自定义脚本\n    \"\"\"\n    objects = None\n    script_name = models.CharField(\n        \"脚本名称\", max_length=32, null=False, help_text=\"自定义脚本名称\")\n    script_content = models.TextField(\n        \"脚本内容\", max_length=10000, null=False, help_text=\"脚本内容\")\n    metrics = models.JSONField(\"指标列表\", null=False, help_text=\"指标列表\")\n    metric_num = models.IntegerField(\n        \"metric数量\", help_text=\"metric数量\", null=True)\n    scrape_interval = models.IntegerField(\n        \"探测周期\", default=60, help_text=\"prometheus探测周期\")\n    enabled = models.BooleanField(\"是否启用\", default=1, help_text=\"1位启用，0为禁用\")\n    description = models.TextField(\n        \"脚本描述\", max_length=1024, null=False, help_text=\"脚本描述\")\n    bound_hosts = models.JSONField(\"已下发主机列表\", help_text=\"已下发主机列表\")\n\n    def valid_upload_file(self, *args, **kwargs):  # NOQA\n        return \"package_hub/custom_scripts\"\n\n    def upload_file_url(self, file_name, **kwargs):  # NOQA\n        return os.path.join(\"package_hub/custom_scripts\", file_name)\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_custom_script\"\n        verbose_name = verbose_name_plural = \"自定义脚本\"\n"
  },
  {
    "path": "omp_server/db_models/models/email.py",
    "content": "import logging\nimport os\n\nimport requests\nfrom django.db import models\nfrom ruamel import yaml\n\nfrom .monitor import AlertSendWaySetting\n\n\nlogger = logging.getLogger('server')\n\n\nclass EmailSMTPSetting(models.Model):\n    email_host = models.EmailField(\"邮箱SMTP主机地址:smtp.163.com\", null=True)\n    email_port = models.IntegerField(\"邮箱SMTP端口:465\", null=True)\n    email_host_user = models.CharField(\n        \"邮箱SMTP服务器用户名:a@163.com\", max_length=128, null=True)\n    email_host_password = models.CharField(\n        \"邮箱SMTP服务器秘钥\", max_length=128, null=True)\n\n    class Meta:\n        db_table = 'omp_email_smtp_setting'\n        verbose_name = verbose_name_plural = '平台邮件服务器配置'\n\n    alert_manage_key = {\n        \"EMAIL_SEND\": \"email_host_user\",\n        \"smtp_auth_username\": \"email_host_user\",\n        \"EMAIL_SEND_PASSWORD\": \"email_host_password\",\n        \"smtp_auth_password\": \"email_host_password\",\n        \"EMAIL_ADDRESS\": \"email_url\",\n    }\n\n    def get_dict(self):\n        return {\n            \"host\": self.email_host,\n            \"port\": self.email_port,\n            \"username\": self.email_host_user,\n            \"password\": self.email_host_password\n        }\n\n    @property\n    def email_url(self):\n        if hasattr(self, \"email\"):\n            return self.email\n        email_send = AlertSendWaySetting.objects.filter(\n            way_name=\"email\").first()\n        email = \"\"\n        if email_send and email_send.used:\n            email = email_send.server_url.split(\",\")[0]\n        setattr(self, \"email\", email)\n        return email\n\n    def update_config(self, key):\n        if key in {\"EMAIL_SEND_USER\", \"smtp_from\"}:\n            value = self.email_host_user\n        elif key in {\"SMTP_SMARTHOST\", \"smtp_smarthost\"}:\n            value = f\"{self.email_host}:{self.email_port}\"\n        elif key in self.alert_manage_key:\n            value = getattr(self, self.alert_manage_key.get(key))\n        elif key in {\"smtp_hello\", \"SMTP_HELLO\"}:\n            value = \".\".join(self.email_host.split(\".\")[-2:])\n        elif key == \"RECEIVER\":\n            value = \"cloudwise\"\n        else:\n            value = None\n        return value\n\n    @staticmethod\n    def reload_alert_manage():\n        from promemonitor.alertmanager import Alertmanager\n        alertmanager_url = Alertmanager.get_alertmanager_config()\n        _basic_auth = Alertmanager().basic_auth\n        try:\n            response = requests.post(f\"http://{alertmanager_url}/-/reload\", auth=_basic_auth)  # NOQA\n        except Exception as e:\n            logger.error(f\"重载alertmanager配置出错！错误信息：{str(e)}\")\n            return False\n        if response.status_code == 200:\n            return True\n        return False\n\n    def set_omp_conf(self):\n        # 更新omp.yaml\n        from utils.parse_config import config_file_path\n        with open(config_file_path, \"r\", encoding=\"utf8\") as fp:\n            content = fp.read()\n        my_yaml = yaml.YAML()\n        code = my_yaml.load(content)\n        for key, value in code.get(\"alert_manager\", {}).items():\n            if key == \"send_email\":\n                value = bool(self.email_url)\n            else:\n                value = self.update_config(key)\n            if value is None:\n                continue\n            code[\"alert_manager\"][key] = value\n        with open(config_file_path, \"w\", encoding=\"utf8\") as fp:\n            my_yaml.dump(code, fp)\n\n    def set_alert_manage_config(self):\n        # 更新alert manage配置\n        from omp_server.settings import PROJECT_DIR\n        config_path = os.path.join(\n            PROJECT_DIR, \"component/alertmanager/conf/alertmanager.yml\")\n        with open(config_path, \"r\", encoding=\"utf8\") as fp:\n            code = yaml.load(fp.read(), yaml.Loader)\n        for key, value in code.get(\"global\", {}).items():\n            value = self.update_config(key)\n            if value is None:\n                continue\n            code[\"global\"][key] = value\n\n        if not self.email_url:\n            for receiver in code.get(\"receivers\"):\n                if \"email_configs\" in receiver:\n                    code.get(\"receivers\")[0].pop(\"email_configs\", {})\n        else:\n            email_configs = [\n                {\n                    \"send_resolved\": bool(self.email_url),\n                    \"to\": self.email_url,\n                    \"headers\": {\"Subject\": \"OMP ALERT\"},\n                    \"html\": '{{ template \\\"email.to.html\\\" . }}'\n                }\n            ]\n            code.get(\"receivers\")[0][\"email_configs\"] = email_configs\n        code[\"templates\"] = [\n            f\"{PROJECT_DIR}/component/alertmanager/templates/*tmpl\"]\n        with open(config_path, \"w\", encoding=\"utf8\") as fp:\n            yaml.dump(code, fp, Dumper=yaml.RoundTripDumper)\n        return self.reload_alert_manage()\n\n    def update_setting_config(self):\n        \"\"\"\n        更新配置文件\n        :return:\n        \"\"\"\n        self.set_omp_conf()\n        return self.set_alert_manage_config(), self.email_url\n\n\nclass ModuleSendEmailSetting(models.Model):\n    module = models.CharField(\n        \"功能模块:BackupSetting,JobSetting\", max_length=64)\n    send_email = models.BooleanField(\"是否开启邮件推送\", default=False)\n    to_users = models.TextField(\"邮箱接收用户\", default=\"\")\n    env_id = models.IntegerField(\"环境id\", default=1)\n\n    class Meta:\n        db_table = 'omp_module_email_send_setting'\n        verbose_name = verbose_name_plural = '平台邮件发送账号配置'\n\n    @classmethod\n    def get_email_settings(cls, env_id, module):\n        try:\n            _obj = cls.objects.get(env_id=env_id, module=module)\n        except Exception as e:\n            logger.error(e)\n            logger.error(f\"module: {module}, env_id:{env_id}邮箱配置不存在\")\n            return None\n        return _obj\n\n    @classmethod\n    def update_email_settings(cls, env_id, module, send_email, to_users):\n        _obj, _ = cls.objects.get_or_create(env_id=env_id, module=module)\n        _obj.send_email = send_email\n        _obj.to_users = to_users\n        _obj.save()\n"
  },
  {
    "path": "omp_server/db_models/models/env.py",
    "content": "from django.db import models\n\n\nclass Env(models.Model):\n    \"\"\" 环境表 \"\"\"\n\n    objects = None\n    name = models.CharField(\n        \"环境名称\", max_length=256, help_text=\"环境名称\")\n    created = models.DateTimeField(\n        '创建时间', null=True, auto_now_add=True, help_text='创建时间')\n\n    class Meta:\n        db_table = \"omp_env\"\n        verbose_name = verbose_name_plural = \"环境\"\n"
  },
  {
    "path": "omp_server/db_models/models/execution.py",
    "content": "from django.db import models\nfrom db_models.mixins import TimeStampMixin\n\n\nclass ModuleChoices(models.TextChoices):\n    INSTALL = \"MainInstallHistory\", \"安装\"\n    UPGRADE = \"UpgradeHistory\", \"升级\"\n    ROLLBACK = \"RollbackHistory\", \"回滚\"\n\n\nclass StateChoices(models.TextChoices):\n\n    MAININSTALLHISTORY_0 = \"INSTALL_STATUS_READY\", \"等待安装\"\n    MAININSTALLHISTORY_1 = \"INSTALL_STATUS_INSTALLING\", \"正在安装\"\n    MAININSTALLHISTORY_2 = \"INSTALL_STATUS_SUCCESS\", \"安装成功\"\n    MAININSTALLHISTORY_3 = \"INSTALL_STATUS_FAILED\", \"安装失败\"\n    MAININSTALLHISTORY_4 = \"INSTALL_STATUS_REGISTER\", \"正在注册\"\n\n    UPGRADEHISTORY_0 = \"UPGRADE_WAIT\", \"等待升级\"\n    UPGRADEHISTORY_1 = \"UPGRADE_ING\", \"正在升级\"\n    UPGRADEHISTORY_2 = \"UPGRADE_SUCCESS\", \"升级成功\"\n    UPGRADEHISTORY_3 = \"UPGRADE_FAIL\", \"升级失败\"\n\n    ROLLBACKHISTORY_0 = \"ROLLBACK_WAIT\", \"等待回滚\"\n    ROLLBACKHISTORY_1 = \"ROLLBACK_ING\", \"正在回滚\"\n    ROLLBACKHISTORY_2 = \"ROLLBACK_SUCCESS\", \"回滚成功\"\n    ROLLBACKHISTORY_3 = \"ROLLBACK_FAIL\", \"回滚失败\"\n\n\nclass ExecutionRecord(TimeStampMixin):\n    # 通过django信号同步生成记录(create_execution_record)\n\n    module = models.CharField(\n        \"执行记录的module名\",\n        max_length=32,\n        choices=ModuleChoices.choices,\n        default=ModuleChoices.INSTALL\n    )\n    # UpgradeHistory.id & MainInstallHistory.operation_uuid\n    module_id = models.CharField(\"执行记录的id\", max_length=36, default=\"0\")\n    operator = models.CharField(\n        \"操作用户\",\n        max_length=150,\n        default=\"admin\"\n    )\n    state = models.CharField(\n        \"状态\",\n        max_length=32,\n        choices=StateChoices.choices,\n        default=StateChoices.MAININSTALLHISTORY_0\n    )\n    count = models.IntegerField(\"服务数量\", default=0)\n    end_time = models.DateTimeField(\"更新时间\", null=True)\n\n    class Meta:\n        db_table = \"omp_execution_record\"\n        verbose_name = verbose_name_plural = '执行记录'\n"
  },
  {
    "path": "omp_server/db_models/models/host.py",
    "content": "from django.db import models\n\nfrom db_models.mixins import TimeStampMixin, DeleteMixin\nfrom .env import Env\n\n\nclass Host(TimeStampMixin, DeleteMixin):\n    \"\"\" 主机表 \"\"\"\n\n    AGENT_RUNNING = 0\n    AGENT_RESTART = 1\n    AGENT_START_ERROR = 2\n    AGENT_DEPLOY_ING = 3\n    AGENT_DEPLOY_ERROR = 4\n    AGENT_DEPLOY_DELETE = 5\n    AGENT_STATUS_CHOICES = (\n        (AGENT_RUNNING, \"正常\"),\n        (AGENT_RESTART, \"重启中\"),\n        (AGENT_START_ERROR, \"启动失败\"),\n        (AGENT_DEPLOY_ING, \"部署中\"),\n        (AGENT_DEPLOY_ERROR, \"部署失败\"),\n        (AGENT_DEPLOY_DELETE, \"删除中\"),\n    )\n\n    INIT_SUCCESS = 0\n    INIT_NOT_EXECUTED = 1\n    INIT_EXECUTING = 2\n    INIT_FAILED = 3\n    INIT_STATUS_CHOICES = (\n        (INIT_SUCCESS, \"成功\"),\n        (INIT_NOT_EXECUTED, \"未执行\"),\n        (INIT_EXECUTING, \"执行中\"),\n        (INIT_FAILED, \"失败\")\n    )\n    NTPDATE_INSTALL_SUCCESS = 0\n    NTPDATE_NOT_INSTALL = 1\n    NTPDATE_INSTALLING = 2\n    NTPDATE_INSTALL_FAILED = 3\n    NTPDATE_STATUS_CHOICES = (\n        (NTPDATE_INSTALL_SUCCESS, \"执行成功\"),\n        (NTPDATE_NOT_INSTALL, \"未执行\"),\n        (NTPDATE_INSTALLING, \"执行中\"),\n        (NTPDATE_INSTALL_FAILED, \"执行失败\")\n    )\n\n    objects = None\n    instance_name = models.CharField(\n        \"实例名\", max_length=64, help_text=\"实例名\", unique=True)\n    ip = models.GenericIPAddressField(\n        \"IP地址\", help_text=\"IP地址\", unique=True)\n    port = models.IntegerField(\n        \"SSH端口\", default=22, help_text=\"SSH端口\")\n    username = models.CharField(\n        \"SSH登录用户名\", max_length=256, help_text=\"SSH登录用户名\")\n    password = models.CharField(\n        \"SSH登录密码\", max_length=256, help_text=\"SSH登录密码\")\n    data_folder = models.CharField(\n        \"数据分区\", max_length=256, default=\"/data\", help_text=\"数据分区\")\n    service_num = models.IntegerField(\n        \"服务个数\", default=0, help_text=\"服务个数\")\n    alert_num = models.IntegerField(\n        \"告警次数\", default=0, help_text=\"告警次数\")\n    operate_system = models.CharField(\n        \"操作系统\", max_length=128, help_text=\"操作系统\")\n    host_name = models.CharField(\n        \"主机名\", max_length=64,\n        blank=True, null=True, help_text=\"主机名\")\n    memory = models.IntegerField(\n        \"内存\", blank=True, null=True, help_text=\"内存\")\n    cpu = models.IntegerField(\n        \"CPU\", blank=True, null=True, help_text=\"CPU\")\n    disk = models.JSONField(\n        \"磁盘\", blank=True, null=True, help_text=\"磁盘\")\n    host_agent = models.CharField(\n        \"主机Agent状态\", max_length=16, help_text=\"主机Agent状态\",\n        choices=AGENT_STATUS_CHOICES, default=AGENT_DEPLOY_ING)\n    monitor_agent = models.CharField(\n        \"监控Agent状态\", max_length=16, help_text=\"监控Agent状态\",\n        choices=AGENT_STATUS_CHOICES, default=AGENT_DEPLOY_ING)\n    host_agent_error = models.CharField(\n        \"主机Agent异常信息\", max_length=256,\n        blank=True, null=True, help_text=\"主机Agent异常信息\")\n    monitor_agent_error = models.CharField(\n        \"监控Agent异常信息\", max_length=256,\n        blank=True, null=True, help_text=\"监控Agent异常信息\")\n    is_maintenance = models.BooleanField(\n        \"维护模式\", default=False, help_text=\"维护模式\")\n    agent_dir = models.CharField(\n        \"Agent安装目录\", max_length=256, default=\"/data\", help_text=\"Agent安装目录\")\n    env = models.ForeignKey(\n        Env, null=True, on_delete=models.SET_NULL,\n        verbose_name=\"环境\", help_text=\"环境\")\n    init_status = models.CharField(\n        \"主机初始化状态\", max_length=16, help_text=\"主机初始化状态\",\n        choices=INIT_STATUS_CHOICES, default=INIT_NOT_EXECUTED)\n    use_ntpd = models.BooleanField(\"是否开启时间同步服务\", default=False)\n    ntpd_server = models.GenericIPAddressField(\"时间同步服务器地址\", default=\"127.0.0.1\")\n    ntpdate_install_status = models.CharField(\n        \"安装ntpdate状态\", max_length=16, choices=NTPDATE_STATUS_CHOICES, default=NTPDATE_NOT_INSTALL\n    )\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_host\"\n        verbose_name = verbose_name_plural = \"主机\"\n        ordering = (\"-created\",)\n\n\nclass HostOperateLog(models.Model):\n    \"\"\" 主机操作记录表 \"\"\"\n\n    objects = None\n    username = models.CharField(\n        \"操作用户\", max_length=128, help_text=\"操作用户\")\n    description = models.CharField(\n        \"用户行为描述\", max_length=1024, help_text=\"用户行为描述\")\n    result = models.CharField(\n        \"操作结果\", max_length=1024, default=\"success\", help_text=\"操作结果\")\n    created = models.DateTimeField(\n        '发生时间', null=True, auto_now_add=True, help_text='发生时间')\n    host = models.ForeignKey(\n        Host, null=True, on_delete=models.SET_NULL, verbose_name=\"主机\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_host_operate_log\"\n        verbose_name = verbose_name_plural = \"主机操作记录\"\n        ordering = (\"-created\",)\n"
  },
  {
    "path": "omp_server/db_models/models/inspection.py",
    "content": "import os\n\nfrom django.db import models\n\nfrom .env import Env\n\n\nclass InspectionHistory(models.Model):\n    \"\"\"巡检记录历史表\"\"\"\n    objects = None\n    id = models.AutoField(primary_key=True, unique=True, help_text=\"自增主键\")\n    inspection_name = models.CharField(\n        max_length=256, null=False, blank=False, help_text=\"报告名称:巡检类型名称\")\n    inspection_type = models.CharField(\n        max_length=32, default=\"service\", help_text=\"巡检类型，service、host、deep\")\n    inspection_status = models.IntegerField(\n        default=0, help_text=\"巡检状态:1-进行中；2-成功；3-失败\")\n    execute_type = models.CharField(\n        max_length=32, null=False, blank=False, default=\"man\",\n        help_text=\"执行方式: 手动-man；定时：auto\")\n    inspection_operator = models.CharField(\n        max_length=16, null=False, blank=False, help_text=\"操作人员-当前登录人\")\n    hosts = models.JSONField(\n        null=True, blank=True, help_text=\"巡检主机:['10.0.9.158']\")\n    services = models.JSONField(\n        null=True, blank=True, help_text=\"巡检组件: [8,9]\")\n    start_time = models.DateTimeField(auto_now_add=True, help_text=\"开始时间\")\n    end_time = models.DateTimeField(null=True, help_text=\"结束时间，后端后补\")\n    duration = models.IntegerField(default=0, help_text=\"巡检用时：单位s，后端后补\")\n    env = models.ForeignKey(\n        Env, null=True, on_delete=models.SET_NULL, verbose_name=\"当前环境id\",\n        help_text=\"当前环境id\")\n    NOT_SEND = 3\n    SUCCESS = 1\n    ING = 2\n    FAIL = 0\n    SEND_RESULT_CHOICES = (\n        (\"发送成功\", SUCCESS),\n        (\"发送中\", ING),\n        (\"发送失败\", FAIL),\n        (\"未发送\", NOT_SEND)\n    )\n    send_email_result = models.IntegerField(\n        \"邮件推送状态\", choices=SEND_RESULT_CHOICES, default=NOT_SEND)\n\n    class Meta:\n        db_table = 'inspection_history'\n        verbose_name = verbose_name_plural = \"巡检记录历史表\"\n        ordering = (\"-start_time\",)\n\n    def send_email_content(self):\n        return f\"\"\"\n                巡检任务名称：{self.inspection_name}\\n\n                巡检时间：{self.start_time.strftime(\"%Y-%m-%d %H:%M:%S\")}\n                \"\"\"\n\n    def fetch_file_kwargs(self):\n        from omp_server.settings import PROJECT_DIR\n        inspection_report = InspectionReport.objects.filter(\n            inst_id=self.id).first()\n        file_path = os.path.join(\n            PROJECT_DIR, f\"data/inspection_file/{inspection_report.file_name}\")\n        return {\"path\": file_path}\n\n\nclass InspectionCrontab(models.Model):\n    \"\"\"巡检任务 定时配置表\"\"\"\n    j_type = (\n        (0, \"深度分析\"),\n        (1, \"主机巡检\"),\n        (2, \"组件巡检\")\n    )\n\n    objects = None\n    id = models.AutoField(primary_key=True, unique=True, help_text=\"自增主键\")\n    job_type = models.IntegerField(\n        default=0, choices=j_type, help_text=\"任务类型：0-深度分析 1-主机巡检 2-组建巡检\")\n    job_name = models.CharField(\n        max_length=128, null=False, blank=False, help_text=\"任务名称\")\n    is_start_crontab = models.IntegerField(\n        default=0, help_text=\"是否开启定时任务：0-开启，1-关闭\")\n    crontab_detail = models.JSONField(help_text=\"定时任务详情\")\n    create_date = models.DateTimeField(auto_now_add=True, help_text=\"创建时间\")\n    update_time = models.DateTimeField(auto_now=True, help_text=\"修改时间\")\n    env = models.ForeignKey(\n        Env, null=True, on_delete=models.SET_NULL, verbose_name=\"环境\",\n        help_text=\"环境\")\n\n    class Meta:\n        \"\"\"表名等信息\"\"\"\n        db_table = 'inspection_crontab'\n        verbose_name = verbose_name_plural = \"巡检任务 定时配置表\"\n        ordering = (\"id\",)\n\n\nclass InspectionReport(models.Model):\n    \"\"\"巡检 报告\"\"\"\n    objects = None\n    id = models.AutoField(primary_key=True, unique=True, help_text=\"自增主键\")\n    file_name = models.CharField(\n        max_length=128, null=True, blank=True, help_text=\"导出文件名\")\n    scan_info = models.JSONField(null=True, blank=True, help_text=\"扫描统计\")\n    scan_result = models.JSONField(null=True, blank=True, help_text=\"分析结果\")\n    risk_data = models.JSONField(null=True, blank=True, help_text=\"风险指标\")\n    host_data = models.JSONField(null=True, blank=True, help_text=\"主机列表\")\n    serv_plan = models.JSONField(null=True, blank=True, help_text=\"服务平面图\")\n    serv_data = models.JSONField(null=True, blank=True, help_text=\"服务列表\")\n    inst_id = models.OneToOneField(\n        InspectionHistory, null=True, on_delete=models.SET_NULL,\n        verbose_name=\"巡检记录历史表\", help_text=\"巡检记录历史表id\")\n\n    class Meta:\n        \"\"\"表名等信息\"\"\"\n        db_table = 'inspection_report'\n        verbose_name = verbose_name_plural = \"巡检任务 报告数据\"\n        ordering = (\"id\",)\n"
  },
  {
    "path": "omp_server/db_models/models/install.py",
    "content": "from django.db import models\n\nfrom db_models.mixins import TimeStampMixin\nfrom .service import Service\n\n\nclass MainInstallHistory(TimeStampMixin):\n    \"\"\" 主安装记录表 \"\"\"\n\n    objects = None\n    INSTALL_STATUS_READY = 0\n    INSTALL_STATUS_INSTALLING = 1\n    INSTALL_STATUS_SUCCESS = 2\n    INSTALL_STATUS_FAILED = 3\n    INSTALL_STATUS_REGISTER = 4\n    INSTALL_STATUS_CHOICES = (\n        (INSTALL_STATUS_READY, \"待安装\"),\n        (INSTALL_STATUS_INSTALLING, \"安装中\"),\n        (INSTALL_STATUS_SUCCESS, \"安装成功\"),\n        (INSTALL_STATUS_FAILED, \"安装失败\"),\n        (INSTALL_STATUS_REGISTER, \"正在注册\"),\n    )\n    operator = models.CharField(\n        \"操作用户\", max_length=32,\n        null=False, blank=False, default=\"admin\", help_text=\"用户\"\n    )\n    operation_uuid = models.CharField(\n        \"部署操作uuid\", max_length=36,\n        null=False, blank=False, help_text=\"部署操作uuid\")\n    task_id = models.CharField(\n        \"异步任务id\", max_length=36,\n        null=True, blank=True, help_text=\"异步任务id\")\n    # 直接代表整体的安装状态\n    install_status = models.IntegerField(\n        \"安装状态\", choices=INSTALL_STATUS_CHOICES,\n        default=0, help_text=\"安装状态\")\n    install_args = models.JSONField(\n        \"主表安装信息\", null=True, blank=True, help_text=\"主表安装信息\")\n    install_log = models.TextField(\"MAIN安装日志\", help_text=\"MAIN安装日志\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_main_install_history\"\n        verbose_name = verbose_name_plural = \"主安装记录表\"\n\n    @property\n    def execution_record_state(self):\n        # 执行记录使用\n        return self.install_status\n\n    def operate_count(self, exclude_service_ids=None):\n        # 安装服务个数, exclude_service_ids删除服务前触发\n        queryset = self.detailinstallhistory_set.filter(\n            service__isnull=False\n        ).exclude(service__service_split=1)\n        if exclude_service_ids:\n            queryset = queryset.exclude(service_id__in=exclude_service_ids)\n        return queryset.values(\"service_id\").distinct().count()\n\n    @property\n    def module_id(self):\n        return self.operation_uuid\n\n\nclass PreInstallHistory(TimeStampMixin):\n    \"\"\" 记录安装过程中主机的操作记录内容 \"\"\"\n    objects = None\n    main_install_history = models.ForeignKey(\n        MainInstallHistory, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"关联主安装记录\")\n    name = models.CharField(\n        \"名称\", max_length=32, blank=False, null=False,\n        default=\"初始化安装流程\", help_text=\"名称\"\n    )\n    ip = models.GenericIPAddressField(\n        \"主机ip地址\", blank=False, null=False, help_text=\"主机ip地址\")\n    install_flag = models.IntegerField(\n        \"安装标志\", default=0,\n        help_text=\"0-待安装 1-安装中 2-安装成功 3-安装失败\")\n    install_log = models.TextField(\"主机层安装日志\", help_text=\"主机层安装日志\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_pre_install_history\"\n        verbose_name = verbose_name_plural = \"前置安装记录\"\n\n\nclass PostInstallHistory(TimeStampMixin):\n    \"\"\" 记录安装完成后的其他操作，如注册、tengine、nacos更新 \"\"\"\n    objects = None\n    main_install_history = models.ForeignKey(\n        MainInstallHistory, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"关联主安装记录\")\n    name = models.CharField(\n        \"名称\", max_length=32, blank=False, null=False,\n        default=\"安装后续任务\", help_text=\"名称\"\n    )\n    ip = models.CharField(\n        \"fake主机ip地址\", blank=False, null=False, default=\"postAction\",\n        max_length=128, help_text=\"主机ip地址\")\n    install_flag = models.IntegerField(\n        \"安装标志\", default=0,\n        help_text=\"0-待执行 1-执行中 2-执行成功 3-执行失败\")\n    install_log = models.TextField(\"安装后续任务日志\", help_text=\"安装后续任务日志\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_post_install_history\"\n        verbose_name = verbose_name_plural = \"后置安装记录\"\n\n\nclass DetailInstallHistory(TimeStampMixin):\n    \"\"\"\n    安装细节表，针对单服务\n    在下发安装任务之前，需要对安装顺序进行排序确定\n    \"\"\"\n\n    objects = None\n    INSTALL_STATUS_READY = 0\n    INSTALL_STATUS_INSTALLING = 1\n    INSTALL_STATUS_SUCCESS = 2\n    INSTALL_STATUS_FAILED = 3\n    INSTALL_STEP_CHOICES = (\n        (INSTALL_STATUS_READY, \"待安装\"),\n        (INSTALL_STATUS_INSTALLING, \"安装中\"),\n        (INSTALL_STATUS_SUCCESS, \"安装成功\"),\n        (INSTALL_STATUS_FAILED, \"安装失败\"),\n    )\n    # 若修改on_delete，需处理update_execution_record\n    service = models.ForeignKey(\n        Service, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"关联服务对象\")\n    main_install_history = models.ForeignKey(\n        MainInstallHistory, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"关联主安装记录\")\n    # 单服务安装步骤:\n    install_step_status = models.IntegerField(\n        \"安装步骤状态\", choices=INSTALL_STEP_CHOICES,\n        default=0, help_text=\"安装步骤状态\")\n\n    # 安装细节标记及日志\n    send_flag = models.IntegerField(\n        \"发包状态\", default=0,\n        help_text=\"0-待发送 1-发送中 2-发送成功 3-发送失败\")\n    send_msg = models.TextField(\"发包日志\", help_text=\"发包日志\")\n    unzip_flag = models.IntegerField(\n        \"解压包状态\", default=0,\n        help_text=\"0-待解压 1-解压中 2-解压成功 3-解压失败\")\n    unzip_msg = models.TextField(\"解压日志\", help_text=\"解压日志\")\n    install_flag = models.IntegerField(\n        \"安装执行状态\", default=0,\n        help_text=\"0-待安装 1-安装中 2-安装成功 3-安装失败\")\n    install_msg = models.TextField(\"安装日志\", help_text=\"安装日志\")\n    init_flag = models.IntegerField(\n        \"初始化执行状态\", default=0,\n        help_text=\"0-待初始化 1-初始化中 2-初始化成功 3-初始化失败\")\n    init_msg = models.TextField(\"初始化日志\", help_text=\"初始化日志\")\n    start_flag = models.IntegerField(\n        \"启动执行状态\", default=0,\n        help_text=\"0-待启动 1-启动中 2-启动成功 3-启动失败\")\n    start_msg = models.TextField(\"启动日志\", help_text=\"启动日志\")\n    install_detail_args = models.JSONField(\n        \"详情表安装信息\", null=True, blank=True, help_text=\"详情表安装信息\")\n    post_action_flag = models.IntegerField(\n        \"安装后执行动作标记\", default=0,\n        help_text=\"0-待执行 1-执行中 2-执行成功 3-执行失败 4-无需执行\")\n    post_action_msg = models.TextField(\n        \"安装后执行动作日志\", default=\"\", help_text=\"安装后执行动作日志\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_detail_install_history\"\n        verbose_name = verbose_name_plural = \"安装记录详情表\"\n\n    def __str__(self):\n        return self.service.service_instance_name + f\"({self.service.ip})\"\n\n\nclass DeploymentPlan(models.Model):\n    \"\"\" 部署计划 \"\"\"\n    plan_name = models.CharField(\n        \"部署计划名称\", max_length=32,\n        null=False, blank=False, help_text=\"部署计划名称\")\n    host_num = models.IntegerField(\n        \"主机数量\", default=0, help_text=\"主机数量\")\n    product_num = models.IntegerField(\n        \"产品数量\", default=0, help_text=\"产品数量\")\n    service_num = models.IntegerField(\n        \"服务数量\", default=0, help_text=\"服务数量\")\n    create_user = models.CharField(\n        \"创建用户\", max_length=16,\n        null=False, blank=False, help_text=\"创建用户\")\n    operation_uuid = models.CharField(\n        \"部署操作uuid\", max_length=36,\n        null=False, blank=False, help_text=\"部署操作uuid\")\n    created = models.DateTimeField(\n        \"创建时间\", null=True, auto_now_add=True, help_text=\"创建时间\")\n\n    class Meta:\n        db_table = 'omp_deployment_plan'\n        verbose_name = verbose_name_plural = '部署计划'\n"
  },
  {
    "path": "omp_server/db_models/models/monitor.py",
    "content": "from django.db import models\n\nfrom .env import Env\n\n\nclass MonitorUrl(models.Model):\n    \"\"\" 用户操作记录表 \"\"\"\n\n    objects = None\n    name = models.CharField(\n        \"监控类别\", max_length=32, unique=True, help_text=\"监控类别\")\n    monitor_url = models.CharField(\n        \"请求地址\", max_length=128, help_text=\"请求地址\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_promemonitor_url\"\n        verbose_name = verbose_name_plural = \"监控地址记录\"\n\n\nclass Alert(models.Model):\n    \"\"\"告警数据表\"\"\"\n\n    objects = None\n    is_read = models.IntegerField(\n        \"已读\", default=0, help_text=\"此消息是否已读，0-未读；1-已读\")\n    alert_type = models.CharField(\n        \"告警类型\", max_length=32, default=\"\", help_text=\"告警类型，主机host，服务service\")\n    alert_host_ip = models.CharField(\n        \"告警主机ip\", max_length=64, default=\"\", help_text=\"告警来源主机ip\")\n    alert_service_name = models.CharField(\n        \"告警服务名称\", max_length=128, default=\"\", help_text=\"服务类告警中的服务名称\")\n    alert_instance_name = models.CharField(\n        \"告警实例名称\", max_length=128, default=\"\", help_text=\"告警实例名称\")\n    alert_service_type = models.CharField(\n        \"告警服务类型\", max_length=128, default=\"\", help_text=\"服务所属类型\")\n    alert_level = models.CharField(\n        \"告警级别\", max_length=1024, default=\"\", help_text=\"告警级别\")\n    alert_describe = models.CharField(\n        \"告警描述\", max_length=1024, default=\"\", help_text=\"告警描述\")\n    alert_receiver = models.CharField(\n        \"告警接收人\", max_length=256, default=\"\", help_text=\"告警接收人\")\n    alert_resolve = models.CharField(\n        \"告警解决方案\", max_length=1024, default=\"\", help_text=\"告警解决方案\")\n    alert_time = models.DateTimeField(\n        \"告警发生时间\", help_text=\"告警发生时间\")\n    create_time = models.DateTimeField(\n        \"告警信息入库时间\", auto_now_add=True, help_text=\"告警信息入库时间\")\n    monitor_path = models.CharField(\n        \"跳转监控路径\", max_length=2048, blank=True, null=True, help_text=\"跳转grafana路由\")\n    monitor_log = models.CharField(\n        \"跳转监控日志路径\", max_length=2048, blank=True, null=True, help_text=\"跳转grafana日志页面路由\")\n    fingerprint = models.CharField(\n        \"告警的唯一标识\", max_length=1024, blank=True, null=True, help_text=\"告警的唯一标识\")\n    env = models.ForeignKey(\n        Env, null=True, on_delete=models.SET_NULL,\n        verbose_name=\"环境\", help_text=\"环境\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_alert'\n        verbose_name = verbose_name_plural = \"告警记录\"\n\n\nclass Maintain(models.Model):\n    \"\"\"\n    维护记录表\n    \"\"\"\n    objects = None\n\n    matcher_name = models.CharField(\n        \"匹配标签\", max_length=1024, null=False, help_text=\"匹配标签\")\n    matcher_value = models.CharField(\n        \"匹配值\", max_length=1024, null=False, help_text=\"匹配值\")\n    maintain_id = models.CharField(\n        \"维护唯一标识\", max_length=1024, null=False, help_text=\"维护唯一标识\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_maintain'\n        verbose_name = verbose_name_plural = \"维护记录\"\n\n\nclass GrafanaMainPage(models.Model):\n    \"\"\"Grafana 主面板信息表\"\"\"\n    instance_name = models.CharField(\n        \"实例名字\", max_length=32, unique=True, help_text=\"信息面板实例名字\")\n    instance_url = models.CharField(\n        \"实例地址\", max_length=255, unique=True, help_text=\"实例文根地址\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_grafana_url\"\n        verbose_name = verbose_name_plural = \"grafana面板记录\"\n\n\nclass AlertSendWaySetting(models.Model):\n    used = models.BooleanField(\"是否启用\", default=False)\n    env_id = models.IntegerField(\"环境id\", default=0)\n    way_name = models.CharField(\"告警推送服务名称\", max_length=64)\n    server_url = models.TextField(\"告警推送服务url\", default=\"\")\n    way_token = models.CharField(\"服务token\", max_length=255, default=\"\")\n    extra_info = models.JSONField(\"服务其他信息\", default=dict)\n\n    class Meta:\n        db_table = 'omp_alert_send_way_setting'\n        verbose_name = verbose_name_plural = '告警推送通道设置'\n\n    # 暂时屏蔽doem的配置\n    # @property\n    # def get_doem_init_kwargs(self):\n    #     \"\"\"\n    #     返回告警推送服务类对象初始化参数\n    #     :return:\n    #     \"\"\"\n    #     return {\"app_key\": self.way_token, \"server_url\": self.server_url}\n    #\n    # @property\n    # def get_doem_dict(self):\n    #     return {\n    #         \"way_token\": self.way_token,\n    #         \"server_url\": self.server_url,\n    #         \"used\": self.used\n    #     }\n\n    @property\n    def get_email_dict(self):\n        return {\n            \"server_url\": self.server_url,\n            \"used\": self.used\n        }\n\n    @classmethod\n    def get_v1_5_email_dict(cls, env_id):\n        obj = cls.objects.filter(way_name=\"email\").first()\n        kwargs = {\n            \"server_url\": \"\",\n            \"used\": False\n        }\n        if obj:\n            kwargs.update(server_url=obj.server_url, used=obj.used)\n            cls.objects.create(\n                env_id=env_id,\n                way_name=\"email\",\n                server_url=obj.server_url,\n                used=obj.used)\n        return kwargs\n\n    def get_self_dict(self):\n        # 前端展示\n        return getattr(self, f\"get_{self.way_name}_dict\")\n\n    # def get_func_class_obj(self):\n    #     \"\"\"\n    #     返回告警推送服务类对象\n    #     :return:\n    #     \"\"\"\n    #     from base import base_monitor\n    #     kwargs = getattr(self, f\"get_{self.way_name}_init_kwargs\")\n    #     return getattr(\n    #         base_monitor, f\"SendAlertTo{self.way_name.capitalize()}Way\"\n    #     )(**kwargs)\n\n    @classmethod\n    def update_email_config(cls, used, user_emails):\n        cls.objects.filter(\n            way_name=\"email\"\n        ).update(used=used, server_url=user_emails)\n"
  },
  {
    "path": "omp_server/db_models/models/product.py",
    "content": "from django.db import models\n\nfrom db_models.mixins import TimeStampMixin, DeleteMixin\n\n\nclass Labels(models.Model):\n    \"\"\" 应用&产品标签表 \"\"\"\n\n    LABEL_TYPE_COMPONENT = 0\n    LABEL_TYPE_APPLICATION = 1\n    LABELS_CHOICES = (\n        (LABEL_TYPE_COMPONENT, \"组件\"),\n        (LABEL_TYPE_APPLICATION, \"应用\")\n    )\n    label_name = models.CharField(\n        \"标签名称\", max_length=16,\n        null=False, blank=False, help_text=\"标签名称\")\n    label_type = models.IntegerField(\n        \"标签类型\", choices=LABELS_CHOICES,\n        default=0, help_text=\"标签类型\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_labels'\n        verbose_name = verbose_name_plural = \"应用产品标签表\"\n\n\nclass UploadPackageHistory(TimeStampMixin, DeleteMixin):\n    \"\"\" 上传安装包记录表，存储产品包及服务包 \"\"\"\n\n    objects = None\n    PACKAGE_STATUS_SUCCESS = 0\n    PACKAGE_STATUS_FAILED = 1\n    PACKAGE_STATUS_PARSING = 2\n    PACKAGE_STATUS_PUBLISH_SUCCESS = 3\n    PACKAGE_STATUS_PUBLISH_FAILED = 4\n    PACKAGE_STATUS_PUBLISHING = 5\n    PACKAGE_STATUS_CHOICES = (\n        (PACKAGE_STATUS_SUCCESS, \"成功\"),\n        (PACKAGE_STATUS_FAILED, \"失败\"),\n        (PACKAGE_STATUS_PARSING, \"解析中\"),\n        (PACKAGE_STATUS_PUBLISH_SUCCESS, \"发布成功\"),\n        (PACKAGE_STATUS_PUBLISH_FAILED, \"发布失败\"),\n        (PACKAGE_STATUS_PUBLISHING, \"发布中\"),\n    )\n    operation_uuid = models.CharField(\n        \"唯一操作uuid\", max_length=64,\n        null=False, blank=False, help_text=\"唯一操作uuid\")\n    operation_user = models.CharField(\n        \"操作用户\", max_length=64,\n        null=True, blank=True, help_text=\"操作用户\")\n    package_name = models.CharField(\n        \"安装包名称\", max_length=256,\n        null=False, blank=False, help_text=\"安装包名称\")\n    package_md5 = models.CharField(\n        \"安装包MD5值\", max_length=64,\n        null=False, blank=False, help_text=\"安装包MD5值\")\n    # 安装包相对路径，相对于package_hub\n    package_path = models.CharField(\n        \"安装包路径\", max_length=512,\n        null=False, blank=False, help_text=\"安装包路径\")\n    package_status = models.IntegerField(\n        \"安装包状态\", choices=PACKAGE_STATUS_CHOICES,\n        default=2, help_text=\"安装包状态\")\n    error_msg = models.CharField(\n        \"错误消息\", max_length=1024,\n        null=True, blank=True, help_text=\"错误消息\")\n    package_parent = models.ForeignKey(\n        to=\"self\", null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"父级包\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_upload_package_history'\n        verbose_name = verbose_name_plural = \"上传安装包记录\"\n\n\nclass ProductHub(TimeStampMixin):\n    \"\"\" 存储产品级别模型类 (应用) \"\"\"\n    # 使用is_release标识此条数据是否已发布，是否可用\n    objects = None\n    is_release = models.BooleanField(\n        \"是否发布\", default=False, help_text=\"是否发布\")\n    pro_name = models.CharField(\n        \"产品名称\", max_length=256,\n        null=False, blank=False, help_text=\"产品名称\")\n    pro_version = models.CharField(\n        \"产品版本\", max_length=256,\n        null=False, blank=False, help_text=\"产品版本\")\n    pro_labels = models.ManyToManyField(to=Labels, help_text=\"所属标签\")\n    pro_description = models.CharField(\n        \"产品描述\", max_length=2048,\n        null=True, blank=True, help_text=\"产品描述\")\n    # 以下字段在入库时使用json.dumps方法处理，读取时使用json.loads方法反向解析\n    # 产品依赖默认向下兼容\n    # [\"cmdb\", \"douc\"]\n    pro_dependence = models.TextField(\n        \"产品依赖\", null=True, blank=True, help_text=\"产品依赖\")\n    # [{\"name\": \"cmdbServer\", \"version\": \"1.1.0\"}]\n    pro_services = models.TextField(\n        \"服务列表\", null=True, blank=True, help_text=\"服务列表\")\n    # 关联的安装包\n    pro_package = models.ForeignKey(\n        UploadPackageHistory, null=True, blank=True,\n        on_delete=models.SET_NULL, verbose_name=\"安装包\", help_text=\"安装包\")\n    # 产品图标读取svg数据进行渲染pro_name.svg\n    pro_logo = models.TextField(\n        \"产品图标\", null=True, blank=True, help_text=\"产品图标\")\n    # 冗余字段，用于存储未定义的其他产品相关数据\n    extend_fields = models.JSONField(\n        \"冗余字段\", null=True, blank=True, help_text=\"冗余字段\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_product'\n        verbose_name = verbose_name_plural = \"应用商店产品\"\n\n\nclass ApplicationHub(TimeStampMixin):\n    \"\"\" 服务级别模型类 (组件) \"\"\"\n    objects = None\n    APP_TYPE_COMPONENT = 0\n    APP_TYPE_SERVICE = 1\n    APP_TYPE_CHOICES = (\n        (APP_TYPE_COMPONENT, \"组件\"),\n        (APP_TYPE_SERVICE, \"服务\")\n    )\n    is_release = models.BooleanField(\n        \"是否发布\", default=False, help_text=\"是否发布\")\n    app_type = models.IntegerField(\n        \"应用类型\", choices=APP_TYPE_CHOICES,\n        default=0, help_text=\"应用类型\")\n    app_name = models.CharField(\n        \"应用名称\", max_length=256,\n        null=False, blank=False, help_text=\"应用名称\")\n    app_labels = models.ManyToManyField(to=Labels, help_text=\"所属标签\")\n    app_version = models.CharField(\n        \"应用版本\", max_length=256,\n        null=False, blank=False, help_text=\"应用版本\")\n    app_description = models.CharField(\n        \"应用描述\", max_length=2048,\n        null=True, blank=True, help_text=\"应用描述\")\n    # 应用端口使用TextField字段进行存储\n    # 在入库时使用json.dumps方法处理，读取时使用json.loads方法反向解析\n    # 存储数据格式为[{\"default\": 18080, \"key\": \"http_port\", \"name\": \"服务端口\"}]\n    app_port = models.TextField(\n        \"应用端口\", null=True, blank=True, help_text=\"应用端口\")\n    # 以下字段使用方法同应用端口\n    # 服务依赖默认向下兼容\n    # [{\"name\": \"mysql\", \"version\": \"5.7\"}]\n    app_dependence = models.TextField(\n        \"应用依赖\", null=True, blank=True, help_text=\"应用依赖\")\n    # [{\"name\": \"安装目录\", \"key\": \"base_dir\", \"default\": \"{data_path}/abc\"}]\n    app_install_args = models.TextField(\n        \"安装参数\", null=True, blank=True, help_text=\"安装参数\")\n    # {\"start\": \"./start.sh\", \"stop\": \"./stop.sh\"}\n    app_controllers = models.TextField(\n        \"应用控制脚本\", null=True, blank=True, help_text=\"应用控制脚本\")\n    # 关联的安装包\n    app_package = models.ForeignKey(\n        UploadPackageHistory, null=True, blank=True,\n        on_delete=models.SET_NULL, verbose_name=\"安装包\", help_text=\"安装包\")\n    product = models.ForeignKey(\n        ProductHub, null=True, blank=True,\n        on_delete=models.SET_NULL, verbose_name=\"所属产品\", help_text=\"所属产品\")\n    # 应用图标读取svg数据进行渲染app_name.svg\n    app_logo = models.TextField(\n        \"应用图标\", null=True, blank=True, help_text=\"应用图标\")\n    # 冗余字段，用于存储未定义的其他服务相关数据\n    extend_fields = models.JSONField(\n        \"冗余字段\", null=True, blank=True, help_text=\"冗余字段\")\n    # 解析服务数据后，如果服务为jdk、python等，则其为基础数据\n    is_base_env = models.BooleanField(\n        \"是否为基础环境\", default=False, help_text=\"是否为基础环境\")\n    app_monitor = models.JSONField(\n        \"监控使用字段\", null=True, blank=True, help_text=\"监控使用字段\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_application'\n        verbose_name = verbose_name_plural = \"应用商店服务\"\n        # 服务、组件名称和版本形成联合唯一索引，不允许重复\n        unique_together = (\"app_name\", \"app_version\")\n\n    @property\n    def pro_info(self):\n        \"\"\" 查询是product名字 \"\"\"\n        if self.product:\n            return {\"pro_name\": self.product.pro_name, \"pro_version\": self.product.pro_version}\n        return None\n\n\nclass Product(TimeStampMixin):\n    \"\"\" 已安装产品表 \"\"\"\n    objects = None\n    # 用于存储安装产品时使用的实例名称\n    product_instance_name = models.CharField(\n        \"产品实例名称\", max_length=64,\n        null=True, blank=True, help_text=\"安装产品时输入的实例名称\")\n    # 所属产品的相关信息，可通过此外键查看其对应的产品仓库中的数据\n    product = models.ForeignKey(\n        ProductHub, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"所属产品\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_product_instance'\n        verbose_name = verbose_name_plural = \"产品实例表\"\n        ordering = (\"-created\",)\n"
  },
  {
    "path": "omp_server/db_models/models/self_heal.py",
    "content": "from django.db import models\nfrom django.core.validators import MaxValueValidator\n\n\nclass WaitSelfHealing(models.Model):\n    # 0 等待自愈 1 自愈中 存在自愈中的服务不被允许再次触发自愈，保证自愈过程中只有一个自愈在进行。\n    service_name = models.CharField(\"自愈服务名称\", max_length=64, default=\"\")\n    repair_ser = models.JSONField(\"自愈缓存服务详情\", default=dict)\n    repair_status = models.IntegerField(\"自愈缓存服务\", default=0)\n\n    class Meta:\n        db_table = \"omp_wait_self_healing\"\n        verbose_name = verbose_name_plural = \"自愈等待队列\"\n\n\nclass SelfHealingSetting(models.Model):\n    \"\"\"自愈策略设置表\"\"\"\n    REPAIR_CHOICES = [\n        (0, \"启动\"),\n        (1, \"重新启动\")\n    ]\n\n    used = models.BooleanField(\"是否启用\", default=False)\n    max_healing_count = models.IntegerField(\"最多自愈操作次数\", default=5, validators=[MaxValueValidator(20)])\n    fresh_rate = models.IntegerField(\"周期内采集告警消息频次\", default=10, validators=[MaxValueValidator(60)])\n    instance_tp = models.IntegerField(\"实例类别\", choices=REPAIR_CHOICES, default=0)\n    repair_instance = models.JSONField(\"修复实例\", default=dict, blank=False, null=False)\n\n    class Meta:\n        db_table = \"omp_self_healing_setting\"\n        verbose_name = verbose_name_plural = \"自愈设置\"\n\n\nclass SelfHealingHistory(models.Model):\n    \"\"\"自愈历史记录\"\"\"\n    is_read = models.IntegerField(\"此消息是否已读，0-未读；1-已读\", default=0)\n    host_ip = models.CharField(\"自愈主机ip\", max_length=64)\n    service_name = models.CharField(\"自愈服务名称\", max_length=64, default=\"\")\n    instance_name = models.CharField(\"自愈实例名称\", max_length=128, default=\"\", help_text=\"自愈实例名称\")\n    HEALING_FAIL = 0\n    HEALING_ING = 2\n    HEALING_SUCCESS = 1\n    STATE_CHOICES = (\n        (HEALING_ING, \"自愈中\"),\n        (HEALING_FAIL, \"自愈失败\"),\n        (HEALING_SUCCESS, \"自愈成功\")\n    )\n    state = models.IntegerField(\"自愈状态\", choices=STATE_CHOICES, default=HEALING_ING)\n    healing_count = models.IntegerField(\"已运行自愈次数\", default=0)\n    start_time = models.DateTimeField(\"自愈开始时间\", null=True)\n    end_time = models.DateTimeField(\"自愈结束时间\", null=True)\n    healing_log = models.TextField(\"自愈日志\", default=\"\")\n    alert_time = models.DateTimeField(\"关联告警时间，与fingerprint确定同一次告警\", null=True)\n    alert_content = models.TextField(\"告警日志内容\", default=\"\")\n    monitor_log = models.TextField(\"grafana日志url\", default=\"\")\n\n    class Meta:\n        db_table = \"omp_self_healing_history\"\n        verbose_name = verbose_name_plural = \"自愈历史记录\"\n"
  },
  {
    "path": "omp_server/db_models/models/service.py",
    "content": "import json\nimport os\n\nfrom django.db import models\n\nfrom db_models.mixins import TimeStampMixin, DeleteMixin\nfrom utils.common.exceptions import GeneralError\nfrom .env import Env\nfrom .product import ApplicationHub\n\n\nclass ServiceConnectInfo(TimeStampMixin):\n    \"\"\" 存储用户名密码信息 \"\"\"\n    # 服务用户名、密码信息，同一个集群公用一套用户名、密码\n    objects = None\n    service_name = models.CharField(\n        \"服务名\", max_length=32,\n        null=False, blank=False, help_text=\"服务名\")\n    service_username = models.CharField(\n        \"用户名\", max_length=512,\n        null=True, blank=True, help_text=\"用户名\")\n    service_password = models.CharField(\n        \"密码\", max_length=512,\n        null=True, blank=True, help_text=\"密码\")\n    service_username_enc = models.CharField(\n        \"加密用户名\", max_length=512,\n        null=True, blank=True, help_text=\"加密用户名\")\n    service_password_enc = models.CharField(\n        \"加密密码\", max_length=512,\n        null=True, blank=True, help_text=\"加密密码\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_service_connect_info\"\n        verbose_name = verbose_name_plural = \"用户名密码信息表\"\n\n\nclass ClusterInfo(TimeStampMixin, DeleteMixin):\n    \"\"\" 集群信息表 \"\"\"\n\n    objects = None\n    cluster_service_name = models.CharField(\n        \"集群所属服务\", max_length=36,\n        null=True, blank=True, help_text=\"集群所属服务\")\n    # 选择的集群类型\n    cluster_type = models.CharField(\n        \"集群类型\", max_length=36,\n        null=True, blank=True, help_text=\"集群类型\")\n    # 集群实例名称，虚拟名称\n    cluster_name = models.CharField(\n        \"集群名称\", max_length=64,\n        null=False, blank=False, help_text=\"集群名称\")\n    # 集群连接的信息，公共组件可能存在集群信息，自研服务一般无集群概念\n    connect_info = models.CharField(\n        \"集群连接信息\", max_length=512,\n        null=True, blank=True, help_text=\"集群连接信息\")\n    service_connect_info = models.ForeignKey(\n        ServiceConnectInfo, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"用户名密码信息\")\n    # 预留解析字段，集群连接有多种方式\n    # eg1: 10.0.0.1:18117,10.0.0.2:18117,10.0.0.3:18117\n    # eg2: 10.0.0.1,10.0.0.2,10.0.0.3:18117\n    # 在安装时应该按照指定的方式拼接集群信息\n    connect_info_parse_type = models.CharField(\n        \"连接信息解析方式\", max_length=32,\n        null=True, blank=True, help_text=\"连接信息解析方式\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_cluster_info\"\n        verbose_name = verbose_name_plural = \"集群信息表\"\n\n    def __str__(self):\n        return f\"<instance> of {self.cluster_name}\"\n\n\nclass ExcludeSplit(models.Manager):\n    \"\"\"\n    拆分前服务过滤，展示监控纳管专用\n    \"\"\"\n\n    def get_queryset(self):\n        return super(ExcludeSplit, self).get_queryset().exclude(service_split=1)\n\n\nclass AfterSplit(models.Manager):\n    \"\"\"\n    拆分后服务过滤,安装升级专用\n    \"\"\"\n\n    def get_queryset(self):\n        return super(AfterSplit, self).get_queryset().exclude(service_split=2)\n\n\nclass AllManage(models.Manager):\n    def get_queryset(self):\n        return super(AllManage, self).get_queryset()\n\n\nclass Service(TimeStampMixin):\n    \"\"\" 服务表 (删除前会触发update_execution_record)\"\"\"\n    split_objects = AfterSplit()\n    objects = ExcludeSplit()\n    all_objects = AllManage()\n    SERVICE_STATUS_NORMAL = 0\n    SERVICE_STATUS_STARTING = 1\n    SERVICE_STATUS_STOPPING = 2\n    SERVICE_STATUS_RESTARTING = 3\n    SERVICE_STATUS_STOP = 4\n    SERVICE_STATUS_UNKNOWN = 5\n    SERVICE_STATUS_INSTALLING = 6\n    SERVICE_STATUS_INSTALL_FAILED = 7\n    SERVICE_STATUS_READY = 8\n    SERVICE_STATUS_DELETING = 9\n    SERVICE_STATUS_UPGRADE = 10\n    SERVICE_STATUS_ROLLBACK = 11\n    SERVICE_STATUS_CHOICES = (\n        (SERVICE_STATUS_NORMAL, \"正常\"),\n        (SERVICE_STATUS_STARTING, \"启动中\"),\n        (SERVICE_STATUS_STOPPING, \"停止中\"),\n        (SERVICE_STATUS_RESTARTING, \"重启中\"),\n        (SERVICE_STATUS_STOP, \"停止\"),\n        (SERVICE_STATUS_UNKNOWN, \"未知\"),\n        (SERVICE_STATUS_INSTALLING, \"安装中\"),\n        (SERVICE_STATUS_INSTALL_FAILED, \"安装失败\"),\n        (SERVICE_STATUS_READY, \"待安装\"),\n        (SERVICE_STATUS_DELETING, \"删除中\"),\n        (SERVICE_STATUS_UPGRADE, \"升级中\"),\n        (SERVICE_STATUS_ROLLBACK, \"回滚中\")\n    )\n\n    PRE_IS_SPLIT = 1\n    NO_SPLIT = 0\n    AFT_IS_SPLIT = 2\n    SERVICE_STATUS_SPLIT = (\n        (\n            (PRE_IS_SPLIT, \"拆分前\"),\n            (AFT_IS_SPLIT, \"拆分后\"),\n            (NO_SPLIT, \"未拆分\")\n        )\n    )\n\n    # 是否用外键关联？\n    ip = models.GenericIPAddressField(\n        \"服务所在主机\", help_text=\"服务所在主机\")\n\n    service_instance_name = models.CharField(\n        \"服务实例名称\", max_length=64,\n        null=False, blank=False, help_text=\"服务实例名称\")\n\n    service_split = models.IntegerField(\n        \"拆分服务前对象\", choices=SERVICE_STATUS_SPLIT,\n        default=0, help_text=\"拆分服务前对象\")\n\n    service = models.ForeignKey(\n        ApplicationHub, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"服务表外键\")\n\n    # 以下字段含义同 ApplicationHub 但具备定制化场景，无法做得到唯一关联\n    # 存储格式[{\"port\": 18080, \"key\": \"http_port\"}]\n    service_port = models.TextField(\n        \"服务端口\", null=True, blank=True, help_text=\"服务端口\")\n    # 服务控制脚本，按照其所安装的主机拼接绝对路径并进行存储(主机数据目录存在被更改风险)\n    # {\"start\": \"./start.sh\", \"stop\": \"./stop.sh\"}\n    service_controllers = models.JSONField(\n        \"服务控制脚本\", null=True, blank=True, help_text=\"服务控制脚本\")\n\n    # 以下字段用于角色及集群使用\n    service_role = models.CharField(\n        \"服务角色\", max_length=128,\n        null=True, blank=True, help_text=\"服务角色\")\n    cluster = models.ForeignKey(\n        ClusterInfo, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"所属集群\")\n    env = models.ForeignKey(\n        Env, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"所属环境\")\n\n    service_connect_info = models.ForeignKey(\n        ServiceConnectInfo, null=True, blank=True,\n        on_delete=models.SET_NULL, help_text=\"用户名密码信息\")\n\n    # 服务状态信息\n    service_status = models.IntegerField(\n        \"服务状态\", choices=SERVICE_STATUS_CHOICES,\n        default=5, help_text=\"服务状态\")\n\n    # 服务告警、自愈、监控信息\n    alert_count = models.IntegerField(\n        \"告警次数\", default=0, help_text=\"告警次数\")\n    self_healing_count = models.IntegerField(\n        \"服务自愈次数\", default=0, help_text=\"服务自愈次数\")\n    # 服务实例间的依赖关系，此字段标明当前服务实例所依赖的其他服务实例关系\n    # 注意，当被依赖服务为base_env时，不在此字段中体现\n    # 使用json.dumps存储 list结构数据\n    service_dependence = models.TextField(\n        \"服务依赖关系\", null=True, blank=True, help_text=\"服务依赖关系\")\n\n    vip = models.GenericIPAddressField(\n        \"vip地址\", null=True, blank=True, default=None, help_text=\"vip地址\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = 'omp_service'\n        verbose_name = verbose_name_plural = \"服务实例表\"\n        ordering = (\"-created\",)\n\n    def update_port(self, app_ports):\n        \"\"\"\n        比较服务端口，取并集\n        :param app_ports: 目标app服务端口\n        :return: new service_ports\n        \"\"\"\n        # 存储数据格式为[{\"default\": 18080, \"key\": \"http_port\", \"name\": \"服务端口\"}]\n        if not app_ports:\n            return []\n        port_dict = {}\n        service_ports = json.loads(self.service_port or [])\n        for service_port in service_ports:\n            port_dict[service_port.get(\"key\")] = service_port.get(\"default\")\n        for app_port in app_ports:\n            if app_port.get(\"key\") not in port_dict:\n                service_ports.append(app_port)\n        return service_ports\n\n    def update_controllers(self, application, install_folder):\n        \"\"\"\n        更新服务管理命令\n        :param application: 服务目标app\n        :param install_folder: 安装目录（用于更新服务命令）\n        :return: new service_controllers\n        \"\"\"\n        _app_controllers = json.loads(application.app_controllers)\n        # 获取服务家目录\n        install_args = json.loads(application.app_install_args)\n        _home = \"\"\n        for el in install_args:\n            if \"dir_key\" in el and el[\"key\"] == \"base_dir\":\n                _home = el[\"default\"]\n        real_home = os.path.join(install_folder, _home.rstrip(\"/\"))\n        _new_controller = dict()\n        # 更改服务控制脚本、拼接相对路径\n        for key, value in _app_controllers.items():\n            if not value:\n                continue\n            # 对于hadoop管控命令不动\n            if application.app_name == \"hadoop\" and key in \\\n                    {\"start\", \"stop\", \"restart\"}:\n                _new_controller[key] = self.service_controllers.get(key)\n                continue\n            _new_controller[key] = os.path.join(real_home, value)\n        # 在每次安装完所有服务后，需要搜索出相应的post_action并统一执行\n        if \"post_action\" in application.extend_fields and \\\n                application.extend_fields.get(\"post_action\"):\n            _new_controller[\"post_action\"] = os.path.join(\n                real_home, application.extend_fields[\"post_action\"]\n            )\n        return _new_controller\n\n    def update_service_connect_info(self):\n        \"\"\"\n        更新服务链接信息\n        \"\"\"\n        infos = {\"username\", \"password\", \"username_enc\", \"password_enc\"}\n        connect_infos = {}\n        for app_info in json.loads(self.service.app_install_args):\n            key = app_info.get(\"key\")\n            if key in infos:\n                connect_infos[f\"service_{key}\"] = app_info.get(\"default\")\n\n        if not connect_infos:\n            return\n        if self.service_connect_info:\n            for k, v in connect_infos.items():\n                if not getattr(self.service_connect_info, k) and v:\n                    setattr(self.service_connect_info, k, v)\n            self.service_connect_info.save()\n            return\n        conn_obj, _ = ServiceConnectInfo.objects.get_or_create(\n            service_name=self.service.app_name,\n            **connect_infos\n        )\n        self.service_connect_info = conn_obj\n\n    @classmethod\n    def update_dependence(cls, service_dependence, app_dependence):\n        # 暂不考虑服务依赖减少\n        if not app_dependence:\n            return []\n        dependence_dict = {}\n        dependents = json.loads(service_dependence or '[]')\n        for dependence in dependents:\n            dependence_dict[dependence.get(\"name\")] = dependence\n        for _dependence in app_dependence:\n            service_name = _dependence.get(\"name\")\n            if service_name not in dependence_dict:\n                service = Service.objects.filter(\n                    service__app_name=service_name\n                ).first()\n                if not service:\n                    raise GeneralError(f\"缺少依赖服务{service_name}！\")\n                _dict = {\n                    \"name\": service_name,\n                    \"cluster_name\": None,\n                    \"instance_name\": None\n                }\n                if service.cluster:\n                    _dict[\"cluster_name\"] = service.cluster.cluster_name\n                else:\n                    _dict[\"instance_name\"] = service.service_instance_name\n                dependents.append(_dict)\n        return dependents\n\n    def update_application(self, application, success, install_folder):\n        \"\"\"\n        更新服务信息\n        :param application: 服务目标app\n        :param success: 是否成功（用于更新服务状态）\n        :param install_folder: 安装目录（用于更新服务命令）\n        :return: self\n        \"\"\"\n        self.service = application\n        self.service_port = json.dumps(\n            self.update_port(json.loads(application.app_port or '[]'))\n        )\n        self.service_dependence = json.dumps(\n            self.update_dependence(\n                self.service_dependence,\n                json.loads(application.app_dependence or '[]')\n            )\n        )\n        self.service_controllers = self.update_controllers(\n            application, install_folder)\n        if success:\n            self.service_status = self.SERVICE_STATUS_NORMAL\n        else:\n            self.service_status = self.SERVICE_STATUS_UNKNOWN\n        self.update_service_connect_info()\n        self.save()\n        return self\n\n\nclass ServiceHistory(models.Model):\n    \"\"\" 服务操作记录表 \"\"\"\n\n    objects = None\n    username = models.CharField(\n        \"操作用户\", max_length=128, help_text=\"操作用户\")\n    description = models.CharField(\n        \"用户行为描述\", max_length=1024, help_text=\"用户行为描述\")\n    # success or failed\n    result = models.CharField(\n        \"操作结果\", max_length=1024, default=\"success\", help_text=\"操作结果\")\n    created = models.DateTimeField(\n        '发生时间', null=True, auto_now_add=True, help_text='发生时间')\n    service = models.ForeignKey(\n        Service, null=True, on_delete=models.SET_NULL, verbose_name=\"服务\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_service_operate_log\"\n        verbose_name = verbose_name_plural = \"服务操作记录\"\n        ordering = (\"-created\",)\n\n    @classmethod\n    def create_history(cls, service, operation_obj=None, **kwargs):\n        \"\"\"\n        创建服务操作记录\n        :param service: 被操作的服务\n        :param operation_obj: 操作对象：UpgradeDetail、RollbackDetail\n        :param kwargs: 记录参数，在无操作对象情况下传\n        :return: obj\n        \"\"\"\n        if operation_obj:\n            operation_kwargs = operation_obj.get_service_history()\n            kwargs.update(operation_kwargs)\n        service_history = cls.objects.create(\n            service=service,\n            **kwargs\n        )\n        return service_history\n"
  },
  {
    "path": "omp_server/db_models/models/threshold.py",
    "content": "import hashlib\nfrom uuid import uuid4\nfrom django.db import models\n\n\n\nclass HostThreshold(models.Model):\n    \"\"\"主机阈值设置表\"\"\"\n    # 主机指标项名称：cpu_used;memory_used;disk_root_used;disk_data_used\n    index_type = models.CharField(\n        max_length=64, null=False, blank=False, help_text=\"指标项名称\")\n    condition = models.CharField(\n        max_length=32, null=False, blank=False, help_text=\"阈值条件\")\n    condition_value = models.CharField(\n        max_length=128, null=False, blank=False, help_text=\"阈值数值，百分号格式\")\n    # 告警级别: warning critical\n    alert_level = models.CharField(\n        max_length=32, null=False, blank=False, help_text=\"告警级别\")\n    create_date = models.DateTimeField(auto_now_add=True)\n    env_id = models.IntegerField(help_text=\"环境id\", default=1)\n\n    class Meta:\n        db_table = 'omp_host_threshold'\n\n    def get_dic(self):\n        return {\n            \"index_type\": self.index_type,\n            \"condition\": self.condition,\n            \"value\": self.condition_value,\n            \"level\": self.alert_level,\n        }\n\n    def __str__(self):\n        return str(self.index_type) + \"-\" + str(self.condition) + \"-\" \\\n               + str(self.condition_value) + \"-\" + str(self.alert_level)\n\n\nclass ServiceThreshold(models.Model):\n    \"\"\"服务阈值设置表\"\"\"\n    # 服务指标项名称：service_active;service_cpu_used;service_memory_used\n    index_type = models.CharField(\n        \"指标项名称\", max_length=64, null=False, blank=False)\n    condition = models.CharField(\n        \"阈值条件\", max_length=32, null=False, blank=False)\n    condition_value = models.CharField(\n        \"阈值数值，百分号格式\", max_length=128, null=False, blank=False)\n    # 告警级别: warning critical\n    alert_level = models.CharField(\n        \"告警级别\", max_length=32, null=False, blank=False)\n    create_date = models.DateTimeField(auto_now_add=True)\n    env_id = models.IntegerField(\"环境id\", default=1)\n\n    class Meta:\n        db_table = 'omp_service_threshold'\n\n    def get_dic(self):\n        return {\n            \"index_type\": self.index_type,\n            \"condition\": self.condition,\n            \"value\": self.condition_value,\n            \"level\": self.alert_level,\n        }\n\n    def __str__(self):\n        return str(self.index_type) + \"-\" + str(self.condition) + \"-\" + \\\n               str(self.condition_value) + \"-\" + str(self.alert_level)\n\n\nclass ServiceCustomThreshold(models.Model):\n    \"\"\"服务特殊指标阈值设置(临时)\"\"\"\n    service_name = models.CharField(\n        \"服务名称\", max_length=64, null=False, blank=False)\n    index_type = models.CharField(\n        \"指标项名称\", max_length=64, null=False, blank=False)\n    condition = models.CharField(\n        \"阈值条件\", max_length=32, null=False, blank=False)\n    condition_value = models.CharField(\n        \"阈值数值\", max_length=128, null=False, blank=False)\n    # 告警级别: warning critical\n    alert_level = models.CharField(\n        \"告警级别\", max_length=32, null=False, blank=False)\n    create_date = models.DateTimeField(auto_now_add=True)\n    env_id = models.IntegerField(\"环境id\", default=1)\n\n    class Meta:\n        db_table = 'omp_service_custom_threshold'\n        verbose_name = verbose_name_plural = '服务定制指标阈值设置'\n\n\nclass Rule(models.Model):\n    \"\"\"\n    表达式存储\n    \"\"\"\n    name = models.CharField(\"指标名称\", max_length=255,null=False)\n    expr = models.TextField(\"监控指标表达式，报警语法\", null=False, blank=False)\n    service = models.CharField(\"服务名称\",max_length=255, null=False)\n    description = models.TextField(\"描述\",null=True)\n\n    class Meta:\n        \"\"\"\n        元数据\n        \"\"\"\n        db_table = \"omp_rule\"\n        verbose_name = verbose_name_plural = \"规则表达式\"\n\n\n\n\nclass AlertRule(models.Model):\n    \"\"\"\n    告警规则\n    \"\"\"\n    env_id = models.IntegerField(\"环境id\", default=1)\n    expr = models.TextField(\"监控指标表达式，报警语法\", null=False, blank=False)\n    threshold_value = models.FloatField(\"阈值的数值\", null=False, blank=False)\n    compare_str = models.CharField(\"比较符\", max_length=64)\n    for_time = models.CharField(\"持续一段时间获取不到信息就触发告警\", max_length=64)\n    severity = models.CharField(\"告警级别\", max_length=64)\n    alert = models.TextField(\"标题，自定义摘要\")\n    service = models.CharField(\"指标所属服务名称\", max_length=255)\n    status = models.IntegerField(\"启用状态\", default=0)\n    name = models.CharField(\"内置指标名称\", max_length=255, null=True)\n    TYPE = (\n        (0, \"builtins\"),\n        (1, \"custom\"),\n        (2, \"log\")\n    )\n    quota_type = models.IntegerField(\"指标的类型\", choices=TYPE, default=0)\n    labels = models.JSONField(\"额外指定标签\",null=True)\n    # builtins_not_enabled = models.IntegerField(\"内置未启用的规则\", default=0)\n    summary = models.TextField(\"描述, 告警指标描述\", null=True)\n    description = models.TextField(\"描述, 告警指标描述\", null=True)\n    create_time = models.DateTimeField(\"告警规则入库时间\", auto_now_add=True)\n    update_time = models.DateTimeField(\"告警规则更新时间\", auto_now_add=True)\n    forbidden = models.IntegerField(\"禁止删除\", default=1)  # 1能删除  2 禁止删除\n    hash_data = models.CharField(\"唯一hash值\", null=True, blank=True, max_length=255) # 唯一hash禁止规则重复\n\n    class Meta:\n        \"\"\"\n        元数据\n        \"\"\"\n        db_table = \"omp_alert_ruler\"\n        verbose_name = verbose_name_plural = \"自定义告警规则\"\n"
  },
  {
    "path": "omp_server/db_models/models/tool.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: tools\n# Author: jon.liu@yunzhihui.com\n# Create time: 2022-02-08 16:04\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n实用工具数据库表结构\n原始小工具校验前地址：omp/package_hub/tool/verify_tar/\n原始小工具tar包地址：omp/package_hub/tool/tar/\n小工具解压后包地址: omp/package_hub/tool/folder/\n上传的文件地址: omp/package_hub/tool/upload_data/\n运行产生的的文件地址: omp/package_hub/tool/download_data/\n\"\"\"\nimport os\n\nfrom django.conf import settings\nfrom django.db import models\n\nfrom db_models.mixins import TimeStampMixin\nfrom utils.plugin.public_utils import timedelta_strftime\nfrom db_models.models import Host\n\n\nclass ToolInfo(TimeStampMixin):\n    \"\"\" 实用工具基本信息记录表 \"\"\"\n\n    objects = None\n\n    KIND_MANAGEMENT = 0\n    KIND_CHECK = 1\n    KIND_SECURITY = 2\n    KIND_OTHER = 3\n    TOOL_KIND_CHOICES = (\n        (KIND_MANAGEMENT, \"管理工具\"),\n        (KIND_CHECK, \"检查工具\"),\n        (KIND_SECURITY, \"安全工具\"),\n        (KIND_OTHER, \"其他工具\")\n    )\n\n    SCRIPT_TYPE_PYTHON3 = 0\n    SCRIPT_TYPE_SHELL = 1\n    SCRIPT_TYPE_CHOICES = (\n        (SCRIPT_TYPE_PYTHON3, \"python3\"),\n        (SCRIPT_TYPE_SHELL, \"shell\")\n    )\n\n    OUTPUT_TERMINAL = 0\n    OUTPUT_FILE = 1\n    OUTPUT_TYPE_CHOICES = (\n        (OUTPUT_TERMINAL, \"终端输出\"),\n        (OUTPUT_FILE, \"文件输出\")\n    )\n\n    name = models.CharField(\n        max_length=128, null=False,\n        blank=False, help_text=\"实用工具名称\")\n    kind = models.IntegerField(\n        \"实用工具分类\", choices=TOOL_KIND_CHOICES,\n        default=0, help_text=\"实用工具分类\")\n    script_type = models.IntegerField(\n        \"脚本类型\", choices=SCRIPT_TYPE_CHOICES,\n        default=0, help_text=\"脚本类型\")\n    # 脚本执行的目标对象，主机为host，服务为服务名称\n    target_name = models.CharField(\n        \"脚本执行的目标对象\",\n        max_length=128,\n        default='host',\n        help_text=\"脚本执行的目标对象\")\n    # 原始脚本包MD5值，预留字段\n    source_package_md5 = models.CharField(\n        \"源码包md5值\", max_length=32,\n        blank=True, null=True, help_text=\"源码包md5值\")\n    # 原始tar包相对路径，package_hub/{tool/tar/kafka_tool.tar.gz}\n    source_package_path = models.CharField(\n        \"源码包相对路径\", max_length=128, null=False)\n    # 存储实用工具目录路径，如package_hub/{tool/folder/kafka-package_md5}\n    tool_folder_path = models.CharField(\n        \"实用工具目录相对路径\", max_length=128,\n        null=False, blank=False, help_text=\"实用工具目录相对路径\")\n    # 存储脚本路径，如kafka.py\n    script_path = models.CharField(\n        \"脚本相对路径\", max_length=128,\n        null=False, blank=False, help_text=\"脚本相对路径\")\n    send_package = models.JSONField(\n        \"需要发送的文件相对路径\", max_length=128,\n        default=list, help_text=\"需要发送的文件相对路径\")\n    # 存储readme的内容\n    readme_info = models.TextField(\n        \"readme信息\", null=True, blank=True, help_text=\"readme信息\")\n    # 如果脚本需要模板文件，那么该模板文件的相对路径需要存储到下面字段中\n    # 此字段存储列表类型数据\n    template_filepath = models.JSONField(\n        \"模板文件相对路径\", default=list, help_text=\"模板文件相对路径\")\n    # 在执行对象为服务时需要获取除ServiceConnectInfo中以外的信息\n    # [\"service_port\", \"metrics_port\"]\n    obj_connection_args = models.JSONField(\"目标对象连接信息\", default=list, )\n    # 存储脚本执行参数，存储列表类型数据\n    # 在入库时需要对每个参数的类型进行校验（前端展示效果）\n    script_args = models.JSONField(\"脚本执行参数\", default=list)\n    # 脚本输出的类型，终端/文件\n    output = models.IntegerField(\n        \"脚本的输出类型\", choices=OUTPUT_TYPE_CHOICES,\n        default=0, help_text=\"脚本的输出类型\")\n    description = models.TextField(\"描述信息\", help_text=\"描述信息\")\n    logo = models.URLField(\"logo url\", default=\"\")\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_tool_info\"\n        verbose_name = verbose_name_plural = \"实用工具基本信息表\"\n\n    def load_default_form(self):\n        return {\n            \"task_name\": self.name,\n            \"target_objs\": [],\n            'runuser': '',\n            'timeout': 60\n        }\n\n    # 目前支持的参数类型（\"select_multiple\"：多选暂不支持）\n    # \"select\"：单选, \"file\"：文件, \"input\"：单行文本\n\n    @property\n    def package_name(self):\n        return self.tool_folder_path.split(\"/\")[-1]\n\n    @property\n    def templates(self):\n        templates = []\n        for template in self.template_filepath:\n            templates.append(\n                {\n                    \"name\": template.split(\"/\")[-1],\n                    \"sub_url\": os.path.join(self.tool_folder_path, template)\n                }\n            )\n        return templates\n\n    @property\n    def tar_url(self):\n        return os.path.join(self.source_package_path)\n\n    def valid_upload_file(self, *args, **kwargs):\n        return f\"package_hub/tool/upload_data/{self.package_name}\"\n\n    def upload_file_url(self, file_name, **kwargs):\n        return os.path.join(f\"tool/upload_data/{self.package_name}\", file_name)\n\n\nclass ToolExecuteMainHistory(models.Model):\n    \"\"\" 实用工具执行记录 \"\"\"\n\n    objects = None\n\n    STATUS_READY = 0\n    STATUS_RUNNING = 1\n    STATUS_SUCCESS = 2\n    STATUS_FAILED = 3\n    STATUS_TYPE_CHOICES = (\n        (STATUS_READY, \"待执行\"),\n        (STATUS_RUNNING, \"执行中\"),\n        (STATUS_SUCCESS, \"执行成功\"),\n        (STATUS_FAILED, \"执行失败\"),\n    )\n\n    tool = models.ForeignKey(\n        ToolInfo, on_delete=models.CASCADE, help_text=\"实用工具对象\")\n    task_name = models.CharField(\n        \"任务标题\", max_length=128, null=True, help_text=\"任务标题\")\n    operator = models.CharField(\n        \"操作人\", max_length=128, null=True, blank=True, help_text=\"操作人\")\n    status = models.IntegerField(\n        \"main执行状态\", choices=STATUS_TYPE_CHOICES,\n        default=0, help_text=\"main执行状态\")\n    start_time = models.DateTimeField(\n        \"开始时间\", null=True, auto_now_add=True, help_text=\"开始时间\")\n    end_time = models.DateTimeField(\"结束时间\", null=True, help_text=\"结束时间\")\n    form_answer = models.JSONField(\"任务表单提交结果\", default=dict)\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_tool_execute_main_history\"\n        verbose_name = verbose_name_plural = \"实用工具执行记录\"\n\n    @property\n    def duration(self):\n        if not all([self.end_time, self.start_time]):\n            return \"-\"\n        return timedelta_strftime(self.end_time - self.start_time)\n\n    def get_input_files(self):\n        files = []\n        for answer in self.form_answer.get(\"script_args\", {}):\n            if answer.get(\"type\") == \"file\" and answer.get(\"default\"):\n                files.append(answer.get(\"default\").get(\"file_url\"))\n        return files\n\n\nclass ToolExecuteDetailHistory(TimeStampMixin):\n    \"\"\" 实用工具执行详情记录 \"\"\"\n\n    objects = None\n\n    STATUS_READY = 0\n    STATUS_RUNNING = 1\n    STATUS_SUCCESS = 2\n    STATUS_FAILED = 3\n    STATUS_TIMEOUT = 4\n    STATUS_TYPE_CHOICES = (\n        (STATUS_READY, \"待执行\"),\n        (STATUS_RUNNING, \"执行中\"),\n        (STATUS_SUCCESS, \"执行成功\"),\n        (STATUS_FAILED, \"执行失败\"),\n        (STATUS_TIMEOUT, \"执行超时\"),\n    )\n\n    main_history = models.ForeignKey(\n        ToolExecuteMainHistory, on_delete=models.CASCADE,\n        help_text=\"实用工具对象\")\n    # 脚本需要在某台主机上执行，此处代表该主机的ip地址\n    target_ip = models.CharField(\n        \"目标IP地址\", max_length=64,\n        null=False, blank=False, help_text=\"目标IP地址\")\n    time_out = models.IntegerField(\"超时时间\", default=60)\n    run_user = models.CharField(\"执行用户\", max_length=64, default=\"\")\n    status = models.IntegerField(\n        \"detail执行状态\", choices=STATUS_TYPE_CHOICES,\n        default=0, help_text=\"detail执行状态\")\n    # 脚本执行的参数详情信息\n    execute_args = models.JSONField(\n        \"执行参数信息\", default=dict, help_text=\"执行参数信息\")\n    execute_log = models.TextField(\"执行日志\", help_text=\"执行日志\")\n    # 脚本有输出时使用,{\"message\": \"\", \"file\": [\"a.txt\", \"b.log\"]}\n    # 执行脚本有返回写message，有receive_files写file，只写文件名\n    output = models.JSONField(\"脚本输出内容\", default=dict)\n\n    class Meta:\n        \"\"\"元数据\"\"\"\n        db_table = \"omp_tool_execute_detail_history\"\n        verbose_name = verbose_name_plural = \"实用工具执行详情表\"\n\n    @property\n    def get_data_dir(self):\n        if hasattr(self, \"data_dir\"):\n            return self.data_dir\n        data_obj = Host.objects.filter(ip=self.target_ip).first()\n        data_dir = data_obj.data_folder if data_obj else \"/tmp\"\n        setattr(self, \"data_dir\", data_dir)\n        return data_dir\n\n    @property\n    def get_tools_dir(self):\n        if hasattr(self, \"tools_dir\"):\n            return self.tools_dir\n        tool = self.main_history.tool\n        tools_dir = {\n            \"tool_folder_path\": tool.tool_folder_path,\n            \"script_path\": tool.script_path,\n            \"send_package\": tool.send_package\n        }\n        setattr(self, \"tools_dir\", tools_dir)\n        return tools_dir\n\n    def get_cmd_str(self):\n        \"\"\"\n        获取执行命令\n        :return: 命令字符串\n        \"\"\"\n        # 自定义不支持\n        interpret_dir = os.path.join(\n            self.get_data_dir, \"omp_salt_agent/env/bin/python3\")\n        tool_dir = self.get_tools_dir\n        scripts_dir = os.path.join(\n            self.get_data_dir,\n            f\"omp_packages/{tool_dir.get('tool_folder_path', '')}\",\n            tool_dir.get(\"script_path\")\n        )\n        interpret_dir = interpret_dir + \" \" + scripts_dir\n        for key, value in self.execute_args.items():\n            if value:\n                interpret_dir += \" --{0} {1}\".format(key, value)\n        return interpret_dir\n\n    def get_send_files(self):\n        \"\"\"\n        获取需要发送的文件\n        :return: local_files：需要发送的文件，send_to：发送的位置\n        \"\"\"\n        local_files = self.main_history.get_input_files()\n        tool_dir = self.get_tools_dir\n        tool_folder_path = tool_dir.get(\"tool_folder_path\")\n        local_files.append(\n            os.path.join(tool_folder_path, tool_dir.get(\"script_path\"))\n        )\n        for file in tool_dir.get(\"send_package\", []):\n            local_files.append(os.path.join(tool_folder_path, file))\n        return [\n            {\n                \"local_file\": local_file,\n                \"remote_file\": os.path.join(\n                    self.get_data_dir, \"omp_packages\", local_file\n                )\n            } for local_file in local_files\n        ]\n\n    def get_receive_files(self):\n        \"\"\"\n        获取需要接受的文件\n        :return: output_files：需要接受的文件，receive_to：接收文件的存放位置\n        \"\"\"\n        output = self.execute_args.get(\"output\", \"\").split(\",\")\n        return {\n            \"output_files\": output if len(output[0]) != 0 else [],\n            \"receive_to\": os.path.join(\n                settings.PROJECT_DIR,\n                \"package_hub/tool/download_data/\"\n            )\n        }\n"
  },
  {
    "path": "omp_server/db_models/models/upgrade.py",
    "content": "from django.db import models\n\nfrom .product import ApplicationHub\nfrom .env import Env\nfrom .service import Service\nfrom .user import UserProfile\nfrom db_models.mixins import TimeStampMixin, UpgradeStateMixin, \\\n    UpgradeStateChoices, RollBackStateMixin\n\n\nclass UpgradeHistory(UpgradeStateMixin, TimeStampMixin):\n\n    COMMON_OPERATION = [\n        {\"name\": \"更新data.json\", \"key\": \"update_data_json\", \"result\": False}\n    ]\n\n    operator = models.ForeignKey(\n        UserProfile, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"用户\")\n    env = models.ForeignKey(\n        Env, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"所属环境\")\n    pre_upgrade_state = models.IntegerField(\n        \"升级前置结果\",\n        choices=UpgradeStateChoices.choices,\n        default=UpgradeStateChoices.UPGRADE_WAIT\n    )\n    pre_upgrade_result = models.JSONField(\"升级前置信息\", default=dict)\n\n    class Meta:\n        db_table = \"omp_upgrade_history\"\n        verbose_name = verbose_name_plural = '升级历史记录'\n\n    @property\n    def can_roll_back(self):\n        return self.upgradedetail_set.filter(\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_SUCCESS,\n                UpgradeStateChoices.UPGRADE_FAIL\n            ],\n            has_rollback=False\n        ).exists()\n\n    @property\n    def execution_record_state(self):\n        # 执行记录使用\n        return self.upgrade_state\n\n    def operate_count(self, exclude_service_ids=None):\n        # 升级服务个数\n        queryset = self.upgradedetail_set.filter(service__isnull=False)\n        if exclude_service_ids:\n            queryset = queryset.exclude(\n                service_id__in=exclude_service_ids\n            )\n        return queryset.count()\n\n    @property\n    def module_id(self):\n        return str(self.id)\n\n\nclass UpgradeDetail(UpgradeStateMixin, TimeStampMixin):\n    history = models.ForeignKey(\n        UpgradeHistory,\n        on_delete=models.SET_NULL,\n        null=True,\n        verbose_name=\"升级历史记录\")\n    union_server = models.CharField(\n        \"唯一服务实例:ip-app_name(适配hadoop，单服务裂开多个服务)\",\n        max_length=199,\n        null=False\n    )\n    # null=True待定\n    service = models.ForeignKey(\n        Service, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"服务\")\n    target_app = models.ForeignKey(\n        ApplicationHub,\n        null=True,\n        related_name=\"target_app_set\",\n        on_delete=models.SET_NULL,\n        verbose_name=\"升级目标服务\")\n    current_app = models.ForeignKey(\n        ApplicationHub,\n        null=True,\n        related_name=\"current_app_set\",\n        on_delete=models.SET_NULL,\n        verbose_name=\"升级前服务\")\n    path_info = models.JSONField(\"服务包备份路径信息\", default=dict)\n    handler_info = models.JSONField(\"升级步骤信息\", default=dict)\n    message = models.TextField(\"升级日志信息\", default=\"\")\n    has_rollback = models.BooleanField(\"是否已回滚\", default=False)\n\n    class Meta:\n        db_table = \"omp_upgrade_detail\"\n        verbose_name = verbose_name_plural = '单个服务升级记录'\n\n    def get_service_history(self):\n        \"\"\"\n        组织服务操作记录参数\n        :return: dict\n        \"\"\"\n        return {\n            \"username\": self.history.operator.username,\n            \"description\": f\"服务自版本{self.current_app.app_version}\"\n                           f\"升级至版本{self.target_app.app_version}\",\n            \"result\": \"success\" if self.upgrade_state == 2 else \"failed\",\n            \"created\": self.created\n        }\n\n\nclass RollbackHistory(RollBackStateMixin, TimeStampMixin):\n\n    operator = models.ForeignKey(\n        UserProfile, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"用户\")\n    env = models.ForeignKey(\n        Env, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"所属环境\")\n\n    class Meta:\n        db_table = \"omp_rollback_history\"\n        verbose_name = verbose_name_plural = '回滚历史记录'\n\n    @property\n    def execution_record_state(self):\n        # 执行记录使用\n        return self.rollback_state\n\n    def operate_count(self, exclude_service_ids=None):\n        # 升级服务个数\n        queryset = self.rollbackdetail_set.filter(\n            upgrade__service__isnull=False\n        )\n        if exclude_service_ids:\n            queryset = queryset.exclude(\n                upgrade__service_id__in=exclude_service_ids\n            )\n        return queryset.count()\n\n    @property\n    def module_id(self):\n        return str(self.id)\n\n\nclass RollbackDetail(RollBackStateMixin, TimeStampMixin):\n    history = models.ForeignKey(\n        RollbackHistory,\n        on_delete=models.SET_NULL,\n        null=True,\n        verbose_name=\"回滚历史记录\")\n    upgrade = models.ForeignKey(\n        UpgradeDetail, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"升级记录\")\n    path_info = models.JSONField(\"服务包备份路径信息\", default=dict)\n    handler_info = models.JSONField(\"回滚步骤信息\", default=dict)\n    message = models.TextField(\"回滚日志信息\", default=\"\")\n\n    class Meta:\n        db_table = \"omp_rollback_detail\"\n        verbose_name = verbose_name_plural = '单个服务回滚记录'\n\n    def get_service_history(self):\n        \"\"\"\n        组织服务操作记录参数\n        :return: dict\n        \"\"\"\n        return {\n            \"username\": self.history.operator.username,\n            \"description\": f\"服务自版本{self.upgrade.target_app.app_version}\"\n                           f\"回滚至版本{self.upgrade.current_app.app_version}\",\n            \"result\": \"success\" if self.rollback_state == 2 else \"failed\",\n            \"created\": self.created\n        }\n"
  },
  {
    "path": "omp_server/db_models/models/upload.py",
    "content": "import os\nimport random\nimport string\nfrom datetime import datetime\n\nfrom django.conf import settings\nfrom django.core.files.storage import FileSystemStorage\nfrom django.db import models\n\nfrom utils.plugin.public_utils import file_md5, format_location_size\nfrom .user import UserProfile\nfrom db_models.mixins import TimeStampMixin\n\n\nclass UploadFileHistory(TimeStampMixin):\n\n    STORAGE_KLASS = {\"location\"}\n\n    module = models.CharField(\"需要上传文件的model\", max_length=32, default=\"\")\n    module_id = models.IntegerField(\"需要上传文件的model id\", default=0)\n    union_id = models.CharField(\"文件md5值\", max_length=64, default=\"\")\n    user = models.ForeignKey(\n        UserProfile, null=True,\n        on_delete=models.SET_NULL,\n        verbose_name=\"用户\")\n    # 目前只支持location\n    storage_klass = models.CharField(\n        \"存储方式\", max_length=64, default=\"location\")\n    # location时相对于omp目录\n    relative_path = models.TextField(\"文件存储相对路径\", default=\"\")\n    file_name = models.CharField(\"文件名称\", max_length=64, default=\"\")\n    # 单位：K,M,G\n    file_size = models.CharField(\"文件大小\", max_length=16, default=\"0K\")\n    # location时除ip外url\n    file_url = models.TextField(\"文件访问路径\", default=\"\")\n    deleted = models.BooleanField(\"删除\", default=False)\n\n    class Meta:\n        db_table = \"omp_upload_file\"\n        verbose_name = verbose_name_plural = \"上传文件记录\"\n\n    @classmethod\n    def format_file_name(cls, file_name):\n        _time = datetime.now().strftime('%Y%m%d%H%M%S')\n        random_sub = ''.join(\n            random.sample(string.digits+string.ascii_lowercase, 4))\n        if len(file_name) > 10:\n            file_name = file_name[-10:]\n        return f\"{_time}-{random_sub}-{file_name}\"\n\n    @classmethod\n    def location(cls, file, module_obj=None, user=None, **kwargs):\n        cls_kwargs = {\"relative_path\": 'tmp/', \"user\": user}\n        if module_obj:\n            cls_kwargs = {\n                \"module\": module_obj.__class__.__name__,\n                \"module_id\": module_obj.id,\n            }\n            relative_path = module_obj.valid_upload_file(file, **kwargs)\n            cls_kwargs[\"relative_path\"] = relative_path\n        else:\n            relative_path = 'tmp/'\n        save_path = os.path.join(settings.PROJECT_DIR, relative_path)\n        storage = FileSystemStorage(save_path)\n        _time = datetime.now().strftime('%Y%m%d%H%M%S')\n        file_name = cls.format_file_name(file.name)\n        cls_kwargs[\"file_name\"] = file_name\n        if module_obj:\n            cls_kwargs[\"file_url\"] = module_obj.upload_file_url(file_name)\n        else:\n            cls_kwargs[\"file_url\"] = f\"download/{file_name}\"\n        storage.save(storage.generate_filename(file_name), file)\n        # FileSystemStorage save 同时计算md5?\n        md5 = file_md5(os.path.join(save_path, file_name))\n        if md5:\n            exists_obj = cls.objects.filter(union_id=md5).last()\n            if exists_obj:\n                old_file = os.path.join(\n                    settings.PROJECT_DIR,\n                    exists_obj.relative_path,\n                    exists_obj.file_name\n                )\n                if os.path.exists(old_file):\n                    os.remove(os.path.join(save_path, file_name))\n                else:\n                    exists_obj.file_name = file_name\n                    exists_obj.relative_path = relative_path\n                    exists_obj.save()\n                return exists_obj\n        cls_kwargs[\"file_size\"] = format_location_size(file.size)\n        return cls.objects.create(union_id=md5, **cls_kwargs)\n"
  },
  {
    "path": "omp_server/db_models/models/user.py",
    "content": "from django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\n\nclass UserProfile(AbstractUser):\n    \"\"\" 自定义用户表 \"\"\"\n    role = models.CharField(\"用户角色\", max_length=128, null=True, blank=True, help_text=\"用户角色\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_user_profile\"\n        verbose_name = verbose_name_plural = \"用户\"\n\n    def __str__(self):\n        \"\"\" 显示用户 \"\"\"\n        return f\"用户: {self.username}\"\n\n\nclass OperateLog(models.Model):\n    \"\"\" 用户操作记录表 \"\"\"\n\n    objects = None\n    username = models.CharField(\n        \"操作用户\", max_length=128, help_text=\"操作用户\")\n    request_method = models.CharField(\n        \"请求方法\", max_length=32, help_text=\"请求方法\")\n    request_ip = models.GenericIPAddressField(\n        \"请求源IP\", blank=True, null=True, help_text=\"请求源IP\")\n    request_url = models.CharField(\n        \"用户目标URL\", max_length=256, help_text=\"用户目标URL\")\n    description = models.CharField(\n        \"用户行为描述\", max_length=256, help_text=\"用户行为描述\")\n    response_code = models.IntegerField(\n        \"响应状态码\", default=0, help_text=\"响应状态码\")\n    request_result = models.TextField(\n        \"请求结果\", default=\"success\", help_text=\"请求结果\")\n    create_time = models.DateTimeField(\n        \"操作发生时间\", auto_now_add=True, help_text=\"操作发生时间\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        db_table = \"omp_user_operate_log\"\n        verbose_name = verbose_name_plural = \"用户操作记录\"\n\n\nclass UserLoginLog(models.Model):\n    \"\"\"用户登陆记录\"\"\"\n    objects = None\n    username = models.CharField(max_length=128, verbose_name=\"Username\")\n    login_time = models.DateTimeField(\n        blank=True, null=True, verbose_name=\"Login time\")\n    ip = models.CharField(max_length=32, null=True,\n                          blank=True, verbose_name=\"Login ip\")\n    role = models.CharField(\n        max_length=128, verbose_name=\"role \", null=True, blank=True)\n    request_result = models.CharField(\n        \"请求结果\", max_length=512,\n        null=True, blank=True, help_text=\"请求结果\")\n\n    class Meta:\n        db_table = \"omp_login_log\"\n        verbose_name = verbose_name_plural = \"用户登陆记录\"\n"
  },
  {
    "path": "omp_server/db_models/receivers/__init__.py",
    "content": "from .execution_record import install_execution_record,\\\n    upgrade_execution_record, rollback_execution_record\nfrom .service import update_execution_record\n\n\n__all__ = [\n    install_execution_record,\n    upgrade_execution_record,\n    rollback_execution_record,\n    update_execution_record\n]\n\n\n"
  },
  {
    "path": "omp_server/db_models/receivers/execution_record.py",
    "content": "from django.dispatch import receiver\nfrom django.db.models.signals import post_save\n\nfrom db_models.models import ExecutionRecord, MainInstallHistory, \\\n    UpgradeHistory, RollbackHistory\nfrom db_models.models.execution import StateChoices\n\n\ndef create_execution_record(instance, created=False, *args, **kwargs):\n    module = instance.__class__.__name__\n    obj, _ = ExecutionRecord.objects.get_or_create(\n        module=module,\n        module_id=instance.module_id\n    )\n    old_state = obj.state\n    state = f\"{module.upper()}_{instance.execution_record_state}\"\n    obj.state = getattr(StateChoices, state)\n    if isinstance(instance.operator, str):\n        obj.operator = instance.operator\n    else:\n        obj.operator = instance.operator.username\n    obj.created = instance.created\n    if (\"SUCCESS\" in obj.state or \"FAIL\" in obj.state) and \\\n            obj.state != old_state:\n        obj.end_time = instance.modified\n    obj.count = instance.operate_count()\n    obj.save()\n\n\n@receiver(post_save, sender=MainInstallHistory)\ndef install_execution_record(sender, instance, *args, **kwargs):\n    create_execution_record(instance, *args, **kwargs)\n\n\n@receiver(post_save, sender=UpgradeHistory)\ndef upgrade_execution_record(sender, instance, created, *args, **kwargs):\n    create_execution_record(instance, created, *args, **kwargs)\n\n\n@receiver(post_save, sender=RollbackHistory)\ndef rollback_execution_record(sender, instance, created, *args, **kwargs):\n    create_execution_record(instance, created, *args, **kwargs)\n"
  },
  {
    "path": "omp_server/db_models/receivers/service.py",
    "content": "from django.db.models.signals import pre_delete, post_save\nfrom django.dispatch import receiver\n\nfrom db_models.mixins import UpgradeStateChoices, RollbackStateChoices\nfrom db_models.models import Service, MainInstallHistory, \\\n    ExecutionRecord, UpgradeHistory, RollbackHistory, UpgradeDetail, \\\n    RollbackDetail, DetailInstallHistory, SelfHealingSetting\nfrom utils.plugin.crontab_utils import change_task\n\n\ndef update_upgrade_history(history, union_server):\n    UpgradeDetail.objects.filter(\n        union_server=union_server).delete()\n    details = history.upgradedetail_set.exclude(\n        upgrade_state=UpgradeStateChoices.UPGRADE_SUCCESS\n    ).exclude(union_server=union_server)\n    if not details.exists():\n        history.upgrade_state = UpgradeStateChoices.UPGRADE_SUCCESS\n        history.save()\n\n\ndef update_rollback_history(history, union_server):\n    RollbackDetail.objects.filter(\n        upgrade__union_server=union_server).delete()\n    details = history.rollbackdetail_set.exclude(\n        rollback_state=RollbackStateChoices.ROLLBACK_SUCCESS\n    ).exclude(upgrade__union_server=union_server)\n    if not details.exists():\n        history.rollback_state = RollbackStateChoices.ROLLBACK_SUCCESS\n        history.save()\n\n\n@receiver(pre_delete, sender=Service)\ndef update_execution_record(sender, instance, *args, **kwargs):\n    # models.SET_NULL必须改！\n    filter_keys = [\n        (MainInstallHistory, \"detailinstallhistory__service\"),\n        (RollbackHistory, \"rollbackdetail__upgrade__service\"),\n        (UpgradeHistory, \"upgradedetail__service\"),\n    ]\n    if instance.service.app_name == \"hadoop\" and instance.service_split == 2:\n        Service.split_objects.filter(\n            ip=instance.ip,\n            service__app_name=\"hadoop\"\n        ).delete()\n    union_server = f\"{instance.ip}-{instance.service.app_name}\"\n    for model_cls, filter_key in filter_keys:\n        history = model_cls.objects.filter(**{filter_key: instance}).first()\n        if not history:\n            continue\n        execution_record = ExecutionRecord.objects.filter(\n            module=history.__class__.__name__,\n            module_id=history.module_id\n        ).first()\n        if not execution_record:\n            continue\n        execution_record.count = history.operate_count([instance.id])\n        execution_record.save()\n        if model_cls.__name__ == \"RollbackHistory\" and \\\n                history.rollback_state != RollbackStateChoices.ROLLBACK_SUCCESS:\n            update_rollback_history(history, union_server)\n        if model_cls.__name__ == \"UpgradeHistory\" and \\\n                history.upgrade_state != UpgradeStateChoices.UPGRADE_SUCCESS:\n            update_upgrade_history(history, union_server)\n    # 删除安装记录, 修复卸载产品再重试安装\n    DetailInstallHistory.objects.filter(service=instance).delete()\n\n@receiver(post_save, sender=SelfHealingSetting)\ndef update_self_health(sender, instance, *args, **kwargs):\n    data = {'is_on': instance.used,\n            'task_func': 'services.self_healing.self_healing',\n            'task_name': f'self_health_cron_task_{instance.id}',\n            'crontab_detail': dict(day_of_month='*', day_of_week='*', hour=\"*\", minute=f\"*/{instance.fresh_rate}\",\n                                   month_of_year='*')}\n\n    change_task(instance.id, data=data)\n\n\n@receiver(pre_delete, sender=SelfHealingSetting)\ndef delete_self_health(sender, instance, *args, **kwargs):\n    data = {\n        \"is_on\": False,\n        'task_func': 'services.self_healing.self_healing',\n        'task_name': f'self_health_cron_task_{instance.id}',\n    }\n    change_task(instance.id, data=data)\n\n"
  },
  {
    "path": "omp_server/db_models/signals/__init__.py",
    "content": "__all__ = [\n]\n"
  },
  {
    "path": "omp_server/dev_code.md",
    "content": "# 项目及研发规范\n\n## 前后端接口规范\n\n### 后端正确响应返回格式\n```json\n{\n  \"code\": 0,\n  \"message\": \"success\",\n  \"data\": null\n}\n```\n\n### 后端错误响应返回格式\n```json\n{\n  \"code\": 1,\n  \"message\": \"错误描述信息\",\n  \"data\": null\n}\n```\n\n# API文档\n"
  },
  {
    "path": "omp_server/dev_requirement.txt",
    "content": "amqp==5.0.6\nasgiref==3.4.1\naspy.yaml==1.3.0\nattrs==21.2.0\nbackports.entry-points-selectable==1.1.0\nbcrypt==3.2.0\nbilliard==3.6.4.0\nboto3==1.16.42\nbotocore==1.19.42\ncached-property==1.5.2\ncelery==5.1.2\ncertifi==2021.5.30\ncffi==1.14.4\ncfgv==3.3.1\nchardet==3.0.4\ncharset-normalizer==2.0.5\nclick==7.1.2\nclick-didyoumean==0.0.3\nclick-plugins==1.1.1\nclick-repl==0.2.0\nconcurrent-log-handler==0.9.19\ncoreapi==2.3.3\ncoreschema==0.0.4\ncryptography==3.3.1\ndistlib==0.3.2\ndistro==1.5.0\nDjango==3.1.4\ndjango-celery-beat==2.2.1\ndjango-celery-results==2.2.0\ndjango-filter==2.4.0\ndjango-ipware==4.0.0\ndjango-timezone-field==4.2.1\ndjangorestframework==3.12.4\ndjangorestframework-jwt==1.11.0\nemoji==1.5.0\net-xmlfile==1.1.0\nfilelock==3.0.12\nidentify==2.2.14\nidna==3.2\niniconfig==1.1.1\nipaddress==1.0.23\nitypes==1.2.0\njdcal==1.4.1\nJinja2==3.0.1\njmespath==0.10.0\nkombu==5.1.0\nMarkupSafe==2.0.1\nmsgpack==1.0.2\nnodeenv==1.6.0\npackaging==21.0\nparamiko==2.7.2\nPillow==9.1.0\nplatformdirs==2.3.0\npluggy==1.0.0\nportalocker==2.3.0\npre-commit==2.15.0\nprompt-toolkit==3.0.20\npsutil==5.8.0\npy==1.10.0\npycparser==2.20\npycryptodome==3.10.1\npycryptodomex==3.9.9\nPyJWT==1.7.1\nPyMySQL==1.0.2\nPyNaCl==1.4.0\npyparsing==2.4.7\npysqlite3==0.4.5\npytest==6.2.5\npython-crontab==2.5.1\npython-dateutil==2.8.2\npytz==2021.1\nPyYAML==5.4.1\npyzmq==20.0.0\nredis==3.5.3\nrequests==2.26.0\nruamel.yaml==0.17.10\nruamel.yaml.clib==0.2.6\ns3transfer==0.3.3\nsalt==3002.2\nscp==0.13.3\nsix==1.16.0\nsqlparse==0.4.1\ntoml==0.10.2\ntzlocal==2.1\nuritemplate==3.0.1\nurllib3==1.26.6\nuWSGI==2.0.19.1\nvine==5.0.0\nvirtualenv==20.7.2\nwcwidth==0.2.5\nWerkzeug==1.0.1\nxlrd==1.2.0\n"
  },
  {
    "path": "omp_server/hosts/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/hosts/admin.py",
    "content": "# from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/hosts/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass HostsConfig(AppConfig):\n    name = 'hosts'\n"
  },
  {
    "path": "omp_server/hosts/hosts_filters.py",
    "content": "\"\"\"\n主机相关过滤器\n\"\"\"\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\n\nfrom db_models.models import (Host, HostOperateLog)\n\n\nclass HostFilter(FilterSet):\n    \"\"\" 主机过滤类 \"\"\"\n    ip = django_filters.CharFilter(\n        help_text=\"IP，模糊匹配\", field_name=\"ip\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = Host\n        fields = (\"ip\",)\n\n\nclass HostOperateFilter(FilterSet):\n    \"\"\" 主机日志过滤类 \"\"\"\n\n    host_id = django_filters.CharFilter(\n        help_text=\"主机 ID\", field_name=\"host_id\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = HostOperateLog\n        fields = (\"host_id\",)\n"
  },
  {
    "path": "omp_server/hosts/hosts_serializers.py",
    "content": "\"\"\"\n主机序列化器\n\"\"\"\nimport logging\nimport socket\nimport struct\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\n\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.serializers import (\n    ModelSerializer, Serializer\n)\n\nfrom db_models.models import (\n    Host, Env, HostOperateLog, Service\n)\nfrom hosts.tasks import (\n    host_agent_restart, init_host,\n    insert_host_celery_task, deploy_agent,\n    reinstall_monitor_celery_task,\n    delete_hosts\n)\n\nfrom utils.plugin.ssh import SSH\nfrom utils.plugin.crypto import AESCryptor\nfrom utils.common.validators import (\n    ReValidator, NoEmojiValidator, NoChineseValidator\n)\nfrom utils.common.exceptions import OperateError\nfrom utils.common.serializers import HostIdsSerializer\nfrom utils.parse_config import THREAD_POOL_MAX_WORKERS\nfrom promemonitor.alertmanager import Alertmanager\n\nlogger = logging.getLogger(\"server\")\n\n\nclass HostUninstallSerializer(ModelSerializer):\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Host\n        fields = ('id',)\n\n    def validate(self, attrs):\n        \"\"\" 校验主机是否存在服务 \"\"\"\n\n        request_data = self.context.get('request').data\n        host_list = request_data.get(\"host_ids\", [])\n        host_ls = list(Host.objects.filter(\n            id__in=host_list).values_list('ip', flat=True))\n        service_obj = Service.objects.filter(ip__in=host_ls).exclude(\n            service__is_base_env=True\n        )\n        if service_obj.count() != 0:\n            ip_str = \",\".join(set(service_obj.values_list('ip', flat=True)))\n            raise ValidationError(f\"IP存在相关的服务:{ip_str}\")\n        attrs['host_ls'] = host_ls\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 删除主机 \"\"\"\n        host_ls = validated_data.get(\"host_ls\")\n        agent_status = {\"host_agent\": Host.AGENT_DEPLOY_DELETE,\n                        \"monitor_agent\": Host.AGENT_DEPLOY_DELETE}\n        Host.objects.filter(ip__in=host_ls).update(**agent_status)\n        delete_hosts.delay(host_ls)\n        return \"任务下发成功\"\n\n\nclass HostSerializer(ModelSerializer):\n    \"\"\" 主机序列化类 \"\"\"\n\n    instance_name = serializers.CharField(\n        help_text=\"实例名\",\n        required=True, max_length=16,\n        error_messages={\n            \"required\": \"必须包含[instance_name]字段\",\n            \"max_length\": \"实例名长度需小于{max_length}\"},\n        validators=[\n            NoEmojiValidator(),\n            NoChineseValidator(),\n            ReValidator(regex=r\"^[-a-zA-Z0-9].*$\"),\n        ])\n    ip = serializers.IPAddressField(\n        help_text=\"IP地址\",\n        required=True,\n        error_messages={\n            \"invalid\": \"IP格式不合法\",\n            \"required\": \"必须包含[ip]字段\",\n        })\n    port = serializers.IntegerField(\n        help_text=\"端口\",\n        required=True,\n        min_value=1, max_value=65535,\n        error_messages={\n            \"invalid\": \"端口格式不合法\",\n            \"required\": \"必须包含[port]字段\",\n            \"max_value\": \"端口超出指定范围\",\n            \"min_value\": \"端口超出指定范围\",\n        })\n    username = serializers.CharField(\n        help_text=\"用户名\",\n        required=True, max_length=16,\n        error_messages={\n            \"required\": \"必须包含[username]字段\",\n            \"max_length\": \"用户名长度需小于{max_length}\"},\n        validators=[\n            ReValidator(regex=r\"^[_a-zA-Z0-9][-_a-zA-Z0-9]+$\"),\n        ])\n    password = serializers.CharField(\n        help_text=\"密码\",\n        required=True,\n        min_length=8, max_length=64,\n        error_messages={\n            \"required\": \"必须包含[password]字段\",\n            \"min_length\": \"密码长度需大于{min_length}\",\n            \"max_length\": \"密码长度需小于{max_length}\"},\n        validators=[\n            NoEmojiValidator(),\n            NoChineseValidator(),\n        ])\n    data_folder = serializers.CharField(\n        help_text=\"数据分区\",\n        required=True, max_length=255,\n        error_messages={\"required\": \"必须包含[data_folder]字段\"},\n        validators=[\n            ReValidator(regex=r\"^/[-_/a-zA-Z0-9]+$\"),\n        ])\n    operate_system = serializers.CharField(\n        help_text=\"操作系统\",\n        required=True, max_length=128,\n        error_messages={\"required\": \"必须包含[operate_system]字段\"})\n    env = serializers.PrimaryKeyRelatedField(\n        help_text=\"环境\",\n        required=False,\n        queryset=Env.objects.all(),\n        error_messages={\"does_not_exist\": \"未找到对应环境\"})\n    run_user = serializers.CharField(\n        help_text=\"运行用户\",\n        required=False, default=\"\",\n        max_length=16, write_only=True,\n        error_messages={\n            \"max_length\": \"运行用户长度需小于{max_length}\"},\n        validators=[\n            ReValidator(regex=r\"^[_a-zA-Z0-9][-_a-zA-Z0-9]+$\"),\n        ])\n    use_ntpd = serializers.BooleanField(\n        help_text=\"是否开启时钟同步\",\n        required=True,\n        error_messages={\"required\": \"必须包含[use_ntpd]字段\"}\n\n    )\n    ntpd_server = serializers.IPAddressField(\n        help_text=\"时间同步服务器IP地址\",\n        required=False,\n        error_messages={\n            \"invalid\": \"ntpd_server格式不合法\"\n        }\n    )\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Host\n        exclude = (\"is_deleted\", \"agent_dir\",)\n        read_only_fields = (\n            \"service_num\", \"alert_num\", \"host_name\", \"operate_system\",\n            \"memory\", \"cpu\", \"disk\", \"is_maintenance\", \"host_agent\",\n            \"monitor_agent\", \"host_agent_error\", \"monitor_agent_error\",\n            \"init_status\"\n        )\n\n    # def get_service_num(self, obj):\n    #     \"\"\"\n    #     获取主机上的\n    #     :param obj:\n    #     :return:\n    #     \"\"\"\n    #     return Service.objects.filter(\n    #         ip=obj.ip, service__is_base_env=False).count()\n\n    def validate_instance_name(self, instance_name):\n        \"\"\" 校验实例名是否重复 \"\"\"\n        queryset = Host.objects.all()\n        if self.instance is not None:\n            queryset = queryset.exclude(id=self.instance.id)\n        if queryset.filter(instance_name=instance_name).exists():\n            raise ValidationError(\"实例名已经存在\")\n        return instance_name\n\n    def validate_ip(self, ip):\n        \"\"\" 校验IP是否重复 \"\"\"\n        if self.instance is not None:\n            if ip != self.instance.ip:\n                raise ValidationError(\"IP不可修改\")\n            return ip\n        if Host.objects.filter(ip=ip).exists():\n            raise ValidationError(\"IP已经存在\")\n        return ip\n\n    def validate_data_folder(self, data_folder):\n        \"\"\" 校验数据分区是否合理 \"\"\"\n        dir_ls = data_folder.split(\"/\")\n        for dir_name in dir_ls:\n            if dir_name != \"\" and dir_name.startswith(\"-\"):\n                raise ValidationError(\"数据分区目录不能以'-'开头\")\n        return data_folder\n\n    def validate_operate_system(self, operate_system):\n        \"\"\" 校验操作系统是否合法 \"\"\"\n        operate_ls = (\"CentOS\", \"RedHat\")\n        if operate_system not in operate_ls:\n            raise ValidationError(f\"操作系统支持{'/'.join(operate_ls)}\")\n        return operate_system\n\n    def validate(self, attrs):\n        \"\"\" 主机信息验证 \"\"\"\n        ip = attrs.get(\"ip\")\n        port = attrs.get(\"port\")\n        username = attrs.get(\"username\")\n        password = attrs.get(\"password\")\n        data_folder = attrs.get(\"data_folder\")\n        run_user = attrs.get(\"run_user\")\n        use_ntpd = attrs.get(\"use_ntpd\")\n        # 默认主机初始化标记为 False\n        attrs[\"init_host\"] = False\n\n        # 如果提供 run_user，需确保用户为 root\n        if run_user and username != \"root\":\n            raise ValidationError({\"username\": \"运行用户仅在用户名为root时可用\"})\n\n        # 校验主机 SSH 连通性\n        ssh = SSH(ip, port, username, password)\n        is_connect, _ = ssh.check()\n        if not is_connect:\n            logger.info(f\"host ssh connection failed: ip-{ip},port-{port},\"\n                        f\"username-{username},password-{password}\")\n            raise ValidationError({\"ip\": \"SSH登录失败\"})\n\n        # 如果数据分区不存在，则创建数据分区\n        success, msg = ssh.cmd(\n            f\"test -d {data_folder} || mkdir -p {data_folder}\")\n        if not success or msg.strip():\n            logger.info(f\"host create data folder failed: ip-{ip},port-{port},\"\n                        f\"username-{username},password-{password},\"\n                        f\"data_folder-{data_folder}\")\n            raise ValidationError({\"data_folder\": \"创建数据分区操作失败\"})\n\n        # 当用户为 root 或具有 sudo 权限时，自动进行初始化\n        is_sudo, _ = ssh.is_sudo()\n        if is_sudo or username == \"root\":\n            attrs[\"init_host\"] = True\n\n        # 如果未传递 env，则指定默认环境\n        if not attrs.get(\"env\") and not self.instance:\n            attrs[\"env\"] = Env.objects.filter(id=1).first()\n        # 主机密码加密处理\n        if attrs.get(\"password\"):\n            attrs[\"password\"] = AESCryptor().encode(attrs.get(\"password\"))\n        # 启用ntpd，验证ntpd服务器是否可用\n        if use_ntpd:\n            ntpd_server = attrs.get(\"ntpd_server\")\n            # udp 检测ip:port是否可用\n            REF_TIME_1970 = 2208988800\n            client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n            data = b'\\x1b' + 47 * b'\\0'\n            ip_port = (ntpd_server, 123)\n            client.sendto(data, ip_port)\n            client.settimeout(20)\n            try:\n                data, address = client.recvfrom(1024)\n            except socket.timeout as e:\n                logger.info(f\"ntpd服务端不可用:{e}\")\n                data = bytes('', encoding='utf-8')\n            t = 0\n            if data:\n                t = struct.unpack('!12I', data)[10]\n                t -= REF_TIME_1970\n            if t == 0:\n                raise ValidationError(f\"{ntpd_server}上的ntpd服务端不可用\")\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 创建主机 \"\"\"\n        ip = validated_data.get(\"ip\")\n        init_flag = validated_data.pop(\"init_host\", False)\n        # 如果 run_user 存在，则删除\n        if \"run_user\" in validated_data:\n            validated_data.pop(\"run_user\")\n        # 指定 Agent 安装目录为 data_folder\n        validated_data[\"agent_dir\"] = validated_data.get(\"data_folder\")\n        instance = super(HostSerializer, self).create(validated_data)\n        logger.info(f\"host[{ip}] - create success\")\n        # 写入操作记录\n        HostOperateLog.objects.create(\n            username=self.context[\"request\"].user.username,\n            description=\"创建主机\",\n            host=instance)\n        # 下发异步任务: 初始化主机、安装 Agent\n        logger.info(f\"host[{ip}] - ADD celery task\")\n        insert_host_celery_task.delay(\n            instance.id, init=init_flag)\n        # deploy_agent.delay(instance.id)\n        return instance\n\n    def update(self, instance, validated_data):\n        \"\"\" 更新主机 \"\"\"\n        validated_data.pop(\"init_host\")\n        # 如果 run_user 存在，则删除\n        if \"run_user\" in validated_data:\n            validated_data.pop(\"run_user\")\n        log_ls = []\n        username = self.context[\"request\"].user.username\n\n        # 获取所有发生修改字段\n        for key, new_value in validated_data.items():\n            old_value = getattr(instance, key)\n            if old_value != new_value:\n                description = f\"修改[{getattr(Host, key).field.help_text}]\"\n                if key != \"password\":\n                    description += f\": 由[{getattr(instance, key)}]修改为[{new_value}]\"\n                log_ls.append(HostOperateLog(\n                    username=username,\n                    description=description,\n                    host=instance))\n\n        # 写入主机操作记录表中\n        HostOperateLog.objects.bulk_create(log_ls)\n\n        return super(HostSerializer, self).update(instance, validated_data)\n\n\nclass HostOperateLogSerializer(ModelSerializer):\n    \"\"\" 主机操作记录序列化器类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = HostOperateLog\n        fields = '__all__'\n\n\nclass HostDetailSerializer(ModelSerializer):\n    \"\"\" 主机详细信息序列化类 \"\"\"\n\n    history = HostOperateLogSerializer(\n        source=\"hostoperatelog_set.all\", many=True)\n    deployment_information = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Host\n        exclude = (\"is_deleted\", \"agent_dir\", \"password\", \"host_name\", \"env\",\n                   \"host_agent_error\", \"monitor_agent_error\")\n\n    def get_deployment_information(self, obj):\n        result_ls = []\n        base_env_queryset = Service.objects.filter(\n            ip=obj.ip, service__is_base_env=True)\n        host_env_queryset = Host.objects.filter(ip=obj.ip).exclude(\n            ntpdate_install_status=Host.NTPDATE_NOT_INSTALL)\n        if base_env_queryset.exists():\n            result_ls = list(base_env_queryset.values(\n                \"service__app_name\", \"service__app_version\", \"service__app_logo\"))\n        if host_env_queryset.exists():\n            result_ls.append(\n                {\"service__app_name\": \"ntpdate\",\n                 \"service__app_version\": \"4.2.8\",\n                 \"service__app_logo\": \"\"\n                 }\n            )\n        return result_ls\n\n\nclass HostFieldCheckSerializer(ModelSerializer):\n    \"\"\" 主机字段重复性校验序列化器 \"\"\"\n\n    id = serializers.IntegerField(\n        help_text=\"主机ID，更新时需要此字段\",\n        required=False)\n    instance_name = serializers.CharField(\n        help_text=\"实例名\",\n        max_length=16, required=False,\n        validators=[\n            NoEmojiValidator(),\n            NoChineseValidator(),\n            ReValidator(regex=r\"^[-a-zA-Z0-9].*$\"),\n        ])\n\n    ip = serializers.IPAddressField(\n        help_text=\"IP地址\", required=False)\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Host\n        fields = (\"id\", \"instance_name\", \"ip\",)\n\n    def validate(self, attrs):\n        \"\"\" 校验 instance_name / ip 是否重复 \"\"\"\n        host_id = attrs.get(\"id\")\n        instance_name = attrs.get(\"instance_name\")\n        ip = attrs.get(\"ip\")\n        queryset = Host.objects.all()\n        if host_id is not None:\n            queryset = queryset.exclude(id=host_id)\n        if instance_name and \\\n                queryset.filter(instance_name=instance_name).exists():\n            raise ValidationError({\"instance_name\": \"实例名已经存在\"})\n        if ip and queryset.filter(ip=ip).exists():\n            raise ValidationError({\"ip\": \"IP已经存在\"})\n        return attrs\n\n\nclass HostMaintenanceSerializer(HostIdsSerializer):\n    \"\"\" 主机维护模式序列化类 \"\"\"\n\n    is_maintenance = serializers.BooleanField(\n        help_text=\"开启/关闭维护模式\",\n        required=True,\n        error_messages={\"required\": \"必须包含[is_maintenance]字段\"})\n\n    def write_host_log(self, host_queryset, status, result):\n        \"\"\" 写入主机日志 \"\"\"\n        log_ls = []\n        for host in host_queryset:\n            log_ls.append(HostOperateLog(\n                username=self.context[\"request\"].user.username,\n                description=f\"{status}[维护模式]\",\n                result=result,\n                host=host))\n        HostOperateLog.objects.bulk_create(log_ls)\n\n    def validate(self, attrs):\n        \"\"\" 校验列表中主机 '维护模式' 字段值是否正确 \"\"\"\n        queryset = Host.objects.filter(\n            id__in=attrs.get(\"host_ids\"),\n            is_maintenance=attrs.get(\"is_maintenance\"))\n        if queryset.exists():\n            status = \"开启\" if attrs.get(\"is_maintenance\") else \"关闭\"\n            raise ValidationError({\n                \"host_ids\": f\"主机列表中存在已 '{status}' 维护模式的主机\"\n            })\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 进入 / 退出维护模式 \"\"\"\n        host_ids = validated_data.get(\"host_ids\")\n        is_maintenance = validated_data.get(\"is_maintenance\")\n        status = \"开启\" if is_maintenance else \"关闭\"\n        en_status = \"open\" if is_maintenance else \"close\"\n        host_queryset = Host.objects.filter(id__in=host_ids)\n        host_ls = list(host_queryset.values(\"ip\"))\n\n        # 根据 is_maintenance 判断主机进入 / 退出维护模式\n        alert_manager = Alertmanager()\n        if is_maintenance:\n            res_ls = alert_manager.set_maintain_by_host_list(host_ls)\n        else:\n            res_ls = alert_manager.revoke_maintain_by_host_list(host_ls)\n\n        # 操作失败\n        if not res_ls:\n            logger.error(f\"host {en_status} maintain failed: {host_ids}\")\n            # 操作失败记录写入\n            self.write_host_log(host_queryset, status, \"failed\")\n            raise OperateError(f\"主机'{status}'维护模式失败\")\n\n        # 操作成功\n        host_queryset.update(is_maintenance=is_maintenance)\n        logger.info(f\"host {en_status} maintain success: {host_ids}\")\n        # 操作成功记录写入\n        self.write_host_log(host_queryset, status, \"success\")\n        return validated_data\n\n\nclass HostAgentRestartSerializer(HostIdsSerializer):\n    \"\"\" 主机Agent重启序列化类 \"\"\"\n\n    def create(self, validated_data):\n        \"\"\" 主机Agent重启 \"\"\"\n        host_ids = validated_data.get(\"host_ids\", [])\n        filter_host_ids = list(\n            Host.objects.filter(\n                id__in=host_ids,\n                host_agent__in=[\n                    str(Host.AGENT_RUNNING),\n                    str(Host.AGENT_RESTART),\n                    str(Host.AGENT_START_ERROR)\n                ]\n            ).values_list(\"id\", flat=True)\n        )\n        for item in filter_host_ids:\n            host_agent_restart.delay(item)\n        # 下发任务后批量更新重启主机状态\n        Host.objects.filter(\n            id__in=filter_host_ids\n        ).update(host_agent=Host.AGENT_RESTART)\n        return validated_data\n\n\nclass HostBatchValidateSerializer(Serializer):\n    \"\"\" 主机数据批量验证序列化类 \"\"\"\n\n    host_list = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"主机数据列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[host_list]字段\"}\n    )\n\n    def host_info_validate(self, host_data):\n        \"\"\" 单个主机信息验证 \"\"\"\n        host_serializer = HostSerializer(data=host_data)\n        if host_serializer.is_valid():\n            host_data[\"init_host\"] = \\\n                host_serializer.validated_data.get(\"init_host\")\n            return \"correct\", host_data\n        err_ls = []\n        ip_err = \"Enter a valid IPv4 or IPv6 address.\"\n        for k, v in host_serializer.errors.items():\n            if ip_err in v:\n                v = [f\"{k} 格式不合法\"]\n            err_ls.extend(v)\n        host_data[\"validate_error\"] = \"; \".join(err_ls)\n        return \"error\", host_data\n\n    def validate(self, attrs):\n        \"\"\" 校验主机数据列表 \"\"\"\n        host_list = attrs.get(\"host_list\")[:]\n        result_dict = {\n            \"correct\": [],\n            \"error\": []\n        }\n        logger.info(f\"host batch validate start: {host_list}\")\n\n        # 校验主机列表中是否存在相同实例名或IP的数据\n        no_repeat_host = []\n        instance_name_list = list(\n            map(lambda x: x.get(\"instance_name\"), host_list))\n        ip_list = list(map(lambda x: x.get(\"ip\"), host_list))\n        for index, host in enumerate(host_list):\n            repeat_ls = []\n            if instance_name_list.count(host.get(\"instance_name\")) > 1:\n                repeat_ls.append(\"实例名\")\n            if ip_list.count(host.get(\"ip\")) > 1:\n                repeat_ls.append(\"IP\")\n            if repeat_ls:\n                host_info = host_list[index]\n                host_info[\"validate_error\"] = f\"{'、'.join(repeat_ls)}在表格中重复\"\n                result_dict[\"error\"].append(host_info)\n                continue\n            no_repeat_host.append(host_list[index])\n\n        # 校验主机数据正确性\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            future_list = []\n            for host_data in no_repeat_host:\n                future_obj = executor.submit(\n                    self.host_info_validate, host_data)\n                future_list.append(future_obj)\n            for future in as_completed(future_list):\n                flag, host_data = future.result()\n                result_dict[flag].append(host_data)\n\n        # 按照 row 行号对列表进行排序\n        for v in result_dict.values():\n            if len(v) > 0:\n                v.sort(key=lambda x: x.get(\"row\", 999))\n        attrs[\"result_dict\"] = result_dict\n        logger.info(\"host batch validate end\")\n        return attrs\n\n\nclass HostBatchImportSerializer(Serializer):\n    \"\"\" 主机数据批量创建序列化类 \"\"\"\n\n    host_list = serializers.ListField(\n        child=serializers.DictField(),\n        help_text=\"主机数据列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[host_list]字段\"}\n    )\n\n\nclass HostInitSerializer(HostIdsSerializer):\n    \"\"\" 主机初始化序列化类 \"\"\"\n\n    def create(self, validated_data):\n        \"\"\" 主机初始化 \"\"\"\n        host_ids = validated_data.get(\"host_ids\", [])\n        for host_id in host_ids:\n            init_host.delay(host_id)\n        # 下发任务后批量更新主机初始化状态\n        Host.objects.filter(\n            id__in=host_ids\n        ).update(init_status=Host.INIT_EXECUTING)\n        return validated_data\n\n\nclass HostsAgentStatusSerializer(Serializer):\n    \"\"\" 主机 agent 状态序列化类 \"\"\"\n\n    ip_list = serializers.ListField(\n        child=serializers.CharField(),\n        help_text=\"主机ip列表\",\n        required=True, allow_empty=False,\n        error_messages={\"required\": \"必须包含[ip_list]字段\"}\n    )\n\n\nclass HostReinstallSerializer(HostIdsSerializer):\n    \"\"\" 主机重新安装序列化类 \"\"\"\n\n    def create(self, validated_data):\n        \"\"\" 主机重装 \"\"\"\n        host_ids = validated_data.get(\"host_ids\", [])\n        # 不重装监控agent\n        for host_id in host_ids:\n            deploy_agent.delay(host_id, need_monitor=False)\n        Host.objects.filter(\n            id__in=host_ids\n        ).update(host_agent=Host.AGENT_DEPLOY_ING)\n        return validated_data\n\n\nclass MonitorReinstallSerializer(HostIdsSerializer):\n    \"\"\" 监控重新安装序列化类 \"\"\"\n\n    def create(self, validated_data):\n        \"\"\" 监控重装 \"\"\"\n        host_ids = validated_data.get(\"host_ids\", [])\n        user_name = self.context[\"request\"].user.username\n        for host_id in host_ids:\n            reinstall_monitor_celery_task.delay(host_id, user_name)\n        Host.objects.filter(\n            id__in=host_ids\n        ).update(monitor_agent=Host.AGENT_DEPLOY_ING)\n        return validated_data\n"
  },
  {
    "path": "omp_server/hosts/tasks.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: tasks\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-12 11:54\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n主机相关异步任务\n\"\"\"\n\nimport os\nimport time\nimport logging\nimport traceback\nimport subprocess\nimport requests\nimport json\n\nfrom django.conf import settings\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\nfrom promemonitor.alertmanager import Alertmanager\nfrom promemonitor.prometheus_utils import PrometheusUtils\n\nfrom db_models.models import (\n    Host, Service,\n    HostOperateLog,\n    Alert\n)\nfrom utils.plugin.ssh import SSH\nfrom utils.plugin.monitor_agent import MonitorAgentManager\nfrom utils.plugin.crypto import AESCryptor\nfrom utils.plugin.agent_util import Agent\nfrom app_store.tasks import add_prometheus\nfrom utils.parse_config import HOSTNAME_PREFIX\nfrom utils.plugin.install_ntpdate import InstallNtpdate\nfrom omp_server.settings import PROJECT_DIR\nfrom concurrent.futures import ThreadPoolExecutor\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\ndef deploy_monitor_agent(host_obj, salt_flag=True):\n    \"\"\"\n    部署监控Agent\n    :param host_obj: 主机对象\n    :param salt_flag: 部署主机Agent成功或失败标志\n    :return:\n    \"\"\"\n    logger.info(f\"Deploy monitor agent for {host_obj.ip}\")\n    if not salt_flag:\n        Host.objects.filter(ip=host_obj.ip).update(\n            monitor_agent=4,\n            monitor_agent_error=\"主机salt-agent部署失败!\"\n        )\n        logger.error(\n            \"Deploy monitor agent failed because salt agent deploy failed\")\n        return\n    monitor_manager = MonitorAgentManager(host_objs=[host_obj])\n    install_flag, install_msg = monitor_manager.install()\n    logger.info(\n        f\"Deploy monitor agent, \"\n        f\"install_flag: {install_flag}; install_msg: {install_msg}\")\n    if not install_flag:\n        Host.objects.filter(ip=host_obj.ip).update(\n            monitor_agent=4,\n            monitor_agent_error=install_msg if len(\n                install_msg) < 200 else install_flag[:200]\n        )\n    else:\n        Host.objects.filter(ip=host_obj.ip).update(monitor_agent=0)\n\n\ndef real_deploy_agent(host_obj, need_monitor=True):\n    \"\"\"\n    部署主机Agent\n    :param host_obj: 主机对象\n    :type host_obj Host\n    :param need_monitor: 是否部署monitor\n    :type need_monitor bool\n    :return:\n    \"\"\"\n    logger.info(\n        f\"Deploy Agent for {host_obj.ip}, Params: \"\n        f\"username: {host_obj.username}; \"\n        f\"port: {host_obj.port}; \"\n        f\"install_dir: {host_obj.agent_dir}!\")\n    _obj = Agent(\n        host=host_obj.ip,\n        port=host_obj.port,\n        username=host_obj.username,\n        password=AESCryptor().decode(host_obj.password),\n        install_dir=host_obj.agent_dir\n    )\n    flag, message = _obj.agent_deploy()\n    logger.info(\n        f\"Deploy Agent for {host_obj.ip}, \"\n        f\"Res Flag: {flag}; Res Message: {message}\")\n    # 更新主机Agent状态，0 正常；4 部署失败\n    # 使用filter查询然后使用update方法进行处理，防止多任务环境\n    if flag:\n        Host.objects.filter(ip=host_obj.ip).update(host_agent=0)\n    else:\n        Host.objects.filter(ip=host_obj.ip).update(\n            host_agent=4,\n            host_agent_error=str(message)[:200] if len(\n                str(message)) > 200 else str(message)\n        )\n    # 部署监控agent\n    if need_monitor:\n        deploy_monitor_agent(host_obj=host_obj, salt_flag=flag)\n\n\n@shared_task\ndef deploy_agent(host_id, need_monitor=True):\n    \"\"\"\n    部署主机Agent\n    :param host_id:\n    :param need_monitor: 是否部署monitor\n    :type need_monitor bool\n    :return:\n    \"\"\"\n    try:\n        # edit by vum:\n        # obj.save 在异步任务并发读写下存在数值覆盖问题\n        host_query = Host.objects.filter(id=host_id)\n        host_query.update(host_agent=3)\n        real_deploy_agent(\n            host_obj=host_query.first(),\n            need_monitor=need_monitor\n        )\n    except Exception as e:\n        logger.error(\n            f\"Deploy Host Agent For {host_id} Failed with error: {str(e)};\\n\"\n            f\"detail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            host_agent=4, host_agent_error=str(e))\n\n\ndef real_host_agent_restart(host_obj):\n    \"\"\"\n    重启主机Agent\n    :param host_obj: 主机对象\n    :type host_obj Host\n    :return:\n    \"\"\"\n    logger.info(\n        f\"Restart Agent for {host_obj.ip}, Params: \"\n        f\"username: {host_obj.username}; \"\n        f\"port: {host_obj.port}; \"\n        f\"install_dir: {host_obj.agent_dir}!\")\n    _obj = SSH(\n        hostname=host_obj.ip,\n        port=host_obj.port,\n        username=host_obj.username,\n        password=AESCryptor().decode(host_obj.password),\n    )\n    _script_path = os.path.join(\n        host_obj.agent_dir, \"omp_salt_agent/bin/omp_salt_agent\")\n    flag, message = _obj.cmd(f\"bash {_script_path} restart\")\n    logger.info(\n        f\"Restart host agent for {host_obj.ip}: \"\n        f\"get flag: {flag}; get res: {message}\")\n    # 使用filter查询然后使用update方法进行处理，防止多任务环境\n    if flag:\n        # host_obj.host_agent = 0\n        Host.objects.filter(ip=host_obj.ip).update(host_agent=0)\n    else:\n        # host_obj.host_agent = 2\n        # host_obj.host_agent_error = \\\n        #     str(message)[:200] if len(str(message)) > 200 else str(message)\n        Host.objects.filter(ip=host_obj.ip).update(\n            host_agent=2,\n            host_agent_error=str(message)[:200] if len(\n                str(message)) > 200 else str(message)\n        )\n\n\n@shared_task\ndef host_agent_restart(host_id):\n    \"\"\"\n    主机Agent的重启操作\n    :param host_id: 主机的id\n    :return:\n    \"\"\"\n    try:\n        host_obj = Host.objects.get(id=host_id)\n        real_host_agent_restart(host_obj=host_obj)\n    except Exception as e:\n        logger.error(\n            f\"Restart Host Agent For {host_id} Failed with error: {str(e)};\\n\"\n            f\"detail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            host_agent=2, host_agent_error=str(e))\n\n\ndef real_init_host(host_obj):\n    \"\"\"\n    初始化主机\n    :param host_obj: 主机对象\n    :type host_obj Host\n    :return:\n    \"\"\"\n    logger.info(f\"init host Begin [{host_obj.id}]\")\n    _ssh = SSH(\n        hostname=host_obj.ip,\n        port=host_obj.port,\n        username=host_obj.username,\n        password=AESCryptor().decode(host_obj.password),\n    )\n    # 验证用户权限\n    is_sudo, _ = _ssh.is_sudo()\n    if not is_sudo:\n        logger.error(f\"init host [{host_obj.id}] failed: permission failed\")\n        raise Exception(\"permission failed\")\n\n    # 发送脚本\n    init_script_name = \"init_host.py\"\n    init_script_path = os.path.join(\n        settings.BASE_DIR.parent,\n        \"package_hub\", \"_modules\", init_script_name)\n    script_push_state, script_push_msg = _ssh.file_push(\n        file=init_script_path,\n        remote_path=\"/tmp\",\n    )\n    if not script_push_state:\n        logger.error(f\"init host [{host_obj.id}] failed: send script failed, \"\n                     f\"detail: {script_push_msg}\")\n        raise Exception(\"send script failed\")\n    modified_host_name = str(HOSTNAME_PREFIX) + \"\".join(\n        item.zfill(3) for item in host_obj.ip.split(\".\"))\n    # 执行初始化\n    is_success, script_msg = _ssh.cmd(\n        f\"python /tmp/{init_script_name} init_valid {modified_host_name} {host_obj.ip}\")\n    if not (is_success and \"init success\" in script_msg and \"valid success\" in script_msg):\n        logger.error(f\"init host [{host_obj.id}] failed: execute init failed, \"\n                     f\"detail: {script_push_msg}\")\n        raise Exception(\"execute failed\")\n    Host.objects.filter(\n        id=host_obj.id\n    ).update(init_status=Host.INIT_SUCCESS)\n    logger.info(\"init host Success\")\n\n\n@shared_task\ndef init_host(host_id):\n    \"\"\" 初始化主机 \"\"\"\n    try:\n        # edit by vum:\n        # obj.save 在异步任务并发读写下存在数值覆盖问题\n        host_query = Host.objects.filter(id=host_id)\n        host_query.update(init_status=Host.INIT_EXECUTING)\n        real_init_host(host_obj=host_query.first())\n    except Exception as e:\n        print(e)\n        logger.error(\n            f\"Init Host For {host_id} Failed with error: {str(e)};\\n\"\n            f\"detail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            init_status=Host.INIT_FAILED)\n\n\n@shared_task\ndef insert_host_celery_task(host_id, init=False):\n    \"\"\" 添加主机 celery 任务 \"\"\"\n    # 执行主机初始化\n    if init:\n        try:\n            num = 0\n            host_obj = Host.objects.filter(id=host_id).first()\n            while host_obj is None and num < 10:\n                host_obj = Host.objects.filter(id=host_id).first()\n                time.sleep(2)\n                num += 1\n            if host_obj is None:\n                raise Exception(\"Host Object not found\")\n            # edit by vum:\n            # obj.save 在异步任务并发读写下存在数值覆盖问题\n            host_query = Host.objects.filter(id=host_id)\n            host_query.update(init_status=Host.INIT_EXECUTING)\n            real_init_host(host_obj=host_query.first())\n        except Exception as e:\n            print(e)\n            logger.error(\n                f\"Init Host For {host_id} Failed with error: {str(e)};\\n\"\n                f\"detail: {traceback.format_exc()}\"\n            )\n            Host.objects.filter(id=host_id).update(\n                init_status=Host.INIT_FAILED)\n    # 部署 agent\n    try:\n        num = 0\n        host_obj = Host.objects.filter(id=host_id).first()\n        while host_obj is None and num < 10:\n            host_obj = Host.objects.filter(id=host_id).first()\n            time.sleep(2)\n            num += 1\n        if host_obj is None:\n            raise Exception(\"Host Object not found\")\n        host_query = Host.objects.filter(id=host_id)\n        host_query.update(host_agent=Host.AGENT_DEPLOY_ING)\n        real_deploy_agent(host_obj=host_query.first())\n    except Exception as e:\n        logger.error(\n            f\"Deploy Host Agent For {host_id} Failed with error: {str(e)};\\n\"\n            f\"detail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            host_agent=Host.AGENT_DEPLOY_ERROR,\n            host_agent_error=str(e))\n    # 部署ntpdate\n    try:\n        host_obj = Host.objects.filter(id=host_id).first()\n        host_id = host_obj.id\n        if host_obj.use_ntpd:\n            InstallNtpdate(host_obj_list=[host_obj]).install()\n    except Exception as e:\n        logger.error(\n            f\"Deplot ntpdate for {id} Failed with error: {str(e)};\\n\"\n            f\"detail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            ntpdate_install_status=Host.NTPDATE_INSTALL_FAILED)\n\n\ndef write_host_log(host_queryset, status, result, username):\n    \"\"\" 写入主机日志 \"\"\"\n    log_ls = []\n    for host in host_queryset:\n        log_ls.append(HostOperateLog(\n            username=username,\n            description=f\"{status}[维护模式]\",\n            result=result,\n            host=host))\n    HostOperateLog.objects.bulk_create(log_ls)\n\n\ndef maintenance(host_obj, entry, username):\n    \"\"\" 进入 / 退出维护模式 \"\"\"\n    # 根据 is_maintenance 判断主机进入 / 退出维护模式\n    status = \"开启\" if entry else \"关闭\"\n    en_status = \"open\" if entry else \"close\"\n    alert_manager = Alertmanager()\n    host_ls = [{\"ip\": host_obj.ip}]\n    if entry:\n        res_ls = alert_manager.set_maintain_by_host_list(host_ls)\n    else:\n        res_ls = alert_manager.revoke_maintain_by_host_list(host_ls)\n    # 操作失败\n    if not res_ls:\n        logger.error(f\"host {en_status} maintain failed: {host_obj.ip}\")\n        # 操作失败记录写入\n        write_host_log([host_obj], status, \"failed\", username)\n    Host.objects.filter(\n        id=host_obj.id\n    ).update(is_maintenance=entry)\n    logger.info(f\"host {en_status} maintain success: {host_obj.ip}\")\n    # 操作成功记录写入\n    write_host_log([host_obj], status, \"success\", username)\n\n\n@shared_task\ndef reinstall_monitor_celery_task(host_id, username):\n    \"\"\" 重新安装主机监控 celery 任务 \"\"\"\n    host_obj = Host.objects.filter(id=host_id).first()\n    maintenance(host_obj, True, username)\n    logger.info(\n        f\"Restart Agent for {host_obj.ip}, Params: \"\n        f\"username: {host_obj.username}; \"\n        f\"port: {host_obj.port}; \"\n        f\"install_dir: {host_obj.agent_dir}!\")\n    _obj = SSH(\n        hostname=host_obj.ip,\n        port=host_obj.port,\n        username=host_obj.username,\n        password=AESCryptor().decode(host_obj.password),\n    )\n    flag, message = _obj.cmd(\n        \"ps -ef | grep omp_monitor_agent | grep -v grep | awk -F ' ' '{print $2}' | xargs kill -9\")\n    logger.info(\n        f\"Stop monitor agent for {host_obj.ip}: \"\n        f\"get flag: {flag}; get res: {message}\")\n    # 删除目录，防止agent_dir异常保护系统\n    if host_obj.agent_dir:\n        monitor_dir = os.path.join(host_obj.agent_dir, \"omp_monitor_agent\")\n        flag, message = _obj.cmd(f\"/bin/rm -rf {monitor_dir}\")\n    logger.info(\n        f\"Stop monitor agent for {host_obj.ip}: \"\n        f\"get flag: {flag}; get res: {message}\")\n    deploy_monitor_agent(host_obj=host_obj, salt_flag=flag)\n    host_obj.refresh_from_db()\n    if host_obj.monitor_agent == 4:\n        maintenance(host_obj, False, username)\n        return\n    # 刷新prometheus服务列表监控配置,优化功能\n    service_obj_list = Service.objects.filter(ip=host_obj.ip)\n    detail_obj_list = []\n    for service_obj in service_obj_list:\n        detail_obj = service_obj.detailinstallhistory_set.first()\n        if detail_obj:\n            detail_obj_list.append(detail_obj)\n    if len(detail_obj_list) != 0:\n        add_prometheus(9999, detail_obj_list)\n    maintenance(host_obj, False, username)\n\n\nclass UninstallHosts(object):\n    def __init__(self, all_host):\n        self.is_success = True\n        self.all_host = all_host\n\n    @staticmethod\n    def cmd(command):\n        \"\"\"执行本地shell命令\"\"\"\n        p = subprocess.Popen(\n            command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True\n        )\n        stdout, stderr = p.communicate()\n        _out, _err, _code = stdout.decode(\n            \"utf8\"), stderr.decode(\"utf8\"), p.returncode\n        return _out, _err, _code\n\n    def delete_salt_key(self, key_list):\n        \"\"\"删除所有的salt-key\"\"\"\n        python_path = os.path.join(PROJECT_DIR, 'component/env/bin/python3')\n        salt_key_path = os.path.join(PROJECT_DIR, \"component/env/bin/salt-key\")\n        salt_config_path = os.path.join(PROJECT_DIR, \"config/salt\")\n        for item in key_list:\n            _out, _err, _code = self.cmd(\n                f\"{python_path} {salt_key_path} -y -d '{item}' -c {salt_config_path}\"\n            )\n            if _code != 0:\n                print(f\"删除{item}获取到stdout: {_out}; stderr: {_err}\")\n                self.is_success = False\n            logger.info(f\"删除{item}获取到哦的stdout: {_out}; stderr: {_err}\")\n\n    @staticmethod\n    def del_single_agent(obj):\n        \"\"\"\n        删除单个节点的agent（salt and monitor）\n        \"\"\"\n        ip = obj.ip\n        agent_dir = obj.agent_dir\n        data_dir = obj.data_folder\n        _ssh_obj = SSH(\n            hostname=ip,\n            port=obj.port,\n            username=obj.username,\n            password=AESCryptor().decode(obj.password)\n        )\n        # 删除目录\n        if not data_dir:\n            raise Exception(f\"主机{ip}无数据目录\")\n        # TODO /app/bash_profile目前是指定目录\n        delete_cmd_str = f\"rm -rf {data_dir}/omp_packages; \" \\\n                         f\"/bin/rm -rf {data_dir}/app/bash_profile; /bin/rm -rf /tmp/upgrade_openssl\"\n        cmd_res, msg = _ssh_obj.cmd(\n            delete_cmd_str,\n            timeout=120\n        )\n        logger.info(f\"执行{ip} [delete] package and tmp 操作 {cmd_res}, 原因: {msg}\")\n\n        # 卸载salt agent\n        salt_agent_dir = os.path.join(agent_dir, \"omp_salt_agent\")\n        _delete_cron_cmd = \"crontab -l|grep -v omp_salt_agent 2>/dev/null | crontab -;\"\n        _stop_agent = (\n            f\"bash {salt_agent_dir}/bin/omp_salt_agent stop; /bin/rm -rf {salt_agent_dir}\"\n        )\n        final_cmd = f\"{_delete_cron_cmd} {_stop_agent}\"\n        salt_res_flag, salt_res_msg = _ssh_obj.cmd(final_cmd, timeout=60)\n        logger.info(f\"卸载{ip}上的omp_salt_agent的命令为: {final_cmd}\")\n        logger.info(\n            f\"卸载{ip}上的omp_salt_agent的结果为: {salt_res_flag} {salt_res_msg}\")\n        # 卸载monitor agent\n        monitor_agent_dir = os.path.join(agent_dir, \"omp_monitor_agent\")\n        _delete_monitor_cron_cmd = \"crontab -l|grep -v omp_monitor_agent \" \\\n                                   \"2>/dev/null | crontab -;\"\n        _uninstall_monitor_agent_cmd = f\"cd {monitor_agent_dir} &&\" \\\n                                       f\" ./manage stop_all &&\" \\\n                                       f\" bash monitor_agent.sh stop &&\" \\\n                                       f\" cd {agent_dir} &&\" \\\n                                       f\" /bin/rm -rf omp_monitor_agent\"\n        monitor_res_flag, monitor_res_msg = _ssh_obj.cmd(\n            _uninstall_monitor_agent_cmd, timeout=120)\n        res, msg = _ssh_obj.cmd(\n            _delete_monitor_cron_cmd, timeout=120)\n\n        cmd_ntpd_uninstall = \"/bin/rm -rf {0}/app/ntpdate &&\" \\\n                             \"crontab -l| grep -v {0}/app/ntpdate 2>/dev/null\" \\\n                             \" | crontab -;\".format(data_dir)\n        if obj.username != \"root\":\n            cmd_ntpd_uninstall = \"sudo /bin/rm -rf {0}/app/ntpdate &&\" \\\n                                 \"sudo crontab -l| grep -v {0}/app/ntpdate 2>/dev/null\" \\\n                                 \" | sudo crontab -;\".format(data_dir)\n        ntpd_res, ntpd_msg = _ssh_obj.cmd(\n            cmd_ntpd_uninstall, timeout=120)\n        logger.info(\n            f\"卸载{ip}上的ntpd的结果为: {ntpd_res} {ntpd_msg}\")\n        logger.info(\n            f\"卸载{ip}上的omp_monitor_agent的命令为: {_uninstall_monitor_agent_cmd}\")\n        logger.info(\n            f\"卸载{ip}上的omp_monitor_agent的结果为: {monitor_res_flag} {monitor_res_msg}\")\n        if not all([cmd_res, salt_res_flag, monitor_res_flag]):\n            return False, f\"({ip}上卸载文件清除：{cmd_res}-{msg};\\n salt:{salt_res_flag}-{salt_res_msg};\\n monitor:{monitor_res_flag}-{monitor_res_msg};\\n)\"\n        return True, \"success\"\n\n    @staticmethod\n    def execute_uninstall(host_obj_list, thread_name_prefix, function, max_num=8):\n        \"\"\"卸载执行函数\"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=max_num,\n            thread_name_prefix=thread_name_prefix\n        )\n        # future_list: [(ip, future),..]\n        future_list = list()\n        # result_list:[(ip, res_bool, res_msg), ...]\n        result_list = list()\n        for obj in host_obj_list:\n            future = thread_p.submit(function, obj)\n            future_list.append((obj.ip, future))\n        for f in future_list:\n            result_list.append((f[0], f[1].result()[0], f[1].result()[1]))\n        thread_p.shutdown(wait=True)\n        failed_msg = \"\"\n        for item in result_list:\n            if not item[1]:\n                failed_msg += f\"{item[0]}: (execute_flag: {item[1]}; execute_msg: {item[2]})\"\n        if failed_msg:\n            return False, failed_msg\n        return True, \"success\"\n\n    def delete_all_omp_agent(self):\n        \"\"\"清理所有omp agent(salt and monitor)\"\"\"\n        _uninstall_flag, _uninstall_msg = self.execute_uninstall(host_obj_list=self.all_host,\n                                                                 thread_name_prefix=\"uninstall_agent_\",\n                                                                 function=self.del_single_agent)\n        ips = self.all_host.values_list(\"ip\", flat=True)\n        pro_obj = PrometheusUtils()\n        write_str = []\n        node_path = os.path.join(\n            pro_obj.prometheus_targets_path, \"nodeExporter_all.json\")\n        for node in pro_obj.get_dic_from_yaml(node_path):\n            if node.get(\"targets\", [\"\"])[0].split(\":\")[0] in ips:\n                continue\n            write_str.append(node)\n        with open(node_path, \"w\") as f2:\n            json.dump(write_str, f2, ensure_ascii=False, indent=4)\n        time.sleep(2)\n        reload_prometheus_url = \"http://localhost:19011/-/reload\"\n        requests.post(reload_prometheus_url,\n                      auth=pro_obj.basic_auth)\n\n        if not _uninstall_flag:\n            print(_uninstall_msg)\n            self.is_success = False\n        self.delete_salt_key([item.ip for item in self.all_host])\n        return self.is_success\n\n\n@shared_task()\ndef delete_hosts(host_ids):\n    \"\"\"\n    执行删除异步任务\n    \"\"\"\n    host_objs = Host.objects.filter(ip__in=host_ids)\n    uninstall_objs = UninstallHosts(host_objs)\n    uninstall_objs.delete_all_omp_agent()\n    host_objs.delete()\n    Service.objects.filter(ip__in=host_ids).delete()\n    Alert.objects.filter(alert_host_ip__in=host_ids).delete()\n"
  },
  {
    "path": "omp_server/hosts/urls.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: urls\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-12 11:44\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom rest_framework.routers import DefaultRouter\n\nfrom hosts.views import (\n    HostListView, HostDetailView, HostUpdateView,\n    HostFieldCheckView, IpListView, HostMaintenanceView,\n    HostAgentRestartView, HostOperateLogView, HostBatchValidateView,\n    HostBatchImportView, HostInitView, HostsAgentStatusView,\n    HostReinstallView, MonitorReinstallView,\n    HostUninstallView\n)\n\nrouter = DefaultRouter()\nrouter.register(\"hosts\", HostListView, basename=\"hosts\")\nrouter.register(\"hosts\", HostUpdateView, basename=\"hosts\")\nrouter.register(\"hostsDetail\", HostDetailView, basename=\"hostsDetail\")\nrouter.register(\"fields\", HostFieldCheckView, basename=\"fields\")\nrouter.register(\"ips\", IpListView, basename=\"ips\")\nrouter.register(\"maintain\", HostMaintenanceView, basename=\"maintain\")\nrouter.register(\"restartHostAgent\", HostAgentRestartView,\n                basename=\"restartHostAgent\")\nrouter.register(\"operateLog\", HostOperateLogView, basename=\"operateLog\")\nrouter.register(\"batchValidate\", HostBatchValidateView,\n                basename=\"batchValidate\")\nrouter.register(\"batchImport\", HostBatchImportView,\n                basename=\"batchImport\")\nrouter.register(\"hostInit\", HostInitView, basename=\"hostInit\")\nrouter.register(\"hostsAgentStatus\",\n                HostsAgentStatusView, basename=\"hostsAgentStatus\")\nrouter.register(\"hostReinstall\",\n                HostReinstallView, basename=\"hostReinstall\")\nrouter.register(\"monitorReinstall\",\n                MonitorReinstallView, basename=\"monitorReinstall\")\nrouter.register(\"hostUninstall\",\n                HostUninstallView, basename=\"hostUninstall\")\n"
  },
  {
    "path": "omp_server/hosts/views.py",
    "content": "\"\"\"\n主机相关视图\n\"\"\"\nimport logging\nfrom django.db import transaction\n\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin, UpdateModelMixin,\n    RetrieveModelMixin\n)\nfrom rest_framework.filters import OrderingFilter\nfrom rest_framework.response import Response\nfrom rest_framework.exceptions import ValidationError\n\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\n\nfrom db_models.models import (Env, Host, HostOperateLog)\nfrom utils.plugin.crypto import AESCryptor\nfrom utils.common.paginations import PageNumberPager\nfrom hosts.tasks import insert_host_celery_task\nfrom hosts.hosts_filters import (HostFilter, HostOperateFilter)\nfrom hosts.hosts_serializers import (\n    HostSerializer, HostMaintenanceSerializer,\n    HostFieldCheckSerializer, HostAgentRestartSerializer,\n    HostOperateLogSerializer, HostBatchValidateSerializer,\n    HostBatchImportSerializer, HostDetailSerializer,\n    HostInitSerializer, HostsAgentStatusSerializer,\n    MonitorReinstallSerializer, HostUninstallSerializer,\n    HostReinstallSerializer\n)\nfrom promemonitor.prometheus import Prometheus\nfrom promemonitor.grafana_url import explain_url\nfrom utils.common.exceptions import OperateError\nfrom utils.common.views import BaseDownLoadTemplateView\n\nlogger = logging.getLogger(\"server\")\n\n\nclass HostListView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n        list:\n        查询主机列表\n\n        create:\n        创建一个新主机\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostSerializer\n    pagination_class = PageNumberPager\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = HostFilter\n    ordering_fields = (\"ip\", \"host_agent\", \"monitor_agent\",\n                       \"service_num\", \"alert_num\")\n    # 动态排序字段\n    dynamic_fields = (\"cpu_usage\", \"mem_usage\",\n                      \"root_disk_usage\", \"data_disk_usage\")\n    # 操作描述信息\n    get_description = \"查询主机\"\n    post_description = \"创建主机\"\n\n    def list(self, request, *args, **kwargs):\n        # 获取序列化数据列表\n        queryset = self.filter_queryset(self.get_queryset())\n        serializer = self.get_serializer(\n            self.paginate_queryset(queryset), many=True)\n        serializer_data = serializer.data\n        # 主机密码解密\n        for host_info in serializer_data:\n            aes_crypto = AESCryptor()\n            password = aes_crypto.decode(host_info.get(\"password\"))\n            # 密码返回 base64 编码结果\n            import base64\n            host_info[\"password\"] = base64.b64encode(password.encode())\n\n        # 获取监控及日志的url\n        serializer_data = explain_url(serializer_data, is_host=True)\n\n        # 实时获取主机动态\n        prometheus_obj = Prometheus()\n        serializer_data = prometheus_obj.get_host_info(serializer_data)\n\n        # 获取请求中 ordering 字段\n        query_field = request.query_params.get(\"ordering\", \"\")\n        reverse_flag = False\n        if query_field.startswith(\"-\"):\n            reverse_flag = True\n            query_field = query_field[1:]\n        # 若排序字段在类视图 dynamic_fields 中，则对根据动态数据进行排序\n        none_ls = list(filter(\n            lambda x: x.get(query_field) is None,\n            serializer_data))\n        exists_ls = list(filter(\n            lambda x: x.get(query_field) is not None,\n            serializer_data))\n        if query_field in self.dynamic_fields:\n            exists_ls = sorted(\n                exists_ls,\n                key=lambda x: x.get(query_field),\n                reverse=reverse_flag)\n        exists_ls.extend(none_ls)\n\n        return self.get_paginated_response(exists_ls)\n\n\nclass HostReinstallView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        重装主机Agent接口\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostReinstallSerializer\n    # 操作信息描述\n    post_description = \"重装主机Agent\"\n\n\nclass MonitorReinstallView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        重装监控Agent接口\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = MonitorReinstallSerializer\n    # 操作信息描述\n    post_description = \"重装监控Agent\"\n\n\nclass HostUninstallView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        卸载Agent接口\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostUninstallSerializer\n    # 操作信息描述\n    post_description = \"卸载主机agent\"\n\n\nclass HostDetailView(GenericViewSet, RetrieveModelMixin):\n    \"\"\"\n        read:\n        查询主机详情\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostDetailSerializer\n    # 操作描述信息\n    get_description = \"查询主机详情\"\n\n\nclass HostUpdateView(GenericViewSet, UpdateModelMixin):\n    \"\"\"\n        update:\n        更新一个主机\n\n        partial_update:\n        更新一个现有主机的一个或多个字段\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostSerializer\n    # 操作描述信息\n    put_description = patch_description = \"更新主机\"\n\n\nclass HostFieldCheckView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        验证主机 instance_name/ip 字段重复性\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostFieldCheckSerializer\n    # 操作信息描述\n    post_description = \"校验主机字段重复性\"\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        return Response(serializer.is_valid())\n\n\nclass IpListView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查询所有 IP 列表\n    \"\"\"\n    queryset = Host.objects.filter(\n        is_deleted=False).values_list(\"ip\", flat=True)\n    # 操作信息描述\n    get_description = \"查询主机\"\n\n    def list(self, request, *args, **kwargs):\n        return Response(list(self.get_queryset()))\n\n\nclass HostMaintenanceView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        主机进入 / 退出维护模式\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostMaintenanceSerializer\n    # 操作信息描述\n    post_description = \"修改主机维护模式\"\n\n\nclass HostAgentRestartView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        主机重启Agent接口\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostAgentRestartSerializer\n    # 操作信息描述\n    post_description = \"重启主机Agent\"\n\n\nclass HostOperateLogView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        主机操作记录\n    \"\"\"\n    queryset = HostOperateLog.objects.all()\n    serializer_class = HostOperateLogSerializer\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = HostOperateFilter\n    # 操作信息描述\n    get_description = \"查询主机操作记录\"\n\n\nclass HostBatchValidateView(BaseDownLoadTemplateView, CreateModelMixin):\n    \"\"\"\n        list:\n        获取主机批量导入模板\n\n        create:\n        主机数据批量验证\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostBatchValidateSerializer\n    # 操作描述信息\n    get_description = \"获取主机批量导入模板\"\n    post_description = \"主机数据批量验证\"\n\n    def list(self, request, *args, **kwargs):\n        return super(HostBatchValidateView, self).list(\n            request, template_file_name=\"import_hosts_template.xlsx\",\n            *args, **kwargs)\n\n    def create(self, request, *args, **kwargs):\n        ips = Host.objects.all().values_list(\"ip\", flat=True)\n        request_data = {\"host_list\": []}\n        repeat_data = []\n        for host in request.data.get(\"host_list\"):\n            if not host.get(\"ip\") in ips:\n                request_data[\"host_list\"].append(host)\n            else:\n                host[\"init_host\"] = True\n                repeat_data.append(host)\n        if len(request_data[\"host_list\"]) == 0:\n            return Response({\"correct\": repeat_data, \"error\": []})\n        serializer = self.get_serializer(data=request_data)\n        if not serializer.is_valid():\n            logger.error(f\"host batch validate failed:{request.data}\")\n            raise ValidationError(\"数据格式错误\")\n        serializer.validated_data.get(\"result_dict\", {}).get(\n            \"correct\", []).extend(repeat_data)\n        return Response(serializer.validated_data.get(\"result_dict\"))\n\n\nclass HostBatchImportView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        主机批量添加\n    \"\"\"\n    serializer_class = HostBatchImportSerializer\n    # 操作描述信息\n    post_description = \"主机批量添加\"\n\n    def create(self, request, *args, **kwargs):\n        # 信任数据，只进行格式校验\n        serializer = self.get_serializer(data=request.data)\n        if not serializer.is_valid():\n            logger.error(f\"host batch import failed:{request.data}\")\n            raise ValidationError(\"数据格式错误\")\n        try:\n            # 主机、操作记录数据入库\n            default_env = Env.objects.filter(id=1).first()\n            with transaction.atomic():\n                # 主机初始化信息，批量创建过程中无 id，故以 ip 作为键\n                host_init_info = {}\n                host_objs = []\n                ips = Host.objects.all().values_list(\"ip\", flat=True)\n                for host in serializer.data.get(\"host_list\"):\n                    if host.get(\"ip\") in ips:\n                        continue\n                    # 若存在行号、运行用户则删除\n                    if \"row\" in host:\n                        host.pop(\"row\")\n                    if \"run_user\" in host:\n                        host.pop(\"run_user\")\n                    host_init_info[host.get(\"ip\")] = host.pop(\n                        \"init_host\", False)\n                    password = host.pop(\"password\")\n                    host_objs.append(Host(\n                        password=AESCryptor().encode(password),\n                        agent_dir=host.get(\"data_folder\"),\n                        env=default_env,\n                        **host,\n                    ))\n                Host.objects.bulk_create(host_objs)\n                # bulk_create 不返回 id，需重查获取\n                instance_name_list = list(\n                    map(lambda x: x.instance_name, host_objs))\n                host_instances = Host.objects.filter(\n                    instance_name__in=instance_name_list)\n                operate_log_objs = []\n                for instance in host_instances:\n                    operate_log_objs.append(HostOperateLog(\n                        username=request.user.username,\n                        description=\"创建主机\",\n                        host=instance,\n                    ))\n                    # 下发异步 celery 任务\n                    insert_host_celery_task.delay(\n                        instance.id, init=host_init_info.get(instance.ip))\n                HostOperateLog.objects.bulk_create(operate_log_objs)\n        except Exception as err:\n            logger.error(f\"batch import host err: {err}\")\n            import traceback\n            logger.error(traceback.print_exc())\n            raise OperateError(\"批量导入主机失败\")\n        return Response(\"添加成功\")\n\n\nclass HostInitView(BaseDownLoadTemplateView, CreateModelMixin):\n    \"\"\"\n        create:\n        主机初始化\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostInitSerializer\n    # 操作描述信息\n    get_description = \"应用商店下载组件模板\"\n    # 操作信息描述\n    post_description = \"主机初始化\"\n\n    def list(self, request, *args, **kwargs):\n        return super(HostInitView, self).list(\n            request, template_file_name=\"init_host.py\",\n            parent_path=\"_modules\", *args, **kwargs)\n\n\nclass HostsAgentStatusView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        主机agent状态查询\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = HostsAgentStatusSerializer\n    # 操作信息描述\n    post_description = \"主机agent状态查询\"\n\n    def create(self, request, *args, **kwargs):\n        serializer = self.get_serializer(data=request.data)\n        if not serializer.is_valid():\n            logger.error(f\"hosts agent status failed:{request.data}\")\n            raise ValidationError(\"数据格式错误\")\n        ip_set = set(serializer.data.get(\"ip_list\"))\n        agent_online_ip_set = set(self.get_queryset().filter(\n            ip__in=ip_set, host_agent=Host.AGENT_RUNNING\n        ).values_list(\"ip\", flat=True))\n        return Response(ip_set == agent_online_ip_set)\n"
  },
  {
    "path": "omp_server/inspection/__init__.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 6:00 下午\n# Description:\n"
  },
  {
    "path": "omp_server/inspection/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n\nfrom db_models.models import InspectionHistory\n\nadmin.site.register([InspectionHistory])\n"
  },
  {
    "path": "omp_server/inspection/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass InspectionConfig(AppConfig):\n    name = 'inspection'\n"
  },
  {
    "path": "omp_server/inspection/filters.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 9:02 下午\n# Description: 巡检查询\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\nfrom db_models.models import (\n    InspectionHistory, InspectionCrontab, InspectionReport)\n\n\nclass InspectionHistoryFilter(FilterSet):\n    \"\"\" 巡检历史记录过滤类 \"\"\"\n    inspection_name = django_filters.CharFilter(\n        help_text=\"报告名称：模糊搜索\", field_name=\"inspection_name\",\n        lookup_expr=\"icontains\")\n    inspection_type = django_filters.CharFilter(\n        help_text=\"报告类型:service、host、deep\", field_name=\"inspection_type\",\n        lookup_expr=\"icontains\")\n    execute_type = django_filters.CharFilter(\n        help_text=\"执行方式:手动-man；定时：auto\", field_name=\"execute_type\",\n        lookup_expr=\"icontains\")\n    inspection_status = django_filters.NumberFilter(\n        help_text=\"执行结果:1-进行中；2-成功；3-失败\", field_name=\"inspection_status\",\n        lookup_expr=\"icontains\")\n\n    class Meta:\n        model = InspectionHistory\n        fields = (\"inspection_type\", \"execute_type\", \"inspection_status\")\n\n\nclass InspectionCrontabFilter(FilterSet):\n    \"\"\" 巡检任务配置过滤类 \"\"\"\n    job_type = django_filters.CharFilter(\n        help_text=\"任务类型：0-深度分析 1-主机巡检 2-组建巡检\", field_name=\"job_type\",\n        lookup_expr=\"icontains\")\n\n    class Meta:\n        model = InspectionCrontab\n        fields = (\"job_type\",)\n\n\nclass InspectionReportFilter(FilterSet):\n    \"\"\" 巡检报告过滤类 \"\"\"\n    inst_id = django_filters.CharFilter(\n        help_text=\"巡检记录历史表id\", field_name=\"inst_id\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = InspectionReport\n        fields = (\"inst_id\",)\n"
  },
  {
    "path": "omp_server/inspection/get_prometheus_risk_data.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 11:53 上午\n# Description:\nfrom db_models.models import Host, Service, ApplicationHub\nfrom utils.prometheus.prometheus import Prometheus\n\n\ndef get_risk_data(handle, hosts, services):\n    \"\"\"\n     查询 prometheus 异常数据\n     :handle: deep host service\n     :hosts: []\n     :services: []\n     \"\"\"\n    risks = Prometheus().query_alerts()\n    hosts = hosts if hosts else []\n    services = services if services else []\n\n    host_list = []\n    service_list = []\n    _host = Host.objects\n    _service = Service.objects\n    app_name = list(ApplicationHub.objects.filter(\n        id__in=services).values_list('app_name', flat=True))\n    for i in risks:\n        if handle in ['host', 'deep']:\n            # 主机\n            if handle == 'host' and\\\n                    i.get('labels').get('instance') not in hosts:\n                continue\n            if i.get('labels').get('job') == 'nodeExporter':\n                _ = _host.filter(ip=i.get('labels').get('instance')).first()\n                tmp = {'host_ip': i.get('labels').get('instance'),\n                       'resolve_info': \"-\",\n                       'risk_describe': i.get('annotations').get('description'),\n                       'risk_level': i.get('labels').get('severity'),\n                       'system': _.operate_system if _ else '-'}\n                host_list.append(tmp)\n        if handle in ['service', 'deep']:\n            # 组件\n            if handle == 'service' and \\\n                    i.get('labels').get('job').replace('Exporter', '') \\\n                    not in app_name:\n                continue\n            if i.get('labels').get('job') != 'nodeExporter':\n                tmp = {'host_ip': i.get('labels').get('instance'),\n                       'resolve_info': \"-\",\n                       'risk_describe': i.get('annotations').get('description'),\n                       'risk_level': i.get('labels').get('severity'),\n                       'service_name': i.get('labels').get('job'),\n                       'service_port': '-'}\n                service_list.append(tmp)\n\n    risk_num = len(host_list) + len(service_list)\n    return risk_num, {'host_list': host_list, 'service_list': service_list}\n"
  },
  {
    "path": "omp_server/inspection/get_service_topology.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 2:04 下午\n# Description:\n\nfrom db_models.models import Service\n\n\ndef get_topology_data():\n    \"\"\"\n    获取服务平面图数据\n    \"return :\" [{'host_ip': 'ip', 'service_list': ['redis', 'mysql']}......]\n    \"\"\"\n    topologies = dict()\n    _ = Service.objects.all()\n    for i in _:\n        if i.ip in topologies:\n            topologies[i.ip]['service_list'].append(i.service_instance_name)\n        else:\n            topologies[i.ip] = \\\n                {'host_ip': i.ip, 'service_list': [i.service_instance_name]}\n\n    return list(topologies.values())\n"
  },
  {
    "path": "omp_server/inspection/inspection_utils.py",
    "content": "import logging\nimport traceback\n\nfrom db_models.models import InspectionHistory, InspectionReport\nfrom inspection.joint_json_report import joint_json_data\nfrom utils.plugin.send_email import ModelSettingEmailBackend, many_send\nfrom utils.prometheus.create_html_tar import create_html_tar\nfrom utils.plugin import send_email as send_email_module\n\nlogger = logging.getLogger(\"server\")\n\n\ndef send_email(inspection, emails):\n    \"\"\"\n    发送邮件\n    :param inspection: 巡检对象\n    :param emails: 邮箱列表\n    :return:\n    \"\"\"\n    if not inspection:\n        return False, \"无巡检对象\"\n    inspection_report = InspectionReport.objects.filter(\n        inst_id=inspection.id).first()\n    if not inspection_report.file_name:\n        return False, \"未找到巡检报告！\"\n    inspection.send_email_result = inspection.ING\n    inspection.save()\n    reason = \"\"\n    try:\n        connection = ModelSettingEmailBackend()\n        if inspection.inspection_type == \"host\" or inspection.inspection_type == \"service\":\n            content_name = \"SendNormalInspectionEmailContent\"\n        elif inspection.inspection_type == \"deep\":\n            content_name = \"SendDeepInspectionEmailContent\"\n        else:\n            content_name = \"SendEmailContent\"\n        content = getattr(send_email_module, content_name)(inspection)\n        fail_user = many_send(connection, content, emails)\n    except Exception as e:\n        logger.error(f\"发送邮件失败， 错误信息：{str(e)}\")\n        reason = \"系统异常，请重试!\"\n        fail_user = emails\n    if fail_user:\n        inspection.send_email_result = inspection.FAIL\n        reason = \"发件失败，请检查smtp邮箱服务器配置！\"\n    else:\n        inspection.send_email_result = inspection.SUCCESS\n    inspection.email_fail_reason = reason\n    inspection.save()\n    return not bool(fail_user), reason\n\n\n# def create_inspection_html(inspection):\n#     \"\"\"\n#     生成巡检报告文件、更新巡检对象信息(重复，不修改发送邮件状态)\n#     :param inspection:\n#     :return:\n#     \"\"\"\n#     if not inspection:\n#         return False, \"无巡检对象\"\n#     if inspection.inspection_status != 2:\n#         return False, \"巡检结果未成功！\"\n#     report_data = inspection.report_data\n#     time_str = inspection.inspection_name.split(\"-\")[1]\n#     new_html_dir_name = f\"{inspection.__class__.__name__.lower()}-{time_str}\"\n#     try:\n#         state, result = create_html_tar(new_html_dir_name, report_data)\n#     except Exception as e:\n#         logger.error(f\"打包巡检报告发生错误：{str(e)}, 详情为：\\n{traceback.format_exc()}\")\n#         inspection.email_fail_reason = \"打包巡检报告发生错误!\"\n#         inspection.save()\n#         return False, \"打包巡检报告发生错误!\"\n#     if not state:\n#         inspection.email_fail_reason = result\n#         inspection.save()\n#         return False, result\n#     inspection.file_name = result\n#     inspection.save()\n#     return True, result\n\n\ndef create_send_inspection_html(inspection):\n    \"\"\"\n    生成巡检报告文件、更新巡检对象信息（更新发送邮件状态）\n    :param inspection:\n    :return:\n    \"\"\"\n    if not inspection:\n        return False, \"无巡检对象\"\n    if inspection.inspection_status != 2:\n        return False, \"巡检结果未成功！\"\n    inspection_report = InspectionReport.objects.filter(inst_id=inspection.id)\n    if not inspection_report:\n        return False, \"未找到巡检报告！\"\n    report_data = joint_json_data(\n        inspection.inspection_type, inspection_report, inspection)\n    time_str = inspection.inspection_name.split(\"-\")[1]\n    new_html_dir_name = f\"{inspection.__class__.__name__.lower()}-{time_str}\"\n    try:\n        state, result = create_html_tar(new_html_dir_name, report_data)\n    except Exception as e:\n        logger.error(f\"打包巡检报告发生错误：{str(e)}, 详情为：\\n{traceback.format_exc()}\")\n        inspection.send_email_result = 0\n        inspection.email_fail_reason = \"打包巡检报告发生错误!\"\n        inspection.save()\n        return False, \"打包巡检报告发生错误!\"\n    if not state:\n        inspection.send_email_result = 0\n        inspection.email_fail_reason = result\n        inspection.save()\n        return False, result\n    inspection_report.file_name = result\n    inspection.save()\n    inspection_report.save()\n    return True, result\n\n\ndef send_report_email(inspection_module, inspection_id, emails):\n    \"\"\"\n    生成、发送报告\n    :param inspection_module: DeepInspection、NormalInspection\n    :param inspection_id:\n    :param emails: 邮箱list\n    :return:\n    \"\"\"\n    inspection = InspectionHistory.objects.filter(id=inspection_id).first()\n    if not inspection:\n        return False, \"未找到对应的巡检！\"\n    if inspection.inspection_status != 2:\n        return False, \"巡检结果未成功！\"\n    inspection_report = InspectionReport.objects.filter(\n        inst_id=inspection_id).first()\n    if not inspection_report:\n        return False, \"未找到对应的巡检报告！\"\n    if inspection.send_email_result == 2:\n        return False, \"正在发送巡检报告，请稍后再试！\"\n    inspection.send_email_result = 2\n    inspection.save()\n    if not inspection_report.file_name:\n        try:\n            state, result = create_send_inspection_html(inspection)\n        except Exception as e:\n            logger.error(\n                f\"打包巡检报告发生错误：{str(e)}, 详情为：\\n{traceback.format_exc()}\")\n            inspection.send_email_result = inspection.FAIL\n            inspection.email_fail_reason = \"打包巡检报告发生错误！\"\n            inspection.save()\n            return False, \"打包巡检报告发生错误！\"\n        if not state:\n            return False, result\n    try:\n        state, email_fail_reason = send_email(inspection, emails)\n    except Exception as e:\n        logger.error(f\"发送邮件发生错误：{str(e)}, 详情为：\\n{traceback.format_exc()}\")\n        return \"发送邮件发生错误!\"\n    return state, email_fail_reason\n"
  },
  {
    "path": "omp_server/inspection/joint_json_report.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/25 10:30 上午\n# Description:\n\n\ndef joint_json_data(handle, _r, _h):\n    \"\"\"\n    巡检导出、报告展示时，组装数据结构\n    \"\"\"\n    ret = {\n        \"summary\": {\n            \"task_info\": {\n                \"task_name\": _h.inspection_name,\n                \"operator\": _h.inspection_operator,\n                \"task_status\": _h.inspection_status\n            },\n            \"time_info\": {\n                \"start_time\": _h.start_time.strftime('%Y-%m-%d %H:%M:%S'),\n                \"end_time\":\n                    _h.end_time.strftime('%Y-%m-%d %H:%M:%S') if\n                    _h.end_time else '',\n                \"cost\": _h.duration\n            },\n            \"scan_info\": _r.scan_info,\n            \"scan_result\": _r.scan_result\n        },\n        \"risks\": _r.risk_data\n        if _r.risk_data else {\"host_list\": [], \"service_list\": []},\n        \"detail_dict\": {},\n        \"file_name\": _r.file_name\n    }\n\n    # 主机巡检\n    if handle == 'host':\n        ret['detail_dict']['host'] = _r.host_data\n\n    # 组件巡检\n    if handle == 'service':\n        ret['detail_dict']['component'] = _r.serv_data\n\n    # 深度巡检\n    if handle == 'deep':\n        # 服务平面图\n        ret[\"service_topology\"] = _r.serv_plan if _r.serv_plan else []\n        ret['detail_dict']['host'] = _r.host_data\n        ret['detail_dict'][\"service\"] = _r.serv_data\n        ret['detail_dict'][\"database\"] = []\n        ret['detail_dict'][\"component\"] = []\n\n    return ret\n"
  },
  {
    "path": "omp_server/inspection/serializers.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 8:36 下午\n# Description: 巡检序列化\nfrom rest_framework.serializers import (ModelSerializer,)\nfrom db_models.models import (\n    InspectionHistory, InspectionCrontab, InspectionReport)\n\n\nclass InspectionHistorySerializer(ModelSerializer):\n    \"\"\" 巡检记录历史表 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = InspectionHistory\n        fields = \"__all__\"\n\n\nclass InspectionCrontabSerializer(ModelSerializer):\n    \"\"\" 巡检任务 配置表 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = InspectionCrontab\n        fields = \"__all__\"\n\n\nclass InspectionReportSerializer(ModelSerializer):\n    \"\"\" 巡检报告表 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = InspectionReport\n        fields = \"__all__\"\n"
  },
  {
    "path": "omp_server/inspection/tasks.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 6:06 下午\n# Description: 巡检异步任务及定时任务\n\nimport logging\nimport traceback\nfrom datetime import datetime\nfrom celery import shared_task\n\nfrom inspection.inspection_utils import send_email\nfrom utils.prometheus.thread import MyThread\nfrom celery.utils.log import get_task_logger\nfrom db_models.models import Host, Env, Service, ModuleSendEmailSetting\nfrom db_models.models import InspectionHistory, InspectionReport\nfrom utils.prometheus.prometheus import back_fill\nfrom utils.prometheus.target_host import target_host_thread\nfrom utils.prometheus.target_service import target_service_run\nfrom utils.prometheus.create_html_tar import create_html_tar\nfrom inspection.joint_json_report import joint_json_data\nfrom inspection.get_prometheus_risk_data import get_risk_data\nfrom inspection.get_service_topology import get_topology_data\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\ndef get_hosts_data(env, hosts):\n    \"\"\"\n    查询多主机prometheus数据，组装后进行反填\n    :env: 环境queryset\n    :hosts: 主机列表，例：[\"主机ip\"]\n    \"\"\"\n    temp_list = list()\n    threads = list()\n    total_no = error_no = 0     # 总指标数/异常指标数\n    for instance in hosts:\n        total_no += 23          # 总指标数;当前共23个\n        threads.append(MyThread(func=target_host_thread, args=(env, instance)))\n\n    for t in threads:\n        t.start()\n\n    for t in threads:\n        t.join()                    # 用join等待线程执行结束\n        temp_list.append(t.res)     # 组装每个线程的返回值\n\n    scan_result = {\n        \"all_target_num\": total_no, \"abnormal_target\": error_no, \"healthy\": \"\"\n    }\n    scan_info = {\"host\": len(hosts), \"service\": 0, \"component\": 0}  # 扫描统计\n    return scan_info, scan_result, temp_list\n\n\n@shared_task\ndef get_prometheus_data(env_id, hosts, services, history_id, report_id, handle):\n    \"\"\"\n    异步任务：查询多巡检类型prometheus数据，组装后进行反填\n    :env_id: 环境，例：Env id\n    :hosts: 主机列表，例：[\"主机ip\"]\n    :services: 服务列表，例：[8]\n    :history_id: 巡检历史表id，例：1\n    :report_id: 巡检报告表id，例：1\n    :handle: 巡检类型 service-服务巡检、host-主机巡检、deep-深度巡检\n    \"\"\"\n    try:\n        file_name = ''  # 导出文件名\n        env = Env.objects.filter(id=env_id).first()\n        _h = InspectionHistory.objects.filter(id=history_id).first()\n        kwargs = {'history_id': history_id, 'report_id': report_id}\n        if handle == 'host':\n            # 主机巡检\n            file_name = f\"hostinspection{_h.inspection_name.split('-')[1]}\"\n            scan_info, scan_result, host_data = get_hosts_data(env, hosts)\n            kwargs.update({\n                'scan_info': scan_info, 'scan_result': scan_result,\n                'host_data': host_data,\n                'file_name': f\"{file_name}.tar.gz\"})\n        elif handle == 'service':\n            # 组件巡检\n            file_name = f\"serviceinspection{_h.inspection_name.split('-')[1]}\"\n            scan_info, scan_result, serv_data = \\\n                target_service_run(env, services)\n            kwargs.update({\n                'scan_info': scan_info, 'scan_result': scan_result,\n                'serv_data': serv_data,\n                'file_name': f\"{file_name}.tar.gz\"})\n        elif handle == 'deep':\n            # 主机巡检\n            file_name = f\"deepinspection{_h.inspection_name.split('-')[1]}\"\n            hosts = Host.objects.filter(\n                env=env.id).values_list('ip', flat=True)\n            if len(hosts) > 0:\n                h_info, h_result, host_data = get_hosts_data(env, list(hosts))\n            else:\n                h_info, host_data = {'host': 0}, []\n                h_result = {'all_target_num': 0, 'abnormal_target': 0}\n\n            # 组件巡检\n            _ = Service.objects.filter(\n                service__is_base_env=False).exclude(\n                service_status__in=[5, 6, 7])\n            services = list(_.values_list('id', flat=True))\n            if len(services) > 0:\n                s_info, s_result, serv_data = target_service_run(env, services)\n            else:\n                s_info, serv_data = {'service': 0}, []\n                s_result = {'all_target_num': 0, 'abnormal_target': 0}\n\n            # 主机巡检 + 组件巡检 合并结果\n            scan_info = {\"host\": h_info.get('host'), \"component\": 0,\n                         \"service\": s_info.get('service')}\n            all_target_num = \\\n                h_result.get('all_target_num') + s_result.get('all_target_num')\n            scan_result = {\"all_target_num\": all_target_num,\n                           \"abnormal_target\": 0, \"healthy\": \"-\"}\n            kwargs.update({\n                'scan_info': scan_info, 'scan_result': scan_result,\n                'serv_data': serv_data, 'host_data': host_data,\n                'file_name': f\"{file_name}.tar.gz\"})\n            # 服务平面图\n            kwargs['serv_plan'] = get_topology_data()\n\n        # 风险指标\n        risk_num, risk_data = get_risk_data(handle, hosts, services)\n        kwargs['risk_data'] = risk_data\n        # 根据风险指标更新\n        kwargs['scan_result']['abnormal_target'] = risk_num\n        # 反填巡检记录、巡检报告 数据\n        back_fill(**kwargs)\n        # 打包html文件\n        _r = InspectionReport.objects.filter(id=report_id).first()\n        _h = InspectionHistory.objects.filter(id=history_id).first()\n        ret = joint_json_data(_h.inspection_type, _r, _h)\n        create_html_tar(file_name, ret)\n        if _h.inspection_status == 2:\n            mses = ModuleSendEmailSetting.get_email_settings(\n                env_id, \"inspection\")\n            if not mses:\n                return\n            if mses.send_email == 0:\n                return\n            email_users = mses.to_users.split(\",\")\n            if len(email_users) > 0:\n                send_email(_h, email_users)\n    except Exception as e:\n        logger.error(\n            f\"Inspection man task failed with error: {traceback.format_exc(e)}\")\n\n\n@shared_task\ndef inspection_crontab(**kwargs):\n    \"\"\"\n    巡检 定时任务，由增加及修改接口增加的celery任务调起执行\n    :kwargs: {\"env\": 1, \"job_type\": 1, \"job_name\": \"主机巡检\"}\n    \"\"\"\n    try:\n        env = kwargs.get('env')\n        job_type = kwargs.get('job_type')\n        job_name = kwargs.get('job_name')\n        # 1、查询环境是否存在\n        env = Env.objects.filter(id=env).first()\n        if not env:\n            logger.error(\n                f\"Inspection auto task failed with error: ID={env}的环境不存在\")\n        else:\n            hosts, services = [], []\n            if job_type in [0, 1]:\n                # 2、查询环境下主机信息\n                hosts = Host.objects.filter(env=env.id).values_list(\n                    'ip', flat=True)\n                if len(hosts) == 0:\n                    logger.error(f\"Inspection auto task failed with error: \"\n                                 f\"ID={env.id}环境下无主机数据\")\n            if job_type in [0, 2]:\n                # 2、查询环境下组件信息\n                _ = Service.objects.filter(\n                    service__is_base_env=False\n                ).exclude(service_status__in=[5, 6, 7])\n                services = list(_.values_list('id', flat=True))\n                if len(services) == 0:\n                    logger.error(f\"Inspection auto task failed with error: \"\n                                 f\"ID={env.id}环境下无组件数据\")\n\n            # job_type 与 inspection_type 参数对应\n            inspection_type = {0: 'deep', 1: 'host', 2: 'service'}\n            # 3、组装巡检历史表入库数据，并存储入库\n            now = datetime.now()\n            num = InspectionHistory.objects.filter(\n                start_time__year=now.year, start_time__month=now.month,\n                start_time__day=now.day).count()\n            his_dict = {\n                'inspection_name':\n                    f\"{job_name}定时巡检-{now.strftime('%Y%m%d')}{num + 1}\",\n                'inspection_type': inspection_type.get(job_type),\n                'inspection_status': 1, 'execute_type': 'auto',\n                'inspection_operator': 'admin',\n                'hosts': list(hosts), 'services': list(services), 'env': env}\n            his_obj = InspectionHistory(**his_dict)\n            his_obj.save()\n            # 4、组装巡检报告表数据，并存储入库\n            rep_obj = InspectionReport(**{'inst_id': his_obj})\n            rep_obj.save()\n            # 5、查询prometheus数据，组装后进行反填\n            get_prometheus_data(\n                env_id=env.id, hosts=list(hosts), services=list(services),\n                history_id=his_obj.id, report_id=rep_obj.id,\n                handle=inspection_type.get(job_type))\n    except Exception as e:\n        logger.error(f\"Inspection auto task failed with error:\"\n                     f\"{traceback.format_exc(e)}\")\n"
  },
  {
    "path": "omp_server/inspection/urls.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 8:52 下午\n# Description: 巡检 路由\nfrom rest_framework.routers import DefaultRouter\nfrom inspection.views import (\n    InspectionHistoryView, InspectionCrontabView, InspectionReportView,\n    InspectionServiceView, InspectionSendEmailSettingView, InspectionSendEmailAPIView)\n\nrouter = DefaultRouter()\n# 巡检 历史记录\nrouter.register(\"history\", InspectionHistoryView, basename=\"history\")\n# 巡检-组件巡检 组件列表\nrouter.register(\"services\", InspectionServiceView, basename=\"services\")\n# 巡检 定时任务配置\nrouter.register(\"crontab\", InspectionCrontabView, basename=\"crontab\")\n# 巡检 报告\nrouter.register(\"report\", InspectionReportView, basename=\"report\")\nrouter.register(\"inspectionSendEmailSetting\",\n                InspectionSendEmailSettingView, basename=\"inspectionSendEmailSetting\")\nrouter.register(\"inspectionSendEmail\",\n                InspectionSendEmailAPIView, \"inspectionSendEmail\")\n"
  },
  {
    "path": "omp_server/inspection/views.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/13 6:06 下午\n# Description: 巡检视图\nimport datetime\nimport logging\nimport traceback\n\nfrom django.core.validators import EmailValidator\nfrom rest_framework import status\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import Serializer\n\nfrom inspection.inspection_utils import send_report_email\nfrom utils.common.paginations import PageNumberPager\nfrom rest_framework.viewsets import GenericViewSet\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin)\nfrom utils.plugin.crontab_utils import CrontabUtils\nfrom inspection.tasks import get_prometheus_data\nfrom db_models.models import (\n    Env, Service, InspectionHistory, InspectionCrontab, InspectionReport, ModuleSendEmailSetting)\nfrom inspection.filters import (\n    InspectionHistoryFilter, InspectionCrontabFilter, )\nfrom inspection.serializers import (\n    InspectionHistorySerializer, InspectionCrontabSerializer,\n    InspectionReportSerializer)\nfrom rest_framework.filters import OrderingFilter\nfrom inspection.joint_json_report import joint_json_data\n\nlogger = logging.getLogger('server')\n\n\nclass InspectionServiceView(ListModelMixin, GenericViewSet):\n    \"\"\"\n        list: 组件巡检 组件列表\n    \"\"\"\n\n    def list(self, request, *args, **kwargs):\n        # 只能是安装成功的组件\n        rets = list()\n        _ = Service.objects.filter(service__is_base_env=False).exclude(service__app_port=None).exclude(\n            service_status__in=[5, 6, 7])\n        for i in _:\n            rets.append({'service__id': i.id,\n                         'service__app_name': i.service_instance_name})\n\n        return Response(data=rets, status=status.HTTP_200_OK)\n\n\nclass InspectionHistoryView(ListModelMixin, GenericViewSet, CreateModelMixin):\n    \"\"\"\n        list:  查询巡检记录历史记录列表\n    \"\"\"\n    queryset = InspectionHistory.objects.all()\n    serializer_class = InspectionHistorySerializer\n    pagination_class = PageNumberPager\n    # 过滤字段\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = InspectionHistoryFilter\n    # 动态排序字段\n    dynamic_fields = (\"start_time\",)\n    # 操作描述信息\n    get_description = \"查询巡检历史记录列表\"\n    post_description = \"查询巡检历史记录列表\"\n\n    def list(self, request, *args, **kwargs):\n        # 获取序列化数据列表\n        queryset = self.filter_queryset(self.get_queryset())\n        serializer = self.get_serializer(\n            self.paginate_queryset(queryset), many=True)\n        serializer_data = serializer.data\n\n        # 获取请求中 ordering 字段\n        query_field = request.query_params.get(\"ordering\", \"\")\n        reverse_flag = False\n        if query_field.startswith(\"-\"):\n            reverse_flag = True\n            query_field = query_field[1:]\n        # 若排序字段在类视图 dynamic_fields 中，则对根据动态数据进行排序\n        none_ls = list(filter(\n            lambda x: x.get(query_field) is None,\n            serializer_data))\n        exists_ls = list(filter(\n            lambda x: x.get(query_field) is not None,\n            serializer_data))\n        if query_field in self.dynamic_fields:\n            exists_ls = sorted(\n                exists_ls,\n                key=lambda x: x.get(query_field),\n                reverse=reverse_flag)\n        exists_ls.extend(none_ls)\n\n        return self.get_paginated_response(exists_ls)\n\n    @staticmethod\n    def joint_inspection_name(data_dict):\n        now = datetime.datetime.now()\n        num = InspectionHistory.objects.filter(\n            start_time__year=now.year, start_time__month=now.month,\n            start_time__day=now.day).count()\n        tp = {'deep': '深度巡检', 'host': '主机巡检', 'service': '组件巡检'}\n        name = f\"{tp.get(data_dict.get('inspection_type'))}-\" \\\n               f\"{now.strftime('%Y%m%d')}{num + 1}\"\n        return name\n\n    def create(self, request, *args, **kwargs):\n        data_dict = request.data\n        # 一、创建巡检历史表数据\n        env_obj = Env.objects.filter(id=data_dict['env']).first()\n        data_dict['env'] = env_obj\n        data_dict['inspection_name'] = self.joint_inspection_name(data_dict)\n        his_obj = InspectionHistory(**data_dict)\n        his_obj.save()\n        # 二、创建巡检记录历史表关联的报告表的数据\n        rep_obj = InspectionReport(**{'inst_id': his_obj})\n        rep_obj.save()\n        # 三、手动序列化数据，不是json的不能response\n        data_dict.update({'id': his_obj.id, 'env': data_dict['env'].id})\n        # 四、下发celery异步任务\n        handle = data_dict.get('inspection_type')\n        # 异步下发\n        get_prometheus_data.delay(\n            env_id=env_obj.id, hosts=his_obj.hosts, services=his_obj.services,\n            history_id=his_obj.id, report_id=rep_obj.id, handle=handle)\n        return Response(data_dict, status=status.HTTP_200_OK)\n\n\nclass InspectionCrontabView(RetrieveModelMixin, ListModelMixin, GenericViewSet,\n                            CreateModelMixin, UpdateModelMixin):\n    \"\"\"\n        list: 查询巡检任务列表\n        create: 创建一个新巡检任务\n        update: 更新一个现有巡检任务\n    \"\"\"\n    queryset = InspectionCrontab.objects.all()\n    serializer_class = InspectionCrontabSerializer\n    pagination_class = PageNumberPager\n    # 过滤字段\n    lookup_field = 'job_type'\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = InspectionCrontabFilter\n    # 操作描述信息\n    get_description = \"查询巡检任务配置列表\"\n    post_description = \"新建巡检任务配置列表\"\n    put_description = \"更新巡检任务配置列表\"\n\n    @staticmethod\n    def transfer_week(request):\n        \"\"\"\n        因前端day_of_week参数传递 不符合规范，只能适配了呗\n        \"\"\"\n        day_of_week = request.data.get('crontab_detail').get('day_of_week')\n        if day_of_week == '6':\n            day_of_week = '0'\n        elif day_of_week == '*':\n            pass\n        else:\n            day_of_week = str(int(day_of_week) + 1)\n\n        return day_of_week\n\n    def create(self, request, *args, **kwargs):\n        # 判断是否需要下发任务到celery：0-开启，1-关闭\n        is_success = True\n        request.data['job_type'] = int(request.data.get('job_type'))\n        if request.data.get('is_start_crontab') == 0:\n            tp = {0: 'deep', 1: 'host', 2: 'service'}\n            task_name = \\\n                f\"inspection_cron_task_{tp.get(request.data.get('job_type'))}\"\n            task_func = \"inspection.tasks.inspection_crontab\"\n            cron_obj = CrontabUtils(task_name=task_name, task_func=task_func,\n                                    task_kwargs=request.data)\n            cron_args = {\n                'minute': request.data.get('crontab_detail').get('minute'),\n                'hour': request.data.get('crontab_detail').get('hour'),\n                'day_of_month': request.data.get('crontab_detail').get('day'),\n                'month_of_year':\n                    request.data.get('crontab_detail').get('month'),\n                'day_of_week': self.transfer_week(request)\n            }\n            is_success, job_name = cron_obj.create_crontab_job(**cron_args)\n        else:\n            pass\n\n        if is_success:\n            # 只是想在增加时加个判断及对应操作，增加还是执行父类的create\n            return CreateModelMixin.create(self, request, *args, **kwargs)\n        else:\n            return Response(data='定时任务已存在，请勿重复操作',\n                            status=status.HTTP_200_OK)\n\n    def update(self, request, *args, **kwargs):\n        # 判断是否需要下发任务到celery：0-开启，1-关闭\n        is_success = True\n        request.data['job_type'] = int(request.data.get('job_type'))\n        if request.data.get('is_start_crontab') == 0:\n            tp = {0: 'deep', 1: 'host', 2: 'service'}\n            task_name = \\\n                f\"inspection_cron_task_{tp.get(request.data.get('job_type'))}\"\n            task_func = 'inspection.tasks.inspection_crontab'\n            cron_obj = CrontabUtils(task_name=task_name, task_func=task_func,\n                                    task_kwargs=request.data)\n            cron_args = {\n                'minute': request.data.get('crontab_detail').get('minute'),\n                'hour': request.data.get('crontab_detail').get('hour'),\n                'day_of_month': request.data.get('crontab_detail').get('day'),\n                'month_of_year':\n                    request.data.get('crontab_detail').get('month'),\n                'day_of_week': self.transfer_week(request)\n            }\n            # 删除定时任务\n            cron_obj.delete_job()\n            # 增加定时任务\n            is_success, job_name = cron_obj.create_crontab_job(**cron_args)\n        else:\n            tp = {0: 'deep', 1: 'host', 2: 'service'}\n            task_name = \\\n                f\"inspection_cron_task_{tp.get(request.data.get('job_type'))}\"\n            task_func = 'inspection.tasks.inspection_crontab'\n            cron_obj = CrontabUtils(task_name=task_name, task_func=task_func,\n                                    task_kwargs=request.data)\n            # 删除定时任务\n            cron_obj.delete_job()\n\n        if is_success:\n            # 只是想在修改时加个判断及对应操作，修改还是执行父类的update\n            return UpdateModelMixin.update(self, request, *args, **kwargs)\n        else:\n            return Response(data={'code': 500, 'message': '定时任务修改失败，请重试'},\n                            status=status.HTTP_200_OK)\n\n\nclass InspectionReportView(GenericViewSet, RetrieveModelMixin):\n    \"\"\"\n        list: 查询巡检报告列表\n    \"\"\"\n    queryset = InspectionReport.objects.all()\n    serializer_class = InspectionReportSerializer\n    # 过滤字段\n    lookup_field = 'inst_id'\n    # 操作描述信息\n    get_description = \"查询巡检任务配置列表\"\n\n    def retrieve(self, request, *args, **kwargs):\n        data_dict = request.parser_context.get('kwargs')\n        _r = InspectionReport.objects.filter(\n            inst_id=data_dict.get('inst_id')).first()\n        _h = InspectionHistory.objects.filter(\n            id=data_dict.get('inst_id')).first()\n        if not _r or not _h:\n            return Response('巡检报告缺失，暂不可查看')\n\n        ret = joint_json_data(_h.inspection_type, _r, _h)\n        return Response(ret)\n\n\nclass InspectionSendEmailSettingView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n    读写巡检邮箱收件配置\n    \"\"\"\n    get_description = \"读取巡检邮箱设置\"\n    post_description = \"更新巡检邮箱设置\"\n\n    serializer_class = Serializer\n\n    def list(self, request, *args, **kwargs):\n        # env_id = request.GET.get(\"env_id\")\n        env_id = 1  # 单环境暂为1\n        email_setting = ModuleSendEmailSetting.get_email_settings(\n            env_id, \"inspection\")  # TODO 暂写死为巡检\n        if not email_setting:\n            return Response(data={})\n        return Response(\n            data={\n                \"to_users\": email_setting.to_users,\n                \"send_email\": email_setting.send_email\n            }\n        )\n\n    def create(self, request, *args, **kwargs):\n        env_id = request.data.get(\"env_id\")\n        send_email = request.data.get(\"send_email\", False)\n        to_users = request.data.get(\"to_users\")\n        if not to_users and send_email:\n            return Response(data={\"code\": 1, \"message\": \"收件邮箱必填！\"})\n        if to_users:\n            emails = to_users.split(\",\")\n            for email in emails:\n                try:\n                    EmailValidator()(email)\n                except Exception as e:\n                    message = f\"收件箱{email}格式错误！\"\n                    logger.error(f\"{message} 错误信息：{str(e)}\")\n                    return Response(data={\"code\": 1, \"message\": message})\n        try:\n            ModuleSendEmailSetting.update_email_settings(\n                env_id, \"inspection\", send_email, to_users)  # TODO 暂写死为巡检\n        except Exception as e:\n            logger.info(f\"更新邮箱配置失败：{str(e)}，详情为：{traceback.format_exc()}\")\n            return Response(data={\"code\": 1, \"message\": \"更新邮箱配置失败！\"})\n        return Response({})\n\n\nclass InspectionSendEmailAPIView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n    巡检邮件推送\n    \"\"\"\n    post_description = \"巡检邮件推送\"\n    serializer_class = Serializer\n\n    def create(self, request, *args, **kwargs):\n        inspection_id = request.data.get(\"id\")\n        inspection_module = request.data.get(\"module\")\n        if inspection_module not in (\"host\", \"service\", \"deep\"):\n            return Response(data={\"code\": 1, \"message\": \"请选择正确的巡检对象！\"})\n        to_users = request.data.get(\"to_users\")\n        if not to_users:\n            return Response(data={\"code\": 1, \"message\": \"请填入接收邮件邮箱！\"})\n        emails = to_users.split(\",\")\n        for email in emails:\n            try:\n                EmailValidator()(email)\n            except Exception as e:\n                message = f\"收件箱{email}格式错误！\"\n                logger.error(f\"{message} 错误信息：{str(e)}\")\n                return Response(data={\"code\": 1, \"message\": message})\n        state, result = send_report_email(\n            inspection_module, inspection_id, emails)\n        if not state:\n            return Response(data={\"code\": 1, \"message\": result})\n        return Response({})\n"
  },
  {
    "path": "omp_server/manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n    \"\"\"Run administrative tasks.\"\"\"\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'omp_server.settings')\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "omp_server/omp_server/__init__.py",
    "content": "# This will make sure the app is always imported when\n# Django starts so that shared_task will use this app.\nimport pymysql\n\nfrom .celery import app as celery_app\n\n__all__ = ('celery_app',)\npymysql.install_as_MySQLdb()\n"
  },
  {
    "path": "omp_server/omp_server/asgi.py",
    "content": "\"\"\"\nASGI config for omp_server project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/howto/deployment/asgi/\n\"\"\"\n\nimport os\n\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'omp_server.settings')\n\napplication = get_asgi_application()\n"
  },
  {
    "path": "omp_server/omp_server/celery.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: celery\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-12 11:30\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\ncelery相关\n\"\"\"\n\nimport os\n\nfrom celery import Celery\nfrom utils.parse_config import OMP_REDIS_HOST\nfrom utils.parse_config import OMP_REDIS_PORT\nfrom utils.parse_config import OMP_REDIS_PASSWORD\n\n# Set the default Django settings module for the 'celery' program.\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'omp_server.settings')\n\napp = Celery('omp_server')\n\n# Using a string here means the worker doesn't have to serialize\n# the configuration object to child processes.\n# - namespace='CELERY' means all celery-related configuration keys\n#   should have a `CELERY_` prefix.\napp.config_from_object('django.conf:settings', namespace='CELERY')\napp.conf.broker_url = \\\n    f'redis://:{OMP_REDIS_PASSWORD}@{OMP_REDIS_HOST}:{OMP_REDIS_PORT}/0'\n\n# Load task modules from all registered Django apps.\napp.autodiscover_tasks()\n\n\n# @app.task(bind=True)\n# def debug_task(self):\n#     print(f'Request: {self.request!r}')\n"
  },
  {
    "path": "omp_server/omp_server/settings.py",
    "content": "\"\"\"\nDjango settings for omp_server project.\n\nGenerated by 'django-admin startproject' using Django 3.1.4.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/3.1/ref/settings/\n\"\"\"\nimport os\nimport random\nimport datetime\nfrom pathlib import Path\nfrom utils.parse_config import OMP_MYSQL_HOST, OMP_MYSQL_PORT, \\\n    OMP_MYSQL_USERNAME, OMP_MYSQL_PASSWORD, TOKEN_EXPIRATION, \\\n    SSH_CMD_TIMEOUT, PRIVATE_KEY\n\nSSH_CMD_TIMEOUT = SSH_CMD_TIMEOUT\nPRIVATE_KEY = PRIVATE_KEY\n# Build paths inside the project like this: BASE_DIR / 'subdir'.\nBASE_DIR = Path(__file__).resolve().parent.parent\nPROJECT_DIR = os.path.dirname(BASE_DIR)\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = 'rofvdj3gbyg0(vb-ck=d(*1o=jx=l2_%c0*ox^rv%2s36(u3-@'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = True\n\n# 允许所有\nALLOWED_HOSTS = [\"*\"]\n\n# Application definition\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'django_celery_results',\n    'django_celery_beat',\n    'rest_framework',\n    'db_models',\n    'users',\n    'tests',\n    'inspection',\n    'service_upgrade',\n    'tool',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n    'utils.middleware_handler.RoleAuthenticationMiddleware',\n    'utils.middleware_handler.OperationLogMiddleware'\n]\n\nROOT_URLCONF = 'omp_server.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'omp_server.wsgi.application'\n\n# Database\n# https://docs.djangoproject.com/en/3.1/ref/settings/#databases\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.mysql',\n        'NAME': 'omp',\n        'USER': OMP_MYSQL_USERNAME,\n        'PASSWORD': OMP_MYSQL_PASSWORD,\n        'HOST': OMP_MYSQL_HOST,\n        'PORT': int(OMP_MYSQL_PORT),\n        'TEST': {\n            'CHARSET': 'utf8',\n            'COLLATION': 'utf8_general_ci',\n            \"NAME\": f\"test_omp_{random.randint(100, 200)}\"\n        },\n        'OPTIONS': {\n            'init_command': 'SET sql_mode=STRICT_TRANS_TABLES',\n        }\n    }\n}\n\n# Password validation\n# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.'\n                'UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.'\n                'MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.'\n                'CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.'\n                'NumericPasswordValidator',\n    },\n]\n\n# Internationalization\n# https://docs.djangoproject.com/en/3.1/topics/i18n/\n\nLANGUAGE_CODE = 'en-us'\nUSE_I18N = True\nUSE_L10N = True\nTIME_ZONE = 'Asia/Shanghai'\nUSE_TZ = False\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/3.1/howto/static-files/\n\nSTATIC_URL = '/static/'\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static/\")\n\n# DRF 相关设置\nREST_FRAMEWORK = {\n    \"DEFAULT_AUTHENTICATION_CLASSES\": (\n        \"rest_framework_jwt.authentication.JSONWebTokenAuthentication\",\n    ),\n    \"EXCEPTION_HANDLER\": \"utils.exception_handler.common_exception_handler\",\n    \"DEFAULT_RENDERER_CLASSES\": (\n        \"utils.response_handler.APIRenderer\",\n    ),\n    \"DEFAULT_PERMISSION_CLASSES\": (\n        \"rest_framework.permissions.IsAuthenticated\",\n    ),\n    \"DEFAULT_SCHEMA_CLASS\": \"rest_framework.schemas.coreapi.AutoSchema\",\n}\n\n# JWT相关设置\nJWT_AUTH = {\n    \"JWT_EXPIRATION_DELTA\": datetime.timedelta(days=int(TOKEN_EXPIRATION)),\n    \"JWT_ALLOW_REFRESH\": True,\n    \"JWT_AUTH_COOKIE\": \"jwtToken\",\n}\n\nAUTH_USER_MODEL = \"db_models.UserProfile\"\n\n# celery 相关配置\nCELERY_RESULT_BACKEND = \"django-db\"\nCELERY_ENABLE_UTC = False\nCELERY_TIMEZONE = TIME_ZONE\nDJANGO_CELERY_BEAT_TZ_AWARE = False\nCELERY_BEAT_SCHEDULER = \"django_celery_beat.schedulers:DatabaseScheduler\"\nCELERY_IMPORTS = (\"hosts.tasks\", \"inspection.tasks\")\n\nLOGGER_CLASS = 'concurrent_log_handler.ConcurrentRotatingFileHandler'\nLOG_BACKUP_SIZE = 1024 * 1024 * 100\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': False,\n    'formatters': {\n        'standard': {\n            'format': '[%(asctime)s][%(levelname)s] %(pathname)s %(lineno)d -> %(message)s'}\n    },\n    'filters': {\n    },\n    'handlers': {\n        'mail_admins': {\n            'level': 'ERROR',\n            'class': 'django.utils.log.AdminEmailHandler',\n            'include_html': True,\n        },\n        'default': {\n            'level': 'DEBUG',\n            'class': LOGGER_CLASS,\n            'filename': os.path.join(PROJECT_DIR, \"logs/debug.log\"),  # 日志输出文件\n            'maxBytes': LOG_BACKUP_SIZE,  # 文件大小\n            'backupCount': 5,  # 备份份数\n            'formatter': 'standard',  # 使用哪种formatters日志格式\n        },\n        'error': {\n            'level': 'ERROR',\n            'class': LOGGER_CLASS,\n            'filename': os.path.join(PROJECT_DIR, \"logs/error.log\"),\n            'maxBytes': LOG_BACKUP_SIZE,\n            'backupCount': 5,\n            'formatter': 'standard',\n        },\n        'console': {\n            'level': 'DEBUG',\n            'class': 'logging.StreamHandler',\n            'formatter': 'standard'\n        },\n        'request_handler': {\n            'level': 'DEBUG',\n            'class': LOGGER_CLASS,\n            'filename': os.path.join(PROJECT_DIR, \"logs/request.log\"),\n            'maxBytes': LOG_BACKUP_SIZE,\n            'backupCount': 5,\n            'formatter': 'standard',\n        },\n    },\n    'loggers': {\n        'django': {\n            'handlers': ['request_handler'],\n            'level': 'INFO',\n            'propagate': True,\n        },\n        'server': {\n            'handlers': ['default', 'error'],\n            'level': \"INFO\",\n            'propagate': True\n        }\n    }\n}\n# DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'\n\n# grafana跳转使用\nX_FRAME_OPTIONS = 'SAMEORIGIN'\n\n# 发邮件使用,是否使用ssl，使用端口：465/994 不使用：25\nEMAIL_USE_SSL = True\n\n# 可定制化指标的服务及指标\nCUSTOM_THRESHOLD_SERVICES = {\n    \"kafka\": {\"kafka_consumergroup_lag\"}\n}\n\n# 备份相关配置\n# 可备份的组件\nBACKUP_DEFAULT_PATH = os.path.join(PROJECT_DIR, \"data/backup/\")\n\nSCAN_TOOL_LOCK_KEY = \"tool_package_verify\"\n\nINTERFACE_KINDS = {\"/api/appStore/upload/\": \"修改\",\n                   \"/api/appStore/remove/\": \"删除\",\n                   \"/api/appStore/publish/\": \"修改\",\n                   \"/api/appStore/executeLocalPackageScan/\": \"修改\",\n                   \"/api/appStore/deploymentPlanValidate/\": \"查看\",\n                   \"/api/appStore/deploymentPlanImport/\": \"增加\",\n                   \"/api/appStore/createInstallInfo/\": \"增加\",\n                   \"/api/appStore/executeInstall/\": \"增加\",\n                   \"/api/appStore/checkInstallInfo/\": \"查看\",\n                   \"/api/appStore/createServiceDistribution/\": \"增加\",\n                   \"/api/appStore/checkServiceDistribution/\": \"查看\",\n                   \"/api/appStore/createInstallPlan/\": \"新增\",\n                   \"/api/appStore/createComponentInstallInfo/\": \"新增\",\n                   \"/api/appStore/retryInstall/\": \"修改\",\n                   \"/api/backups/backupSettings/\": \"修改\",\n                   \"/api/backups/backupOnce/\": \"新增\",\n                   \"/api/backups/backupHistory/\": \"删除\",\n                   \"/api/backups/backupSendEmail/\": \"新增\",\n                   \"/api/hosts/hosts/\": \"修改\",\n                   \"/api/hosts/fields/\": \"查看\",\n                   \"/api/hosts/maintain/\": \"修改\",\n                   \"/api/hosts/restartHostAgent/\": \"修改\",\n                   \"/api/hosts/batchValidate/\": \"查看\",\n                   \"/api/hosts/batchImport/\": \"新增\",\n                   \"/api/hosts/hostInit/\": \"修改\",\n                   \"/api/hosts/hostsAgentStatus/\": \"查询\",\n                   \"/api/hosts/hostReinstall/\": \"修改\",\n                   \"/api/hosts/monitorReinstall/\": \"修改\",\n                   \"/api/inspection/history/\": \"查询\",\n                   \"/api/inspection/crontab/\": \"新增\",\n                   \"/api/inspection/inspectionSendEmailSetting/\": \"修改\",\n                   \"/api/inspection/inspectionSendEmail/\": \"查询\",\n                   \"/api/promemonitor/monitorurl/\": \"修改\",\n                   \"/api/promemonitor/updateAlert/\": \"修改\",\n                   \"/api/promemonitor/restartMonitorAgent/\": \"修改\",\n                   \"/api/promemonitor/globalMaintain/\": \"修改\",\n                   \"/api/promemonitor/receiveAlert/\": \"新增\",\n                   \"/api/promemonitor/updateSendEmailConfig/\": \"修改\",\n                   \"/api/promemonitor/updateSendAlertSetting/\": \"新增\",\n                   \"/api/promemonitor/hostThreshold/\": \"修改\",\n                   \"/api/promemonitor/serviceThreshold/\": \"修改\",\n                   \"/api/promemonitor/customThreshold/\": \"修改\",\n                   \"/api/upgrade/do-upgrade/\": \"修改\",\n                   \"/api/rollback/do-rollback/\": \"修改\",\n                   \"/api/services/action/\": \"修改\",\n                   \"/api/services/delete/\": \"查询\",\n                   \"/api/services/SelfHealingSetting/\": \"修改\",\n                   \"/api/services/UpdateSelfHealingHistory/\": \"修改\",\n                   \"/api/users/users/\": \"新增\",\n                   \"/api/users/updatePassword/\": \"修改\"\n                   }\n\n# for automated testing\nDATA_JSON_SECRET = \"Yunweiguanli@OMP_123\"\n"
  },
  {
    "path": "omp_server/omp_server/urls.py",
    "content": "\"\"\"omp_server URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/3.1/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nfrom django.contrib import admin\nfrom django.urls import path, include, re_path\n\n# coreAPI documentation\nfrom rest_framework.documentation import include_docs_urls\nfrom rest_framework.permissions import AllowAny\nfrom rest_framework.authentication import (\n    SessionAuthentication, BasicAuthentication\n)\n\nfrom users.urls import router as users_router\nfrom users.views import JwtAPIView\n\nfrom hosts.urls import router as hosts_router\nfrom app_store.urls import router as app_store_router\nfrom promemonitor.urls import router as promemonitor_router\nfrom promemonitor.grafana_views import grafana_proxy_view\nfrom inspection.urls import router as router_inspection\nfrom services.urls import urlpatterns as services_urlpatterns\nfrom backups.urls import router as backups_router\nfrom tool.urls import urlpatterns as tool_urlpatterns\nfrom utils.common.urls import router as common_router\nfrom service_upgrade.urls import upgrade_urlpatterns, rollback_urlpatterns\n\nurlpatterns_inside = [\n    path(\"login/\", JwtAPIView.as_view(), name=\"login\"),\n    path(\"users/\", include(users_router.urls), name=\"users\"),\n    path(\"hosts/\", include(hosts_router.urls), name=\"hosts\"),\n    path(\"promemonitor/\",\n         include(promemonitor_router.urls), name=\"promemonitor\"),\n    path(\"appStore/\", include(app_store_router.urls), name=\"appStore\"),\n    path('inspection/', include(router_inspection.urls), name=\"inspection\"),\n    path(\"services/\", include(services_urlpatterns), name=\"services\"),\n    path(\"backups/\", include(backups_router.urls), name=\"backups\"),\n    path(\"upgrade/\", include(upgrade_urlpatterns), name=\"upgrade\"),\n    path(\"rollback/\", include(rollback_urlpatterns), name=\"rollback\"),\n    path(\"tool/\", include(tool_urlpatterns), name=\"tool\"),\n    path(\"common/\", include(common_router.urls), name=\"common\"),\n]\n\nurlpatterns = [\n    path(\"admin/\", admin.site.urls),\n    path(\"api/\", include(urlpatterns_inside)),\n    path(\"docs/\", include_docs_urls(\n        title=\"API 接口文档\",\n        authentication_classes=(\n            SessionAuthentication, BasicAuthentication),\n        permission_classes=(AllowAny,),\n    ), name=\"docs\"),\n    re_path(r'^proxy/v1/grafana/(?P<path>.*)', grafana_proxy_view),\n]\n"
  },
  {
    "path": "omp_server/omp_server/wsgi.py",
    "content": "\"\"\"\nWSGI config for omp_server project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'omp_server.settings')\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "omp_server/promemonitor/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/promemonitor/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/promemonitor/alert_util.py",
    "content": "import datetime\nimport logging\nimport traceback\n\nimport pytz\nfrom omp_server.settings import TIME_ZONE\nfrom db_models.models import Host, Service, ApplicationHub\nfrom promemonitor.grafana_url import explain_url\n\nlogger = logging.getLogger('server')\n\n\ndef get_service_type(ip, service_name):\n    \"\"\"\n    获取服务的中文名称以及服务分类\n    :param ip: ip\n    :param service_name: 服务名称\n    :type service_name str\n    :return: product_cn_name, service_type\n    \"\"\"\n    product_cn_name = service_name\n    service_type = ip\n    return product_cn_name, service_type\n\n\ndef get_monitor_url(ele):\n    try:\n        monitor_url = explain_url(ele)[0].get('monitor_url')\n    except TypeError:\n        logger.error('get monitor url failed')\n        monitor_url = None\n    return monitor_url\n\n\ndef get_log_url(ele):\n    try:\n        monitor_log_url = explain_url(ele)[0].get('log_url')\n    except TypeError:\n        logger.error('get monitor log url failed')\n        monitor_log_url = None\n    return monitor_log_url\n\n\ndef utc_to_local(utc_time_str, utc_format='%Y-%m-%dT%H:%M:%SZ'):\n    \"\"\"\n    时区转换，如果转换报错，那么使用当前时间作为返回值\n    :type utc_time_str str\n    :param utc_time_str: utc时间字符串\n    :type utc_format str\n    :param utc_format: utc时间格式\n    :return:\n    \"\"\"\n    try:\n        utc_time_str = utc_time_str.split(\n            \".\")[0] + utc_time_str.split(\".\")[-1][-1]\n        local_tz = pytz.timezone(TIME_ZONE)\n        local_format = \"%Y-%m-%d %H:%M:%S\"\n        utc_dt = datetime.datetime.strptime(utc_time_str, utc_format)\n        local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)\n        time_str = local_dt.strftime(local_format)\n        return time_str\n    except Exception as e:\n        logger.error(f\"在转化时间格式时报错: {str(e)}\\n详情为: {traceback.format_exc()}\")\n        return datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n\n\nclass AlertAnalysis:\n\n    def __init__(self, item):\n        \"\"\"\n        解析prometheus返回的信息\n        :param item:\n            {\n              \"status\": \"resolved\",\n              \"labels\":\n                {\n                    \"alertname\":\"app state\",\n                    \"app\":\"nbdServer\",\n                    \"instance\":\"10.0.9.61:18215\",\n                    \"ip\":\"10.0.9.61\",\n                    \"job\":\"10.0.9.61\",\n                    \"severity\":\"critical\"\n                },\n              \"annotations\":\n                {\n                  \"consignee\":\"2871253303@qq.com\",\n                  \"description\":\"主机 10.0.12.35 中的 zookeeper_exporter 已经down掉超过一分钟.\",\n                  \"summary\":\"app state(instance 10.0.9.61:18215)\"\n                },\n              \"state\":\"firing\",\n              \"activeAt\":\"2021-04-10T08:36:23.838961588Z\",\n              \"value\":\"0e+00\",\n              \"fingerprint\": \"\" # 非\n            }\n        \"\"\"\n        self.item = item\n        self.labels = self.item.get(\"labels\", {})\n        self.annotations = self.item.get(\"annotations\", {})\n        self.fingerprint = self.item.get(\"fingerprint\", \"\")\n\n    @staticmethod\n    def _get(items, key):\n        return items.get(key, \"DEFAULT_DATA\")\n\n    # @property\n    # def is_resolved(self):\n    #     return self.item.get(\"status\") == 'resolved'\n\n    # @property\n    # def is_alert(self):\n    #     return self._get(self.labels, \"severity\") in [\"critical\", \"warning\"]\n\n    def node_exporter(self):\n        return dict(\n            alert_type=\"host\",\n            alert_host_ip=self._get(self.labels, \"instance\"),\n            alert_service_name=\"\",\n            alert_service_type=\"\",\n            alert_service_en_type=\"\"\n        )\n\n    def exporter(self):\n        alert_host_ip = self._get(self.labels, \"instance\")\n        alert_service = self._get(self.labels, \"job\")\n        alert_service_name = alert_service.replace(\"Exporter\", \"\").strip()\n        alert_service_type, alert_service_en_type = get_service_type(\n            alert_host_ip, alert_service_name)\n        app_name_str = self._get(self.labels, \"app\") if self._get(self.labels, \"app\") else \\\n            self._get(self.labels, \"job\").split(\"Exporter\")[0]\n        if not app_name_str:\n            _alert_type = \"service\"\n        component_list = Service.objects.filter(\n            service__app_type=ApplicationHub.APP_TYPE_COMPONENT).filter(\n            service__is_base_env=False)\n        if list(filter(\n            lambda x: x.service.app_name == app_name_str, list(\n                component_list))):\n            _alert_type = \"component\"\n        else:\n            _alert_type = \"service\"\n        return dict(\n            alert_type=_alert_type,\n            alert_host_ip=self._get(self.labels, \"instance\"),\n            alert_service_name=alert_service_name,\n            alert_service_type=alert_service_type,\n            alert_service_en_type=alert_service_en_type\n        )\n\n    # def other(self):\n    #     alert_host_ip = self._get(self.labels, \"ip\")\n    #     alert_service_name = self._get(self.labels, \"app\").strip()\n    #     alert_service_type, alert_service_en_type = get_service_type(\n    #         alert_host_ip, alert_service_name)\n    #     return dict(\n    #         alert_type=\"service\",\n    #         alert_host_ip=alert_host_ip,\n    #         alert_service_name=alert_service_name,\n    #         alert_service_type=alert_service_type,\n    #         alert_service_en_type=alert_service_en_type\n    #     )\n\n    def get_alert_time(self):\n        start_time = self.item.get(\"startsAt\", \"\")\n        if not start_time:\n            start_time = self.item.get(\"activeAt\", \"\")\n        return utc_to_local(utc_time_str=start_time)\n\n    def analysis_labels(self):\n        old_alert_type = self._get(self.labels, \"job\")\n        if old_alert_type == \"nodeExporter\":\n            kwargs = self.node_exporter()\n        elif \"Exporter\" in old_alert_type:\n            kwargs = self.exporter()\n        else:\n            # kwargs = self.other()\n            kwargs = self.exporter()  # TODO 有other场景出现再换\n        kwargs[\"status\"] = self.item.get(\"status\", \"firing\")\n        kwargs[\"alert_level\"] = self._get(self.labels, \"severity\")\n        kwargs[\"alertname\"] = self._get(self.labels, \"alertname\")\n        kwargs[\"fingerprint\"] = self.fingerprint\n        kwargs.update(alert_time=self.get_alert_time())\n        if kwargs[\"alert_type\"] == \"service\" or kwargs[\"alert_type\"] == \"component\":\n            kwargs[\"monitor\"] = get_monitor_url(\n                [{\n                    \"ip\": kwargs.get(\"alert_host_ip\"),\n                    \"type\": \"service\",\n                    \"instance_name\": kwargs.get(\"alert_service_name\", \"\")\n                }]\n            )\n            kwargs[\"monitor_log\"] = get_log_url(\n                [{\n                    \"ip\": kwargs.get(\"alert_host_ip\"),\n                    \"type\": \"service\",\n                    \"instance_name\": kwargs.get(\"alert_service_name\")\n                }]\n            )\n        else:\n            kwargs[\"monitor\"] = get_monitor_url(\n                [{\n                    \"ip\": kwargs.get(\"alert_host_ip\"),\n                    \"type\": \"host\",\n                    \"instance_name\": \"node\"\n                }]\n            )\n            kwargs[\"monitor_log\"] = get_log_url(\n                [{\n                    \"ip\": kwargs.get(\"alert_host_ip\"),\n                    \"type\": \"host\",\n                    \"instance_name\": \"node\"\n                }]\n            )\n        return kwargs\n\n    def analysis_annotations(self):\n        return dict(\n            alert_receiver=self._get(self.annotations, \"consignee\"),\n            #  服务down：主机 10.0.12.35 中的 zookeeper 已经down掉超过一分钟.\n            #  服务exporter down：\n            #  主机 10.0.12.35 中的 zookeeper_exporter 已经down掉超过一分钟.\n            alert_describe=self._get(self.annotations, \"description\")\n        )\n\n    def __call__(self, env_id=1, *args, **kwargs):\n        \"\"\"\n        :param env_id:\n        :param args:\n        :param kwargs:\n        :return: {\n            \"alert_type\": 告警类型 host or service,\n            \"alert_host_ip\": 告警来自哪个主机,\n            \"alert_host_system\": 告警来自主机的系统,\n            \"alert_service_name\": 告警服务名称,\n            \"alert_service_type\": 告警服务所属产品名称,\n            \"alert_service_en_type\": 告警服务类型，self_dev component database,\n            \"alert_level\": 告警级别,\n            \"alert_describe\": 告警描述,\n            \"alert_receiver\": 告警接收人,\n            \"alert_resolve\": 告警解决方案,\n            \"alert_time\": 告警发生时间,\n            \"monitor\": 跳转grafana地址,\n            \"monitor_log\": 日志跳转url（仅服务存在）,\n            \"fingerprint\": 告警对应的唯一标识,\n            \"status\": 告警状态： resolved，恢复；firing：告警 # 推送使用，其他无用,\n            \"alertname\": 告警指标： # 推送使用，其他无用,\n        }\n        \"\"\"\n        # if not self.is_alert:\n        #     return {}\n        alert_info = self.analysis_labels()\n        if alert_info[\"alert_type\"] == \"host\":\n            host = Host.objects.filter(\n                ip=alert_info[\"alert_host_ip\"]).first()\n            if not host:\n                return {}\n            alert_info[\"env_id\"] = host.env_id\n            alert_info[\"alert_instance_name\"] = host.instance_name\n        else:\n            ser = Service.objects.filter(\n                service__app_name=alert_info[\"alert_service_name\"],\n                ip=alert_info[\"alert_host_ip\"]\n            ).first()\n            # host = Host.objects.filter(\n            #     ip=alert_info[\"alert_host_ip\"]).first()\n            if not ser:\n                return {}\n            alert_info[\"env_id\"] = ser.env_id\n            alert_info[\"alert_instance_name\"] = ser.service_instance_name\n        alert_info.update(**self.analysis_annotations())\n        # if env_id and int(env_id) != alert_info[\"env_id\"]:\n        #     return {}  # TODO 等待env开发完成\n        return alert_info\n"
  },
  {
    "path": "omp_server/promemonitor/alertmanager.py",
    "content": "import json\nimport logging\nfrom datetime import datetime, timedelta\nfrom db_models.models import MonitorUrl\nfrom utils.parse_config import MONITOR_PORT, PROMETHEUS_AUTH\nfrom db_models.models import Maintain\n\nimport pytz\nimport requests\n\nlogger = logging.getLogger('server')\n\n\nclass Alertmanager:\n    \"\"\"\n    定义alertmanager的参数及动作\n    \"\"\"\n\n    def __init__(self):\n        self.basic_url = self.get_alertmanager_config()\n        self.basic_auth = (PROMETHEUS_AUTH.get(\"username\", \"omp\"), PROMETHEUS_AUTH.get(\"plaintext_password\", \"\"))\n        self.headers = {'Content-Type': 'application/json'}\n        self.add_url = f'http://{self.basic_url}/api/v1/silences'  # NOQA\n        self.delete_url = f'http://{self.basic_url}/api/v1/silence'  # NOQA\n        self.select_url = f'http://{self.basic_url}/api/v1/silence'  # NOQA\n\n    @staticmethod\n    def get_alertmanager_config():\n        alertmanager_url_config = MonitorUrl.objects.filter(\n            name='alertmanager').first()\n        if not alertmanager_url_config:\n            # 默认值\n            return f'127.0.0.1:{MONITOR_PORT.get(\"alertmanager\", 19013)}'\n\n        monitor_url = alertmanager_url_config.monitor_url\n        if monitor_url:\n            return monitor_url\n        return f'127.0.0.1:{MONITOR_PORT.get(\"alertmanager\", 19013)}'  # 默认值\n\n    @staticmethod\n    def format_time(_time):\n        \"\"\"\n        时区转换\n        \"\"\"\n        if not _time:\n            return (datetime.now(tz=pytz.UTC)).strftime(\n                \"%Y-%m-%dT%H:%M:%SZ\")\n        if isinstance(_time, datetime):\n            return _time.astimezone(tz=pytz.UTC).strftime(\n                \"%Y-%m-%dT%H:%M:%SZ\")\n        return None\n\n    def add_setting(self, value, name=\"env\", start_time=None, ends_time=None):\n        \"\"\"\n        设置维护模式\n        :param value: 值 instance 对应ip， env对应env_name\n        :param name: 值的key：instance, env\n        :param start_time: startsAt：type: datetime\n        :param ends_time: endsAt：type: datetime\n        :return: 成功返回： \"25b1ea3e-73db-43cd-ae81-a397f9e1bc88\" (silenceId)\n                失败：None\n        \"\"\"\n        start_time_str = self.format_time(start_time)\n        if not start_time_str:\n            return None\n        if not ends_time:\n            ends_time = datetime.now() + timedelta(days=30)\n        ends_time_str = self.format_time(ends_time)\n        if not ends_time_str:\n            return None\n        data = {\n            \"matchers\": [\n                {\"name\": name, \"value\": value}\n            ],\n            \"startsAt\": start_time_str,\n            \"endsAt\": ends_time_str,\n            \"createdBy\": \"api\",\n            \"comment\": \"Silence\",\n            \"status\": {\"state\": \"active\"}\n        }\n        try:\n            logger.info(data)\n            resp = requests.post(\n                self.add_url, data=json.dumps(data),\n                headers=self.headers, timeout=5, auth=self.basic_auth\n            ).json()\n            if resp.get(\"status\") == \"success\":\n                logger.info(resp)\n                return resp.get(\"data\").get(\"silenceId\", None)\n        except Exception as e:\n            logger.error(str(e))\n        return None\n\n    def delete_setting(self, silence_id):\n        \"\"\"\n        删除告警屏蔽规则\n        :param silence_id: 规则id\n        :return: 成功 True， 失败 False\n        \"\"\"\n        try:\n            resp = requests.delete(\n                f\"{self.delete_url}/{silence_id}\", timeout=5, auth=self.basic_auth).json()\n        except Exception as e:\n            logger.error(str(e))\n            return False\n        logger.info(resp)\n        if resp.get(\"status\") != \"success\":\n            logger.error(resp.get(\"error\"))\n            return True\n        return False\n\n    def set_maintain_by_host_list(self, host_list):\n        \"\"\"\n        将单个/多个主机设置为维护状态\n        \"\"\"\n        maintain_list = list()\n        maintain_id_list = list()\n        for item in host_list:\n            maintain_id = self.add_setting(\n                value=item.get('ip'), name='instance')\n            if not maintain_id:\n                logger.error(f'设置主机{item.get(\"ip\")}维护失败!')\n                return None\n            maintain = Maintain(matcher_name='instance',\n                                matcher_value=item.get('ip'), maintain_id=maintain_id)\n            maintain_list.append(maintain)\n            maintain_id_list.append(maintain_id)\n        Maintain.objects.bulk_create(maintain_list)\n        return maintain_id_list\n\n    def set_maintain_by_env_name(self, env_name):\n        \"\"\"\n        将指定env的主机设置为维护状态\n        \"\"\"\n        maintain_id = self.add_setting(value=env_name, name='env')\n        if not maintain_id:\n            return None\n        Maintain.objects.create(matcher_name='env',\n                                matcher_value=env_name, maintain_id=maintain_id)\n        return maintain_id\n\n    def revoke_maintain_by_host_list(self, host_list):\n        for item in host_list:\n            maintain = Maintain.objects.filter(\n                matcher_name='instance', matcher_value=item.get('ip')).first()\n            if not maintain:\n                return False\n            maintain_id = maintain.maintain_id\n            delete_setting_result = self.delete_setting(maintain_id)\n            if not delete_setting_result:\n                try:\n                    resp = requests.get(\n                        f\"{self.select_url}/{maintain_id}\", timeout=5, auth=self.basic_auth).json()\n                    if resp.get(\"status\") == \"success\" and resp.get(\"data\").get(\"status\").get(\"state\") == \"expired\":\n                        Maintain.objects.filter(\n                            maintain_id=maintain_id).delete()\n                        return True\n                except Exception as e:\n                    logger.error(str(e))\n                return False\n            Maintain.objects.filter(maintain_id=maintain_id).delete()\n        return True\n\n    def revoke_maintain_by_env_name(self, env_name):\n        maintain = Maintain.objects.filter(\n            matcher_name='env', matcher_value=env_name).first()\n        if not maintain:\n            return False\n        maintain_id = maintain.maintain_id\n        self.delete_setting(maintain_id)\n        Maintain.objects.filter(maintain_id=maintain_id).delete()\n\n        return True\n"
  },
  {
    "path": "omp_server/promemonitor/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass PromemonitorConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'promemonitor'\n"
  },
  {
    "path": "omp_server/promemonitor/custom_script_serializers.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author:' Lingyang.guo'\n# CreateDate: 14:26\nimport json\n\nfrom rest_framework import serializers\nfrom rest_framework.serializers import ModelSerializer\n\nfrom db_models.models.custom_metric import CustomScript\n\n\nclass CustomScriptSerializer(ModelSerializer):\n\n    bound_hosts_num = serializers.SerializerMethodField()\n\n    class Meta:\n        model = CustomScript\n        fields = \"__all__\"\n\n    def get_bound_hosts_num(self, obj):  # NOQA\n        bound_hosts = obj.bound_hosts\n        if isinstance(bound_hosts, str):\n            bound_hosts_num = len(json.loads(bound_hosts))\n        else:\n            bound_hosts_num = len(bound_hosts)\n        return bound_hosts_num\n"
  },
  {
    "path": "omp_server/promemonitor/custom_script_views.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author:' Lingyang.guo'\n# CreateDate: 14:08\nimport json\nimport logging\nimport os.path\nimport traceback\n\nimport requests\nimport yaml\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom rest_framework.filters import OrderingFilter\n\nfrom rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin\nfrom rest_framework.serializers import Serializer\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.response import Response\n\n# from db_models.models import UploadFileHistory\nfrom db_models.models.custom_metric import CustomScript\nfrom promemonitor.custom_script_serializers import CustomScriptSerializer\nfrom promemonitor.promemonitor_filters import CustomScriptFilter\nfrom utils.common.paginations import PageNumberPager\nfrom promemonitor.prometheus_utils import PrometheusUtils\nfrom utils.parse_config import MONITOR_PORT\nfrom promemonitor.prometheus_utils import CW_TOKEN\n\nlogger = logging.getLogger('server')\n\n\nclass CustomScriptViewSet(GenericViewSet, ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin):\n    \"\"\"\n    list:\n    查询自定义脚本列表\n\n    create:\n    新增自定义脚本记录\n\n    update:\n    更新自定义脚本模型字段\n\n    delete:\n    删除指定自定义脚本\n    \"\"\"\n    get_description = \"读取自定义脚本记录\"\n    post_description = \"更新自定义脚本记录\"\n    serializer_class = CustomScriptSerializer\n    pagination_class = PageNumberPager\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    queryset = CustomScript.objects.all().order_by(\"-created\")\n    filter_class = CustomScriptFilter\n\n    prometheus_util = PrometheusUtils()\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取自定义脚本列表信息\n        \"\"\"\n        queryset = self.filter_queryset(self.get_queryset())  # 分页，过滤，排序\n\n        page = self.paginate_queryset(queryset)\n        if page is not None:\n            serializer = self.get_serializer(page, many=True)\n            return self.get_paginated_response(serializer.data)\n\n        serializer = self.get_serializer(queryset, many=True)\n        return Response(serializer.data)\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        新增一条自定义脚本记录\n        \"\"\"\n\n        file_obj = request.FILES.get('file')\n\n        script_name = file_obj.name\n        if CustomScript.objects.filter(script_name=script_name).exists():\n            return Response(data={\"code\": 1, \"message\": \"该脚本已存在，请重新上传！\"})\n        scrape_interval = request.data.get(\"scrape_interval\", 60)\n        enabled = request.data.get(\"enabled\", 1)\n\n        script_content = file_obj.read()\n        script_content = str(script_content, encoding=\"utf8\")\n        metrics = list()\n        for line in script_content.split(\"\\n\"):\n            if \"def get_\" in line and \":\" in line:\n                metric = line.split(\"def get_\")[1].split(\"(\")[0].strip()\n                metrics.append(metric)\n        metric_num = len(metrics)\n        description = script_content.split(\"\\n\")[0]\n\n        try:\n            custom_script = CustomScript(\n                script_name=script_name,\n                script_content=script_content,\n                metrics=metrics,\n                metric_num=metric_num,\n                scrape_interval=scrape_interval,\n                enabled=enabled,\n                description=description,\n                bound_hosts=[]\n            )\n            custom_script.save()\n            # UploadFileHistory.location(file=file_obj, module_obj=custom_script, user=request.user)\n            script_job_str = script_name.split('.', 1)[0]\n            prom_job_dict = {\n                \"job_name\": f\"{script_job_str}Exporter\",\n                \"metrics_path\": f\"/metrics/monitor/{script_job_str}\",\n                \"file_sd_configs\": [\n                    {\n                        \"refresh_interval\": f\"{scrape_interval}s\",\n                        \"files\": [\n                            f\"targets/{script_job_str}Exporter_all.json\"\n                        ]\n                    }\n                ]\n            }\n            with open(self.prometheus_util.prometheus_conf_path, \"r\") as fr:\n                content = yaml.load(fr.read(), yaml.Loader)\n            content.get(\"scrape_configs\").append(prom_job_dict)\n            with open(self.prometheus_util.prometheus_conf_path, \"w\", encoding=\"utf8\") as fw:\n                yaml.dump(data=content, stream=fw,\n                          allow_unicode=True, sort_keys=False)\n            job_target_json = os.path.join(self.prometheus_util.prometheus_targets_path,\n                                           f\"{script_job_str}Exporter_all.json\")\n            if not os.path.exists(job_target_json):\n                with open(job_target_json, \"w\") as target_fw:\n                    json.dump([], target_fw)\n            reload_prometheus_url = \"http://localhost:19011/-/reload\"\n            requests.post(reload_prometheus_url,\n                          auth=self.prometheus_util.basic_auth)\n            return Response({})\n        except Exception as e:\n            logger.error(traceback.format_exc(e))\n            return Response(data={\"code\": 1, \"message\": \"新增自定义脚本失败！\"})\n\n    def update(self, request, *args, **kwargs):\n        \"\"\"\n        只可以更改绑定主机列表，启用状态和探测周期\n        \"\"\"\n        instance = self.get_object()\n        new_scrape_interval = request.data.get(\"scrape_interval\")\n        new_enabled = request.data.get(\"enabled\", 1)\n        new_bound_host_list = request.data.get(\"bound_hosts\")\n        new_description = request.data.get(\"description\", instance.description)\n\n        add_host_list = set(new_bound_host_list) - set(instance.bound_hosts)\n        deleted_host_list = set(instance.bound_hosts) - \\\n            set(new_bound_host_list)\n\n        instance.scrape_interval = new_scrape_interval\n        instance.enabled = new_enabled\n        instance.bound_hosts = new_bound_host_list\n        instance.description = new_description\n        instance.save()\n\n        headers = {\"Content-Type\": \"application/json\"}\n        headers.update(CW_TOKEN)\n\n        try:\n            script_job_str = instance.script_name.split('.', 1)[0]\n            prom_target_list = list()\n            monitor_agent_port = MONITOR_PORT.get('monitorAgent', 19031)\n            for item in (add_host_list | deleted_host_list):\n                if item in add_host_list:\n                    agent_custom_script_url = f\"http://{item}:{monitor_agent_port}/update/custom_scripts/add\"  # NOQA\n                elif item in deleted_host_list:\n                    agent_custom_script_url = f\"http://{item}:{monitor_agent_port}/update/custom_scripts/delete\"  # NOQA\n                else:\n                    continue\n                payload = {\n                    \"custom_scripts\":\n                        [{\n                            \"script_name\": script_job_str,\n                            \"script_content\": instance.script_content,\n                            \"script_metrics\": instance.metrics\n                        }]\n                }\n                payload = json.dumps(payload)\n                res = requests.post(\n                    url=agent_custom_script_url, headers=headers, data=payload)\n                if res.status_code != 200:\n                    logger.error(f\"向主机{item}agent发送添加自定义脚本信息失败！\")\n                    return Response(data={\"code\": 1, \"message\": f\"向主机{item}agent发送添加自定义脚本信息失败！\"})\n\n            for host in instance.bound_hosts:\n                prom_target_list.append({\n                    \"targets\": [\n                        f\"{host}:{monitor_agent_port}\"\n                    ],\n                    \"labels\": {\n                        \"instance\": f\"{host}\",\n                        \"service_type\": \"service\",\n                        \"env\": \"default\"\n                    }\n                })\n\n            job_target_json = os.path.join(self.prometheus_util.prometheus_targets_path,\n                                           f\"{script_job_str}Exporter_all.json\")\n            with open(job_target_json, \"w\") as jtj_fw:\n                json.dump(prom_target_list, jtj_fw, indent=4)\n        except Exception as e:\n            logger.error(traceback.format_exc(e))\n            logger.error(\"向agent发送添加自定义脚本信息失败！\")\n            return Response(data={\"code\": 1, \"message\": \"添加自定义脚本信息失败！\"})\n\n        return Response()\n\n    def destroy(self, request, *args, **kwargs):\n        \"\"\"\n        向agent发送删除该自定义脚本信息；删除prometheus任务；删除库记录\n        \"\"\"\n        instance = self.get_object()\n        script_job_str = instance.script_name.split('.', 1)[0]\n        with open(self.prometheus_util.prometheus_conf_path, \"r\") as fr:\n            content = yaml.load(fr.read(), yaml.Loader)\n        for i in content.get(\"scrape_configs\"):\n            if i.get(\"job_name\") == f\"{script_job_str}Exporter\":\n                content.get(\"scrape_configs\").remove(i)\n        with open(self.prometheus_util.prometheus_conf_path, \"w\", encoding=\"utf8\") as fw:\n            yaml.dump(data=content, stream=fw,\n                      allow_unicode=True, sort_keys=False)\n        job_target_json = os.path.join(self.prometheus_util.prometheus_targets_path,\n                                       f\"{script_job_str}Exporter_all.json\")\n\n        if os.path.exists(job_target_json):\n            os.remove(job_target_json)\n        if len(instance.bound_hosts) == 0:\n            instance.delete()\n            return Response({})\n        monitor_agent_port = MONITOR_PORT.get('monitorAgent', 19031)\n        headers = {\"Content-Type\": \"application/json\"}\n        headers.update(CW_TOKEN)\n        for host in instance.bound_hosts:\n            agent_delete_custom_script_url = f\"http://{host}:{monitor_agent_port}/update/custom_scripts/delete\"  # NOQA\n            payload = {\n                \"custom_scripts\":\n                    [{\n                        \"script_name\": script_job_str\n                    }]\n            }\n            payload = json.dumps(payload)\n            res = requests.post(\n                url=agent_delete_custom_script_url, headers=headers, data=payload)\n            if res.status_code != 200:\n                logger.error(f\"向主机{host}agent发送删除自定义脚本信息失败！\")\n                return Response(data={\"code\": 1, \"message\": f\"向主机{host}agent发送删除自定义脚本信息失败！\"})\n        instance.delete()\n        return Response({})\n\n\nclass CustomScriptJobInfoView(GenericViewSet, ListModelMixin):\n    get_description = \"读取自定义脚本任务信息\"\n    # queryset = CustomScript.objects.all().order_by(\"-created\")\n    serializer_class = Serializer\n\n    prometheus_util = PrometheusUtils()\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        读取自定义脚本任务信息\n        \"\"\"\n        cs_id = request.query_params.get(\"id\")\n        instance = CustomScript.objects.get(id=cs_id)\n        script_job_str = instance.script_name.split('.', 1)[0]\n        job_str = f\"{script_job_str}Exporter\"\n        prometheus_targets_url = f\"http://127.0.0.1:{MONITOR_PORT.get('prometheus', '19011')}/api/v1/targets\"\n        try:\n            res = requests.get(url=prometheus_targets_url,\n                               auth=self.prometheus_util.basic_auth)\n            if res.status_code != 200:\n                return Response(data={\"code\": 1, \"message\": \"获取自定义脚本任务信息失败！\"})\n            active_targets_list = res.json().get(\"data\").get(\"activeTargets\")\n            custom_script_job_list = list()\n            for active_target in active_targets_list:\n                if active_target.get(\"scrapePool\") == job_str:\n                    custom_script_job_info = {\n                        \"scrape_url\": active_target.get(\"scrapeUrl\"),\n                        \"status\": active_target.get(\"health\"),\n                        \"last_scrape_duration\": active_target.get(\"lastScrapeDuration\"),\n                        \"last_error\": active_target.get(\"lastError\")\n                    }\n                    custom_script_job_list.append(custom_script_job_info)\n            return Response(custom_script_job_list)\n            # return Response(data={\"code\": 1, \"message\": \"获取自定义脚本任务信息失败！\"})\n        except Exception as e:\n            logger.error(traceback.format_exc(e))\n            return Response(data={\"code\": 1, \"message\": \"获取自定义脚本任务信息失败！\"})\n"
  },
  {
    "path": "omp_server/promemonitor/grafana_url.py",
    "content": "from db_models.models import GrafanaMainPage, MonitorUrl, Host, ApplicationHub, Service\nimport requests\nimport json\nimport logging\nimport pytz\nimport datetime\nimport traceback\nfrom omp_server.settings import TIME_ZONE\nfrom utils.parse_config import PROMETHEUS_AUTH\n\nlogger = logging.getLogger('server')\n\n\nclass CurlPrometheus(object):\n    @staticmethod\n    def curl_prometheus():\n        \"\"\"\n          请求prometheus接口返回相应json\n        \"\"\"\n        prometheus_auth = (PROMETHEUS_AUTH.get(\"username\"),\n                           PROMETHEUS_AUTH.get(\"plaintext_password\"))\n        monitor_ip = MonitorUrl.objects.filter(name=\"prometheus\")\n        monitor_url = monitor_ip[0].monitor_url if len(\n            monitor_ip) else \"127.0.0.1:19013\"\n        try:\n            url = f\"http://{monitor_url}/api/v1/alerts\"\n            response = requests.request(\n                \"GET\", url, headers={}, data=\"\", auth=prometheus_auth)\n            return json.loads(response.text)\n        except Exception as e:\n            logger.error(\"prometheus请求alerts失败：\" + str(e))\n            return {\"status\": \"-1\"}\n\n\ndef utc_local(utc_time_str, utc_format='%Y-%m-%dT%H:%M:%SZ'):\n    \"\"\"\n    时区转换，如果转换报错，那么使用当前时间作为返回值\n    :type utc_time_str str\n    :param utc_time_str: utc时间字符串\n    :type utc_format str\n    :param utc_format: utc时间格式\n    :return:\n    \"\"\"\n    try:\n        utc_time_str = utc_time_str.split(\n            \".\")[0] + utc_time_str.split(\".\")[-1][-1]\n        local_tz = pytz.timezone(TIME_ZONE)\n        local_format = \"%Y-%m-%d %H:%M:%S\"\n        utc_dt = datetime.datetime.strptime(utc_time_str, utc_format)\n        local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)\n        time_str = local_dt.strftime(local_format)\n        return time_str\n    except Exception as e:\n        logger.error(f\"在转化时间格式时报错: {str(e)}\\n详情为: {traceback.format_exc()}\")\n        return datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n\n\ndef get_item_type(item, database_list, service_list, component_list):\n    \"\"\"\n    获取item类型\n    \"\"\"\n    app_name_str = \"\"\n    if item.get(\"labels\").get(\"job\") == \"nodeExporter\":\n        return \"host\"\n    app_name_str = item.get(\"labels\").get(\"app\") if item.get(\"labels\").get(\"app\") else \\\n        item.get(\"labels\").get(\"job\", \"\").split(\"Exporter\")[0]\n    if not app_name_str:\n        return \"service\"\n    if list(filter(\n            lambda x: x.service.app_name == app_name_str,\n            list(database_list))):\n        return \"database\"\n    elif list(filter(\n            lambda x: x.service.app_name == app_name_str, list(\n                service_list)\n    )):\n        return \"service\"\n    elif list(filter(\n            lambda x: x.service.app_name == app_name_str, list(\n                component_list)\n    )):\n        return \"component\"\n    else:\n        return \"service\"\n\n\ndef explain_prometheus(params):\n    \"\"\"\n      生成前端异常清单所需要的json列表\n    \"\"\"\n    ignore_status_list = [Service.SERVICE_STATUS_NORMAL,\n                          Service.SERVICE_STATUS_STARTING,\n                          Service.SERVICE_STATUS_STOPPING,\n                          Service.SERVICE_STATUS_RESTARTING,\n                          Service.SERVICE_STATUS_STOP]\n    host_list = Host.objects.values('ip', 'instance_name')\n    host_ip_list = [host.get(\"ip\") for host in host_list]\n    database_list = Service.objects.filter(\n        service__app_type=ApplicationHub.APP_TYPE_COMPONENT).filter(\n        service__app_labels__label_name__contains=\"数据库\").filter(\n        service_status__in=ignore_status_list).filter(\n        service__is_base_env=False)\n    service_list = Service.objects.filter(\n        service__app_type=ApplicationHub.APP_TYPE_SERVICE).filter(\n        service_status__in=ignore_status_list).filter(\n        service__is_base_env=False).filter(\n        service_controllers__start__isnull=False)\n    component_list = Service.objects.filter(\n        service__app_type=ApplicationHub.APP_TYPE_COMPONENT).filter(\n        service_status__in=ignore_status_list).filter(\n        service__is_base_env=False)\n    r = CurlPrometheus.curl_prometheus()\n    if r.get('status') == 'success':\n        prometheus_info = []\n        compare_list = []\n        alerts = r.get('data', {}).get('alerts')\n        prometheus_alerts = sorted(\n            alerts, key=lambda e: e.get('labels').__getitem__('severity'), reverse=False)\n        for lab in prometheus_alerts:\n            if lab.get('status') == 'resolved':\n                continue\n            label = lab.get('labels')\n            if label.get(\"instance\", \"\") not in host_ip_list:\n                continue\n            tmp_dict = {}\n            tmp_list = [label.get('alertname'), label.get(\n                'instance_name'), label.get('job')]\n            if tmp_list in compare_list:\n                continue\n            compare_list.append(tmp_list)\n            _type = get_item_type(item=lab, database_list=database_list, service_list=service_list,\n                                  component_list=component_list)\n            tmp_dict['type'] = _type\n            _ip = label.get('instance').split(\":\")[0] if label.get('instance') else ''\n            tmp_dict['ip'] = _ip\n            s_app = label.get('app') if label.get(\n                'app') else label.get(\"job\", \"\").split(\"Exporter\")[0]\n            tmp_dict['instance_name'] = f\"{s_app}-{_ip.split('.')[2]}-{_ip.split('.')[3]}\" if _type != \"host\" else s_app\n            tmp_dict['severity'] = label.get('severity')\n            annotation = lab.get('annotations')\n            tmp_dict['description'] = annotation.get('description')\n            lab_date = lab.get('activeAt') if lab.get(\n                'activeAt') else lab.get('startsAt')\n            tmp_dict['date'] = utc_local(lab_date)\n            prometheus_info.append(tmp_dict)\n        prometheus_json = explain_url(prometheus_info)\n        if params:\n            prometheus_json = explain_filter(prometheus_json, params)\n        return prometheus_json\n    else:\n        logger.error(\"prometheus请求alerts失败：\" + str(r))\n        return \"error\"\n\n\ndef explain_filter(prometheus_json, params):\n    \"\"\"\n       递归筛选\n    \"\"\"\n    if not params:\n        return prometheus_json\n    fil_filed = params.popitem()\n    fil_info = []\n    for j in prometheus_json:\n        value = j.get(fil_filed[0])\n        if value and fil_filed[1].lower() in value.lower():\n            fil_info.append(j)\n        # TODO 组件集合包含数据库\n        if fil_filed[1].lower() == \"component\" and value.lower() == \"database\":\n            fil_info.append(j)\n    return explain_filter(fil_info, params)\n\n\ndef explain_url(explain_info, is_service=None, is_host=None):\n    \"\"\"\n    封装dict添加grafana的url\n    \"\"\"\n    # monitor_ip = MonitorUrl.objects.filter(name=\"grafana\")\n    # grafana_ins = monitor_ip[0].monitor_url if len(\n    #     monitor_ip) else \"127.0.0.1:19013\"\n    # grafana_url = f\"http://{grafana_ins}\"\n    # 去掉跳转grafana中携带的ip、port\n    grafana_url = \"\"\n    url_dict = {}\n    for i in GrafanaMainPage.objects.all():\n        url_dict[i.instance_name] = i.instance_url\n    for instance_info in explain_info:\n        # TODO 待确认 跳转服务面板使用 app_name 而不是 instance_name ?\n        if is_service:\n            service_name = instance_info.get('app_name')\n        else:\n            service_name = instance_info.get('instance_name', '').split(\"-\")[0]\n        if instance_info.get('is_web'):\n            instance_info['monitor_url'] = None\n            instance_info['log_url'] = None\n            continue\n        service_ip = instance_info.get('ip')\n        if instance_info.get('type') != 'host' and not is_host \\\n                or is_service:\n            service_instance_name = instance_info.get('service_instance_name')\n\n            monitor_url = url_dict.get(service_name.lower() if isinstance(service_name, str) else service_name)\n            instance_info['cluster_url'] = \"\"\n            if monitor_url:\n                instance_info['monitor_url'] = grafana_url + \\\n                                               monitor_url + f\"?var-instance={service_ip}&kiosk=tv\"\n                if Service.objects.filter(\n                        service_instance_name=service_instance_name).first() and Service.objects.filter(\n                    service_instance_name=service_instance_name).first().cluster:\n                    cluster_name = Service.objects.filter(\n                        service_instance_name=service_instance_name).first().cluster.cluster_name\n                    cluster_monitor_url = url_dict.get(f\"{service_name}cluster\", \"\")\n                    instance_info[\n                        'cluster_url'] = cluster_monitor_url + f\"?var-cluster={cluster_name}&kiosk=tv\" if cluster_monitor_url else \"\"\n            else:\n                try:\n                    if service_name and ApplicationHub.objects.filter(\n                            app_name=service_name\n                    ).first() and ApplicationHub.objects.filter(\n                        app_name=service_name\n                    ).first().app_monitor and ApplicationHub.objects.filter(\n                        app_name=service_name\n                    ).first().app_monitor.get(\"type\") == \"JavaSpringBoot\":\n                        instance_info['monitor_url'] = grafana_url + url_dict.get(\n                            'javaspringboot',\n                            'nojavaspringboot') + \\\n                                                       f\"?var-env=default&var-ip={service_ip}\" \\\n                                                       f\"&var-app={service_name}&var-job={service_name}Exporter&kiosk=tv\"\n                    else:\n                        instance_info['monitor_url'] = grafana_url + url_dict.get(\n                            'service', 'noservice') + f\"?var-ip={service_ip}&var-app={service_name}&kiosk=tv\"\n                except Exception as e:\n                    logger.error(e)\n                    instance_info['monitor_url'] = grafana_url + url_dict.get(\n                        'service', 'noservice') + f\"?var-ip={service_ip}&var-app={service_name}&kiosk=tv\"\n            instance_info['log_url'] = grafana_url + \\\n                                       url_dict.get(\n                                           'log',\n                                           'nolog') + f\"?var-env=default&var-app={service_name}\" + f\"&var-instance={service_instance_name}\"\n        else:\n            instance_info['monitor_url'] = grafana_url + \\\n                                           url_dict.get('node', 'nohosts') + \\\n                                           f\"?var-node={service_ip}&kiosk=tv\"\n            instance_info['log_url'] = None\n    return explain_info\n"
  },
  {
    "path": "omp_server/promemonitor/grafana_views.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: grafana_views\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-12 10:27\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n跳转grafana页面使用\n\"\"\"\n\nimport re\nimport logging\nimport requests\nimport traceback\nfrom urllib.parse import urlparse\n\nfrom django.http import QueryDict\n# from django.http import HttpRequest\nfrom django.http import HttpResponse\nfrom django.views.decorators.csrf import csrf_exempt\n\nfrom db_models.models import MonitorUrl\n\nlogger = logging.getLogger(\"server\")\n\n\ndef grafana_proxy(request, url, requests_args=None):\n    \"\"\"\n    跳转grafana使用的函数\n    :type request HttpRequest\n    :param request: http请求对象\n    :type url str\n    :param url: url\n    :type requests_args dict\n    :param requests_args: 请求参数\n    :return:\n    \"\"\"\n    requests_args = (requests_args or {}).copy()\n    headers = {}\n    for key, value in request.META.items():\n        if key.startswith('HTTP_') and key != 'HTTP_HOST':\n            headers[key[5:].replace('_', '-')] = value\n        elif key in ('CONTENT_TYPE', 'CONTENT_LENGTH'):\n            headers[key.replace('_', '-')] = value\n    params = request.GET.copy()\n    if 'headers' not in requests_args:\n        requests_args['headers'] = {}\n    if 'data' not in requests_args:\n        requests_args['data'] = request.body\n    if 'params' not in requests_args:\n        requests_args['params'] = QueryDict('', mutable=True)\n    headers.update(requests_args['headers'])\n    params.update(requests_args['params'])\n    for key in list(headers.keys()):\n        if key.lower() == 'content-length':\n            del headers[key]\n    headers[\"X-CW-USER\"] = \"omp\"\n    requests_args['headers'] = headers\n    requests_args['params'] = params\n    response = requests.request(\n        request.method, url, **requests_args)\n    proxy_response = HttpResponse(\n        response.content,\n        status=response.status_code)\n\n    excluded_headers = {\n        'connection', 'keep-alive',\n        'proxy-authenticate', 'proxy-authorization', 'te', 'trailers',\n        'transfer-encoding', 'upgrade', 'content-encoding', 'content-length'\n    }\n\n    for key, value in response.headers.items():\n        if key.lower() in excluded_headers:\n            continue\n        elif key.lower() == 'location':\n            absolute_pattern = re.compile(r'^[a-zA-Z]+://.*$')\n            if absolute_pattern.match(value):\n                proxy_response[key] = value\n            parsed_url = urlparse(response.url)\n            if value.startswith('//'):\n                proxy_response[key] = parsed_url.scheme + ':' + value\n            elif value.startswith('/'):\n                proxy_response[key] = parsed_url.scheme + \\\n                    '://' + parsed_url.netloc + value\n            else:\n                proxy_response[key] = \\\n                    parsed_url.scheme + '://' + \\\n                    parsed_url.netloc + \\\n                    parsed_url.path.rsplit('/', 1)[0] + '/' + value\n        else:\n            proxy_response[key] = value\n    return proxy_response\n\n\n@csrf_exempt\ndef grafana_proxy_view(request, path):\n    \"\"\"\n    获取grafana页面\n    :type request HttpRequest\n    :param request: 请求对象\n    :type path str\n    :param path: 请求路径\n    :return:\n    \"\"\"\n    grafana = \"default\"\n    try:\n        monitor_ip = MonitorUrl.objects.filter(name=\"grafana\")\n        grafana_ins = monitor_ip[0].monitor_url if len(\n            monitor_ip) else \"127.0.0.1:19014\"\n        grafana = f\"http://{grafana_ins}\"\n        remote_url = f'{grafana}/' + path\n        return grafana_proxy(request, remote_url)\n    except Exception as e:\n        logger.error(\n            f\"跳转grafana失败: {str(e)};\\n详情为: {traceback.format_exc()}\")\n        return HttpResponse(\n            content=f\"请确认grafana配置[{grafana}]可用!\", status=200)\n"
  },
  {
    "path": "omp_server/promemonitor/promemonitor_filters.py",
    "content": "\"\"\"\n主机相关过滤器\n\"\"\"\nimport time\n\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\n\nfrom db_models.models import (Alert, CustomScript, AlertRule)\nfrom rest_framework.filters import BaseFilterBackend\n\n\nclass AlertFilter(FilterSet):\n    \"\"\" Alert过滤类 \"\"\"\n    alert_host_ip = django_filters.CharFilter(\n        help_text=\"ALERT_HOST_IP，模糊匹配\", field_name=\"alert_host_ip\", lookup_expr=\"icontains\")\n    alert_instance_name = django_filters.CharFilter(\n        help_text=\"ALERT_INSTANCE_NAME，模糊匹配\", field_name=\"alert_instance_name\", lookup_expr=\"icontains\")\n    alert_level = django_filters.CharFilter(\n        help_text=\"ALERT_LEVEL，模糊匹配\", field_name=\"alert_level\", lookup_expr=\"icontains\")\n    alert_type = django_filters.CharFilter(\n        help_text=\"ALERT_TYPE，模糊匹配\", field_name=\"alert_type\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = Alert\n        fields = (\n            \"alert_host_ip\", \"alert_instance_name\", \"alert_instance_name\",\n            \"alert_level\", \"alert_type\"\n        )\n\n\nclass QuotaFilter(FilterSet):\n    \"\"\"\n    指标规则过滤类\n    \"\"\"\n    alert = django_filters.CharFilter(\n        help_text=\"alert，规则名称模糊匹配\", field_name=\"alert\",\n        lookup_expr=\"icontains\"\n    )\n\n    class Meta:\n        model = AlertRule\n        fields = (\n            \"alert\",\n        )\n\n\nclass MyTimeFilter(BaseFilterBackend):\n\n    def filter_queryset(self, request, queryset, view):\n        start_alert_time = request.GET.get('start_alert_time')\n        end_alert_time = request.GET.get('end_alert_time')\n        if (not start_alert_time) or (not end_alert_time):\n            return queryset.all()\n        try:\n            time.strptime(start_alert_time, \"%Y-%m-%d %H:%M:%S\")\n            time.strptime(end_alert_time, \"%Y-%m-%d %H:%M:%S\")\n        except ValueError:\n            return queryset.all()\n        return queryset.filter(alert_time__range=(start_alert_time, end_alert_time))\n\n\nclass CustomScriptFilter(FilterSet):\n    \"\"\" 自定义脚本过滤类 \"\"\"\n    description = django_filters.CharFilter(\n        help_text=\"自定义脚本描述，模糊匹配\", field_name=\"description\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = CustomScript\n        fields = (\"description\",)\n"
  },
  {
    "path": "omp_server/promemonitor/promemonitor_serializers.py",
    "content": "import datetime\nimport logging\n\nfrom django.db.models import F\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.serializers import ModelSerializer, ListSerializer, \\\n    Serializer\n\nfrom db_models.models import Host, MonitorUrl, Alert, Maintain, Service, Rule, AlertRule, WaitSelfHealing\nfrom promemonitor.alert_util import AlertAnalysis\nfrom promemonitor.alertmanager import Alertmanager\nfrom promemonitor.tasks import monitor_agent_restart\nfrom utils.common.exceptions import OperateError\nfrom utils.common.serializers import HostIdsSerializer\nfrom utils.common.validators import (\n    NoEmojiValidator, NoChineseValidator\n)\n\nlogger = logging.getLogger('server')\n\n\nclass MonitorUrlListSerializer(ListSerializer):\n\n    def update(self, instance, validated_data):\n        pass\n\n    def to_internal_value(self, data):\n        return data.get('data')\n\n    def validate(self, data):\n        queryset = MonitorUrl.objects.all()\n        method = self.context[\"request\"].method\n        for i in data:\n            if method in ('PATCH', 'PUT', 'DELETE'):\n                if not i.get(\"id\"):\n                    raise serializers.ValidationError(\"id是必须字段\")\n            monitor_url = i.get(\"monitor_url\")\n            if method != 'GET':\n                if not monitor_url:\n                    raise serializers.ValidationError(\"monitor_url是必须字段\")\n                if len(monitor_url) > 128:\n                    raise serializers.ValidationError(\n                        f\"monitor_url字段超过128,detail:{monitor_url}\")\n                name = i.get(\"name\")\n                if name:\n                    if queryset.filter(name=name).exists():\n                        raise serializers.ValidationError(\n                            f\"name字段已经存在,detail:{name}\")\n                    if len(name) > 32:\n                        raise serializers.ValidationError(\n                            f\"name字段长度超过32,detail:{name}\")\n                else:\n                    raise serializers.ValidationError(\"name字段不为空\")\n        return data\n\n    def create(self, validated_data):\n        monitor = [MonitorUrl(**item) for item in validated_data]\n        return MonitorUrl.objects.bulk_create(monitor)\n\n\nclass MonitorUrlSerializer(ModelSerializer):\n    \"\"\"\n    监控配置项序列化\n    \"\"\"\n    name = serializers.CharField(\n        max_length=32, required=True,\n        error_messages={\"invalid\": \"监控名字重复\"},\n        help_text=\"监控名字\")\n    id = serializers.IntegerField(\n        max_value=100, required=False,\n        error_messages={\"invalid\": \"id格式不正确\"},\n        help_text=\"id\")\n\n    monitor_url = serializers.CharField(\n        required=True,\n        error_messages={\"invalid\": \"监控地址格式不正确\"},\n        validators=[\n            NoEmojiValidator(message=\"url地址存在非法字符\"),\n            NoChineseValidator(message=\"url地址存在非法字符\"),\n        ],\n        help_text=\"监控地址\")\n\n    class Meta:\n        model = MonitorUrl\n        fields = \"__all__\"\n        list_serializer_class = MonitorUrlListSerializer\n\n    def validate_name(self, name):\n        \"\"\" 校验name是否重复 \"\"\"\n        queryset = MonitorUrl.objects.all()\n        if self.instance is not None:\n            queryset = queryset.exclude(id=self.instance.id)\n        if queryset.filter(name=name).exists():\n            raise ValidationError(\"name已经存在\")\n        return name\n\n    def validate_monitor_url(self, monitor_url):\n        if len(monitor_url.split(\" \")) > 1:\n            raise ValidationError(\"url地址存在非法字符\")\n        return monitor_url\n\n\nclass ListAlertSerializer(ModelSerializer):\n    class Meta:\n        model = Alert\n        fields = \"__all__\"\n\n\nclass UpdateAlertSerializer(Serializer):\n    \"\"\"\n    告警记录序列化\n    \"\"\"\n\n    ids = serializers.ListField(\n        help_text=\"告警记录ID列表\",\n        required=True,\n        error_messages={\"required\": \"必须包含ID列表字段\"}\n    )\n\n    is_read = serializers.IntegerField(\n        help_text=\"是否已读\",\n        required=True,\n        error_messages={\"required\": \"必须包含是否已读字段\"}\n    )\n\n    # def validate(self, attrs):\n    #     # TODO 做校验\n    #     return attrs\n\n    def create(self, validated_data):\n        Alert.objects.filter(id__in=validated_data.get('ids')).update(\n            is_read=validated_data.get('is_read'))\n        return validated_data\n\n    def update(self, instance, validated_data):\n        pass\n\n\nclass MaintainSerializer(ModelSerializer):\n    maintain_id = serializers.CharField(\n        help_text=\"维护唯一标识\",\n        required=False, max_length=1024,\n        error_messages={\"required\": \"maintain_id不可重复\"}\n    )\n    matcher_name = serializers.CharField(\n        help_text=\"匹配标签\",\n        required=True, max_length=1024,\n        error_messages={\"required\": \"必须包含匹配标签\"}\n    )\n    matcher_value = serializers.CharField(\n        help_text=\"维护唯一标识\",\n        required=True, max_length=1024,\n        error_messages={\"required\": \"必须包含匹配标签值\"}\n    )\n\n    def validate(self, attrs):\n        \"\"\" 校验env是否存在 \"\"\"\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 进入 / 退出维护模式 \"\"\"\n        matcher_name = validated_data.get(\"matcher_name\")\n        matcher_value = validated_data.get(\"matcher_value\")\n        maintain_queryset = Maintain.objects.filter(\n            matcher_name=matcher_name, matcher_value=matcher_value)\n\n        status = \"开启\" if not maintain_queryset.exists() else \"关闭\"\n\n        # 根据 maintain_id 判断主机进入 / 退出维护模式\n        alert_manager = Alertmanager()\n        if not maintain_queryset.exists():\n            res_ls = alert_manager.set_maintain_by_env_name(matcher_value)\n        else:\n            res_ls = alert_manager.revoke_maintain_by_env_name(matcher_value)\n\n        # 操作失败\n        if not res_ls:\n            logger.error(f\"全局{status}维护模式失败\")\n            # 操作失败记录写入\n            raise OperateError(f\"全局{status}维护模式失败\")\n\n        # 操作成功\n        logger.info(f\"全局{status}维护模式成功\")\n        return validated_data\n\n    class Meta:\n        model = Maintain\n        fields = \"__all__\"\n\n\nclass ReceiveAlertSerializer(Serializer):\n    receiver = serializers.CharField(\n        help_text=\"接收者\",\n        required=True,\n        error_messages={\"required\": \"不可为空\"},\n    )\n    status = serializers.CharField(\n        help_text=\"状态\",\n        required=True,\n        error_messages={\"required\": \"不可为空\"},\n    )\n    alerts = serializers.ListField(\n        help_text=\"告警内容\",\n        required=True,\n        error_messages={\"required\": \"不可为空\"},\n    )\n\n    def update(self, instance, validated_data):\n        pass\n\n    def create(self, validated_data):\n        alerts = validated_data.get('alerts')\n        for ele in alerts:\n            alert_analysis = AlertAnalysis(ele)\n            alert_info = alert_analysis()\n            if not alert_info:\n                continue\n            if alert_info.get('status') != 'firing':\n                continue\n            alert = Alert(\n                is_read=0,\n                alert_type=alert_info.get('alert_type'),\n                alert_host_ip=alert_info.get('alert_host_ip'),\n                alert_service_name=alert_info.get('alert_service_name'),\n                alert_instance_name=alert_info.get('alert_instance_name'),\n                alert_service_type='',  # TODO 暂时拿不到值\n                alert_level=alert_info.get('alert_level'),\n                alert_describe=alert_info.get('alert_describe'),\n                alert_receiver=alert_info.get('alert_receiver'),\n                alert_resolve='',  # TODO 待后续\n                alert_time=alert_info.get('alert_time'),\n                create_time=datetime.datetime.now().strftime(\n                    \"%Y-%m-%d %H:%M:%S\"),\n                monitor_path=alert_info.get('monitor'),\n                monitor_log=alert_info.get('monitor_log'),\n                fingerprint=alert_info.get('fingerprint'),\n                # env='default'  # TODO 此版本默认不赋值\n            )\n            alert.save()\n            # TODO service_name暂时为告警类型\n            service_name = alert_info.get('alert_type')\n            WaitSelfHealing.objects.create(repair_ser=alert_info, service_name=service_name)\n            if alert_info.get('alert_type') == 'host':\n                Host.objects.filter(ip=alert_info.get('alert_host_ip')).update(\n                    alert_num=F(\"alert_num\") + 1)\n            if alert_info.get('alert_type') == 'service':\n                Service.objects.filter(service_instance_name=alert_info.get(\n                    'alert_instance_name')).filter(\n                    ip=alert_info.get('alert_host_ip')).update(\n                    service_status=Service.SERVICE_STATUS_STOP,\n                    alert_count=F(\"alert_count\") + 1)  # TODO 后续在模型中增加异常字段\n        return validated_data\n\n\nclass MonitorAgentRestartSerializer(HostIdsSerializer):\n    \"\"\" 监控Agent重启序列化类 \"\"\"\n\n    def update(self, instance, validated_data):\n        pass\n\n    def create(self, validated_data):\n        \"\"\" 监控Agent重启 \"\"\"\n        host_ids = validated_data.get(\"host_ids\", [])\n        filter_host_ids = list(\n            Host.objects.filter(\n                id__in=host_ids,\n                monitor_agent__in=[\n                    str(Host.AGENT_RUNNING),\n                    str(Host.AGENT_RESTART),\n                    str(Host.AGENT_START_ERROR)\n                ]\n            ).values_list(\"id\", flat=True)\n        )\n        for item in filter_host_ids:\n            monitor_agent_restart.delay(item)\n        # 下发任务后批量更新重启主机状态\n        Host.objects.filter(\n            id__in=filter_host_ids\n        ).update(monitor_agent=Host.AGENT_RESTART)\n        return validated_data\n\n\nclass RuleSerializer(ModelSerializer):\n    \"\"\"\n    内置规则序列化类\n    \"\"\"\n\n    class Meta:\n        \"\"\"\n        元数据\n        \"\"\"\n        model = Rule\n        fields = '__all__'\n\n\nclass QuotaSerializer(ModelSerializer):\n    \"\"\"\n    告警规则序列化类\n    \"\"\"\n\n    class Meta:\n        \"\"\"\n        元数据\n        \"\"\"\n        model = AlertRule\n        fields = '__all__'\n"
  },
  {
    "path": "omp_server/promemonitor/prometheus.py",
    "content": "import json\nimport logging\nimport math\n\nimport requests\n\nfrom db_models.models import MonitorUrl\nfrom utils.parse_config import MONITOR_PORT, PROMETHEUS_AUTH\n\nlogger = logging.getLogger('server')\n\n\nclass Prometheus:\n    \"\"\"\n    定义prometheus的一些参数以及动作\n    \"\"\"\n    STATUS = (\"normal\", \"warning\", \"critical\")\n\n    def __init__(self):\n        self.basic_url = self.get_prometheus_config()\n        self.prometheus_api_query_url = f'http://{self.basic_url}/api/v1/query?query='  # NOQA\n        self.basic_auth = (PROMETHEUS_AUTH.get(\n            \"username\", \"omp\"), PROMETHEUS_AUTH.get(\"plaintext_password\", \"\"))\n        self.headers = {'Content-Type': 'application/json'}\n\n    @staticmethod\n    def get_prometheus_config():\n        prometheus_url_config = MonitorUrl.objects.filter(\n            name='prometheus').first()\n        if not prometheus_url_config:\n            return f'127.0.0.1:{MONITOR_PORT.get(\"prometheus\", 19011)}'  # 默认值\n\n        monitor_url = prometheus_url_config.monitor_url\n        if monitor_url:\n            return monitor_url\n        return f'127.0.0.1:{MONITOR_PORT.get(\"prometheus\", 19011)}'  # 默认值\n\n    @staticmethod\n    def get_host_threshold(env_id=1,**kwargs):\n        host_threshold = {\n            'cpu': (80, 90),\n            'mem': (80, 90),\n            'root_disk': (80, 90),\n            'data_disk': (80, 90),\n        }\n        try:\n\n            from db_models.models import AlertRule\n            cpu_warning_ht = AlertRule.objects.filter(env_id=env_id,\n                                                          name=\"CPU使用率\",\n                                                          severity=\"warning\").first()\n            cpu_critical_ht = AlertRule.objects.filter(env_id=env_id,\n                                                           name=\"CPU使用率\",\n                                                           severity=\"critical\").first()\n            mem_warning_ht = AlertRule.objects.filter(env_id=env_id,\n                                                          name=\"内存使用率\",\n                                                          severity=\"warning\").first()\n            mem_critical_ht = AlertRule.objects.filter(env_id=env_id,\n                                                           name=\"内存使用率\",\n                                                           severity=\"critical\").first()\n            root_disk_warning_ht = AlertRule.objects.filter(env_id=env_id,\n                                                                name=\"根分区使用率\",\n                                                                severity=\"warning\").first()\n            root_disk_critical_ht = AlertRule.objects.filter(env_id=env_id,\n                                                                 name=\"根分区使用率\",\n                                                                 severity=\"critical\").first()\n            data_disk_warning_ht = None\n            data_disk_critical_ht = None\n            if kwargs.get(\"data_dir\"):\n                \"\"\"\n                从指标规则中获取指定路径的数据分区\n                \"\"\"\n                data_disk_warning_ht = AlertRule.objects.filter(env_id=env_id,\n                                                                    name=\"数据分区使用率\",\n                                                                    severity=\"warning\").first()\n                data_disk_critical_ht = AlertRule.objects.filter(env_id=env_id,\n                                                                     name=\"数据分区使用率\",\n                                                                     severity=\"critical\").first()\n\n\n            #\n            # from db_models.models import HostThreshold\n            # cpu_warning_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"cpu_used\",\n            #                                               alert_level=\"warning\").first()\n            # cpu_critical_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"cpu_used\",\n            #                                                alert_level=\"critical\").first()\n            # mem_warning_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"memory_used\",\n            #                                               alert_level=\"warning\").first()\n            # mem_critical_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"memory_used\",\n            #                                                alert_level=\"critical\").first()\n            # root_disk_warning_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"disk_root_used\",\n            #                                                     alert_level=\"warning\").first()\n            # root_disk_critical_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"disk_root_used\",\n            #                                                      alert_level=\"critical\").first()\n            # data_disk_warning_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"disk_data_used\",\n            #                                                     alert_level=\"warning\").first()\n            # data_disk_critical_ht = HostThreshold.objects.filter(env_id=env_id, index_type=\"disk_data_used\",\n            #                                                      alert_level=\"critical\").first()\n            host_threshold.update(\n                cpu=(\n                    int(cpu_warning_ht.threshold_value) if cpu_warning_ht else 0,\n                    int(cpu_critical_ht.threshold_value) if cpu_critical_ht else 100,\n                ),\n                mem=(\n                    int(mem_warning_ht.threshold_value) if mem_warning_ht else 0,\n                    int(mem_critical_ht.threshold_value) if mem_critical_ht else 100,\n                ),\n                root_disk=(\n                    int(root_disk_warning_ht.threshold_value) if root_disk_warning_ht else 0,\n                    int(root_disk_critical_ht.threshold_value) if root_disk_critical_ht else 100,\n                ),\n                data_disk=(\n                    int(data_disk_warning_ht.threshold_value) if data_disk_warning_ht else 0,\n                    int(data_disk_critical_ht.threshold_value) if data_disk_critical_ht else 100,\n                )\n            )\n        except Exception as e:\n            logger.error(f\"获取主机阈值失败，详情为：{e}\")\n        return host_threshold\n\n    def get_host_metric_status(self, metric, metric_value, **kwargs):\n        if metric_value is None:\n            return None\n        host_threshold = self.get_host_threshold(**kwargs)\n        if metric_value > max(host_threshold.get(metric)):\n            status = 'critical'\n        elif metric_value < min(host_threshold.get(metric)):\n            status = 'normal'\n        else:\n            status = 'warning'\n        return status\n\n    def get_host_cpu_usage(self, host_list):\n        \"\"\"\n        获取指定主机cpu使用率\n        \"\"\"\n        query_url = f'{self.prometheus_api_query_url}(1 - avg(rate(node_cpu_seconds_total' \\\n                    f'{{mode=\"idle\"}}[2m])) by (instance))*100'\n        # print(query_url)\n        try:\n            get_cpu_response = requests.get(\n                url=query_url, headers=self.headers, auth=self.basic_auth)\n            if get_cpu_response.status_code == 200:\n                cpu_usage_dict = get_cpu_response.json()\n                if cpu_usage_dict.get('status') != 'success':\n                    logger.error(get_cpu_response.text)\n                    logger.error('获取主机CPU使用率失败！')\n                    return host_list\n                for index, host in enumerate(host_list.copy()):\n                    for item in cpu_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == host.get('ip'):\n                            host_list[index]['cpu_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            host_list[index]['cpu_status'] = self.get_host_metric_status('cpu', math.ceil(\n                                float(item.get('value')[1])))\n                            break\n                        host_list[index]['cpu_usage'] = None\n                        host_list[index]['cpu_status'] = None  # TODO  待阈值判断\n\n                return host_list\n            else:\n                logger.error(get_cpu_response.text)\n                logger.error('获取主机CPU使用率失败！')\n                return host_list\n        except requests.ConnectionError as e:\n            logger.error(e)\n            logger.error('获取主机CPU使用率失败！')\n            return host_list\n        except Exception as e:\n            logger.error(e)\n            logger.error('获取主机CPU使用率失败！')\n            return host_list\n\n    def get_host_mem_usage(self, host_list):\n        \"\"\"\n        获取指定主机内存使用率\n        \"\"\"\n        query_url = f'{self.prometheus_api_query_url}(1 - (node_memory_MemAvailable_bytes / ' \\\n                    f'(node_memory_MemTotal_bytes)))* 100'\n        # print(query_url)\n\n        try:\n            get_mem_response = requests.get(\n                url=query_url, headers=self.headers, auth=self.basic_auth)\n            if get_mem_response.status_code == 200:\n                mem_usage_dict = get_mem_response.json()\n                if mem_usage_dict.get('status') != 'success':\n                    logger.error(get_mem_response.text)\n                    logger.error('获取主机内存使用率失败！')\n                    return host_list\n                for index, host in enumerate(host_list.copy()):\n                    for item in mem_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == host.get('ip'):\n                            host_list[index]['mem_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            host_list[index]['mem_status'] = self.get_host_metric_status('mem', math.ceil(\n                                float(item.get('value')[1])))\n                            break\n                        host_list[index]['mem_usage'] = None\n                        host_list[index]['mem_status'] = None  # TODO  待阈值判断\n                return host_list\n            else:\n                logger.error(get_mem_response.text)\n                logger.error('获取主机内存使用率失败！')\n                return host_list\n        except requests.ConnectionError as e:\n            logger.error(e)\n            logger.error('获取主机内存使用率失败！')\n            return host_list\n        except Exception as e:\n            logger.error(e)\n            logger.error('获取主机内存使用率失败！')\n            return host_list\n\n    def get_host_root_disk_usage(self, host_list):\n        \"\"\"\n        获取指定主机磁盘根分区使用率\n        \"\"\"\n        query_url = f'{self.prometheus_api_query_url}(node_filesystem_size_bytes{{mountpoint=\"/\"}} - ' \\\n                    f'node_filesystem_free_bytes{{mountpoint=\"/\",fstype!=\"rootfs\"}}) / ' \\\n                    f'(node_filesystem_avail_bytes{{mountpoint=\"/\"}}-node_filesystem_free_bytes{{mountpoint=\"/\"}} - ' \\\n                    f'(-node_filesystem_size_bytes{{mountpoint=\"/\"}}))*100'\n        # print(query_url)\n\n        try:\n            get_root_disk_response = requests.get(\n                url=query_url, headers=self.headers, auth=self.basic_auth)\n            if get_root_disk_response.status_code == 200:\n                root_disk_usage_dict = get_root_disk_response.json()\n                if root_disk_usage_dict.get('status') != 'success':\n                    logger.error(get_root_disk_response.text)\n                    logger.error('获取主机磁盘根分区使用率失败！')\n                    return host_list\n                for index, host in enumerate(host_list.copy()):\n                    for item in root_disk_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == host.get('ip'):\n                            host_list[index]['root_disk_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            host_list[index]['root_disk_status'] = self.get_host_metric_status('root_disk', math.ceil(\n                                float(item.get('value')[1])))\n                            break\n                        host_list[index]['root_disk_usage'] = None\n                        host_list[index]['root_disk_status'] = None  # TODO\n                return host_list\n            else:\n                logger.error(get_root_disk_response.text)\n                logger.error('获取主机磁盘根分区使用率失败！')\n                return host_list\n        except requests.ConnectionError as e:\n            logger.error(e)\n            logger.error('获取主机磁盘根分区使用率失败！')\n            return host_list\n        except Exception as e:\n            logger.error(e)\n            logger.error('获取主机磁盘根分区使用率失败！')\n            return host_list\n\n    def get_host_data_disk_usage(self, host_list):\n        \"\"\"\n        获取指定主机磁盘数据分区使用率\n        \"\"\"\n        for index, host in enumerate(host_list.copy()):\n            host_ip = host.get('ip')\n            host_data_disk = host.get('data_folder')\n            query_url = f'{self.prometheus_api_query_url}' \\\n                        f'(node_filesystem_size_bytes{{mountpoint=\"{host_data_disk}\",instance=\"{host_ip}\"}} - ' \\\n                        f'node_filesystem_free_bytes{{mountpoint=\"{host_data_disk}\",instance=\"{host_ip}\"}}) / ' \\\n                        f'(node_filesystem_avail_bytes{{mountpoint=\"{host_data_disk}\",instance=\"{host_ip}\"}} - ' \\\n                        f'node_filesystem_free_bytes{{mountpoint=\"{host_data_disk}\",instance=\"{host_ip}\"}} - ' \\\n                        f'(-node_filesystem_size_bytes{{mountpoint=\"{host_data_disk}\",instance=\"{host_ip}\"}}))*100'\n            try:\n                get_data_disk_response = requests.get(\n                    url=query_url, headers=self.headers, auth=self.basic_auth)\n                if get_data_disk_response.status_code == 200:\n                    data_disk_usage_dict = get_data_disk_response.json()\n                    if data_disk_usage_dict.get('status') != 'success':\n                        logger.error(get_data_disk_response.text)\n                        logger.error('获取主机磁盘数据分区使用率失败！')\n                        continue\n                    if not data_disk_usage_dict.get('data').get('result'):\n                        host_list[index]['data_disk_usage'] = None\n                        host_list[index]['data_disk_status'] = None\n                    for item in data_disk_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == host.get('ip'):\n                            host_list[index]['data_disk_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            host_list[index]['data_disk_status'] = self.get_host_metric_status('data_disk', math.ceil(\n                                float(item.get('value')[1])),data_dir=host_data_disk)\n                            break\n                        host_list[index]['data_disk_usage'] = None\n                        host_list[index]['data_disk_status'] = None\n                else:\n                    logger.error(get_data_disk_response.text)\n                    logger.error('获取主机磁盘数据分区使用率失败！')\n                    continue\n            except requests.ConnectionError as e:\n                logger.error(e)\n                logger.error('获取主机磁盘数据分区使用率失败！')\n                continue\n            except Exception as e:\n                logger.error(e)\n                logger.error('获取主机磁盘数据分区使用率失败！')\n                continue\n        return host_list\n\n    def get_host_info(self, host_list):\n        \"\"\"\n        获取主机负载基本信息\n        \"\"\"\n        for index, host in enumerate(host_list.copy()):\n            host_list[index]['cpu_usage'] = None\n            host_list[index]['cpu_status'] = None\n            host_list[index]['mem_usage'] = None\n            host_list[index]['mem_status'] = None\n            host_list[index]['root_disk_usage'] = None\n            host_list[index]['root_disk_status'] = None\n            host_list[index]['data_disk_usage'] = None\n            host_list[index]['data_disk_status'] = None\n        host_list = self.get_host_cpu_usage(host_list)\n        host_list = self.get_host_mem_usage(host_list)\n        host_list = self.get_host_root_disk_usage(host_list)\n        host_list = self.get_host_data_disk_usage(host_list)\n        return host_list\n\n    def get_all_service_status(self):\n        \"\"\"\n        获取服务状态  0-运行; 1-停止\n        :return:\n        \"\"\"\n        query_url = f'{self.prometheus_api_query_url}probe_success'\n        try:\n            res_body = requests.get(\n                url=query_url, headers=self.headers, auth=self.basic_auth)\n            res_dic = json.loads(res_body.text)\n            if res_dic.get(\"status\") != \"success\":\n                return False, {}\n            service_data = res_dic.get(\"data\", {}).get(\"result\", [])\n            service_status_dic = dict()\n            for item in service_data:\n                metric = item.get(\"metric\", {})\n                if metric.get(\"service_type\") != \"service\":\n                    continue\n                _key = metric.get(\"instance\", \"\") + \"_\" + \\\n                    metric.get(\"instance_name\", \"\")\n                _value = True if int(\n                    item.get(\"value\", [0, 0])[-1]) == 1 else False\n                service_status_dic[_key] = _value\n            return True, service_status_dic\n        except Exception as e:\n            logger.error(f\"从prometheus获取数据失败: {str(e)}\")\n            return False, {}\n\n    def get_all_host_targets(self):\n        query_url = f'{self.basic_url}/api/v1/targets'\n        host_targets = list()\n        try:\n            res_body = requests.get(url=f\"http://{query_url}\", headers=self.headers, auth=self.basic_auth)  # NOQA\n            res_dic = json.loads(res_body.text)\n            if res_dic.get(\"status\") != \"success\":\n                return False, {}\n            targets_data = res_dic.get(\"data\", {}).get(\"activeTargets\")\n            for item in targets_data:\n                if item.get(\"labels\", {}).get(\"job\") != \"nodeExporter\":\n                    continue\n                host_targets.append(item.get(\"labels\").get(\"instance\"))\n            return True, host_targets\n        except Exception as e:\n            logger.error(f\"从prometheus获取主机targets失败: {str(e)}\")\n            return False, []\n\n    def get_all_service_targets(self):\n        query_url = f'{self.basic_url}/api/v1/targets'\n        service_targets = list()\n        try:\n            res_body = requests.get(url=f\"http://{query_url}\", headers=self.headers, auth=self.basic_auth)  # NOQA\n            res_dic = json.loads(res_body.text)\n            if res_dic.get(\"status\") != \"success\":\n                return False, {}\n            targets_data = res_dic.get(\"data\", {}).get(\"activeTargets\")\n            for item in targets_data:\n                if item.get(\"labels\", {}).get(\"service_type\") != \"service\":\n                    continue\n                service_targets.append(\n                    f'{item.get(\"labels\").get(\"instance\")}_{item.get(\"labels\").get(\"instance_name\")}')\n            return True, service_targets\n        except Exception as e:\n            logger.error(f\"从prometheus获取服务targets失败: {str(e)}\")\n            return False, []\n\n    @staticmethod\n    def get_service_threshold(env_id=1):\n        service_threshold = {\n            'cpu': (80, 90),\n            'mem': (80, 90)\n        }\n        # try:\n        #     from db_models.models import ServiceThreshold\n        #     cpu_warning_st = ServiceThreshold.objects.filter(env_id=env_id, index_type=\"service_cpu_used\",\n        #                                                      alert_level=\"warning\").first()\n        #     cpu_critical_st = ServiceThreshold.objects.filter(env_id=env_id, index_type=\"service_cpu_used\",\n        #                                                       alert_level=\"critical\").first()\n        #     mem_warning_st = ServiceThreshold.objects.filter(env_id=env_id, index_type=\"service_memory_used\",\n        #                                                      alert_level=\"warning\").first()\n        #     mem_critical_st = ServiceThreshold.objects.filter(env_id=env_id, index_type=\"service_memory_used\",\n        #                                                       alert_level=\"critical\").first()\n        #     service_threshold.update(\n        #         cpu=(\n        #             int(cpu_warning_st.condition_value) if cpu_warning_st else 0,\n        #             int(cpu_critical_st.condition_value) if cpu_critical_st else 100,\n        #         ),\n        #         mem=(\n        #             int(mem_warning_st.condition_value) if mem_warning_st else 0,\n        #             int(mem_critical_st.condition_value) if mem_critical_st else 100,\n        #         )\n        #     )\n        # except Exception as e:\n        #     logger.error(f\"获取服务阈值失败，详情为：{e}\")\n        return service_threshold\n\n    def get_service_metric_status(self, metric, metric_value):\n        if metric_value is None:\n            return None\n        service_threshold = self.get_service_threshold()\n        if metric_value > max(service_threshold.get(metric)):\n            status = 'critical'\n        elif metric_value < min(service_threshold.get(metric)):\n            status = 'normal'\n        else:\n            status = 'warning'\n        return status\n\n    def get_service_cpu_usage(self, service_list):\n        \"\"\"\n        获取服务cpu使用率\n        \"\"\"\n        open_source_query_url = f'{self.prometheus_api_query_url}service_process_cpu_percent'\n        self_service_query_url = f'{self.prometheus_api_query_url}process_cpu_usage * 100'\n        try:\n            os_cpu_response = requests.get(\n                url=open_source_query_url, headers=self.headers, auth=self.basic_auth)\n            if os_cpu_response.status_code == 200:\n                os_cpu_usage_dict = os_cpu_response.json()\n                if os_cpu_usage_dict.get('status') != 'success':\n                    logger.error(os_cpu_response.text)\n                    logger.error('获取开源服务CPU使用率失败！')\n                    return service_list\n                for index, os_service in enumerate(service_list.copy()):\n                    if os_service.get('app_name') == 'hadoop':\n                        os_service['app_name'] = os_service.get(\n                            'service_instance_name', 'hadoop').split('_')[0]\n                    for item in os_cpu_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == os_service.get('ip') \\\n                                and item.get('metric').get('app') == os_service.get('app_name') \\\n                                and item.get('metric').get('env') == os_service.get('env'):\n                            service_list[index]['cpu_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            service_list[index]['cpu_status'] = self.get_service_metric_status('cpu', math.ceil(\n                                float(item.get('value')[1])))\n                            service_list[index]['has_cpu_value'] = 1\n                            break\n                        service_list[index]['cpu_usage'] = None\n                        service_list[index]['cpu_status'] = None  # TODO  待阈值判断\n\n            else:\n                logger.error(os_cpu_response.text)\n                logger.error('获取开源服务CPU使用率失败！')\n\n            ss_cpu_response = requests.get(\n                url=self_service_query_url, headers=self.headers, auth=self.basic_auth)\n            if ss_cpu_response.status_code == 200:\n                ss_cpu_usage_dict = ss_cpu_response.json()\n                if ss_cpu_usage_dict.get('status') != 'success':\n                    logger.error(ss_cpu_response.text)\n                    logger.error('获取自研服务CPU使用率失败！')\n                    return service_list\n                for index, ss_service in enumerate(service_list.copy()):\n                    if ss_service.get('has_cpu_value', 0) == 1:\n                        continue\n                    for item in ss_cpu_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == ss_service.get('ip') \\\n                                and item.get('metric').get('job') == f\"{ss_service.get('app_name')}Exporter\" \\\n                                and item.get('metric').get('env') == ss_service.get('env'):\n                            service_list[index]['cpu_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            service_list[index]['cpu_status'] = self.get_service_metric_status('cpu', math.ceil(\n                                float(item.get('value')[1])))\n                            break\n                        service_list[index]['cpu_usage'] = None\n                        service_list[index]['cpu_status'] = None  # TODO  待阈值判断\n\n            else:\n                logger.error(os_cpu_response.text)\n                logger.error('获取自研服务CPU使用率失败！')\n            return service_list\n        except Exception as e:\n            logger.error(f'获取服务cpu使用率失败，报错信息为：{e}')\n            return service_list\n\n    def get_service_mem_usage(self, service_list):\n        open_source_query_url = f'{self.prometheus_api_query_url}service_process_memory_percent'\n        jvm_total_bytes_query_url = f'{self.prometheus_api_query_url}sum(jvm_memory_max_bytes{{area=\"heap\"}}) by (instance,job,application,env)'\n        node_total_bytes_query_url = f'{self.prometheus_api_query_url}node_memory_MemTotal_bytes'\n        try:\n            os_mem_response = requests.get(\n                url=open_source_query_url, headers=self.headers, auth=self.basic_auth)\n            if os_mem_response.status_code == 200:\n                os_mem_usage_dict = os_mem_response.json()\n                if os_mem_usage_dict.get('status') != 'success':\n                    logger.error(os_mem_response.text)\n                    logger.error('获取开源服务内存使用率失败！')\n                    return service_list\n                for index, os_service in enumerate(service_list.copy()):\n                    service_list[index]['mem_usage'] = None\n                    service_list[index]['mem_status'] = None\n                    for item in os_mem_usage_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == os_service.get('ip') \\\n                                and item.get('metric').get('app') == os_service.get('app_name') \\\n                                and item.get('metric').get('env') == os_service.get('env'):\n                            service_list[index]['mem_usage'] = math.ceil(\n                                float(item.get('value')[1]))\n                            service_list[index]['mem_status'] = self.get_service_metric_status('mem', math.ceil(\n                                float(item.get('value')[1])))\n                            service_list[index]['has_mem_value'] = 1\n                            break\n                        # service_list[index]['mem_usage'] = None\n                        # service_list[index]['mem_status'] = None\n\n            else:\n                logger.error(os_mem_response.text)\n                logger.error('获取开源服务内存使用率失败！')\n\n            jtb_response = requests.get(\n                url=jvm_total_bytes_query_url, headers=self.headers, auth=self.basic_auth)\n            if jtb_response.status_code == 200:\n                jtb_dict = jtb_response.json()\n                if jtb_dict.get('status') != 'success':\n                    logger.error(jtb_response.text)\n                    logger.error('获取自研服务内存使用量失败！')\n                    return service_list\n            else:\n                logger.error(os_mem_response.text)\n                logger.error('获取自研服务内存使用率失败！')\n                return service_list\n\n            ntb_response = requests.get(\n                url=node_total_bytes_query_url, headers=self.headers, auth=self.basic_auth)\n            if ntb_response.status_code == 200:\n                ntb_dict = ntb_response.json()\n                if ntb_dict.get('status') != 'success':\n                    logger.error(ntb_response.text)\n                    logger.error('获取自研服务内存使用量失败！')\n                    return service_list\n            else:\n                logger.error(os_mem_response.text)\n                logger.error('获取主机内存资源量失败！')\n                return service_list\n\n            for index, ss_service in enumerate(service_list.copy()):\n                if ss_service.get('has_mem_value', 0) == 1:\n                    continue\n                for item in jtb_dict.get('data').get('result'):\n                    for ele in ntb_dict.get('data').get('result'):\n                        if item.get('metric').get('instance') == ss_service.get('ip') \\\n                                and item.get('metric').get('instance') == ele.get('metric').get('instance') \\\n                                and item.get('metric').get('job') == f\"{ss_service.get('app_name')}Exporter\" \\\n                                and item.get('metric').get('env') == ss_service.get('env'):\n                            service_list[index]['mem_usage'] = math.ceil(\n                                float(item.get('value')[1]) / float(ele.get('value')[1]) * 100)\n                            service_list[index]['mem_status'] = self.get_service_metric_status('mem',\n                                                                                               service_list[index][\n                                                                                                   'mem_usage'])\n                            break\n            return service_list\n        except Exception as e:\n            logger.error(f'获取服务mem使用率失败，报错信息为：{e}')\n            return service_list\n\n    def get_service_info(self, service_list):\n        \"\"\"\n        获取服务负载基本信息\n        \"\"\"\n        for index, service in enumerate(service_list.copy()):\n            service_list[index]['cpu_usage'] = None\n            service_list[index]['cpu_status'] = None\n            service_list[index]['mem_usage'] = None\n            service_list[index]['mem_status'] = None\n        service_list = self.get_service_cpu_usage(service_list)\n        service_list = self.get_service_mem_usage(service_list)\n        return service_list\n\n    def get_quota_res(self,quota):\n        \"\"\"\n        获取指标结果\n        \"\"\"\n        query_url = f\"{self.prometheus_api_query_url}{quota}\"\n        try:\n            response = requests.get(\n                    url=query_url, headers=self.headers, auth=self.basic_auth)\n            if response.status_code != 200:\n                logger.error(f\"测试promsql错误: 状态码为{response.status_code} 错误{response.text}\")\n                return False, response.text\n            print(query_url,response.json())\n            if response.json().get(\"status\") == \"success\":\n                return True, response.json()[\"data\"][\"result\"]\n            return False, response.text\n        except Exception as e:\n            logger.error(f\"测试promsql错误：{e}\")\n            return False, \"访问prometheus错误\"\n\n\nclass UpdatePrometheusRule(object):\n    \"\"\"\n    更新prometheus的配置文件\n    \"\"\"\n"
  },
  {
    "path": "omp_server/promemonitor/prometheus_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: prometheus_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-11 14:31\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nprometheus更新监控项使用的工具集\n\"\"\"\n\nimport os\nimport json\nimport pickle\nimport shutil\nimport logging\nimport requests\nimport hashlib\n\n# import requests\nimport yaml\nfrom ruamel.yaml import YAML\n\nfrom db_models.models import HostThreshold, ServiceCustomThreshold, AlertRule\nfrom omp_server.settings import PROJECT_DIR\nfrom utils.parse_config import MONITOR_PORT, PROMETHEUS_AUTH, LOKI_CONFIG\n\n# from utils.parse_config import MONITOR_PORT\n\nlogger = logging.getLogger(\"server\")\n\nCW_TOKEN = {\n    'CWAccessToken': 'FnQGiEXrYr6n8diKuY6cc61Zw3MMyLW9icwiUlHjyoAkBsBKCDIqmDZbf'}\n\nAGREE = \"http\"\n# 内置exporter列表\nEXPORTERS = [\n    \"beanstalk\",\n    \"clickhouse\",\n    \"elasticsearch\",\n    \"httpd\",\n    \"kafka\",\n    \"mysql\",\n    \"node\",\n    \"postgreSql\",\n    \"redis\",\n    \"tengine\",\n    \"zookeeper\",\n    \"rocketmq\",\n]\n\nMETRICS = {\n    \"arangodb\": \"_admin/metrics\",\n    \"nacos\": \"nacos/actuator/prometheus\",\n}\n\n\nclass PrometheusUtils(object):\n    \"\"\" prometheus工具集 \"\"\"\n\n    def __init__(self):\n        self.prometheus_conf_path = os.path.join(\n            PROJECT_DIR, \"component/prometheus/conf/prometheus.yml\")\n        self.prometheus_rules_path = os.path.join(\n            PROJECT_DIR, \"component/prometheus/conf/rules\")\n        self.prometheus_targets_path = os.path.join(\n            PROJECT_DIR, \"component/prometheus/conf/targets\")\n        self.prometheus_node_rule_tpl = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/prometheus_rules_template/node_rule.yml\")\n        self.prometheus_node_data_rule_tpl = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/prometheus_rules_template/node_data_rule.yml\")\n        self.prometheus_service_status_rule_tpl = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/prometheus_rules_template/service_status_rule.yml\")\n        self.prometheus_exporter_status_rule_tpl = os.path.join(\n            PROJECT_DIR,\n            \"package_hub/prometheus_rules_template/exporter_status_rule.yml\")\n        # 邮件地址\n        self.email_address = \"omp@cloudwise.com\"\n        self.monitor_port = 19031\n        self.node_exporter_targets_file = os.path.join(\n            self.prometheus_targets_path,\n            \"nodeExporter_all.json\"\n        )\n        self.agent_request_header = {}\n        self.basic_auth = (PROMETHEUS_AUTH.get(\n            \"username\", \"omp\"), PROMETHEUS_AUTH.get(\"plaintext_password\", \"\"))\n\n    @staticmethod\n    def replace_placeholder(path, placeholder_list):\n        \"\"\"\n        替换文件中的占位符\n        :param path: 要替换的文件路径\n        :param placeholder_list: 占位符字典列表 [{\"key\":\"value\"}]\n        :return:\n        \"\"\"\n        if not os.path.isfile(path):\n            return False, f\"{path} not exists!\"\n        with open(path, \"r\") as f:\n            data = f.read()\n            for item in placeholder_list:\n                for k, v in item.items():\n                    placeholder = \"${{{}}}\".format(k)\n                    data = data.replace(placeholder, str(v))\n        with open(path, \"w\") as f:\n            f.write(data)\n\n    @staticmethod\n    def json_distinct(iterable_lst):\n        \"\"\"\n        json元素去重\n        :param iterable_lst: 去重对象\n        :return: 去重后的对象\n        \"\"\"\n        res = []\n        for instance in iterable_lst:\n            res.append(pickle.dumps(instance))\n        res = set(res)\n        return [pickle.loads(i) for i in res]\n\n    @staticmethod\n    def get_dic_from_yaml(file_path):\n        \"\"\"\n        从yaml中获取字典\n        :param file_path: 文件路径\n        :return:\n        \"\"\"\n        with open(file_path, \"r\", encoding=\"utf8\") as fp:\n            content = fp.read()\n        my_yaml = YAML()\n        return my_yaml.load(content)\n\n    @staticmethod\n    def write_dic_to_yaml(dic, file_path):\n        \"\"\"\n        将字典写入yaml\n        :param dic: 字典数据\n        :param file_path: yaml文件路径\n        :return:\n        \"\"\"\n        my_yaml = YAML()\n        with open(file_path, \"w\", encoding=\"utf8\") as fp:\n            my_yaml.dump(dic, fp)\n\n    @staticmethod\n    def get_expr(num, env, data_path):\n        \"\"\"\n        获取prometheus数据分区匹配规则\n        :param num:\n        :param env:\n        :param data_path:\n        :return:\n        \"\"\"\n        _expr = \\\n            \"\"\"max((node_filesystem_size_bytes{env=\"ENV\",\n            mountpoint=\"DATA_PATH\"}-node_filesystem_free_bytes{env=\"ENV\",\n            mountpoint=\"DATA_PATH\"})*100/(node_filesystem_avail_bytes{\n            env=\"ENV\",mountpoint=\"DATA_PATH\"}+(node_filesystem_size_bytes{\n            env=\"ENV\",mountpoint=\"DATA_PATH\"}-node_filesystem_free_bytes{\n            env=\"ENV\",mountpoint=\"DATA_PATH\"})))by(instance,env)>= \"\"\"\n        return _expr.replace(\"ENV\", env).replace(\n            \"DATA_PATH\", data_path).replace(\"\\n\", \"\").replace(\n            \" \", \"\"\n        ) + str(num)\n\n    @staticmethod\n    def get_service_port(service_name):\n        port = MONITOR_PORT.get(service_name)\n        return port\n\n    def make_data_node_rule(self, level, data_path, env=\"default\"):\n        \"\"\"\n        生成数据分区告警规则\n        :param level: 告警级别\n        :param data_path: 数据分区路径\n        :param env: 主机所属环境\n        :return:\n        \"\"\"\n        try:\n            from db_models.models import HostThreshold\n            warning_obj = HostThreshold.objects.filter(env_id=1, index_type=\"disk_data_used\",\n                                                       alert_level=\"warning\").first()\n            critical_obj = HostThreshold.objects.filter(env_id=1, index_type=\"disk_data_used\",\n                                                        alert_level=\"critical\").first()\n            warning_threshold = warning_obj.condition_value if warning_obj else \"0\"\n            critical_threshold = critical_obj.condition_value if critical_obj else \"100\"\n        except Exception as e:\n            warning_threshold = \"80\"\n            critical_threshold = \"90\"\n            logger.error(f\"更新数据分区告警于是失败！详情为：{e}\")\n        des = \"主机 {{ $labels.instance }} 数据分区使用率为 \" \\\n              \"{{ $value | humanize }}%, 大于阈值 \"\n        return {\n            \"alert\": \"主机数据分区磁盘使用率过高\",\n            \"annotations\": {\n                \"disk_data_path\": f\"{data_path}\",\n                \"consignee\": f\"{self.email_address}\",\n                \"description\": des + f\"{critical_threshold}%\" if level == \"critical\" else des + f\"{warning_threshold}%\",\n                \"summary\": \"disk_data_used (instance {{ $labels.instance }})\"\n            },\n            \"expr\": self.get_expr(\n                critical_threshold if level == \"critical\" else warning_threshold, env, data_path),\n            \"for\": \"1m\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": level\n            }\n        }\n\n    def update_node_data_rule(self, data_path, env=\"default\"):\n        \"\"\"\n        更新主机data disk分区告警规则，当添加主机时使用\n        :rtype: tuple\n        :param data_path: 数据分区地址\n        :param env: 主机所属环境\n        :return:\n        \"\"\"\n        logger.info(f\"Start update_node_data_rule: {data_path}; {env}\")\n        data_rule_path = os.path.join(\n            self.prometheus_rules_path, f\"{env}_node_data_rule.yml\")\n        _critical = self.make_data_node_rule(\"critical\", data_path, env)\n        _warning = self.make_data_node_rule(\"warning\", data_path, env)\n        # _critical_flag = True\n        # _warning_flag = True\n        if not os.path.exists(data_rule_path):\n            node_data_rule_dic = {\n                \"groups\": [\n                    {\n                        \"name\": \"主机数据分区磁盘使用率过高\",\n                        \"rules\": [\n                            _critical, _warning\n                        ]\n                    }\n                ]\n            }\n            self.write_dic_to_yaml(node_data_rule_dic, data_rule_path)\n            return True, \"success by new create\"\n        node_data_rule_dic = self.get_dic_from_yaml(data_rule_path)\n        groups = node_data_rule_dic.get(\"groups\", []).copy()\n        for item_index, item in enumerate(groups):\n            rules = item.get(\"rules\", []).copy()\n            for el_index, el in enumerate(rules):\n                if el.get(\"annotations\", {}).get(\"disk_data_path\") \\\n                        == data_path and el.get(\"labels\", {}).get(\"severity\") \\\n                        == \"critical\":\n                    # _critical_flag = False\n                    item.get(\"rules\", [])[el_index] = _critical\n                if el.get(\"annotations\", {}).get(\"disk_data_path\") \\\n                        == data_path and el.get(\"labels\", {}).get(\n                    \"severity\") \\\n                        == \"warning\":\n                    # _warning_flag = False\n                    item.get(\"rules\", [])[el_index] = _warning\n            node_data_rule_dic.get(\"groups\", [])[item_index] = item\n        self.write_dic_to_yaml(node_data_rule_dic, data_rule_path)\n        return True, \"success\"\n\n    def add_rules(self, rule_type, env=\"default\"):\n        \"\"\"\n        rule_type:\n            node: 主机告警规则\n            service: 服务状态告警规则\n            exporter: exporter状态告警规则\n        更新prometheus rules规则, 建议在安装prometheus时进行更新\n        :param rule_type: 更新方式\n        :param env: 环境信息\n        :return:\n        \"\"\"\n        logger.info(f\"Start add rules: {rule_type}; {env}\")\n        rules_file_placeholder_script = [\n            {\"ENV\": env},\n            {\"EMAIL_ADDRESS\": self.email_address}\n        ]\n        if rule_type == \"node\":\n            node_rule_yml_file = os.path.join(\n                self.prometheus_rules_path,\n                f\"{env}_node_rule.yml\"\n            )\n            if not os.path.exists(node_rule_yml_file):\n                shutil.copy(\n                    self.prometheus_node_rule_tpl,\n                    node_rule_yml_file\n                )\n                self.replace_placeholder(\n                    node_rule_yml_file,\n                    rules_file_placeholder_script\n                )\n        elif rule_type == \"service\":\n            status_rule_yml_file = os.path.join(\n                self.prometheus_rules_path,\n                f\"{env}_service_status_rule.yml\"\n            )\n            if not os.path.exists(status_rule_yml_file):\n                shutil.copy(\n                    self.prometheus_service_status_rule_tpl,\n                    status_rule_yml_file\n                )\n                self.replace_placeholder(\n                    status_rule_yml_file,\n                    rules_file_placeholder_script\n                )\n        elif rule_type == \"exporter\":\n            exporter_rule_yml_file = os.path.join(\n                self.prometheus_rules_path,\n                f\"{env}_exporter_status_rule.yml\")\n            if not os.path.exists(exporter_rule_yml_file):\n                shutil.copy(\n                    self.prometheus_exporter_status_rule_tpl,\n                    exporter_rule_yml_file\n                )\n                self.replace_placeholder(\n                    exporter_rule_yml_file,\n                    rules_file_placeholder_script\n                )\n        else:\n            return False, \"not support!\"\n        return True, \"success\"\n\n    def delete_rules(self, rule_type, env=\"default\"):\n        \"\"\"\n        删除prometheus rules的规则\n        :param rule_type: 删除类型\n        :param env: 环境名称\n        :return:\n        \"\"\"\n        if rule_type not in (\"node\", \"service\", \"exporter\"):\n            return False, \"not support!\"\n        if rule_type == \"node\":\n            rule_yml_file = os.path.join(\n                self.prometheus_rules_path,\n                f\"{env}_node_rule_yml\"\n            )\n        else:\n            rule_yml_file = os.path.join(\n                self.prometheus_rules_path,\n                f\"{env}_{rule_type}_status_rule.yml\"\n            )\n        if os.path.exists(rule_yml_file):\n            os.remove(rule_yml_file)\n        return True, \"success\"\n\n    def add_node(self, nodes_data):\n        \"\"\"\n        nodes_data = [\n            {\n                \"data_path\": \"/data\",\n                \"env\": \"default\",\n                \"ip\": \"127.0.0.1\",\n                \"instance_name\": \"instance\"\n            }\n        ]\n        添加主机到自监控系统\n        :param nodes_data: 新增的主机信息\n        :return:\n        \"\"\"\n        logger.info(f\"Start add node: {nodes_data}\")\n        if not nodes_data:\n            return False, \"nodes_data can not be null\"\n        node_target_list = list()\n        # 遍历主机数据，添加主机层的告警规则\n        for item in nodes_data:\n            node_target_ele = {\n                \"targets\": [item[\"ip\"] + \":\" + str(self.monitor_port)],\n                \"labels\": {\n                    \"instance\": item[\"ip\"],\n                    \"instance_name\": \"{}\".format(item.get(\"instance_name\")),\n                    \"service_type\": \"host\",\n                    \"env\": item[\"env\"]}\n            }\n            node_target_list.append(node_target_ele)\n            print(\"添加数据分区\", item[\"data_path\"])\n            # 需要像数据库添加数据分区的的规则\n            if item[\"data_path\"]:\n                self.add_data_disk_rules(item[\"data_path\"], item[\"env\"])\n            # # 更新主机node rule\n            # self.add_rules(\"node\", item[\"env\"])\n            # # 更新exporter的告警规则\n            # self.add_rules(\"exporter\", item[\"env\"])\n            # # 更新数据分区的告警规则\n            # if item[\"data_path\"]:\n            #     self.update_node_data_rule(item[\"data_path\"], item[\"env\"])\n        # 增加主机的target配置文件(prometheus/conf/targets)\n        if os.path.exists(self.node_exporter_targets_file):\n            with open(self.node_exporter_targets_file, \"r\") as f:\n                content = f.read()\n                if content:\n                    old_node_target_list = json.loads(content)\n                    node_target_list.extend(old_node_target_list)\n        node_target_list = self.json_distinct(node_target_list)\n        with open(self.node_exporter_targets_file, \"w\") as f2:\n            json.dump(node_target_list, f2, ensure_ascii=False, indent=4)\n        self.reload_prometheus()\n        return True, \"success\"\n\n    def delete_node(self, nodes_data):\n        \"\"\"\n        从自监控系统删除主机信息\n        :param nodes_data: 要删除的主机信息\n        :return:\n        \"\"\"\n        if not nodes_data:\n            return False, \"nodes_data can not be null\"\n        if os.path.exists(self.node_exporter_targets_file):\n            with open(self.node_exporter_targets_file, \"r\") as f:\n                content = f.read()\n                if content:\n                    node_target_list = json.loads(content)\n                else:\n                    node_target_list = list()\n        else:\n            return False, f\"{self.node_exporter_targets_file} not exists!\"\n        if not node_target_list:\n            return True, \"success\"\n        for item in nodes_data:\n            ip = item[\"ip\"]\n            for node in node_target_list:\n                if ip in node[\"labels\"][\"instance\"]:\n                    node_target_list.remove(node)\n                    break\n        with open(self.node_exporter_targets_file, \"w\") as f2:\n            json.dump(node_target_list, f2, ensure_ascii=False, indent=4)\n        return True, \"success\"\n\n    def update_agent_service(self, dest_ip, action, services_data):\n        \"\"\"\n        接收omp传来的参数，解析后发送到monitor_agent\n        :param action: 更新动作 add or delete\n        :param services_data: 要更新的服务信息\n        :param dest_ip:\n        :return:\n        \"\"\"\n        json_dict = dict()\n        json_content = list()\n        headers = CW_TOKEN\n        dest_url = ''\n        if action == 'add':\n            dest_url = 'http://{}:{}/update/service/add'.format(dest_ip, self.monitor_port)  # NOQA\n            for sd in services_data:\n                service_temp_data = dict()\n                service_temp_data['service_port'] = sd.get('listen_port')\n                service_temp_data['run_port'] = sd.get('run_port')\n                if sd.get('service_name') in EXPORTERS:\n                    service_temp_data['exporter_port'] = self.get_service_port(\n                        '{}Exporter'.format(sd.get('service_name')))\n                else:\n                    service_temp_data['exporter_port'] = sd.get(\n                        'metric_port', 0)\n                if sd.get('service_name') in METRICS.keys():\n                    service_temp_data['exporter_metric'] = METRICS.get(\n                        sd.get('service_name'), 0)\n                else:\n                    service_temp_data['exporter_metric'] = 'metrics'\n                service_temp_data['username'] = sd.get('username', '')\n                service_temp_data['password'] = sd.get('password', '')\n                service_temp_data['name'] = sd.get('service_name')\n                service_temp_data['only_process'] = sd.get('only_process')\n                service_temp_data['process_key_word'] = sd.get(\n                    'process_key_word')\n                service_temp_data['instance'] = dest_ip\n                service_temp_data['env'] = sd.get('env')\n                service_temp_data['log_path'] = sd.get('log_path')\n                service_temp_data[\"scrape_log_level\"] = LOKI_CONFIG.get(\n                    \"scrape_log_level\")\n                json_content.append(service_temp_data)\n        elif action == 'delete':\n            dest_url = 'http://{}:{}/update/service/delete'.format(dest_ip, self.monitor_port)  # NOQA\n            for sd in services_data:\n                json_content.append(sd.get('service_name'))\n\n        json_dict['services'] = json_content\n        try:\n            logger.info(f'向agent发送数据{json_dict}')\n            result = requests.post(\n                dest_url, headers=headers, data=json.dumps(json_dict)).json()\n            if result.get('return_code') == 0:\n                logger.info('向{}更新服务{}配置成功！'.format(\n                    dest_ip, services_data[0].get('service_name')))\n            else:\n                logger.error('向{}更新服务{}配置失败！'.format(\n                    dest_ip, services_data[0].get('service_name')))\n                # return False, result.get('return_message')\n        except Exception as e:\n            logger.error('向{}更新服务{}配置失败！'.format(\n                dest_ip, services_data[0].get('service_name')))\n            logger.error(e)\n            # return False, e\n        try:\n            from utils.parse_config import MONITOR_PORT, LOCAL_IP\n            from db_models.models import Host\n            update_agent_promtail_url = f'http://{dest_ip}:{self.monitor_port}/update/promtail/add'  # NOQA\n            host_agent_dir = Host.objects.filter(\n                ip=dest_ip).values_list('agent_dir', flat=True)[0]\n            json_dict['promtail_config'] = {\n                'http_listen_port': MONITOR_PORT.get('promtail'),\n                'loki_url': f'http://{LOCAL_IP}:{MONITOR_PORT.get(\"loki\")}/loki/api/v1/push'  # NOQA\n            }\n            json_dict['agent_dir'] = host_agent_dir\n            logger.info(f'向agent发送数据{json_dict}')\n            promtail_result = requests.post(\n                update_agent_promtail_url, headers=headers, data=json.dumps(json_dict)).json()\n            if promtail_result.get('return_code') == 0:\n                logger.info('向{}更新服务{}日志监控配置成功！'.format(\n                    dest_ip, services_data[0].get('service_name')))\n            else:\n                logger.error('向{}更新服务{}日志监控配置失败！'.format(\n                    dest_ip, services_data[0].get('service_name')))\n                # return False, promtail_result.get('return_message')\n        except Exception as e:\n            logger.error(e)\n            logger.error('向{}更新服务{}日志监控失败！'.format(\n                dest_ip, services_data[0].get('service_name')))\n            # return False, e\n\n        return True, 'success'\n\n    def add_service(self, service_data):\n        \"\"\"\n        service_data = {\n            \"service_name\": \"mysql\",\n            \"instance_name\": \"mysql_dosm\",\n            \"data_path\": \"/data/appData/mysql\",\n            \"log_path\": \"/data/logs/mysql\",\n            \"env\": \"default\",\n            \"ip\": \"127.0.0.1\",\n            \"listen_port\": \"3306\"\n            \"metric_port\": \"19018\"\n        }\n        添加有exporter的组件信息到各自的exporter监控\n        :param service_data: 新增的服务信息\n        :return:\n        \"\"\"\n        if not service_data:\n            return False, \"args cant be null\"\n\n        logger.info(f'收到信息：{service_data}')\n        job_name_str = \"{}Exporter\".format(service_data.get('service_name'))\n        prom_job_dict = {\n            \"job_name\": job_name_str,\n            \"metrics_path\": f\"/metrics/monitor/{service_data.get('service_name')}\",\n            \"file_sd_configs\": [\n                {\n                    \"refresh_interval\": \"30s\",\n                    \"files\": [\n                        f\"targets/{service_data.get('service_name')}Exporter_all.json\"\n                    ]\n                }\n            ]\n        }\n        with open(self.prometheus_conf_path, \"r\") as fr:\n            content = yaml.load(fr.read(), yaml.Loader)\n        content.get(\"scrape_configs\").append(prom_job_dict)\n        content[\"scrape_configs\"] = self.json_distinct(\n            content.get(\"scrape_configs\"))\n        with open(self.prometheus_conf_path, \"w\", encoding=\"utf8\") as fw:\n            yaml.dump(data=content, stream=fw,\n                      allow_unicode=True, sort_keys=False)\n        self_exporter_target_file = os.path.join(self.prometheus_targets_path,\n                                                 \"{}Exporter_all.json\".format(service_data[\"service_name\"]))\n        self_target_list = list()\n        self_target_ele = \"\"\n        try:\n            self_target_ele = {\n                \"labels\": {\n                    \"instance\": \"{}\".format(service_data[\"ip\"]),\n                    \"instance_name\": \"{}\".format(service_data.get(\"instance_name\")),\n                    \"service_type\": \"service\",\n                    \"env\": \"{}\".format(service_data[\"env\"])\n                },\n                \"targets\": [\n                    \"{}:{}\".format(service_data[\"ip\"], self.monitor_port)\n                ]\n            }\n        except KeyError as func_e:\n            logger.error(func_e)\n        self_target_list.append(self_target_ele)\n\n        if os.path.exists(self_exporter_target_file):\n            with open(self_exporter_target_file, 'r') as f:\n                content = f.read()\n                if content:\n                    old_self_target_list = json.loads(content)\n                    self_target_list.extend(old_self_target_list)\n        self_target_list = self.json_distinct(self_target_list)\n\n        with open(self_exporter_target_file, 'w') as f2:\n            json.dump(self_target_list, f2, ensure_ascii=False, indent=4)\n        flag, msg = self.update_agent_service(\n            service_data.get('ip'), 'add', [service_data])\n        if not flag:\n            return False, msg\n        # self.add_rules('service', service_data.get('env'))\n        reload_prometheus_url = 'http://localhost:19011/-/reload'\n        # TODO 确认重载prometheus动作在哪执行\n        try:\n            requests.post(reload_prometheus_url, auth=self.basic_auth)\n        except Exception as e:\n            logger.error(e)\n            logger.error(\"重载prometheus配置失败！\")\n        return True, \"success\"\n\n    def delete_service(self, service_data):\n        \"\"\"\n        从自有的exporter中删除对应的服务信息\n        :param service_data:\n        :return:\n        \"\"\"\n        if not service_data:\n            return False, \"args cant be null\"\n\n        self_exporter_target_file = os.path.join(self.prometheus_targets_path,\n                                                 \"{}Exporter_all.json\".format(service_data[\"service_name\"]))\n\n        self_target_list = list()\n        if os.path.exists(self_exporter_target_file):\n            with open(self_exporter_target_file, 'r') as f:\n                content = f.read()\n                if content:\n                    self_target_list = json.loads(content)\n        else:\n            logger.error(\"{}不存在！\".format(self_exporter_target_file))\n            return False, \"Failed\"\n        try:\n            instance = service_data['ip']\n            env = service_data['env']\n            for service in self_target_list:\n                if (instance == service[\"labels\"][\"instance\"]) and (env == service[\"labels\"][\"env\"]):\n                    self_target_list.remove(service)\n        except KeyError as func_e:\n            logger.error(func_e)\n\n        with open(self_exporter_target_file, 'w') as f2:\n            json.dump(self_target_list, f2, ensure_ascii=False, indent=4)\n        flag, msg = self.update_agent_service(\n            service_data.get('ip'), 'delete', [service_data])\n        if not flag:\n            return False, msg\n        reload_prometheus_url = 'http://localhost:19011/-/reload'\n        # TODO 确认重载prometheus动作在哪执行\n        try:\n            requests.post(reload_prometheus_url, auth=self.basic_auth)\n        except Exception as e:\n            logger.error(e)\n            logger.error(\"重载prometheus配置失败！\")\n        return True, \"success\"\n\n    def update_host_threshold(self, env=\"default\", env_id=1):\n        rules_file_placeholder_script = [\n            {\"ENV\": env},\n            {\"EMAIL_ADDRESS\": self.email_address}\n        ]\n\n        node_rule_yml_file = os.path.join(\n            self.prometheus_rules_path,\n            f\"{env}_node_rule.yml\"\n        )\n        if not os.path.exists(node_rule_yml_file):\n            shutil.copy(\n                self.prometheus_node_rule_tpl,\n                node_rule_yml_file\n            )\n            self.replace_placeholder(\n                node_rule_yml_file,\n                rules_file_placeholder_script\n            )\n        from utils.prometheus.update_threshold import config_update\n        try:\n            # 调用自监控脚本更新环境阈值\n            host_thresholds = HostThreshold.objects.filter(\n                env_id=env_id).values(\n                'index_type', 'condition', 'condition_value', 'alert_level')\n            services_objs = ServiceCustomThreshold.objects.filter(\n                env_id=env_id\n            ).order_by(\"service_name\", \"index_type\", \"condition_value\").values(\n                \"service_name\", \"index_type\", \"condition\",\n                \"condition_value\", \"alert_level\")\n            services_dict = {}\n            for services_obj in services_objs:\n                service_name = services_obj.pop(\"service_name\")\n                info = services_dict.get(service_name)\n                if not info:\n                    services_dict[service_name] = [services_obj]\n                else:\n                    services_dict[service_name].append(services_obj)\n            params = {\n                'env_name': env,\n                'hosts': list(host_thresholds),\n                'services': services_dict\n            }\n            # data_dir = get_path_dir(env_id)\n            # if data_dir:\n            #     params.update(disk_data_path=data_dir)  # TODO 补充替换数据分区阈值的逻辑\n            update_result = config_update(params)\n            if not update_result:\n                return False, \"failed\"\n            return True, \"success\"\n        except Exception as e:\n            import traceback\n            logger.error(f\"同步监控指标出错:{e}{traceback.format_exc()}\")\n            return False, \"failed\"\n\n    def gen_one_rule(self, **kwargs):  # NOQA\n        \"\"\"\n        生成单个规则\n\n        \"\"\"\n        expr = kwargs.get(\"expr\")\n        compare_str = kwargs.get(\"compare_str\")\n        for_time = kwargs.get(\"for_time\")\n        alert = kwargs.get(\"alert\")\n        summary = kwargs.get(\"summary\")\n        description = kwargs.get(\"description\")\n        threshold_value = kwargs.get(\"threshold_value\")\n        expr_data = f\"{expr} {compare_str} {threshold_value}\"\n        labels = kwargs.get(\"labels\")\n        one_rule = {\n            \"alert\": alert,\n            \"annotations\": {\n                \"description\": description,\n                \"summary\": summary,\n            },\n            \"expr\": expr_data,\n            \"for\": for_time,\n            \"labels\": labels\n        }\n        return one_rule\n\n    def get_hash_value(self, expr, severity):  # NOQA\n        data = expr + severity\n        hash_data = hashlib.md5(data.encode(encoding='UTF-8')).hexdigest()\n        return hash_data\n\n    def add_data_disk_rules(self, data_path, env=\"default\"):\n        \"\"\"\n        \"\"\"\n        # threshold = \"80\" if level == \"warning\" else 90\n        logger.error(\"开始添加数据分区规则\")\n        threshold_list = [(\"warning\", \"80\"), (\"critical\", \"90\")]\n        for i in range(2):\n            info = threshold_list[i]\n            level = info[0]\n            threshold = info[1]\n            expr = 'max((node_filesystem_size_bytes{env=\"ENV\",' \\\n                   'mountpoint=\"DATA_PATH\"}-node_filesystem_free_bytes{env=\"ENV\",' \\\n                   'mountpoint=\"DATA_PATH\"})*100/(node_filesystem_avail_bytes{' \\\n                   'env=\"ENV\",mountpoint=\"DATA_PATH\"}+(node_filesystem_size_bytes{' \\\n                   'env=\"ENV\",mountpoint=\"DATA_PATH\"}-node_filesystem_free_bytes{' \\\n                   'env=\"ENV\",mountpoint=\"DATA_PATH\"}))) by (instance,env)'.replace(\n                \"ENV\", env).replace(\"DATA_PATH\", data_path)\n            data = {\n                \"alert\": f\"主机数据分区{data_path}磁盘使用率\",\n                \"description\": '主机 {{ $labels.instance }} 数据分区使用率为 {{ $value | humanize }}%, 大于阈值 $threshold$%'.replace(\n                    \"$threshold$\", threshold),\n                \"expr\": expr,\n                \"compare_str\": \">=\",\n                \"threshold_value\": threshold,\n                \"for_time\": \"60s\",\n                \"severity\": level,\n                \"labels\": {\n                    \"job\": \"nodeExporter\",\n                    \"severity\": level\n                },\n                \"name\": \"数据分区使用率\",\n                \"quota_type\": 0,\n                \"status\": 1,\n                \"service\": \"node\",\n                \"forbidden\": 2,\n\n            }\n            hash_data = self.get_hash_value(expr=expr, severity=level)\n            if AlertRule.objects.filter(expr=expr, severity=level, hash_data=hash_data).exists():\n                continue\n            try:\n                data.update(hash_data=hash_data)\n                AlertRule(**data).save()\n            except Exception as e:\n                logger.error(f\"更新数据分区错误:{e}\")\n        self.update_rule_file(env=env)\n        return True\n\n    def update_rule_file(self, add_data=None, add=False, update=False, delete=False, env=\"default\", rule_id=0,\n                         env_id=1):\n        \"\"\"\n        更新规则文件\n        \"\"\"\n        rule_file_path = os.path.join(\n            self.prometheus_rules_path, f\"{env}_rule.yml\")\n        try:\n            all_rules = AlertRule.objects.filter(status=1).all()\n            if delete or update:\n                all_rules = AlertRule.objects.filter(\n                    status=1).exclude(id=rule_id).all()\n            init_data = {\"groups\": [\n                {\n                    \"name\": \"OMP Alert\",\n                    \"rules\": []\n                }\n            ]}\n            for rule in all_rules:\n                content = {\n                    \"alert\": rule.alert,\n                    \"annotations\": {\n                        \"description\": rule.description,\n                        \"summary\": rule.summary,\n                    },\n                    \"expr\": f\"{rule.expr} {rule.compare_str} {rule.threshold_value}\",\n                    \"for\": rule.for_time,\n                    \"labels\": rule.labels,\n                }\n                init_data[\"groups\"][0][\"rules\"].append(content)\n            if add or update:\n                init_data[\"groups\"][0][\"rules\"].append(\n                    self.gen_one_rule(**add_data)\n                )\n            my_yaml = YAML()\n            with open(rule_file_path, \"w\", encoding=\"utf8\") as fp:\n                my_yaml.dump(init_data, fp)\n\n            return True\n        except Exception as e:\n            logger.error(f\"生成规则文件{rule_file_path}失败{e}\")\n            return False\n\n    def reload_prometheus(self):\n        \"\"\"\n        重载prometheus\n        \"\"\"\n        reload_prometheus_url = 'http://localhost:19011/-/reload'\n        # TODO 确认重载prometheus动作在哪执行\n        try:\n            response = requests.post(\n                reload_prometheus_url, auth=self.basic_auth)\n            logger.error(f\"重载成功 {response.text}\")\n            return True\n        except Exception as e:\n            logger.error(e)\n            logger.error(\"重载prometheus配置失败！\")\n            return False\n"
  },
  {
    "path": "omp_server/promemonitor/tasks.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: tasks\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-09 09:17\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n监控端异步任务\n\"\"\"\n\nimport os\nimport logging\nimport traceback\n\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\n\nfrom db_models.models import Host\nfrom utils.plugin.salt_client import SaltClient\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\ndef real_monitor_agent_restart(host_obj):\n    \"\"\"\n    重启监控Agent\n    :param host_obj: 主机对象\n    :type host_obj Host\n    :return:\n    \"\"\"\n    logger.info(\n        f\"Restart Monitor Agent for {host_obj.ip}, Params: \"\n        f\"username: {host_obj.username}; \"\n        f\"port: {host_obj.port}; \"\n        f\"install_dir: {host_obj.agent_dir}!\")\n    salt_obj = SaltClient()\n    _script_path = os.path.join(\n        host_obj.agent_dir, \"omp_monitor_agent/monitor_agent.sh\")\n    flag, message = salt_obj.cmd(\n        target=host_obj.ip,\n        command=f\"bash {_script_path} restart\",\n        timeout=60\n    )\n    logger.info(\n        f\"Restart monitor agent for {host_obj.ip}: \"\n        f\"get flag: {flag}; get res: {message}\")\n    if flag:\n        Host.objects.filter(ip=host_obj.ip).update(monitor_agent=0)\n    else:\n        Host.objects.filter(ip=host_obj.ip).update(\n            monitor_agent=2,\n            monitor_agent_error=str(message)[:200] if len(\n                str(message)) > 200 else str(message)\n        )\n\n\n@shared_task\ndef monitor_agent_restart(host_id):\n    \"\"\"\n    主机Agent的重启操作\n    :param host_id: 主机的id\n    :return:\n    \"\"\"\n    try:\n        host_obj = Host.objects.get(id=host_id)\n        real_monitor_agent_restart(host_obj=host_obj)\n    except Exception as e:\n        logger.error(\n            f\"Restart Monitor Agent For {host_id} Failed with error: \"\n            f\"{str(e)};\\ndetail: {traceback.format_exc()}\"\n        )\n        Host.objects.filter(id=host_id).update(\n            monitor_agent=2, monitor_agent_error=str(e))\n"
  },
  {
    "path": "omp_server/promemonitor/urls.py",
    "content": "\"\"\"\n监控相关的路由\n\"\"\"\nfrom rest_framework.routers import DefaultRouter\n\nfrom promemonitor.custom_script_views import CustomScriptViewSet, CustomScriptJobInfoView\nfrom promemonitor.views import (\n    MonitorUrlViewSet, ListAlertViewSet, UpdateAlertViewSet,\n    MaintainViewSet, ReceiveAlertViewSet,\n    MonitorAgentRestartView, GrafanaUrlViewSet, InstanceNameListView,\n    InstrumentPanelView, GetSendEmailConfig, UpdateSendEmailConfig,\n    GetSendAlertSettingView, UpdateSendAlertSettingView, HostThresholdView,\n    ServiceThresholdView, CustomThresholdView, BuiltinsRuleView, QuotaView, PromSqlTestView, BatchUpdateRuleView\n)\n\nrouter = DefaultRouter()\nrouter.register(r'monitorurl', MonitorUrlViewSet)\nrouter.register(r'listAlert', ListAlertViewSet, basename='listAlert')\nrouter.register(r'updateAlert', UpdateAlertViewSet, basename='updateAlert')\nrouter.register(r'restartMonitorAgent',\n                MonitorAgentRestartView, basename=\"restartMonitorAgent\")\nrouter.register(\"globalMaintain\", MaintainViewSet, basename='globalMaintain')\nrouter.register(r'receiveAlert', ReceiveAlertViewSet,\n                basename='receiveAlert')\nrouter.register(r'grafanaurl', GrafanaUrlViewSet, basename=\"grafanaurl\")\nrouter.register(r'instanceNameList', InstanceNameListView,\n                basename='instanceNameList')\nrouter.register(r\"instrumentPanel\", InstrumentPanelView,\n                basename=\"instrumentPanel\")\nrouter.register(r'getSendEmailConfig', GetSendEmailConfig,\n                basename='getSendEmailConfig')\nrouter.register(r'updateSendEmailConfig', UpdateSendEmailConfig,\n                basename='updateSendEmailConfig')\nrouter.register(r'getSendAlertSetting', GetSendAlertSettingView,\n                basename='getSendAlertSetting')\nrouter.register(r'updateSendAlertSetting',\n                UpdateSendAlertSettingView, basename='updateSendAlertSetting')\nrouter.register(r'hostThreshold', HostThresholdView, basename='hostThreshold')\nrouter.register(r'serviceThreshold', ServiceThresholdView,\n                basename='serviceThreshold')\nrouter.register(r'customThreshold', CustomThresholdView,\n                basename='customThreshold')\nrouter.register(r'customScript', CustomScriptViewSet, basename='customScript')\nrouter.register(r'customScriptJobInfo', CustomScriptJobInfoView,\n                basename='customScriptJobInfo')\nrouter.register(r'builtinRule', BuiltinsRuleView, basename=\"builtinRule\")\nrouter.register(r'quota', QuotaView, basename=\"quota\")\nrouter.register(r'testPromSql', PromSqlTestView, basename=\"testPromSql\")\nrouter.register(r'batchUpdateRule', BatchUpdateRuleView,\n                basename=\"batchUpdateRule\")\nurlpatterns = router.urls\n"
  },
  {
    "path": "omp_server/promemonitor/views.py",
    "content": "# Create your views here.\n\"\"\"\n监控相关视图\n\"\"\"\nimport json\nimport logging\nimport traceback\n\nimport requests\nfrom django.core.validators import EmailValidator\nfrom django.db import transaction\nfrom django.db.models import F\nfrom django.shortcuts import get_object_or_404\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom rest_framework.decorators import action\nfrom rest_framework.filters import OrderingFilter\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin\n)\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import Serializer\nfrom rest_framework.viewsets import GenericViewSet\n\nfrom db_models.models import (\n    Host, MonitorUrl,\n    Alert, Maintain, ApplicationHub, Service, EmailSMTPSetting,\n    AlertSendWaySetting, HostThreshold, ServiceThreshold,\n    ServiceCustomThreshold, Rule, AlertRule, Env\n)\nfrom omp_server.settings import CUSTOM_THRESHOLD_SERVICES\nfrom promemonitor import grafana_url\nfrom promemonitor.alert_util import utc_to_local, get_monitor_url, get_log_url\nfrom promemonitor.promemonitor_filters import AlertFilter, MyTimeFilter, \\\n    QuotaFilter\nfrom promemonitor.promemonitor_serializers import (\n    MonitorUrlSerializer, ListAlertSerializer, UpdateAlertSerializer,\n    MaintainSerializer, MonitorAgentRestartSerializer,\n    ReceiveAlertSerializer, RuleSerializer, QuotaSerializer\n)\nfrom promemonitor.prometheus import Prometheus\nfrom utils.common.exceptions import OperateError\nfrom utils.common.paginations import PageNumberPager\nfrom utils.parse_config import PROMETHEUS_AUTH\nfrom promemonitor.prometheus_utils import PrometheusUtils\n\nlogger = logging.getLogger('server')\n\n\nclass MonitorUrlViewSet(ListModelMixin, CreateModelMixin, GenericViewSet):\n    \"\"\"\n        list:\n        查询监控地址列表\n\n        create:\n        创建一批监控配置\n\n        multiple_update:\n        更新一个或多个监控配置一个或多个字段\n    \"\"\"\n    serializer_class = MonitorUrlSerializer\n    queryset = MonitorUrl.objects.all()\n    get_description = \"查询监控地址配置\"\n    patch_description = \"修改监控地址配置\"\n\n    def get_serializer(self, *args, **kwargs):\n        serializer_class = self.get_serializer_class()\n        kwargs.setdefault('context', self.get_serializer_context())\n        if self.request:\n            if isinstance(self.request.data.get(\"data\"), list):\n                return serializer_class(many=True, *args, **kwargs)\n            return serializer_class(*args, **kwargs)\n        else:\n            return serializer_class(*args, **kwargs)\n\n    @action(methods=['patch'], detail=False)\n    def multiple_update(self, request, *args, **kwargs):\n        partial = kwargs.pop('partial', True)\n        instances = []\n        for item in request.data.get('data'):\n            instance = get_object_or_404(MonitorUrl, id=int(item['id']))\n            serializer = super().get_serializer(instance, data=item,\n                                                partial=partial)\n            serializer.is_valid(raise_exception=True)\n            serializer.save()\n            instances.append(serializer.data)\n        return Response(instances)\n\n\nclass GrafanaUrlViewSet(ListModelMixin, GenericViewSet):\n    \"\"\"\n        list:\n        查询异常清单列表\n    \"\"\"\n    queryset = MonitorUrl.objects.all()\n\n    def list(self, request, *args, **kwargs):\n        params = request.query_params.dict()\n        asc = params.pop('asc', '0')\n        asc = True if asc == '0' else False\n        ordering = params.pop('ordering', 'date')\n        current = grafana_url.explain_prometheus(params)\n        if current == \"error\":\n            raise OperateError(\"prometheus获取数据失败，请检查prometheus状态\")\n        prometheus_info = sorted(\n            current, key=lambda e: e.__getitem__(ordering), reverse=asc)\n        # prometheus_json = json.dumps(prometheus_info, ensure_ascii=False)\n        return Response(prometheus_info)\n\n\nclass ListAlertViewSet(ListModelMixin, GenericViewSet):\n    \"\"\"\n    获取告警记录列表视图类\n    \"\"\"\n    serializer_class = ListAlertSerializer\n    queryset = Alert.objects.all().order_by(\"-create_time\")  # 分页，过滤，排序\n    pagination_class = PageNumberPager\n    filter_backends = (\n        DjangoFilterBackend,\n        OrderingFilter,\n        MyTimeFilter,\n    )\n    filter_class = AlertFilter\n    ordering_fields = (\"alert_host_ip\", \"alert_instance_name\", \"alert_time\")\n\n\nclass UpdateAlertViewSet(CreateModelMixin, GenericViewSet):\n    \"\"\"\n    更新告警记录视图类\n    \"\"\"\n    serializer_class = UpdateAlertSerializer\n    queryset = Alert.objects.all().order_by('id')\n    post_description = \"更新告警记录（已读/未读）\"\n\n\nclass MaintainViewSet(GenericViewSet, CreateModelMixin, ListModelMixin):\n    \"\"\"\n    create:\n    全局进入 / 退出维护模式\n    \"\"\"\n    queryset = Maintain.objects.filter(\n        matcher_name='env', matcher_value='default')\n    serializer_class = MaintainSerializer\n    # 操作信息描述\n    post_description = \"更新全局维护状态\"\n\n\nclass ReceiveAlertViewSet(GenericViewSet, CreateModelMixin):\n    \"\"\"\n    接收alertmanager发送过来的告警消息后解析入库\n    \"\"\"\n    queryset = None\n    serializer_class = ReceiveAlertSerializer\n    # 操作信息描述\n    post_description = \"接收alertmanager告警信息\"\n    # 关闭权限、认证设置\n    authentication_classes = ()\n    permission_classes = ()\n\n\nclass MonitorAgentRestartView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        重启监控Agent接口\n    \"\"\"\n    queryset = Host.objects.filter(is_deleted=False)\n    serializer_class = MonitorAgentRestartSerializer\n    # 操作信息描述\n    post_description = \"重启监控Agent\"\n\n\nclass InstanceNameListView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    返回主机和服务实例名列表\n    \"\"\"\n    # 操作信息描述\n    post_description = \"返回主机和服务实例名列表\"\n\n    def list(self, request, *args, **kwargs):\n        alert_instance_name_list = list()\n        host_instance_name_list = list(Host.objects.all().values_list(\n            'instance_name', flat=True))\n        service_instance_name_list = []  # TODO 待应用模型完善\n        alert_instance_name_list.append(host_instance_name_list)\n        alert_instance_name_list.append(service_instance_name_list)\n        return Response(alert_instance_name_list)\n\n\nclass InstrumentPanelView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    返回仪表盘所需数据\n    \"\"\"\n    # 操作信息描述\n    get_description = \"查询仪表盘数据\"\n\n    @staticmethod\n    def get_prometheus_alerts():\n        \"\"\"\n        请求prometheus alerts接口返回告警内容\n        \"\"\"\n        mu = MonitorUrl.objects.filter(name=\"prometheus\").first()\n        prometheus_url = mu.monitor_url if mu else \"127.0.0.1:19011\"\n        prometheus_auth = (PROMETHEUS_AUTH.get(\n            \"username\", \"omp\"), PROMETHEUS_AUTH.get(\"plaintext_password\", \"\"))\n        try:\n            prometheus_alerts_url = f\"http://{prometheus_url}/api/v1/alerts\"\n            # NOQA\n            response = requests.get(prometheus_alerts_url, headers={\n            }, data=\"\", auth=prometheus_auth)\n            return True, json.loads(response.text)\n        except Exception as e:\n            logger.error(\"prometheus请求alerts失败：\" + str(e))\n            return False, \"Failed\"\n\n    def get_exc_serializer_info(self):\n        host_info_dict = {}\n        database_info_dict = {}\n        service_info_dict = {}\n        component_info_dict = {}\n        third_info_dict = {}\n\n        host_info_list = []\n        database_info_list = []\n        service_info_list = []\n        component_info_list = []\n        third_info_list = []\n        host_ip_list = []\n\n        host_list = Host.objects.values(\"ip\", \"instance_name\")\n        host_ip_list = [host.get(\"ip\") for host in host_list]\n        ignore_status_list = [Service.SERVICE_STATUS_NORMAL,\n                              Service.SERVICE_STATUS_STARTING,\n                              Service.SERVICE_STATUS_STOPPING,\n                              Service.SERVICE_STATUS_RESTARTING,\n                              Service.SERVICE_STATUS_STOP]\n        database_list = Service.objects.filter(\n            service__app_type=ApplicationHub.APP_TYPE_COMPONENT).filter(\n            service__app_labels__label_name__contains=\"数据库\").filter(\n            service_status__in=ignore_status_list).filter(\n            service__is_base_env=False)\n        service_list = Service.objects.filter(\n            service__app_type=ApplicationHub.APP_TYPE_SERVICE).filter(\n            service_status__in=ignore_status_list).filter(\n            service__is_base_env=False).filter(\n            service_controllers__start__isnull=False)\n        component_list = Service.objects.filter(\n            service__app_type=ApplicationHub.APP_TYPE_COMPONENT).filter(\n            service_status__in=ignore_status_list).filter(\n            service__is_base_env=False)\n        # third_info_all = None  # TODO 暂为空\n\n        host_info_all_count = len(host_list)\n        database_info_all_count = len(database_list)\n        service_info_all_count = len(service_list)\n        component_info_all_count = len(component_list)\n        third_info_all_count = 0  # TODO 暂为空\n\n        # host_info_exc_count = 0\n        database_info_exc_count = 0\n        service_info_exc_count = 0\n        component_info_exc_count = 0\n        third_info_exc_count = 0\n\n        host_info_no_monitor_count = 0\n        database_info_no_monitor_count = 0\n        service_info_no_monitor_count = 0\n        component_info_no_monitor_count = 0\n        third_info_no_monitor_count = 0\n\n        flag, alert_data = self.get_prometheus_alerts()\n        error_database_list = list()\n        error_service_list = list()\n        error_component_list = list()\n        error_host_list = list()\n        if flag:\n            alerts = alert_data.get('data').get('alerts')\n            for ele in alerts:\n                if not isinstance(ele, dict):\n                    continue\n                if ele.get(\"status\") == \"resolved\" or ele.get(\n                        \"state\") == \"resolved\" or ele.get(\"labels\").get(\"instance\") not in host_ip_list:\n                    continue\n                _ip = ele.get(\"labels\").get(\"instance\", \"\")\n                service_instance_str = ele.get(\"labels\").get(\"instance_name\", \"\")\n                if len(service_instance_str) < 1:\n                    app_name_str = ele.get(\"labels\").get(\"app\") if ele.get(\"labels\").get(\"app\") else \\\n                        ele.get(\"labels\").get(\"job\", \"\").split(\"Exporter\")[0]\n                    service_instance_str = f\"{app_name_str}-{_ip.split('.')[2]}-{_ip.split('.')[3]}\"\n                if len(service_instance_str) < 1:\n                    continue\n                ele_dict = {\n                    \"ip\": _ip,\n                    \"instance_name\": service_instance_str,\n                    \"alertname\": ele.get(\"labels\").get(\"alertname\"),\n                    \"severity\": ele.get(\"labels\").get(\"severity\"),\n                    \"date\": utc_to_local(ele.get(\"activeAt\")),\n                    \"describe\": ele.get(\"annotations\").get(\"description\"),\n                    \"monitor_url\": get_monitor_url([{\n                        \"ip\": ele.get(\"labels\").get(\"instance\"),\n                        \"type\": \"service\",\n                        \"instance_name\": ele.get(\"labels\").get(\"instance_name\", \"\")\n                    }]),\n                    \"log_url\": get_log_url([{\n                        \"ip\": ele.get(\"labels\").get(\"instance\"),\n                        \"type\": \"service\",\n                        \"instance_name\": ele.get(\"labels\").get(\"instance_name\", \"\")\n                    }])\n                }\n                if ele.get(\"labels\").get(\"job\") == \"nodeExporter\":\n                    ele_dict[\"monitor_url\"] = get_monitor_url([{\n                        \"ip\": ele.get(\"labels\").get(\"instance\"),\n                        \"type\": \"host\",\n                        \"instance_name\": \"node\"\n                    }])\n                    ele_dict[\"log_url\"] = get_log_url([{\n                        \"ip\": ele.get(\"labels\").get(\"instance\"),\n                        \"type\": \"host\",\n                        \"instance_name\": \"node\"\n                    }])\n                    host_info_list.append(ele_dict)\n                    error_host_list.append(ele.get(\"labels\").get(\"instance\"))\n                if list(filter(\n                        lambda x: x.service_instance_name == service_instance_str,\n                        list(database_list))):\n                    error_database_list.append(service_instance_str)\n                    database_info_list.append(ele_dict)\n                    error_component_list.append(service_instance_str)\n                    component_info_list.append(ele_dict)\n                elif list(filter(\n                        lambda x: x.service_instance_name == service_instance_str, list(\n                            service_list)\n                )):\n                    error_service_list.append(service_instance_str)\n                    service_info_list.append(ele_dict)\n                elif list(filter(\n                        lambda x: x.service_instance_name == service_instance_str, list(\n                            component_list)\n                )):\n                    error_component_list.append(service_instance_str)\n                    component_info_list.append(ele_dict)\n                else:\n                    continue\n        for index, hil in enumerate(host_info_list):\n            if hil.get(\"severity\") == \"warning\":\n                for i, h in enumerate(host_info_list):\n                    if index == i:\n                        continue\n                    if hil.get(\"ip\") == h.get(\"ip\") and hil.get(\n                            \"alertname\") == h.get(\"alertname\") and h.get(\n                        \"severity\") == \"critical\":\n                        host_info_list.pop(index)\n                        break\n            elif hil.get(\"severity\") == \"critical\":\n                for i, h in enumerate(host_info_list):\n                    if index == i:\n                        continue\n                    if hil.get(\"ip\") == h.get(\"ip\") and hil.get(\n                            \"alertname\") == h.get(\"alertname\") and h.get(\n                        \"severity\") == \"warning\":\n                        host_info_list.pop(i)\n                        break\n        _, host_targets = Prometheus().get_all_host_targets()\n        for host in host_list:\n            if host.get(\"ip\") in error_host_list:\n                continue\n            host_dict = {\n                \"ip\": host.get(\"ip\"), \"instance_name\": host.get(\"instance_name\"),\n                \"severity\": \"normal\"}\n            if host.get(\"ip\") not in host_targets:\n                host_dict = {\n                    \"ip\": host.get(\"ip\"), \"instance_name\": host.get(\"instance_name\"),\n                    \"severity\": \"unmonitored\"}\n            host_info_list.append(host_dict)\n\n        _, service_targets = Prometheus().get_all_service_targets()\n        for database in database_list:\n            if database.service_instance_name in error_database_list:\n                continue\n            database_dict = {\"ip\": database.ip,\n                             \"instance_name\": database.service_instance_name,\n                             \"app_name\": database.service.app_name,\n                             \"severity\": \"normal\"}\n            database_ip_instance_name_str = \\\n                f\"{database.ip}_{database.service_instance_name}\"\n            if database_ip_instance_name_str not in service_targets:\n                database_dict = {\"ip\": database.ip,\n                                 \"instance_name\":\n                                     database.service_instance_name,\n                                 \"app_name\": database.service.app_name,\n                                 \"severity\": \"unmonitored\"}\n            database_info_list.append(database_dict)\n        for service in service_list:\n            if service.service_instance_name in error_service_list:\n                continue\n            service_dict = {\"ip\": service.ip,\n                            \"instance_name\": service.service_instance_name,\n                            \"app_name\": service.service.app_name,\n                            \"severity\": \"normal\"}\n            service_ip_instance_name_str = f\"{service.ip}_\" \\\n                                           f\"{service.service_instance_name}\"\n            if service_ip_instance_name_str not in service_targets:\n                service_dict = {\"ip\": service.ip,\n                                \"instance_name\": service.service_instance_name,\n                                \"app_name\": service.service.app_name,\n                                \"severity\": \"unmonitored\"}\n            service_info_list.append(service_dict)\n        for component in component_list:\n            if component.service_instance_name in error_component_list:\n                continue\n            component_dict = {\"ip\": component.ip,\n                              \"instance_name\": component.service_instance_name,\n                              \"app_name\": component.service.app_name,\n                              \"severity\": \"normal\"}\n            component_ip_instance_name_str = f\"{component.ip}_\" \\\n                                             f\"{component.service_instance_name}\"\n            if component_ip_instance_name_str not in service_targets:\n                component_dict = {\"ip\": component.ip,\n                                  \"instance_name\":\n                                      component.service_instance_name,\n                                  \"app_name\": component.service.app_name,\n                                  \"severity\": \"unmonitored\"}\n            component_info_list.append(component_dict)\n\n        host_info_exc_count = len(set(error_host_list))\n        database_info_exc_count = len(set(error_database_list))\n        service_info_exc_count = len(set(error_service_list))\n        component_info_exc_count = len(set(error_component_list))\n\n        host_info_dict.update({\n            \"host_info_all_count\": host_info_all_count,\n            \"host_info_exc_count\": host_info_exc_count,\n            \"host_info_no_monitor_count\": host_info_no_monitor_count,\n            \"host_info_list\": host_info_list\n        })\n        database_info_dict.update({\n            \"database_info_all_count\": database_info_all_count,\n            \"database_info_exc_count\": database_info_exc_count,\n            \"database_info_no_monitor_count\": database_info_no_monitor_count,\n            \"database_info_list\": database_info_list\n        })\n        service_info_dict.update({\n            \"service_info_all_count\": service_info_all_count,\n            \"service_info_exc_count\": service_info_exc_count,\n            \"service_info_no_monitor_count\": service_info_no_monitor_count,\n            \"service_info_list\": service_info_list\n        })\n        component_info_dict.update({\n            \"component_info_all_count\": component_info_all_count,\n            \"component_info_exc_count\": component_info_exc_count,\n            \"component_info_no_monitor_count\": component_info_no_monitor_count,\n            \"component_info_list\": component_info_list\n        })\n        third_info_dict.update({\n            \"third_info_all_count\": third_info_all_count,\n            \"third_info_exc_count\": third_info_exc_count,\n            \"third_info_no_monitor_count\": third_info_no_monitor_count,\n            \"third_info_list\": third_info_list\n        })\n        serializer_info = {\n            \"host\": host_info_dict,\n            \"database\": database_info_dict,\n            \"service\": service_info_dict,\n            \"component\": component_info_dict,\n            \"third\": third_info_dict,\n        }\n        return serializer_info\n\n    def list(self, request, *args, **kwargs):\n        result = self.get_exc_serializer_info()\n        return Response(result)\n\n\nclass GetSendEmailConfig(GenericViewSet, ListModelMixin):\n    \"\"\"\n    获取邮件发送配置\n    \"\"\"\n    get_description = \"获取邮件发送配置\"\n\n    def list(self, request, *args, **kwargs):\n        email_setting = EmailSMTPSetting.objects.first()\n        if email_setting:\n            return Response(data=email_setting.get_dict())\n        return Response({})\n\n\nclass UpdateSendEmailConfig(GenericViewSet, CreateModelMixin):\n    \"\"\"\n    更新邮件发送配置\n    \"\"\"\n    post_description = \"更新邮件发送配置\"\n\n    serializer_class = Serializer\n\n    def create(self, request, *args, **kwargs):\n        email_host = request.data.get(\"host\")\n        if not email_host:\n            return Response(data={\"code\": 1, \"message\": \"请填写SMTP邮件服务器地址！\"})\n        email_port = request.data.get(\"port\")\n        if not email_port or not isinstance(email_port, int):\n            return Response(\n                data={\"code\": 1, \"message\": \"请检查所填的SMTP邮件服务器端口是否正确！\"})\n        email_host_user = request.data.get(\"username\")\n        if not email_host_user:\n            return Response(data={\"code\": 1, \"message\": \"请填写SMTP邮件服务器发件箱！\"})\n        try:\n            EmailValidator()(email_host_user)\n        except Exception as e:\n            message = \"SMTP邮件服务器发件箱格式错误！\"\n            logger.error(f\"{message} 错误信息：{str(e)}\")\n            return Response(data={\"code\": 1, \"message\": message})\n        email_host_password = request.data.get(\"password\")\n        if not email_host_password:\n            return Response(data={\"code\": 1, \"message\": \"填写SMTP邮件服务器发件箱格式错误！\"})\n        try:\n            EmailSMTPSetting.objects.all().delete()\n            email_setting = EmailSMTPSetting.objects.create(\n                email_host=email_host,\n                email_port=email_port,\n                email_host_user=email_host_user,\n                email_host_password=email_host_password\n            )\n        except Exception as e:\n            message = \"修改email邮箱服务器信息出错！\"\n            logger.error(f\"{message} 详细信息：{str(e)}\")\n            return Response(data={\"code\": 1, \"message\": message})\n        state, email_url = email_setting.update_setting_config()\n        if not state:\n            return Response(data={\"code\": 1, \"message\": \"同步到Alert Manage失败！\"})\n        return Response({})\n\n\nclass GetSendAlertSettingView(GenericViewSet, ListModelMixin):\n    \"\"\"\n    获取监控邮箱收件配置\n    \"\"\"\n    get_description = \"获取监控邮箱收件配置\"\n\n    def list(self, request, *args, **kwargs):\n        env_id = request.GET.get(\"env_id\", 0)\n        filter_kwargs = dict(env_id=env_id)\n        way_name = request.GET.get(\"way_name\")\n        if way_name:\n            filter_kwargs[\"way_name\"] = way_name\n        objs = AlertSendWaySetting.objects.filter(**filter_kwargs)\n        data = dict()\n        for obj in objs:\n            data.update({obj.way_name: obj.get_self_dict()})\n        if \"email\" not in data:\n            data[\"email\"] = AlertSendWaySetting.get_v1_5_email_dict(env_id)\n        return Response(data=data)\n\n\nclass UpdateSendAlertSettingView(GenericViewSet, CreateModelMixin):\n    \"\"\"更新监控邮箱收件配置\"\"\"\n    post_description = \"更新监控邮箱收件配置\"\n\n    serializer_class = Serializer\n\n    def create(self, request, *args, **kwargs):\n        env_id = request.data.get(\"env_id\")\n        alert_setting, _ = AlertSendWaySetting.objects.get_or_create(\n            env_id=env_id, way_name=\"email\"\n        )\n        used = request.data.get(\"used\", False)\n        emails = request.data.get(\"server_url\", \"\")\n        if emails:\n            for email in emails.split(\",\"):\n                try:\n                    EmailValidator()(email)\n                except Exception as e:\n                    message = f\"收件箱{email}格式错误！\"\n                    logger.error(f\"{message} 错误信息：{str(e)}\")\n                    return Response(data={\"code\": 1, \"message\": message})\n        AlertSendWaySetting.update_email_config(bool(used), emails)\n        email_setting = EmailSMTPSetting.objects.first()\n        if not email_setting:\n            return Response(\n                data={\"code\": 1, \"message\": \"邮箱SMTP服务器未配置，配置后才可发送告警邮件！\"})\n        state, email_url = email_setting.update_setting_config()\n        if not state:\n            return Response(data={\"code\": 1,\n                                  \"message\": \"同步到Alert Manage失败！请确保Alert \"\n                                             \"Manage可用\"})\n        return Response({})\n\n\nclass HostThresholdView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n    读写主机阈值\n    \"\"\"\n\n    get_description = \"读取主机阈值设置\"\n    post_description = \"更新主机阈值设置\"\n\n    serializer_class = Serializer\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取主机监控指标项设置\n        \"\"\"\n        env_id = request.GET.get('env_id')\n        if not env_id:\n            return Response(data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n        if not HostThreshold.objects.filter(env_id=env_id).exists():\n            return Response(data={\"code\": 1, \"message\": f\"env {env_id}错误\"})\n        host_thresholds = HostThreshold.objects.filter(\n            env_id=env_id,\n            index_type__in=[\"cpu_used\", \"memory_used\", \"disk_root_used\",\n                            \"disk_data_used\"]\n        ).annotate(\n            value=F(\"condition_value\"), level=F(\"alert_level\")\n        ).order_by(\"index_type\", \"level\").values(\n            \"index_type\", \"condition\", \"value\", \"level\")\n        data = {\n            \"cpu_used\": [],\n            \"memory_used\": [],\n            \"disk_root_used\": [],\n            \"disk_data_used\": []\n        }\n        for host_threshold in host_thresholds:\n            data[host_threshold.get(\"index_type\")].append(host_threshold)\n        return Response(data=data)\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        更新主机指标项到自监控平台\n        \"\"\"\n        try:\n            logger.info(f\"主机监控指标更新接口获取到的参数为: {request.data}\")\n            update_data = request.data.get(\"update_data\", {})\n            env_id = request.data.get(\"env_id\")\n            if not update_data:\n                return Response(data={\"code\": 1, \"message\": \"无法正确解析到要更新的数据!\"})\n            if env_id is None:\n                return Response(\n                    data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n            # 同步阈值至prometheus主机告警规则文件中，并做配置检查\n            # if not check_prometheus():\n            #     return Response(1, \"无法连接到prometheus，更改阈值失败！\")\n            _obj_lst = list()\n            hosts_list = list()\n            for key, value in update_data.items():\n                for item in value:\n                    if not item[\"condition\"] or not item[\"value\"] or not item[\n                        \"level\"]:\n                        continue\n                    _obj = HostThreshold()\n                    _obj.index_type = key\n                    _obj.condition = item[\"condition\"]\n                    _obj.condition_value = item[\"value\"]\n                    _obj.alert_level = item[\"level\"]\n                    _obj.env_id = env_id\n                    _obj_lst.append(_obj)\n                    hosts_list.append({\n                        'index_type': key,\n                        'condition': item[\"condition\"],\n                        'condition_value': item[\"value\"],\n                        'alert_level': item[\"level\"]\n                    })\n            with transaction.atomic():\n                HostThreshold.objects.filter(env_id=env_id).delete()\n                HostThreshold.objects.bulk_create(_obj_lst)\n\n            from promemonitor.prometheus_utils import PrometheusUtils\n            prometheus_util = PrometheusUtils()\n            prometheus_util.update_host_threshold(env_id=env_id)\n            data_disk_dir_list = list(Host.objects.all().values_list(\n                \"data_folder\", flat=True).distinct())\n            for ele in data_disk_dir_list:\n                prometheus_util.update_node_data_rule(ele)\n\n            return Response({})\n        except Exception as e:\n            logger.error(f\"更新主机相关阈值过程中出错: {traceback.format_exc()}\")\n            return Response(\n                data={\"code\": 1, \"message\": f\"更新主机相关阈值过程中出错: {str(e)}!\"})\n\n\nclass ServiceThresholdView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n    读写服务阈值\n    \"\"\"\n\n    get_description = \"读取服务阈值设置\"\n    post_description = \"更新服务阈值设置\"\n\n    serializer_class = Serializer\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        获取服务阈值监控指标项设置\n        \"\"\"\n        env_id = request.GET.get('env_id')\n        if not env_id:\n            return Response(data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n        if not ServiceThreshold.objects.filter(env_id=env_id).exists():\n            return Response(data={\"code\": 1, \"message\": f\"env {env_id}错误\"})\n        service_thresholds = ServiceThreshold.objects.filter(\n            env_id=env_id,\n            index_type__in=[\"service_active\", \"service_cpu_used\",\n                            \"service_memory_used\"]\n        ).annotate(\n            value=F(\"condition_value\"), level=F(\"alert_level\")\n        ).order_by(\"index_type\", \"level\").values(\n            \"index_type\", \"condition\", \"value\", \"level\")\n        data = {\n            \"service_active\": [],\n            \"service_cpu_used\": [],\n            \"service_memory_used\": [],\n        }\n        for service_threshold in service_thresholds:\n            data[service_threshold.get(\"index_type\")].append(service_threshold)\n        return Response(data=data)\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        更新服务阈值监控指标项设置\n        \"\"\"\n        try:\n            logger.info(f\"服务监控指标更新接口获取到的参数为: {request.data}\")\n            update_data = request.data.get(\"update_data\", {})\n            env_id = request.data.get(\"env_id\")\n            if not update_data:\n                return Response(data={\"code\": 1, \"message\": \"无法正确解析到要更新的数据!\"})\n            if env_id is None:\n                return Response(\n                    data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n            _obj_lst = list()\n            services_list = list()\n            for key, value in update_data.items():\n                for item in value:\n                    if not item[\"condition\"] or not item[\"value\"] or not item[\n                        \"level\"]:\n                        continue\n                    _obj = ServiceThreshold()\n                    _obj.index_type = key\n                    _obj.condition = item[\"condition\"]\n                    _obj.condition_value = item[\"value\"]\n                    _obj.alert_level = item[\"level\"]\n                    _obj.env_id = env_id\n                    _obj_lst.append(_obj)\n                    services_list.append({\n                        'index_type': key,\n                        'condition': item[\"condition\"],\n                        'condition_value': item[\"value\"],\n                        'alert_level': item[\"level\"]\n                    })\n            with transaction.atomic():\n                ServiceThreshold.objects.filter(env_id=env_id).delete()\n                ServiceThreshold.objects.bulk_create(_obj_lst)\n            return Response({})\n        except Exception as e:\n            logger.error(f\"更新服务相关阈值过程中出错: {traceback.format_exc()}\")\n            return Response(\n                data={\"code\": 1, \"message\": f\"更新服务相关阈值过程中出错: {str(e)}!\"})\n\n\nclass CustomThresholdView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    \"\"\"\n    读写自定义服务指标阈值\n    \"\"\"\n\n    get_description = \"读取自定义服务指标阈值设置\"\n    post_description = \"更新自定义服务指标阈值设置\"\n\n    serializer_class = Serializer\n\n    def list(self, request, *args, **kwargs):\n        \"\"\"\n        暂时只有kafka_consumergroup_lag\n        \"\"\"\n        env_id = request.GET.get('env_id')\n        if not env_id:\n            return Response(data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n        service_thresholds = list(\n            ServiceCustomThreshold.objects.filter(\n                env_id=env_id\n            ).annotate(\n                value=F(\"condition_value\"), level=F(\"alert_level\")\n            ).order_by(\"service_name\", \"index_type\", \"level\").values(\n                \"service_name\", \"index_type\", \"condition\", \"value\", \"level\")\n        )\n        data = dict()\n        for service_threshold in service_thresholds:\n            service_name = service_threshold.get(\"service_name\", \"\")\n            index_type = service_threshold.get(\"index_type\", \"\")\n            if not service_name or not index_type:\n                continue\n            index_type_info = {\n                \"condition\": service_threshold.get(\"condition\"),\n                \"index_type\": index_type,\n                \"level\": service_threshold.get(\"level\"),\n                \"value\": service_threshold.get(\"value\")\n            }\n            threshold_info = data.get(service_name, {})\n            if not threshold_info:\n                data[service_name] = {index_type: [index_type_info]}\n            else:\n                index_type_infos = threshold_info.get(index_type)\n                if not index_type_infos:\n                    threshold_info[index_type] = [index_type_info]\n                else:\n                    threshold_info[index_type].append(index_type_info)\n        return Response(data=data)\n\n    def valid_kafka_kafka_consumergroup_lag(self, value):  # NOQA\n        if isinstance(value, int):\n            return value > 0\n        return value.isdigit() and int(value) > 0\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        更新服务阈值监控指标项设置\n        \"\"\"\n        try:\n            logger.info(f\"自定义服务监控指标更新接口获取到的参数为: {request.data}\")\n            service_name = request.data.get(\"service_name\", \"\")\n            index_types = CUSTOM_THRESHOLD_SERVICES.get(service_name)\n            if not index_types:\n                return Response(data={\"code\": 1, \"message\": \"暂不支持该服务定制化阈值!\"})\n            index_type = request.data.get(\"index_type\", \"\")\n            if not index_type or index_type not in index_types:\n                return Response(data={\"code\": 1, \"message\": \"暂不支持该指标项!\"})\n            index_type_info = request.data.get(\"index_type_info\", [])\n            if not index_type_info:\n                return Response(data={\"code\": 1, \"message\": \"无法正确解析到要更新的数据!\"})\n            env_id = request.data.get(\"env_id\")\n            if not ServiceCustomThreshold.objects.filter(\n                    env_id=env_id).exists():\n                return Response(\n                    data={\"code\": 1, \"message\": f\"env {env_id}不存在\"})\n            # 后续需要增加对环境是否存在的判断\n\n            # 后续可能需要同步阈值至prometheus的rules文件中\n            # if not check_prometheus():\n            #     return Response(1, \"无法连接到prometheus，更改阈值失败！\")\n            _obj_lst = list()\n            # 创建阈值\n            for index_type_value in index_type_info:\n                condition = index_type_value.get(\"condition\")\n                level = index_type_value.get(\"level\")\n                value = index_type_value.get(\"value\")\n                valid = getattr(\n                    self, f\"valid_{service_name}_{index_type}\"\n                )(value)\n                if not valid:\n                    return Response(\n                        data={\"code\": 1, \"message\": \"阈值更新的值不符合要求！\"})\n                _obj = ServiceCustomThreshold(\n                    env_id=env_id,\n                    service_name=service_name,\n                    index_type=index_type,\n                    condition=condition,\n                    condition_value=value,\n                    alert_level=level\n                )\n                _obj_lst.append(_obj)\n            with transaction.atomic():\n                ServiceCustomThreshold.objects.filter(\n                    env_id=env_id,\n                    service_name=service_name,\n                    index_type=index_type\n                ).delete()\n                ServiceCustomThreshold.objects.bulk_create(_obj_lst)\n            return Response({})\n        except Exception as e:\n            logger.error(f\"更新服务相关阈值过程中出错: {traceback.format_exc()}\")\n            return Response(\n                data={\"code\": 1, \"message\": f\"更新服务相关阈值过程中出错: {str(e)}!\"})\n\n\nclass QuotaView(ListModelMixin, GenericViewSet, CreateModelMixin, DestroyModelMixin):\n    \"\"\"\n    读写自定义服务指标阈值\n    \"\"\"\n\n    get_description = \"读取指标规则\"\n    post_description = \"更新指标规则\"\n    delete_description = \"删除指标规则\"\n    serializer_class = QuotaSerializer\n    queryset = AlertRule.objects.all().order_by(\"-create_time\")\n    pagination_class = PageNumberPager\n    filter_backends = (\n        DjangoFilterBackend,\n        OrderingFilter,\n    )\n    filter_class = QuotaFilter\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        添加监控指标项\n        env_id = models.IntegerField(\"环境id\", default=1)\n        expr = models.TextField(\"监控指标表达式，报警语法\", null=False, blank=False)\n        threshold_value = models.FloatField(\"阈值的数值\", null=False, blank=False)\n        compare_str = models.CharField(\"比较符\", max_length=64)\n        for_time = models.CharField(\"持续一段时间获取不到信息就触发告警\", max_length=64)\n        severity = models.CharField(\"告警级别\", max_length=64)\n        alert = models.TextField(\"标题，自定义摘要\")\n        service = models.CharField(\"指标所属服务名称\", max_length=255)\n        status = models.IntegerField(\"启用状态\", default=0)\n        quota_type = models.IntegerField(\"指标的类型\", choices=TYPE, default=0)\n        labels = models.JSONField(\"额外指定标签\")\n        description = models.TextField(\"描述, 告警指标描述\", null=True)\n        \"\"\"\n        p = PrometheusUtils()\n        compare_str_dict = {\n            \">=\": \"大于或等于\",\n            \">\": \"大于\",\n            \"==\": \"等于\",\n            \"!=\": \"不等于\",\n            \"<=\": \"小于或等于\",\n            \"<\": \"小于\"\n        }\n        try:\n            env_id = request.data.get(\"env_id\")\n            if not env_id:\n                return Response(\n                    data={\"code\": 1, \"message\": \"请确认请求参数中包含env_id\"})\n            env_name = Env.objects.filter(id=env_id).first().name\n            logger.info(f\"创建指标规则接口获取到的参数为: {request.data}\")\n            quota_type = request.data.get(\"quota_type\")\n            compare_str = request.data.get(\"compare_str\")\n            severity = request.data.get(\"severity\")\n            threshold_value = request.data.get(\"threshold_value\")\n            id = request.data.pop(\"id\", 0)\n            expr = request.data.get(\"expr\")\n            if quota_type == 0:\n                \"\"\"\n                内置指标\n                \"\"\"\n                builtins_quota = request.data.pop(\"builtins_quota\", None)\n                name = builtins_quota.get(\"name\")\n                expr = builtins_quota.get(\"expr\")\n                description = builtins_quota.get(\"description\")\n                cn_compare = compare_str_dict.get(compare_str)\n                request.data[\"name\"] = name\n                request.data[\"service\"] = builtins_quota.get(\"service\")\n                request.data[\"description\"] = description.replace(\"$compare_str$\", cn_compare).replace(\n                    \"$threshold_value$\", str(threshold_value))\n                request.data[\"expr\"] = expr.replace(\"$env$\", env_name)\n                severity = request.data.get(\"severity\")\n            elif quota_type == 1:\n                \"\"\"\n                自定义promsql\n                \"\"\"\n                pass\n            elif quota_type == 2:\n                \"\"\"\n                日志监控\n                \"\"\"\n                pass\n            else:\n                return Response(\n                    data={\"code\": 1, \"message\": f\"创建指标规则过程中出错: 未识别的规则类型\"})\n            if request.data[\"service\"] != \"service\":\n                request.data[\"labels\"] = {\n                    \"job\": '{}Exporter'.format(request.data[\"service\"]),\n                    \"severity\": severity\n                }\n            else:\n                request.data[\"labels\"] = {\n                    \"severity\": severity\n                }\n            if id != 0:\n                if AlertRule.objects.filter(expr=request.data[\"expr\"],\n                                            severity=severity).exclude(id=id).exists():\n                    return Response(data={\"code\": 1,\n                                          \"message\": f\"更新指标规则过程中出错: \"\n                                                     f\"同一指标规则级别重复添加\"})\n                if not p.update_rule_file(add_data=request.data, update=True, rule_id=id):\n                    return Response(data={\"code\": 1,\n                                          \"message\": f\"更新指标规则错误\"})\n                AlertRule.objects.filter(id=id).update(**request.data)\n                ok = p.reload_prometheus()\n                if not ok:\n                    return Response(data={\"code\": 1, \"message\": \"prometheus 重载规则失败，请手动重启prometheus进行重载\"})\n                return Response()\n            print(request.data[\"expr\"], severity)\n            if AlertRule.objects.filter(expr=request.data[\"expr\"], severity=severity).exists():\n                return Response(data={\"code\": 1,\n                                      \"message\": f\"创建指标规则过程中出错: \"\n                                                 f\"同一指标规则级别重复添加\"})\n            if not p.update_rule_file(add_data=request.data, add=True):\n                return Response(data={\"code\": 1,\n                                      \"message\": f\"创建指标规则错误\"})\n            AlertRule(**request.data).save()\n            ok = p.reload_prometheus()\n            if not ok:\n                return Response(data={\"code\": 1,\n                                      \"message\": \"prometheus 重载规则失败，请手动重启prometheus进行重载\"})\n            return Response()\n        except Exception as e:\n            logger.error(f\"创建指标规则过程中出错: {traceback.format_exc()}\")\n            return Response(\n                data={\"code\": 1, \"message\": f\"创建指标规则过程中出错: {str(e)}!\"})\n\n    def delete(self, request, *args, **kwargs):\n        \"\"\"\n        删除规则\n        \"\"\"\n        id = request.query_params.get(\"id\")\n        p = PrometheusUtils()\n        if not p.update_rule_file(delete=True, rule_id=id):\n            return Response(data={\"code\": 1,\n                                  \"message\": f\"删除指标规则时，更新配置文件失败\"})\n        num, _ = AlertRule.objects.filter(id=id).delete()\n        if num == 0:\n            return Response(data={\"code\": 1, \"message\": \"删除失败\"})\n        ok = p.reload_prometheus()\n        if not ok:\n            return Response(data={\"code\": 1,\n                                  \"message\": \"prometheus \"\n                                             \"重载规则失败，请手动重启prometheus进行重载\"})\n        return Response()\n\n\nclass BuiltinsRuleView(GenericViewSet, ListModelMixin):\n    post_description = \"获取内置指标列表\"\n    serializer_class = RuleSerializer\n    queryset = Rule.objects.all()\n\n    def list(self, request, *args, **kwargs):\n        data = self.get_serializer(self.queryset, many=True).data\n        services = set([i.get(\"service\") for i in data])\n        r_data = {}\n        for service in services:\n            r_data[service] = []\n            for i in data:\n                if service == i.get(\"service\"):\n                    r_data[service].append(i)\n        return Response(data=r_data)\n\n\nclass PromSqlTestView(GenericViewSet, CreateModelMixin):\n    post_description = \"测试指标\"\n    serializer_class = Serializer\n\n    def create(self, request, *args, **kwargs):\n        expr = request.data.get(\"expr\")\n        ok, res = Prometheus().get_quota_res(quota=expr)\n        if ok:\n            return Response(data=res)\n        return Response(data={\"code\": 1, \"message\": res})\n\n\nclass BatchUpdateRuleView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n    批量修改\n    \"\"\"\n    post_description = \"批量修改启用停用状态\"\n    serializer_class = QuotaSerializer\n    queryset = AlertRule.objects.all()\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        id list\n        批量修改接口\n        \"\"\"\n        p = PrometheusUtils()\n        ids = request.data.get(\"ids\")\n        status = request.data.get(\"status\")\n        for id in ids:\n            AlertRule.objects.filter(id=id).update(status=status)\n        if not p.update_rule_file():\n            return Response(data={\"code\": 1,\n                                  \"message\": f\"删除指标规则时，更新配置文件失败\"})\n        ok = p.reload_prometheus()\n        if not ok:\n            return Response(data={\"code\": 1,\n                                  \"message\": \"prometheus 重载规则失败，请手动重启prometheus进行重载\"})\n\n        return Response()\n"
  },
  {
    "path": "omp_server/service_upgrade/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/service_upgrade/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/service_upgrade/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ServiceUpgradeConfig(AppConfig):\n    name = 'service_upgrade'\n"
  },
  {
    "path": "omp_server/service_upgrade/filters.py",
    "content": "from rest_framework.filters import BaseFilterBackend\n\n\nclass RollBackHistoryFilter(BaseFilterBackend):\n\n    def filter_queryset(self, request, queryset, view):\n        params = request.query_params.get(\"history_id\", '')\n        params = params.replace('\\x00', '').replace('null', '')\n        if not params:\n            return queryset\n        queryset = queryset.filter(history_id=int(params))\n        return queryset\n"
  },
  {
    "path": "omp_server/service_upgrade/handler/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/service_upgrade/handler/base.py",
    "content": "import logging\nimport time\nfrom datetime import datetime\nfrom functools import reduce\n\nfrom django.conf import settings\n\nfrom db_models.models import UpgradeDetail, RollbackDetail, Service\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_install_detail(detail, service, operation_uuid):\n    install_history = service.detailinstallhistory_set.filter(\n        install_step_status=2).last()\n    logger.info(f\"{service.service_instance_name}无detail记录或该服务升级前为安装失败\")\n    if not install_history:\n        return None, None\n\n    # 设置安装路径\n    install_args = install_history.install_detail_args\n    install_folder = \"\"\n    for item in install_args.get(\"install_args\"):\n        if item.get(\"key\") == \"base_dir\":\n            install_folder = item.get(\"default\")\n            break\n    if not install_folder:\n        logger.error(\"找不到服务安装目录!\")\n        return None, None\n    setattr(service, \"install_folder\", install_folder)\n\n    # 判断静态服务\n    setattr(\n        service,\n        \"is_static\",\n        bool(not service.service_controllers.get(\"start\"))\n    )\n    # 升级的data path\n    setattr(\n        detail, \"data_path\", install_args.get(\"data_folder\")\n    )\n    # 安装的uuid\n    setattr(\n        service,\n        \"_uuid\",\n        operation_uuid\n    )\n    return detail, service\n\n\ndef load_upgrade_detail(upgrade_detail, operation_uuid):\n    service = upgrade_detail.service\n    upgrade_detail, service = get_install_detail(\n        upgrade_detail, service, operation_uuid)\n    if not upgrade_detail or not service:\n        return None, None, None\n    # 加载关联的升级对象\n    relation_details = list(\n        UpgradeDetail.objects.filter(\n            union_server=upgrade_detail.union_server,\n            history_id=upgrade_detail.history_id\n        ).exclude(id=upgrade_detail.id)\n    )\n    return upgrade_detail, service, relation_details\n\n\ndef load_rollback_detail(rollback_detail, operation_uuid):\n    service = rollback_detail.upgrade.service\n    # 安装成功记录\n    rollback_detail, service = get_install_detail(\n        rollback_detail, service, operation_uuid)\n    # 加载关联的升级对象\n    relation_details = list(\n        RollbackDetail.objects.filter(\n            upgrade__union_server=rollback_detail.upgrade.union_server,\n            history_id=rollback_detail.history_id\n        ).exclude(id=rollback_detail.id).exclude(\n            history_id=rollback_detail.history.id\n        )\n    )\n    return rollback_detail, service, relation_details\n\n\nclass BaseHandler:\n    log_message = \"\"\n    operation_type = \"\"\n    no_need_print = False\n\n    def __init__(self, salt_client):\n        self.salt_client = salt_client\n\n    def _log(self, message, level=\"info\"):\n        message = f\"\\n{' ' * 30}\".join(message.split(\"\\n\"))\n        getattr(logger, level)(message)\n        msg_str = f\"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} \" \\\n                  f\"{level.upper()} \" \\\n                  f\"{message}\"\n        # 适配命令行输出\n        print(msg_str)\n        if not self.detail.message:\n            self.detail.message = \"\"\n        self.detail.message += f\"{msg_str}\\n\"\n        # todo： 优化下写库？\n        self.detail.save()\n\n    def success_handler(self):\n        \"\"\"成功处理\"\"\"\n        self.detail.handler_info[self.__class__.__name__] = True\n        if not (self.service.is_static and self.no_need_print):\n            self._log(\n                self.log_message.format(self.union_server, '成功!'),\n                \"info\"\n            )\n        self.write_db(True)\n        return self.detail, self.service, self.relation_details\n\n    def fail_handler(self):\n        \"\"\"失败处理\"\"\"\n        self.detail.handler_info[self.__class__.__name__] = False\n        self._log(self.log_message.format(self.union_server, '失败!'), \"error\")\n        self.write_db(False)\n        return None\n\n    @classmethod\n    def valid_args(cls, detail_args, detail):\n        if not isinstance(detail_args, tuple) or len(detail_args) != 3:\n            return False\n        if not all([\n            isinstance(detail_args[0], detail),\n            isinstance(detail_args[1], Service)\n        ]):\n            return False\n        return True\n\n    def __call__(self, operation_args, *args, **kwargs):\n        if not self.valid_args(operation_args, self.detail_class):\n            return None\n        self.detail = operation_args[0]\n        self.service = operation_args[1]\n        self.relation_details = operation_args[2]\n\n        # 执行成功部分跳过\n        if self.detail.handler_info.get(self.__class__.__name__):\n            return self.detail, self.service, self.relation_details\n        if not (self.service.is_static and self.no_need_print):\n            self._log(self.log_message.format(\n                self.union_server, '中...'), \"info\"\n            )\n\n        try:\n            success_state = self.handler()\n        except Exception as e:\n            self._log(\n                f\"服务实例:{self.union_server} \"\n                f\"{self.operation_type.lower()}过程中出现异常：{str(e)}\",\n                \"error\"\n            )\n            success_state = False\n        if not success_state:\n            return self.fail_handler()\n        return self.success_handler()\n\n\nclass StartOperationMixin:\n\n    def handler(self):\n        self.set_service_state()\n        return self.detail, self.service, self.relation_details\n\n    def __call__(self, operation_args, *args, **kwargs):\n        if not self.valid_args(operation_args, self.detail_class):\n            return None\n        self.detail = operation_args[0]\n        self.service = operation_args[1]\n        self.relation_details = operation_args[2]\n        self._log(self.log_message.format(self.union_server), \"info\")\n        return self.handler()\n\n\nclass StopServiceMixin:\n    log_message = \"服务实例{}: 停止服务{}\"\n    no_need_print = True\n\n    def stop_service(self, service):\n        for i in range(2):\n            state, message = self.salt_client.cmd(\n                self.service.ip,\n                f'bash {service.service_controllers.get(\"stop\")}',\n                timeout=settings.SSH_CMD_TIMEOUT\n            )\n            if not state:\n                raise Exception(f\"salt执行命令失败，错误输出: {str(message)}\")\n            if \"[not  running]\" in message:\n                # 休眠5秒等待停止\n                time.sleep(5)\n                return True\n            # 回滚有可能服务无包报错,也算停止\n            if \"[running]\" not in message:\n                time.sleep(5)\n                return True\n            time.sleep(5)\n        return False\n\n\nclass StartServiceMixin:\n    log_message = \"服务实例{}: 启动服务{}\"\n    no_need_print = True\n\n    def start_service(self, service):\n        for i in range(2):\n            state, message = self.salt_client.cmd(\n                self.service.ip,\n                service.service_controllers.get(\"start\"),\n                timeout=settings.SSH_CMD_TIMEOUT\n            )\n            if not state:\n                raise Exception(f\"salt执行命令失败，错误输出: {str(message)}\")\n            if \"[running]\" in message:\n                # 休眠5秒等待停止\n                time.sleep(10)\n                return True\n            time.sleep(5)\n        return True\n\n\ndef handler_pipeline(handlers, objs):\n    \"\"\"\n    升级、回滚处理\n    :param handlers: handler类列表: upgrade_handlers, rollback_handlers\n    :param objs: 操作对象(upgrade_detail, service)\n    :return:\n    \"\"\"\n    salt_client = SaltClient()\n    return reduce(lambda x, handler: handler(salt_client)(x), handlers, objs)\n"
  },
  {
    "path": "omp_server/service_upgrade/handler/rollback_handler.py",
    "content": "import logging\nimport os\nimport random\nimport time\nfrom datetime import datetime\n\nfrom django.conf import settings\nfrom django.db import transaction\n\nfrom db_models.mixins import RollbackStateChoices\nfrom db_models.models import RollbackDetail, Service, ServiceHistory\nfrom service_upgrade.handler.base import BaseHandler, StartOperationMixin, \\\n    StartServiceMixin\n\nlogger = logging.getLogger(__name__)\n\n\nclass RollbackBaseHandler(BaseHandler):\n    operation_type = \"ROLLBACK\"\n\n    @transaction.atomic\n    def write_db(self, result=False):\n        \"\"\"写库\"\"\"\n        if not result:\n            # 升级失败，更新upgrade detail、service状态\n            state = f\"{self.operation_type}_FAIL\"\n            self.service.service_status = Service.SERVICE_STATUS_UNKNOWN\n            self.service.save()\n            self.detail.rollback_state = getattr(RollbackStateChoices, state)\n            ServiceHistory.create_history(self.service, self.detail)\n        self.detail.save()\n        # 升级结束更新服务表\n        self.sync_relation_details()\n\n    def sync_relation_details(self):\n        for relation_detail in self.relation_details:\n            for k in {\"path_info\", \"handler_info\", \"message\", \"rollback_state\"}:\n                v = getattr(self.detail, k)\n                setattr(relation_detail, k, v)\n            relation_detail.save()\n            service = relation_detail.upgrade.service\n            service.service_status = self.service.service_status\n            service.save()\n            ServiceHistory.create_history(service, relation_detail)\n            relation_detail.refresh_from_db()\n\n    def handler(self):\n        raise NotImplementedError\n\n    @property\n    def detail_class(self):\n        return RollbackDetail\n\n    @property\n    def union_server(self):\n        return self.detail.upgrade.union_server\n\n\nclass StartRollbackHandler(StartOperationMixin, RollbackBaseHandler):\n    log_message = \"服务实例{}: 开始回滚...\"\n    operation_type = \"ROLLBACK\"\n\n    @transaction.atomic\n    def set_service_state(self):\n        self.detail.rollback_state = RollbackStateChoices.ROLLBACK_ING\n        self.detail.save()\n        self.detail.refresh_from_db()\n        self.service.service_status = Service.SERVICE_STATUS_ROLLBACK\n        self.service.save()\n        self.service.refresh_from_db()\n        for detail in self.relation_details:\n            detail.rollback_state = RollbackStateChoices.ROLLBACK_ING\n            service = detail.upgrade.service\n            service.service_status = Service.SERVICE_STATUS_ROLLBACK\n            service.save()\n            detail.save()\n            detail.refresh_from_db()\n\n\nclass RollBackStopServiceHandler(RollbackBaseHandler):\n    operation_type = \"ROLLBACK\"\n    log_message = \"服务实例{}: 停止服务{}\"\n    no_need_print = True\n\n    def stop_service(self, service):\n        for i in range(2):\n            state, message = self.salt_client.cmd(\n                self.service.ip,\n                f'bash {service.service_controllers.get(\"stop\")}',\n                timeout=settings.SSH_CMD_TIMEOUT\n            )\n            if \"[not  running]\" in message:\n                # 休眠5秒等待停止\n                time.sleep(5)\n                return True\n            time.sleep(5)\n        return True\n\n    def handler(self):\n        if self.service.is_static:\n            self._log(\"服务无需停止,跳过!\")\n            return True\n        success = self.stop_service(self.service)\n        if not success:\n            return False\n        for relation_detail in self.relation_details:\n            success = self.stop_service(relation_detail.upgrade.service)\n            if not success:\n                return False\n        return True\n\n\nclass RollBackServiceHandler(RollbackBaseHandler):\n    log_message = \"服务实例{}: 回滚{}\"\n    operation_type = \"ROLLBACK\"\n\n    def handler(self):\n        rollback_file = self.detail.upgrade.path_info.get('backup_file_path')\n        if not rollback_file:\n            return True\n        data_json_path = os.path.join(\n            self.detail.data_path,\n            f\"omp_packages/{self.service._uuid}.json\"\n        )\n        rollback_path = self.service.service_controllers.get(\"rollback\")\n        if not rollback_path:\n            rollback_path = os.path.join(\n                self.service.install_folder,\n                \"scripts/rollback.py\"\n            )\n        cmd_str = f\"python {rollback_path} \" \\\n                  f\"--local_ip {self.service.ip} \" \\\n                  f\"--data_json {data_json_path} \" \\\n                  f\"--version {self.detail.upgrade.current_app.app_version} \" \\\n                  f\"--backup_path {rollback_file}\"\n        state, message = self.salt_client.cmd(\n            self.service.ip,\n            cmd_str,\n            settings.SSH_CMD_TIMEOUT\n        )\n        if not state:\n            self._log(f\"执行回滚命令失败:{cmd_str}!\\n错误信息: {message}\", \"error\")\n            return False\n        if \"备份路径:\" in message:\n            self.detail.path_info.update(\n                rollback_backup_path=message.split(\"备份路径:\")[-1]\n            )\n        return True\n\n\nclass RollBackStartServiceHandler(StartServiceMixin, RollbackBaseHandler):\n    operation_type = \"ROLLBACK\"\n\n    def sync_relation_details(self):\n        for relation_detail in self.relation_details:\n            for k in {\"path_info\", \"handler_info\", \"message\", \"rollback_state\"}:\n                v = getattr(self.detail, k)\n                setattr(relation_detail, k, v)\n            relation_detail.save()\n            relation_detail.upgrade.has_rollback = True\n            relation_detail.upgrade.save()\n            service = relation_detail.upgrade.service\n            service.update_application(\n                self.detail.upgrade.current_app,\n                True,\n                self.service.install_folder\n            )\n            ServiceHistory.create_history(service, relation_detail)\n            relation_detail.refresh_from_db()\n\n    def write_db(self, result=False):\n        \"\"\"写库\"\"\"\n        state = f\"{self.operation_type}_SUCCESS\"\n        self.detail.rollback_state = getattr(RollbackStateChoices, state)\n        self.detail.upgrade.has_rollback = True\n        self.detail.upgrade.save()\n        self.detail.save()\n        # 创建服务操作记录\n        ServiceHistory.create_history(self.service, self.detail)\n        # 升级结束更新服务表\n        self.service.update_application(\n            self.detail.upgrade.current_app,\n            True,\n            self.service.install_folder\n        )\n        # 同步关联服务\n        self.sync_relation_details()\n\n    def handler(self):\n        if self.service.is_static:\n            self._log(\"服务无需启动,跳过!\")\n            return True\n        self.start_service(self.service)\n        for relation_detail in self.relation_details:\n            self.start_service(relation_detail.upgrade.service)\n        return True\n\n\n\"\"\"\n回滚流程：\n    1、停服务\n    2、调用回滚脚本（参数传目标主机ip、data.json、目标版本version、升级步骤2的包路径）\n    3、启动服务（回滚成功以步骤2的结果为准）\n\"\"\"\nrollback_handlers = [\n    StartRollbackHandler,\n    RollBackStopServiceHandler,\n    RollBackServiceHandler,\n    RollBackStartServiceHandler\n]\n"
  },
  {
    "path": "omp_server/service_upgrade/handler/upgrade_handler.py",
    "content": "import json\nimport logging\nimport os\nimport random\nfrom datetime import datetime\n\nfrom django.conf import settings\nfrom django.db import transaction\n\nfrom db_models.mixins import UpgradeStateChoices\nfrom db_models.models import UpgradeDetail, Service, ServiceHistory\nfrom service_upgrade.handler.base import BaseHandler, StartOperationMixin, \\\n    StopServiceMixin, StartServiceMixin\n\nlogger = logging.getLogger(__name__)\n\n\nclass UpgradeBaseHandler(BaseHandler):\n    operation_type = \"UPGRADE\"\n\n    @transaction.atomic\n    def write_db(self, result=False):\n        \"\"\"写库\"\"\"\n        if not result:\n            # 升级失败，更新upgrade detail、service状态\n            state = f\"{self.operation_type}_FAIL\"\n            self.service.service_status = Service.SERVICE_STATUS_UNKNOWN\n            self.service.save()\n            ServiceHistory.create_history(self.service, self.detail)\n            self.detail.upgrade_state = getattr(UpgradeStateChoices, state)\n        self.detail.save()\n        # 升级结束更新服务表\n        self.sync_relation_details()\n\n    def sync_relation_details(self):\n        for relation_detail in self.relation_details:\n            for k in {\"path_info\", \"handler_info\", \"message\", \"upgrade_state\"}:\n                v = getattr(self.detail, k)\n                setattr(relation_detail, k, v)\n            relation_detail.save()\n            service = relation_detail.service\n            service.service_status = self.service.service_status\n            service.save()\n            ServiceHistory.create_history(service, relation_detail)\n            relation_detail.refresh_from_db()\n\n    def handler(self):\n        raise NotImplementedError\n\n    @property\n    def detail_class(self):\n        return UpgradeDetail\n\n    @property\n    def union_server(self):\n        return self.detail.union_server\n\n\nclass StartUpgradeHandler(StartOperationMixin, UpgradeBaseHandler):\n    log_message = \"服务实例{}: 开始升级...\"\n\n    @transaction.atomic\n    def set_service_state(self):\n        self.detail.upgrade_state = UpgradeStateChoices.UPGRADE_ING\n        self.detail.save()\n        self.detail.refresh_from_db()\n        self.service.service_status = Service.SERVICE_STATUS_UPGRADE\n        self.service.save()\n        self.service.refresh_from_db()\n        for detail in self.relation_details:\n            detail.upgrade_state = UpgradeStateChoices.UPGRADE_ING\n            detail.service.service_status = \\\n                Service.SERVICE_STATUS_UPGRADE\n            detail.service.save()\n            detail.save()\n            detail.refresh_from_db()\n\n\nclass StopServiceHandler(StopServiceMixin, UpgradeBaseHandler):\n\n    def handler(self):\n        if self.service.is_static:\n            self._log(\"服务无需停止,跳过!\")\n            return True\n        success = self.stop_service(self.service)\n        if not success:\n            return False\n        for relation_detail in self.relation_details:\n            success = self.stop_service(relation_detail.service)\n            if not success:\n                return False\n        return True\n\n\nclass BackupServiceHandler(UpgradeBaseHandler):\n    log_message = \"服务实例{}: 备份服务包{}\"\n\n    def do_backup(self, backup_package):\n        # 生成备份文件名\n        time_str = datetime.now().strftime(\"%Y%m%d%H%M\")\n        backup_name = f\"{self.service.service.app_name}.back-{time_str}-\" \\\n                      f\"{random.randint(1, 120)}\"\n        # 备份服务文件\n        backup_file = os.path.join(backup_package, backup_name)\n        backup_str = f\"mkdir -p {backup_package} && \" \\\n            f\"mv {self.service.install_folder} {backup_file}\"\n        state, result = self.salt_client.cmd(\n            self.service.ip, backup_str, settings.SSH_CMD_TIMEOUT)\n        if not state:\n            raise Exception(\"备份原服务文件失败!\\n 错误信息:{}\".format(result))\n        self.detail.path_info.update(\n            {\n                \"old_file_path\": self.service.install_folder,\n                \"backup_file_path\": backup_file,\n            }\n        )\n        return state\n\n    def handler(self):\n        backup_package = os.path.join(\n            os.path.dirname(self.service.install_folder),\n            f\"upgrade_backup/{datetime.today().strftime('%Y%m%d')}\"\n        )\n        return self.do_backup(backup_package)\n\n\nclass SendPackageHandler(UpgradeBaseHandler):\n    log_message = \"服务实例{}: 发送升级包{}\"\n\n    def handler(self):\n        tar_package = os.path.join(\n            self.detail.target_app.app_package.package_path,\n            self.detail.target_app.app_package.package_name\n        )\n        if not tar_package:\n            raise Exception(f\"升级包{tar_package}不存在\")\n        send_packages = os.path.join(\n            self.detail.data_path,\n            f\"omp_packages\"\n        )\n        state, message = self.salt_client.cp_file(\n            self.service.ip,\n            tar_package,\n            send_packages\n        )\n        if not state:\n            raise Exception(\"发送升级包失败!\\n错误信息: {}\".format(message))\n        return True\n\n\nclass UnzipPackageHandler(UpgradeBaseHandler):\n    log_message = \"服务实例{}: 解压升级包{}\"\n\n    def handler(self):\n        tar_packages_path = os.path.join(\n            self.detail.data_path,\n            \"omp_packages\"\n        )\n        package_name = self.detail.target_app.app_package.package_name\n        install_path = os.path.dirname(self.service.install_folder)\n\n        cmd_str = f\"cd {tar_packages_path} &&\" \\\n                  f\"tar -xmf {package_name} -C {install_path}\"\n        state, message = self.salt_client.cmd(\n            self.service.ip, cmd_str, settings.SSH_CMD_TIMEOUT)\n        if not state:\n            raise Exception(f\"解压升级包失败!\\n错误信息: {message}\")\n        return state\n\n\nclass UpgradeServiceHandler(UpgradeBaseHandler):\n    log_message = \"服务实例{}: 升级{}\"\n\n    def handler(self):\n        upgrade_file = json.loads(\n            self.detail.target_app.app_controllers\n        ).get(\"upgrade\", \"scripts/upgrade.py\")\n        upgrade_path = os.path.join(\n            self.service.install_folder,\n            upgrade_file\n        )\n        data_json_path = os.path.join(\n            self.detail.data_path,\n            f\"omp_packages/{self.service._uuid}.json\"\n        )\n        # 适配流水线加的统一版本后缀commit_id\n        version = self.detail.current_app.app_version.split(\"-\")[0]\n        cmd_str = f\"python {upgrade_path} \" \\\n                  f\"--local_ip {self.service.ip} \" \\\n                  f\"--data_json {data_json_path} \" \\\n                  f\"--version {version} \" \\\n                  f\"--backup_path \" \\\n                  f\"{self.detail.path_info.get('backup_file_path')}\"\n        state, message = self.salt_client.cmd(\n            self.service.ip, cmd_str, settings.SSH_CMD_TIMEOUT)\n        if not state:\n            raise Exception(f\"通过salt执行命令:{cmd_str};\\n错误输出:{message}!\")\n        return state\n\n\nclass StartServiceHandler(StartServiceMixin, UpgradeBaseHandler):\n\n    def sync_relation_details(self):\n        for relation_detail in self.relation_details:\n            for k in {\"path_info\", \"handler_info\", \"message\", \"upgrade_state\"}:\n                v = getattr(self.detail, k)\n                setattr(relation_detail, k, v)\n            relation_detail.save()\n            service = relation_detail.service\n            service.update_application(\n                self.detail.target_app,\n                True,\n                self.service.install_folder\n            )\n            relation_detail.refresh_from_db()\n\n    def write_db(self, result=False):\n        \"\"\"写库\"\"\"\n        state = f\"{self.operation_type}_SUCCESS\"\n        self.detail.upgrade_state = getattr(UpgradeStateChoices, state)\n        self.detail.save()\n        self.service.update_application(\n            self.detail.target_app,\n            True,\n            self.service.install_folder\n        )\n        # 升级结束更新服务表\n        self.sync_relation_details()\n\n    def fail_handler(self):\n        \"\"\"失败处理\"\"\"\n        self.detail.handler_info[self.__class__.__name__] = False\n        self._log(self.log_message.format(self.union_server, '失败!'), \"error\")\n        self.write_db(False)\n        return self.detail, self.service, self.relation_details\n\n    def handler(self):\n        if self.service.is_static:\n            self._log(\"服务无需启动,跳过!\")\n            return True\n        self.start_service(self.service)\n        for relation_detail in self.relation_details:\n            self.start_service(relation_detail.service)\n        return True\n\n\n\"\"\"\n升级流程：\n    1、停服务\n    2、备份服务安装目录\n    3、发送服务升级包\n    4、解压升级包\n    5、调用服务升级流程（参数传目标主机ip、data.json、前一版本version、步骤2的包路径）\n    6、启动服务（升级成功以步骤5结果为准）\n\"\"\"\nupgrade_handlers = [\n    StartUpgradeHandler,\n    StopServiceHandler,\n    BackupServiceHandler,\n    SendPackageHandler,\n    UnzipPackageHandler,\n    UpgradeServiceHandler,\n    StartServiceHandler\n]\n"
  },
  {
    "path": "omp_server/service_upgrade/serializers.py",
    "content": "from rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\n\nfrom db_models.mixins import UpgradeStateChoices, RollbackStateChoices\nfrom db_models.models import UpgradeHistory, UpgradeDetail, Service, \\\n    ApplicationHub, RollbackHistory, RollbackDetail\nfrom utils.parse_config import BASIC_ORDER\n\n\nclass UpgradeHistorySerializer(serializers.ModelSerializer):\n    service_count = serializers.SerializerMethodField()\n    operator = serializers.CharField(source=\"operator.username\")\n    state_display = serializers.CharField(source=\"get_upgrade_state_display\")\n    can_rollback = serializers.BooleanField(source=\"can_roll_back\")\n\n    def get_service_count(self, obj):\n        return obj.upgradedetail_set.all().count()\n\n    class Meta:\n        model = UpgradeHistory\n        fields = (\"id\", \"operator\", \"service_count\", \"upgrade_state\",\n                  \"created\", \"can_rollback\", \"state_display\")\n\n\nclass RollbackHistorySerializer(serializers.ModelSerializer):\n    service_count = serializers.SerializerMethodField()\n    operator = serializers.CharField(source=\"operator.username\")\n    state_display = serializers.CharField(source=\"get_rollback_state_display\")\n\n    def get_service_count(self, obj):\n        return obj.rollbackdetail_set.all().count()\n\n    class Meta:\n        model = RollbackHistory\n        fields = (\"id\", \"operator\", \"service_count\", \"rollback_state\",\n                  \"created\", \"state_display\")\n\n\nclass UpgradeDetailSerializer(serializers.ModelSerializer):\n\n    ip = serializers.CharField(source=\"service.ip\")\n    service_name = serializers.CharField(source=\"service.service.app_name\")\n    state_display = serializers.CharField(source=\"get_upgrade_state_display\")\n    instance_name = serializers.CharField(source=\"service.service_instance_name\")\n\n    class Meta:\n        model = UpgradeDetail\n        fields = (\"id\", \"ip\", \"service_name\", \"upgrade_state\", \"message\",\n                  \"state_display\", \"instance_name\")\n\n\nclass RollbackDetailSerializer(serializers.ModelSerializer):\n\n    ip = serializers.CharField(source=\"upgrade.service.ip\")\n    service_name = serializers.CharField(source=\"upgrade.target_app.app_name\")\n    state_display = serializers.CharField(source=\"get_rollback_state_display\")\n    instance_name = serializers.CharField(\n        source=\"upgrade.service.service_instance_name\")\n\n    class Meta:\n        model = RollbackDetail\n        fields = (\"id\", \"ip\", \"service_name\", \"rollback_state\", \"message\",\n                  \"state_display\", \"instance_name\")\n\n\nclass UpgradeHistoryDetailSerializer(serializers.ModelSerializer):\n    operator = serializers.CharField(source=\"operator.username\")\n    upgrade_detail = serializers.SerializerMethodField()\n    can_rollback = serializers.SerializerMethodField()\n    success_count = serializers.SerializerMethodField()\n    all_count = serializers.SerializerMethodField()\n\n    def get_upgrade_details(self, obj, key):\n        if hasattr(obj, key):\n            return getattr(obj, key)\n        details = obj.upgradedetail_set.filter(\n            service__isnull=False\n        )\n        upgrade_details = UpgradeDetailSerializer(details, many=True).data\n        # 合并服务\n        upgrade_result = {}\n        success_count = all_count = 0\n        for detail in upgrade_details:\n            all_count += 1\n            if detail.get(\"upgrade_state\") ==\\\n                    UpgradeStateChoices.UPGRADE_SUCCESS:\n                success_count += 1\n            if detail.get(\"service_name\") not in upgrade_result:\n                upgrade_result[detail.get(\"service_name\")] = [detail]\n            else:\n                upgrade_result[detail.get(\"service_name\")].append(detail)\n        setattr(obj, \"success_count\", success_count)\n        setattr(obj, \"all_count\", all_count)\n        setattr(obj, \"upgrade_result\", upgrade_result)\n        return getattr(obj, key)\n\n    def get_pre_upgrade(self, obj):\n        result = []\n        if not obj.pre_upgrade_result:\n            pre_upgrade_result = {\"update_data_json\": {}}\n        else:\n            pre_upgrade_result = obj.pre_upgrade_result\n        for k, v in pre_upgrade_result.items():\n            default_dict = {\n                \"id\": 0, \"ip\": \"\",\n                \"service_name\": k,\n                \"instance_name\": k,\n                \"upgrade_state\": v.get(\"state\", 1),\n                \"message\": v.get(\"message\", \"\"),\n                \"state_display\": v.get(\"state_display\", \"正在升级\")\n            }\n            result.append(default_dict)\n        return \"升级前置操作\", result\n\n    def get_upgrade_detail(self, obj):\n        result = self.get_upgrade_details(obj, \"upgrade_result\")\n        # 获取服务顺序\n        service_index = {}\n        sum_index = 0\n        for index, services in BASIC_ORDER.items():\n            for sub_index, service in enumerate(services, sum_index):\n                service_index[service] = sub_index\n                sum_index += 1\n        # 升级记录排序\n        results = sorted(\n            result.items(),\n            key=lambda x: service_index.get(x[0], sum_index+1)\n        )\n        pre_upgrade = self.get_pre_upgrade(obj)\n        results.insert(0, pre_upgrade)\n\n        # 调整返回数据结构\n        return [\n            {\n                \"service_name\": service_tmp[0],\n                \"upgrade_details\": service_tmp[1]\n            } for service_tmp in results\n        ]\n\n    def get_can_rollback(self, obj):\n        return obj.can_roll_back\n\n    def get_success_count(self, obj):\n        return self.get_upgrade_details(obj, \"success_count\")\n\n    def get_all_count(self, obj):\n        return self.get_upgrade_details(obj, \"all_count\")\n\n    class Meta:\n        model = UpgradeHistory\n        fields = (\"id\", \"operator\", \"upgrade_detail\", \"upgrade_state\",\n                  \"created\", \"can_rollback\", \"success_count\",\n                  \"all_count\")\n\n\nclass RollbackHistoryDetailSerializer(serializers.ModelSerializer):\n    operator = serializers.CharField(source=\"operator.username\")\n    rollback_detail = serializers.SerializerMethodField()\n    success_count = serializers.SerializerMethodField()\n    all_count = serializers.SerializerMethodField()\n\n    def get_rollback_details(self, obj, key):\n        if hasattr(obj, key):\n            return getattr(obj, key)\n        details = obj.rollbackdetail_set.filter(\n            upgrade__service__isnull=False\n        )\n        rollback_details = RollbackDetailSerializer(details, many=True).data\n        # 合并服务\n        rollback_result = {}\n        success_count = all_count = 0\n        for detail in rollback_details:\n            all_count += 1\n            if detail.get(\"rollback_state\") ==\\\n                    RollbackStateChoices.ROLLBACK_SUCCESS:\n                success_count += 1\n            if detail.get(\"service_name\") not in rollback_result:\n                rollback_result[detail.get(\"service_name\")] = [detail]\n            else:\n                rollback_result[detail.get(\"service_name\")].append(detail)\n        setattr(obj, \"success_count\", success_count)\n        setattr(obj, \"all_count\", all_count)\n        setattr(obj, \"rollback_result\", rollback_result)\n        return getattr(obj, key)\n\n    def get_rollback_detail(self, obj):\n        result = self.get_rollback_details(obj, \"rollback_result\")\n        # 获取服务顺序\n        service_index = {}\n        sum_index = 0\n        for index, services in BASIC_ORDER.items():\n            for sub_index, service in enumerate(services, sum_index):\n                service_index[service] = sub_index\n                sum_index += 1\n        # 升级记录排序\n        results = sorted(\n            result.items(),\n            key=lambda x: service_index.get(x[0], sum_index+1)\n        )\n        # 调整返回数据结构\n        return [\n            {\n                \"service_name\": service_tmp[0],\n                \"rollback_details\": service_tmp[1]\n            } for service_tmp in results\n        ]\n\n    def get_success_count(self, obj):\n        return self.get_rollback_details(obj, \"success_count\")\n\n    def get_all_count(self, obj):\n        return self.get_rollback_details(obj, \"all_count\")\n\n    class Meta:\n        model = RollbackHistory\n        fields = (\"id\", \"operator\", \"rollback_detail\", \"rollback_state\",\n                  \"created\",  \"success_count\",  \"all_count\")\n\n\nclass UpgradeTryAgainSerializer(serializers.ModelSerializer):\n\n    id = serializers.IntegerField(read_only=True)\n\n    class Meta:\n        model = UpgradeHistory\n        fields = (\"id\", )\n\n    def validate(self, attrs):\n        if self.instance.upgrade_state != UpgradeStateChoices.UPGRADE_FAIL:\n            raise ValidationError(\"该升级记录不支持重新再次升级!\")\n        histories = UpgradeHistory.objects.filter(\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_WAIT,\n                UpgradeStateChoices.UPGRADE_ING\n            ]\n        )\n        if histories.exists():\n            raise ValidationError(\"存在正在升级的服务，请稍后重试!\")\n        if UpgradeHistory.objects.filter(id__gt=self.instance.id).exists():\n            raise ValidationError(\"历史记录不可再次升级!\")\n        return True\n\n\nclass RollbackTryAgainSerializer(serializers.ModelSerializer):\n    id = serializers.IntegerField(read_only=True)\n\n    class Meta:\n        model = RollbackHistory\n        fields = (\"id\", )\n\n    def validate(self, attrs):\n        if self.instance.rollback_state != RollbackStateChoices.ROLLBACK_FAIL:\n            raise ValidationError(\"该回滚记录不支持重新再次回滚!\")\n        if RollbackHistory.objects.filter(id__gt=self.instance.id).exists():\n            raise ValidationError(\"历史记录不可再次回滚!\")\n        return True\n\n\nclass ApplicationHubSerializer(serializers.ModelSerializer):\n    app_id = serializers.IntegerField(source=\"id\")\n\n    class Meta:\n        model = ApplicationHub\n        fields = (\"app_id\", \"app_name\", \"app_version\")\n\n\nclass ServiceSerializer(serializers.ModelSerializer):\n    service_id = serializers.IntegerField(source=\"id\")\n    instance_name = serializers.CharField(source=\"service_instance_name\")\n    app_name = serializers.CharField(source=\"service.app_name\")\n    app_id = serializers.IntegerField(source=\"service.id\")\n    version = serializers.CharField(source=\"service.app_version\")\n\n    class Meta:\n        model = Service\n        fields = (\"service_id\", \"ip\", \"instance_name\", \"app_name\", \"version\",\n                  \"app_id\")\n\n\nclass RollbackListSerializer(serializers.ModelSerializer):\n\n    before_rollback_v = serializers.CharField(source=\"target_app.app_version\")\n    after_rollback_v = serializers.CharField(source=\"current_app.app_version\")\n    app_name = serializers.CharField(source=\"target_app.app_name\")\n    ip = serializers.CharField(source=\"service.ip\")\n    instance_name = serializers.CharField(source=\"service.service_instance_name\")\n\n    class Meta:\n        model = UpgradeDetail\n        fields = (\"id\", \"ip\", \"service_id\", \"app_name\", \"before_rollback_v\",\n                  \"after_rollback_v\", \"instance_name\")\n"
  },
  {
    "path": "omp_server/service_upgrade/tasks.py",
    "content": "import logging\nimport time\nfrom concurrent.futures import ThreadPoolExecutor, as_completed, wait, \\\n    ALL_COMPLETED\n\nfrom celery import shared_task\n\nfrom db_models.mixins import UpgradeStateChoices, RollbackStateChoices\nfrom db_models.models import UpgradeHistory, RollbackHistory, \\\n    RollbackDetail, Maintain, MainInstallHistory, Product, ApplicationHub\nfrom promemonitor.alertmanager import Alertmanager\nfrom service_upgrade.handler.base import load_upgrade_detail, \\\n    handler_pipeline, load_rollback_detail\nfrom service_upgrade.handler.rollback_handler import rollback_handlers\nfrom service_upgrade.handler.upgrade_handler import upgrade_handlers\nfrom service_upgrade.update_data_json import DataJsonUpdate\nfrom utils.parse_config import BASIC_ORDER, THREAD_POOL_MAX_WORKERS\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_service_order():\n    # 获取配置文件层次，解析成dict\n    service_layer = {}\n    for index, services in BASIC_ORDER.items():\n        for service in services:\n            service_layer[service] = index\n    return service_layer\n\n\ndef computer_operation_sorted(details):\n    # 通过服务依赖确定服务升级、回滚顺序\n    order_layer_details = {}\n    service_layer = get_service_order()\n    max_index = max(service_layer.values()) + 1\n    union_service = set()\n    for detail in details:\n        if isinstance(detail, RollbackDetail):\n            union_server = detail.upgrade.union_server\n            app_name = detail.upgrade.current_app.app_name\n            extend_fields = detail.upgrade.current_app.extend_fields\n        else:\n            union_server = detail.union_server\n            app_name = detail.current_app.app_name\n            extend_fields = detail.target_app.extend_fields\n        if union_server in union_service:\n            continue\n        union_service.add(union_server)\n        s_i = service_layer.get(app_name, None)\n        if s_i is None:\n            s_i = max_index + int(extend_fields.get(\"level\", 0))\n        if s_i not in order_layer_details:\n            order_layer_details[s_i] = [detail]\n        else:\n            order_layer_details[s_i].append(detail)\n    # [(0, [detail1]), (1, [detail2, detail3])]\n    return sorted(order_layer_details.items(), key=lambda x: x[0])\n\n\ndef set_alert_maintain(env_name):\n    try:\n        has_maintain = Maintain.objects.filter(\n            matcher_name=\"env\", matcher_value=env_name\n        ).exists()\n        if not has_maintain:\n            return Alertmanager().set_maintain_by_env_name(env_name)\n    except Exception as e:\n        logger.error(f\"进入维护模式失败：{str(e)}\")\n    return None\n\n\ndef update_data_json(operation_uuid, details):\n\n    data_json = DataJsonUpdate(operation_uuid)\n    try:\n        data_json.create_json_file(details)\n        fail_message = data_json.send_data_json_all()\n    except Exception as e:\n        logger.error(str(e))\n        return False, str(e)\n    if fail_message:\n        return False, \",\".join(fail_message)\n    return True, \"data.json更新成功！\"\n\n\ndef load_upgrade_service(history):\n    app_ids = list(\n        history.upgradedetail_set.filter(\n            service__service__product__isnull=False\n        ).values_list(\"service__service_id\", flat=True)\n    )\n    return app_ids\n\n\ndef load_rollback_service(history):\n    app_ids = list(\n        history.rollbackdetail_set.filter(\n            upgrade__service__service__product__isnull=False\n        ).values_list(\"upgrade__service__service_id\", flat=True)\n    )\n    return app_ids\n\n\ndef update_product_version(app_ids):\n    product_info = ApplicationHub.objects.filter(\n        id__in=app_ids\n    ).values(\"product__pro_name\", \"product_id\")\n    product_union = {}\n    for product in product_info:\n        product_name = product[\"product__pro_name\"]\n        product_id = product[\"product_id\"]\n        if product_name not in product_union:\n            product_union[product_name] = {product_id}\n        else:\n            product_union[product_name].add(product_id)\n    for product_name, product_ids in product_union.items():\n        if len(product_ids) > 1:\n            continue\n        Product.objects.filter(\n            product_instance_name__startswith=product_name\n        ).update(product_id=product_ids.pop())\n\n\n@shared_task\ndef upgrade_service(upgrade_history_id):\n    history = UpgradeHistory.objects.filter(id=upgrade_history_id).first()\n    if not history:\n        logger.error(f\"未找到id为{upgrade_history_id}的升级操作！\")\n        return\n    if history.upgrade_state not in {\n        UpgradeStateChoices.UPGRADE_WAIT,\n        UpgradeStateChoices.UPGRADE_FAIL\n    }:\n        logger.error(f\"升级记录状态为{history.get_upgrade_state_display()}，\"\n                     f\"不可升级！\")\n        return\n    main_install = MainInstallHistory.objects.order_by(\"-id\").first()\n\n    upgrade_details = history.upgradedetail_set.exclude(\n        upgrade_state=UpgradeStateChoices.UPGRADE_SUCCESS\n    ).exclude(has_rollback=True)\n\n    if history.pre_upgrade_state != UpgradeStateChoices.UPGRADE_SUCCESS:\n        state, msg = update_data_json(\n            main_install.operation_uuid, upgrade_details)\n        # todo:后续优化\n        history.pre_upgrade_result = {\n            \"update_data_json\": {\n                \"state\": 2 if state else 3,\n                \"message\": msg,\n                \"state_display\": \"升级成功\" if state else \"升级失败\"\n            }\n        }\n        if not state:\n            history.pre_upgrade_state = UpgradeStateChoices.UPGRADE_FAIL\n            history.upgrade_state = UpgradeStateChoices.UPGRADE_FAIL\n            history.save()\n            return\n        else:\n            history.pre_upgrade_state = UpgradeStateChoices.UPGRADE_SUCCESS\n            history.save()\n\n    if history.upgrade_state != UpgradeStateChoices.UPGRADE_ING:\n        history.upgrade_state = UpgradeStateChoices.UPGRADE_ING\n        history.save()\n\n    # 排除hadoop等多余服务，升级只升一次\n    order_layer_details = computer_operation_sorted(upgrade_details)\n\n    # 进入维护模式\n    set_alert_maintain(history.env.name)\n\n    with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n        upgrade_state = UpgradeStateChoices.UPGRADE_SUCCESS\n        for sort, details in order_layer_details:\n            all_task = []\n            for detail in details:\n                upgrade_args = load_upgrade_detail(\n                    detail, main_install.operation_uuid)\n                future_obj = executor.submit(\n                    handler_pipeline, upgrade_handlers, upgrade_args)\n                all_task.append(future_obj)\n            wait(all_task, return_when=ALL_COMPLETED)\n            for future in as_completed(all_task):\n                upgrade_args = future.result()\n                if upgrade_args is None:\n                    upgrade_state = UpgradeStateChoices.UPGRADE_FAIL\n                    break\n            if upgrade_state == UpgradeStateChoices.UPGRADE_FAIL:\n                break\n            time.sleep(5)\n    history.upgrade_state = upgrade_state\n    history.save()\n    app_ids = load_upgrade_service(history)\n    update_product_version(app_ids)\n\n\n@shared_task\ndef rollback_service(rollback_history_id):\n    history = RollbackHistory.objects.filter(id=rollback_history_id).first()\n    if not history:\n        logger.error(f\"未找到id为{rollback_history_id}的回滚操作！\")\n        return\n    if history.rollback_state not in {\n        RollbackStateChoices.ROLLBACK_WAIT,\n        RollbackStateChoices.ROLLBACK_FAIL\n    }:\n        logger.error(f\"回滚记录状态为{history.get_rollback_state_display()}，不可回滚！\")\n        return\n    if history.rollback_state != RollbackStateChoices.ROLLBACK_ING:\n        history.rollback_state = RollbackStateChoices.ROLLBACK_ING\n        history.save()\n\n    # 排除hadoop等多余服务，升级只升一次\n    rollback_details = history.rollbackdetail_set.exclude(\n        rollback_state=RollbackStateChoices.ROLLBACK_SUCCESS\n    )\n    order_layer_details = computer_operation_sorted(rollback_details)\n\n    set_alert_maintain(history.env.name)\n\n    main_install = MainInstallHistory.objects.order_by(\"-id\").first()\n\n    with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n        rollback_state = RollbackStateChoices.ROLLBACK_SUCCESS\n        for sort, details in order_layer_details:\n            all_task = []\n            for detail in details:\n                rollback_args = load_rollback_detail(\n                    detail, main_install.operation_uuid)\n                future_obj = executor.submit(\n                    handler_pipeline, rollback_handlers, rollback_args)\n                all_task.append(future_obj)\n            wait(all_task, return_when=ALL_COMPLETED)\n            for future in as_completed(all_task):\n                rollback_args = future.result()\n                if rollback_args is None:\n                    rollback_state = RollbackStateChoices.ROLLBACK_FAIL\n                    break\n            if rollback_state == RollbackStateChoices.ROLLBACK_FAIL:\n                break\n    history.rollback_state = rollback_state\n    history.save()\n    app_ids = load_rollback_service(history)\n    update_product_version(app_ids)\n"
  },
  {
    "path": "omp_server/service_upgrade/update_data_json.py",
    "content": "import json\nimport os\n\nfrom django.conf import settings\n\nfrom db_models.models import DetailInstallHistory, Service, Host, UpgradeDetail\nfrom utils.plugin.salt_client import SaltClient\n\n\nclass DataJsonUpdate(object):\n    \"\"\" 生成data.json数据 \"\"\"\n\n    def __init__(self, operation_uuid):\n        \"\"\"\n        data.json数据生成方法\n        :param operation_uuid: 唯一操作uuid\n        :type operation_uuid: str\n        \"\"\"\n        self.json_name = f\"{operation_uuid}.json\"\n        self.data_path = os.path.join(\"data_files\", self.json_name)\n\n    def get_ser_install_args(self, obj, app_install_args=None):\n        \"\"\"\n        获取服务的安装参数\n        :param obj: Service obj\n        :param app_install_args: app_install_args, list\n        :return:\n        \"\"\"\n        deploy_detail = DetailInstallHistory.objects.filter(\n            service=obj).first()\n        install_args = []\n        deploy_mode = \"\"\n        if deploy_detail:\n            install_args = \\\n                deploy_detail.install_detail_args.get(\"install_args\", [])\n            deploy_mode = \\\n                deploy_detail.install_detail_args.get(\"deploy_mode\")\n        old_arg_dict = {}\n        for old_arg in install_args:\n            old_arg_dict[old_arg[\"key\"]] = old_arg\n        if app_install_args:\n            for new_arg in app_install_args:\n                if new_arg.get(\"key\") not in old_arg_dict:\n                    new_arg[\"default\"] = new_arg[\"default\"].format(\n                        data_path=\"data_path\")\n                    install_args.append(new_arg)\n        if deploy_detail:\n            deploy_detail.install_detail_args[\"install_args\"] = install_args\n            deploy_detail.save()\n        return {\n            \"install_args\": install_args,\n            \"deploy_mode\": deploy_mode\n        }\n\n    def parse_single_service(self, service, tag_app=None):\n        \"\"\"\n        解析单个服务数据\n        :param server: Service\n        :param tag_app: Service\n        :return:\n        \"\"\"\n        _ser_dic = {\n            \"ip\": service.ip,\n            \"name\": service.service.app_name,\n            \"role\": service.service_role if service.service_role else \"master\",\n            \"instance_name\": service.service_instance_name,\n            \"cluster_name\": service.cluster.cluster_name if service.cluster else None,\n            \"vip\": service.vip,\n            \"ports\": json.loads(service.service_port or '[]'),\n            \"dependence\": json.loads(service.service_dependence or '[]'),\n        }\n        if service.service.app_name == \"hadoop\":\n            _ser_dic[\"instance_name\"] = \\\n                \"hadoop-\" + \"-\".join(service.ip.split(\".\")[-2:])\n        if tag_app:\n            _ser_dic[\"ports\"] = service.update_port(\n                json.loads(tag_app.app_port or '[]')\n            )\n            _ser_dic[\"dependence\"] = service.update_dependence(\n                service.service_dependence,\n                json.loads(tag_app.app_dependence or '[]')\n            )\n            _others = self.get_ser_install_args(\n                service, json.loads(tag_app.app_install_args or '[]'))\n        else:\n            _others = self.get_ser_install_args(service)\n        _ser_dic.update(_others)\n        return _ser_dic\n\n    def make_data_json(self, json_lst):\n        \"\"\"\n        创建data.json数据文件\n        :param json_lst: 服务及分布信息组成的列表\n        :type json_lst: list\n        :return:\n        \"\"\"\n        _path = os.path.join(\n            settings.PROJECT_DIR,\n            \"package_hub\",\n            self.data_path\n        )\n        if not os.path.exists(os.path.dirname(_path)):\n            os.makedirs(os.path.dirname(_path))\n        with open(_path, \"w\", encoding=\"utf8\") as fp:\n            json.dump(json_lst, fp, ensure_ascii=False, indent=2)\n\n    def decompose_detail(self, details):\n        _dic = {}\n        if not details:\n            return _dic\n        for detail in details:\n            if isinstance(detail, UpgradeDetail):\n                _dic[detail.service.service_instance_name] = detail.target_app\n            else:\n                _dic[detail.upgrade.service.service_instance_name] = \\\n                    detail.current_app\n        return _dic\n\n    def load_json_lst(self, details):\n        # 在json文件中标记该服务所在主机上的agent的地址\n        ip_agent_dir_dir = {\n            ip: agent_dir for ip, agent_dir in\n            Host.objects.values_list(\"ip\", \"agent_dir\")\n        }\n        json_lst = list()\n        services = Service.split_objects.all()\n        for service in services:\n            tag_app = details.get(service.service_instance_name)\n            _item = self.parse_single_service(service, tag_app)\n            _item[\"agent_dir\"] = ip_agent_dir_dir.get(_item.get(\"ip\"))\n            json_lst.append(_item)\n        return json_lst\n\n    def send_data_json_target(self, salt_obj, target_ip):\n        host = Host.objects.get(ip=target_ip)\n        json_target_path = os.path.join(\n            host.data_folder, \"omp_packages\", self.json_name)\n        return salt_obj.cp_file(\n            target=target_ip,\n            source_path=self.data_path,\n            target_path=json_target_path\n        )\n\n    def send_data_json_all(self):\n        hosts = Host.objects.all().values_list(\"ip\", \"data_folder\")\n        fail_message = []\n        salt_obj = SaltClient()\n        for host in hosts:\n            json_target_path = os.path.join(\n                host[1], \"omp_packages\", self.json_name)\n            state, message = salt_obj.cp_file(\n                target=host[0],\n                source_path=self.data_path,\n                target_path=json_target_path\n            )\n            if not state:\n                fail_message.append(\n                    f\"ip:{host[1]}更新data.json失败，错误：{message}\")\n        return fail_message\n\n    def create_json_file(self, details=None):\n        \"\"\"\n        更新data.json\n        :param details: 升级details\n        :return:\n        \"\"\"\n        details = self.decompose_detail(details)\n        current_json_lst = self.load_json_lst(details)\n        # step2: 生成data.json\n        self.make_data_json(json_lst=current_json_lst)\n"
  },
  {
    "path": "omp_server/service_upgrade/urls.py",
    "content": "from django.urls import path\n\nfrom service_upgrade.views import UpgradeChoiceMaxVersionListAPIView, \\\n    UpgradeHistoryListAPIView, UpgradeHistoryDetailAPIView, DoUpgradeAPIView, \\\n    RollbackHistoryListAPIView, RollbackHistoryDetailAPIView, \\\n    RollbackChoiceListAPIView, DoRollbackAPIView\n\nupgrade_urlpatterns = [\n    path('history', UpgradeHistoryListAPIView.as_view(), name=\"history\"),\n    path(\n        'history/<int:pk>',\n        UpgradeHistoryDetailAPIView.as_view(),\n        name=\"detail\"),\n    path(\n        'can-upgrade',\n        UpgradeChoiceMaxVersionListAPIView.as_view(),\n        name=\"can-upgrade\"),\n    path('do-upgrade', DoUpgradeAPIView.as_view(), name=\"do-upgrade\")\n]\n\nrollback_urlpatterns = [\n    path('history', RollbackHistoryListAPIView.as_view(), name=\"history\"),\n    path(\n        'history/<int:pk>',\n        RollbackHistoryDetailAPIView.as_view(),\n        name=\"detail\"),\n    path(\n        'can-rollback',\n        RollbackChoiceListAPIView.as_view(),\n        name=\"can-rollback\"),\n    path('do-rollback', DoRollbackAPIView.as_view(), name=\"do-rollback\")\n]\n"
  },
  {
    "path": "omp_server/service_upgrade/views.py",
    "content": "import json\nimport logging\nimport traceback\n\nfrom django.db import models, transaction\nfrom rest_framework.filters import OrderingFilter, SearchFilter\nfrom rest_framework.generics import ListAPIView, GenericAPIView,\\\n    RetrieveUpdateAPIView\nfrom rest_framework.response import Response\n\nfrom db_models.mixins import UpgradeStateChoices, RollbackStateChoices\nfrom db_models.models import UpgradeHistory, Service, ApplicationHub, Env, \\\n    UpgradeDetail, RollbackHistory, RollbackDetail\nfrom utils.common.exceptions import GeneralError\nfrom utils.common.paginations import PageNumberPager\nfrom .filters import RollBackHistoryFilter\nfrom .serializers import UpgradeHistorySerializer, ServiceSerializer, \\\n    UpgradeHistoryDetailSerializer,  ApplicationHubSerializer, \\\n    UpgradeTryAgainSerializer, RollbackHistorySerializer, \\\n    RollbackHistoryDetailSerializer, RollbackTryAgainSerializer, \\\n    RollbackListSerializer\nfrom .tasks import upgrade_service, rollback_service\n\nlogger = logging.getLogger(\"server\")\n\n\nclass UpgradeHistoryListAPIView(ListAPIView):\n    # 升级历史记录\n    pagination_class = PageNumberPager\n    queryset = UpgradeHistory.objects.all()\\\n        .prefetch_related(\"upgradedetail_set\")\n    filter_backends = (OrderingFilter, )\n    serializer_class = UpgradeHistorySerializer\n    ordering_fields = (\"id\", )\n    ordering = ('-id',)\n    get_description = \"升级历史记录页\"\n\n\nclass UpgradeHistoryDetailAPIView(RetrieveUpdateAPIView):\n    # 升级历史记录详情\n    queryset = UpgradeHistory.objects.all()\\\n        .prefetch_related(\"upgradedetail_set\")\n    serializer_class = UpgradeHistoryDetailSerializer\n    lookup_url_kwarg = 'pk'\n    get_description = \"升级历史记录详情页\"\n    put_description = \"升级重试\"\n\n    def update(self, request, *args, **kwargs):\n        # put 升级重试\n        instance = self.get_object()\n        serializer = UpgradeTryAgainSerializer(instance, data=request.data)\n        serializer.is_valid(raise_exception=True)\n        upgrade_service.delay(instance.id)\n        return Response()\n\n\nclass UpgradeChoiceAllVersionListAPIView(GenericAPIView):\n    # 可升级服务列表（可选择升级的目标）\n    queryset = Service.split_objects.filter(\n        service_status__in=[0, 1, 2, 3, 4]\n    ).select_related(\"service\")\n    filter_backends = (SearchFilter, )\n    search_fields = (\"service__app_name\",)\n    get_description = \"可升级服务列表\"\n\n    def get_service_info(self, services_data):\n        \"\"\"\n        确定已安装服务最小版本，缩小查询范围及组装数据\n        \"\"\"\n        service_min_app_ids, services_versions = {}, {}\n        for service_data in services_data:\n            app_name = service_data.get(\"app_name\")\n            app_id = service_data.get(\"app_id\")\n            min_app_id = service_min_app_ids.get(app_name, None)\n            if app_name not in services_versions:\n                services_versions[app_name] = [service_data]\n            else:\n                services_versions[app_name].append(service_data)\n            if min_app_id is None or app_id < min_app_id:\n                service_min_app_ids[app_name] = app_id\n        return services_versions, service_min_app_ids\n\n    def get(self, requests):\n        queryset = self.filter_queryset(self.get_queryset())\n        services_data = ServiceSerializer(queryset, many=True).data\n        if not services_data:\n            return Response({\"results\": []})\n        services_v, min_app_ids = self.get_service_info(\n            services_data)\n        # 查询服务可能存在可升级的安装包\n        apps = ApplicationHub.objects.filter(\n            id__gt=min(min_app_ids.values()),\n            app_name__in=services_v.keys()\n        ).order_by(\"-id\").exclude(id__in=min_app_ids.values())\n        if not apps:\n            return Response({\"results\": []})\n        # 确定服务可升级的安装包\n        apps_data = ApplicationHubSerializer(apps, many=True).data\n        for app_data in apps_data:\n            app_id = app_data.get(\"app_id\")\n            app_name = app_data.pop(\"app_name\")\n            if min_app_ids.get(app_name, float(\"inf\")) >= app_id:\n                apps_data.remove(app_data)\n                continue\n            for service_v in services_v.get(app_name):\n                if service_v.get(\"app_id\", float(\"inf\")) >= app_id:\n                    continue\n                if \"can_upgrade\" not in service_v:\n                    service_v[\"can_upgrade\"] = [app_data]\n                else:\n                    service_v[\"can_upgrade\"].append(app_data)\n        # 格式化数据，排除不可升级服务\n        results = []\n        # {\"a\": [{\"\"}, {}]}\n        for app_name, services in services_v.items():\n            if not services:\n                services_v.pop(app_name)\n            upgrade_services = []\n            for service in services:\n                if service.get(\"can_upgrade\"):\n                    upgrade_services.append(service)\n            if upgrade_services:\n                results.append(\n                    {\"app_name\": app_name, \"children\": upgrade_services}\n                )\n        # results: [{\"app_name\": a, \"service\": [{\"can_upgrade\": []...}]\n        return Response({\"results\": results})\n\n\nclass UpgradeChoiceMaxVersionListAPIView(UpgradeChoiceAllVersionListAPIView):\n    # 可升级服务列表（只展示可供升级的最高版本）\n    get_description = \"可升级服务列表\"\n\n    def get_service_max_app(self, apps):\n        max_apps = {}\n        for app in apps:\n            app_info = max_apps.get(app[\"app_name\"], {})\n            if app_info.get(\"app_id\", float(\"-inf\")) <= app[\"app_id\"]:\n                max_apps[app[\"app_name\"]] = app\n        return max_apps\n\n    def get(self, requests):\n        queryset = self.filter_queryset(self.get_queryset())\n        services_data = ServiceSerializer(queryset, many=True).data\n        if not services_data:\n            return Response({\"results\": []})\n        services_v, min_app_ids = self.get_service_info(\n            services_data)\n        # 查询服务可能存在可升级的安装包\n        apps = ApplicationHub.objects.filter(\n            id__gt=min(min_app_ids.values()),\n            app_name__in=services_v.keys()\n        ).order_by(\"-id\").exclude(id__in=min_app_ids.values())\n        if not apps:\n            return Response({\"results\": []})\n        # 确定服务可升级的安装包\n        apps_data = ApplicationHubSerializer(apps, many=True).data\n        max_apps_dict = self.get_service_max_app(apps_data)\n        results = []\n        for app_name, max_app in max_apps_dict.items():\n            services = services_v.get(app_name)\n            upgrade_services = []\n            for service_v in services:\n                if service_v.get(\"app_id\", float(\"inf\")) >= max_app[\"app_id\"]:\n                    continue\n                service_v[\"can_upgrade\"] = [max_app]\n                upgrade_services.append(service_v)\n            if upgrade_services:\n                results.append(\n                    {\n                        \"app_name\": app_name,\n                        \"children\": upgrade_services,\n                        \"can_upgrade\": max_app\n                    }\n                )\n        return Response({\"results\": results})\n\n\nclass DoUpgradeAPIView(GenericAPIView):\n    post_description = \"升级服务\"\n\n    def valid_can_upgrade(self, data):\n        # 校验信息\n        services = list(\n            Service.split_objects.filter(\n                id__in=data.keys(),\n                service_status__in=[0, 1, 2, 3, 4]\n            ).annotate(\n                app_name=models.F(\"service__app_name\"),\n                current_app_id=models.F(\"service_id\")\n            ).values(\n                \"id\", \"app_name\", \"ip\", \"current_app_id\", \"service_dependence\"\n            )\n        )\n        if not services:\n            raise GeneralError(\"请选择需要升级的服务！\")\n        apps = ApplicationHub.objects.filter(\n            id__in=data.values(),\n            is_release=True\n        ).values(\"id\", \"app_name\", \"app_version\", \"app_dependence\")\n        app_dict = {}\n        for app in apps:\n            # todo: app_dependence字段有问题，后续需修改\n            app_dict[app.get(\"app_name\")] = {\n                \"target_app_id\": app.get(\"id\"),\n                \"app_dependence\": json.loads(\n                    app.get(\"app_dependence\") or '[]'\n                )\n            }\n        for service in services:\n            app_name = service.get(\"app_name\")\n            app_info = app_dict.get(app_name, {})\n            if app_info.get(\"target_app_id\", float(\"-inf\")) \\\n                    <= service[\"current_app_id\"]:\n                raise GeneralError(f\"服务{app_name}升级版本小于或等于当前版本！\")\n            try:\n                Service.update_dependence(\n                    service.get(\"service_dependence\"),\n                    app_info.get(\"app_dependence\", [])\n                )\n            except Exception as e:\n                raise GeneralError(\n                    f\"服务{service.get('app_name')}依赖校验失败：{str(e)}\")\n            service.update(app_dict.get(app_name))\n        return services\n\n    def post(self, requests):\n        if UpgradeHistory.objects.filter(\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_WAIT,\n                UpgradeStateChoices.UPGRADE_ING,\n            ]\n        ).exists():\n            raise GeneralError(\"存在正在升级的服务，请稍后！\")\n        if RollbackHistory.objects.filter(\n            rollback_state__in=[\n                RollbackStateChoices.ROLLBACK_WAIT,\n                RollbackStateChoices.ROLLBACK_ING,\n            ]\n        ).exists():\n            raise GeneralError(\"存在正在回滚的服务，请稍后！\")\n        fail_query = UpgradeDetail.objects.filter(\n                upgrade_state=UpgradeStateChoices.UPGRADE_FAIL,\n                service__isnull=False\n        ).exclude(has_rollback=True)\n        if fail_query.exists():\n            fail_services = list(\n                fail_query.values_list(\"union_server\", flat=True)\n            )\n            raise GeneralError(\n                f\"存在升级失败的服务，请继续升级或回滚！失败服务：{fail_services}\")\n        choices = requests.data.get(\"choices\", [])\n        if not choices:\n            raise GeneralError(\"请选择需要升级的服务！\")\n        try:\n            data = {}\n            for choice in choices:\n                if choice.get(\"service_id\") in data:\n                    raise KeyError(f'{choice.get(\"service_id\")}重复！')\n                data[choice.get(\"service_id\")] = choice.get(\"app_id\")\n        except Exception as e:\n            logger.error(\n                f\"解析升级数据错误：{str(e)}, 详情为：\\n{traceback.format_exc()}\")\n            raise GeneralError(\"解析升级数据错误！\")\n        services = self.valid_can_upgrade(data)\n        with transaction.atomic():\n            history = UpgradeHistory.objects.create(\n                env=Env.objects.first(),\n                operator=requests.user\n            )\n            details = []\n            for service in services:\n                service_id = service.pop(\"id\")\n                app_name = service.pop(\"app_name\")\n                ip = service.pop(\"ip\")\n                service.pop(\"app_dependence\")\n                service.pop(\"service_dependence\")\n                details.append(\n                    UpgradeDetail(\n                        history=history,\n                        service_id=service_id,\n                        union_server=f\"{ip}-{app_name}\",\n                        **service,\n                    )\n                )\n            UpgradeDetail.objects.bulk_create(details)\n        upgrade_service.delay(history.id)\n        return Response({\"history\": history.id})\n\n\nclass RollbackHistoryListAPIView(ListAPIView):\n    pagination_class = PageNumberPager\n    queryset = RollbackHistory.objects.all()\\\n        .prefetch_related(\"rollbackdetail_set\")\n    filter_backends = (OrderingFilter, )\n    serializer_class = RollbackHistorySerializer\n    ordering_fields = (\"id\", )\n    ordering = ('-id',)\n    get_description = \"回滚历史记录页\"\n\n\nclass RollbackHistoryDetailAPIView(RetrieveUpdateAPIView):\n    queryset = RollbackHistory.objects.all()\\\n        .prefetch_related(\"rollbackdetail_set\")\n    serializer_class = RollbackHistoryDetailSerializer\n    lookup_url_kwarg = 'pk'\n    get_description = \"回滚历史记录详情页\"\n    put_description = \"回滚重试\"\n\n    def update(self, request, *args, **kwargs):\n        instance = self.get_object()\n        serializer = RollbackTryAgainSerializer(instance, data=request.data)\n        serializer.is_valid(raise_exception=True)\n        rollback_service.delay(instance.id)\n        return Response()\n\n\nclass RollbackChoiceListAPIView(GenericAPIView):\n    queryset = UpgradeDetail.objects.filter(\n        upgrade_state__in=[\n            UpgradeStateChoices.UPGRADE_SUCCESS,\n            UpgradeStateChoices.UPGRADE_FAIL\n        ]\n    ).exclude(has_rollback=True).exclude(service__isnull=True)\n    filter_backends = (SearchFilter, RollBackHistoryFilter)\n    search_fields = (\"target_app__app_name\", )\n    get_description = \"可回滚服务列表页\"\n\n    def get(self, requests):\n        queryset = self.filter_queryset(self.get_queryset())\n        upgrades_data = RollbackListSerializer(queryset, many=True).data\n        service_id_max_d, service_name_max_d = {}, {}\n        for upgrade_data in upgrades_data:\n            service_id = upgrade_data.get(\"service_id\")\n            detail_id = upgrade_data.get(\"id\")\n            app_name = upgrade_data.get(\"app_name\")\n            service_max_data = service_id_max_d.get(service_id, {})\n            if service_max_data.get(\"id\", float(\"-inf\")) > detail_id:\n                continue\n            service_id_max_d[service_id] = upgrade_data\n            if app_name not in service_name_max_d:\n                service_name_max_d[app_name] = {service_id: upgrade_data}\n            else:\n                service_name_max_d[app_name].update({service_id: upgrade_data})\n        response_data = [\n            {\"app_name\": app_name, \"children\": list(max_info.values())}\n            for app_name, max_info in service_name_max_d.items()\n        ]\n        return Response(data={\"results\": response_data})\n\n\nclass DoRollbackAPIView(GenericAPIView):\n    get_description = \"回滚服务\"\n\n    def post(self, requests):\n        if UpgradeHistory.objects.filter(\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_WAIT,\n                UpgradeStateChoices.UPGRADE_ING,\n            ]\n        ).exists():\n            raise GeneralError(\"存在正在升级的服务，请稍后！\")\n        if RollbackHistory.objects.filter(\n            rollback_state__in=[\n                RollbackStateChoices.ROLLBACK_WAIT,\n                RollbackStateChoices.ROLLBACK_ING,\n            ]\n        ).exists():\n            raise GeneralError(\"存在正在回滚的服务，请稍后！\")\n        choices = requests.data.get(\"choices\", [])\n        if not choices:\n            raise GeneralError(\"请选择需要回滚的记录！\")\n        upgrade_details = UpgradeDetail.objects.filter(\n            id__in=choices,\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_SUCCESS,\n                UpgradeStateChoices.UPGRADE_FAIL\n            ]\n        ).values(\"id\", \"current_app_id\", \"union_server\")\n        if upgrade_details.count() != len(choices):\n            raise GeneralError(\"提交信息校验失败，请刷新重试！\")\n        # 校验同一个服务是否回滚至同一版本\n        union_app = {}\n        for detail in upgrade_details:\n            rollback_app_id = detail.get(\"current_app_id\")\n            union_server = detail.get(\"union_server\")\n            if not union_server:\n                raise GeneralError(f\"实例{union_server}不在平台纳管范围！\")\n            if not union_app.get(union_server):\n                union_app[union_server] = rollback_app_id\n                continue\n            if union_app.get(union_server) != rollback_app_id:\n                raise GeneralError(f\"实例{union_server}将回滚的服务版本不一致！\")\n        with transaction.atomic():\n            history = RollbackHistory.objects.create(\n                env=Env.objects.first(),\n                operator=requests.user\n            )\n            RollbackDetail.objects.bulk_create(\n                [\n                    RollbackDetail(\n                        history=history,\n                        upgrade_id=upgrade_detail.get(\"id\")\n                    )\n                    for upgrade_detail in upgrade_details\n                ]\n            )\n        rollback_service.delay(history.id)\n        return Response({\"history\": history.id})\n"
  },
  {
    "path": "omp_server/services/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/services/admin.py",
    "content": "# from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/services/app_check/__init__.py",
    "content": "from .conf_check import ConfCheck\nfrom .manage_ser_exec import ManagerService\n\n__all__ = [\n    ConfCheck, ManagerService\n]\n"
  },
  {
    "path": "omp_server/services/app_check/conf_check.py",
    "content": "import time\nimport logging\nimport json\nimport random\n\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom utils.parse_config import THREAD_POOL_MAX_WORKERS\nfrom app_store.new_install_utils import RedisDB\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.parse_config import SERVICE_DISCOVERY\nfrom db_models.models import (\n    ApplicationHub, Service\n)\n\nlogger = logging.getLogger('server')\n\n\n# 存在ip的筛选\nclass ConfCheck:\n\n    def __init__(self, app_data):\n        self.app_data = app_data\n        self.redis_obj = RedisDB()\n        self.need_key = {'base_dir', 'run_user', 'service_port', 'data_dir', 'log_dir'}\n        self.app_dc = {}\n        self.ips = {}\n        self.response_ls = []\n        self.redis_save = []\n        # self.cluster_dc = {}\n        self.app_ser = {}\n        self.is_common_dc = {}\n        self.is_base_env = ApplicationHub.objects.filter(\n            is_base_env=True\n        ).values_list(\"app_name\", flat=True)\n        self.is_common = ApplicationHub.objects.filter(\n            is_base_env=False, app_type=ApplicationHub.APP_TYPE_COMPONENT).values_list(\"app_name\", flat=True)\n        self.service_ip_name = self._get_service_k_v()\n        # self.service_cluster_ip = self._get_cluster_ip()\n        self.error_ser = set()\n\n    @staticmethod\n    def _get_service_k_v():\n        service_dc = {}\n        service_in_ip = {}\n        service_ls = Service.objects.all().values_list(\"ip\", \"service__app_name\", \"service_instance_name\")\n        for k in service_ls:\n            service_dc.setdefault(k[1], []).append(k[0])\n            service_in_ip.setdefault(k[1], {}).update({k[0]: k[2]})\n        return service_dc, service_in_ip\n\n    @staticmethod\n    def _get_cluster_ip():\n        \"\"\"\n        \"jdk:{1:[\"192.168.0.1\",\"192.168.0.2\"]}\"\n        \"\"\"\n        app_dc = {}\n        service_all = Service.objects.all().values_list(\"ip\", \"cluster\", \"cluster__cluster_service_name\")\n        for app in service_all:\n            if app[1]:\n                app_dc.setdefault(app[2], {}).setdefault(app[1], []).append(app[0])\n        return app_dc\n\n    @staticmethod\n    def explain_json(text):\n        if text:\n            return json.loads(text)\n        return []\n\n    def fix_service(self):\n        \"\"\"\n        整理格式\n        {\n        app_name1:{\n        version:\"xxx\",\n        base_dir:\"xx\",\n        run_user:\"xx\",\n        service_port:\"xx\"\n        },\n        app_name2:{\n        .....\n        }\n        }\n        \"\"\"\n        self.ips = self.app_data.pop(\"ips\", [])\n        for info in self.app_data.get(\"service\", []):\n            if info.get(\"child\"):\n                pro_ser_ls = list(info.get(\"child\").values())[0]\n                for app in pro_ser_ls:\n                    app_install_dc = {}\n                    name = app.pop(\"name\")\n                    app_install_dc[\"version\"] = app.pop(\"version\")\n                    app_install_dc.update(app)\n                    self.app_dc[name] = app_install_dc\n            else:\n                version = {\"version\": info.pop(\"version\")[0]}\n                name = info.pop(\"name\")\n                info.update(version)\n                self.app_dc[name] = info\n\n    def fix_dependence(self):\n        \"\"\"\n        整理依赖\n        app_name:{\n        version:\"xxx\",\n        base_dir:\"xx\",\n        run_user:\"xx\",\n        service_port:\"xx\",\n        de_app_id:[\n        \"1\",\"2\"\n        ]\n        }\n        \"\"\"\n        app_ls = ApplicationHub.objects.filter(app_name__in=list(self.app_dc)). \\\n            values_list(\"app_name\", \"app_dependence\", \"app_version\")\n        app_all_dc = {}\n        # 前缀匹配多版本时使用最新版本,版本需要有已安装的服务.\n        # \"id\", \"app_name\", \"app_version\", \"service\"\n        app_all = ApplicationHub.objects.all().values_list(\n            \"id\", \"app_name\", \"app_version\", \"service__service_instance_name\"\n        )\n        for app in app_all:\n            if app[3]:\n                app_all_dc[f\"{app[1]}:{app[2].split('.')[0]}\"] = app[0]\n                self.app_ser.setdefault(f\"{app[1]}:{app[2]}\", []).append(app[3])\n        # 前期校验完依赖存在，因此认定当前依赖的一定会在服务列表中找寻到\n        for new in app_ls:\n            if self.app_dc[new[0]].get(\"version\") != new[2]:\n                continue\n            for dependence in self.explain_json(new[1]):\n                dependence_key = f'{dependence.get(\"name\")}:{dependence.get(\"version\")}'\n                if app_all_dc.get(dependence_key):\n                    self.app_dc[new[0]].setdefault(\"de_app_id\", []).append(app_all_dc.get(dependence_key))\n\n    def produce_cmd_and_exec(self, ip, agent_dir, install_args, app):\n        \"\"\"\n        校验基本信息\n        1.目录 必须\n        2.用户 必须\n        3.端口存在 可选\n        \"\"\"\n        # 过滤已经纳管的\n        if ip in self.service_ip_name[0].get(app, []):\n            return True, \"\"\n        salt_client = SaltClient()\n        base_dir, is_success, message = \\\n            install_args.get(\"base_dir\").replace('{data_path}', agent_dir), False, \"\"\n        if base_dir:\n            cmd = []\n            dir_name = [\"base_dir\", \"data_dir\", \"log_dir\"]\n            for _ in dir_name:\n                dir_path = install_args.get(_).replace('{data_path}', agent_dir)\n                install_args[_] = dir_path\n                if dir_path:\n                    cmd.append(f\"test -d {dir_path}||echo {dir_path}\")\n            cmd = \"&&\".join(cmd)\n            is_success, message = salt_client.cmd(target=ip, command=cmd, timeout=10)\n            if message:\n                return False, f\"{ip}:{app}:{message}目录不存在\"\n        if is_success and install_args.get(\"run_user\"):\n            f_dir, c_dir = base_dir.rsplit(\"/\", 1)\n            cmd = f\"ls -l {f_dir} | grep {c_dir} | head -1 | awk '{{print $3}}'\"\n            is_success, message = salt_client.cmd(target=ip, command=cmd, timeout=10)\n            if not is_success and message != install_args.get(\"run_user\"):\n                return False, f\"{ip}:{app}:{install_args.get('run_user')}与当前用户{message}不匹配\"\n        if is_success and install_args.get(\"service_port\"):\n            cmd = f\"</dev/tcp/{ip}/{install_args.get('service_port')}\"\n            is_success, message = salt_client.cmd(target=ip, command=cmd, timeout=10)\n            if not is_success:\n                is_success, message = True, f\"{ip}:{app}:{install_args.get('service_port')}\" \\\n                                            f\"端口不存在，建议判断下服务状态再纳管\"\n        # 合法的ip加上\n        self.app_dc[app].setdefault(\"ip\", []).append(ip)\n        return is_success, message\n\n    def get_dependence(self, app, ip):\n        \"\"\"\n        返回依赖信息\n        {\n        \"zookeeper_cluster1\": [\"zookeeper_0_1\", \"zookeeper_0_2\"],\n        \"jdk_172_1\": [\"jdk_172_1\"]\n        }\n        \"\"\"\n\n        dc_all = []\n        de_app_id = self.app_dc[app].get(\"de_app_id\", [])\n        # 存在同一个版本多套集群的情况。\n        dependence_info = Service.objects.filter(\n            service__id__in=list(set(de_app_id))).values_list(\"service_instance_name\", \"cluster__cluster_name\",\n                                                   \"service__app_name\", \"ip\")\n        alone_ser = {}\n        de_dc = {}\n        cluster_ser = {}\n        for de in dependence_info:\n            # 基础组件要求必须在同节点上\n            if de[2] in self.is_base_env and de[3] != ip:\n                continue\n            # 多个集群取其中一个集群\n            if de[1]:\n                cluster_name = cluster_ser.get(de[2])\n                if cluster_name and cluster_name != de[1]:\n                    continue\n                cluster_ser[de[2]] = de[1]\n                de_dc.setdefault(de[2], []).append(de[0])\n            # 多个单节点拿最新的 {\"jdk\":\"jdk-0-1\"}\n            else:\n                alone_ser[de[2]] = de[0]\n        if alone_ser:\n            for name, instance in alone_ser.items():\n                dc_all.append({name: [instance]})\n        for cluster_name, instance_ls in de_dc.items():\n            dc_all.append({cluster_name: instance_ls})\n        # 基础组件没匹配上(jdk).\n        if len(set(de_app_id)) != len(dc_all):\n            return False, dc_all\n        return True, dc_all\n\n    # def explain_scripts_de(self, ser_info):\n    #    \"\"\"\n    #    检查依赖的ip是否在已纳管的服务中\n    #    \"\"\"\n    #    for app_name, ip in ser_info[\"dependence\"].items():\n    #        app_ips = self.service_ip_name[0].get(app_name, [])\n    #        if set(ip) - set(app_ips):\n    #            return False, f\"当前依赖{app_name}在节点处不存在{set(ip) - set(app_ips)}\"\n    #        if len(ip) > 1 and app_name not in self.is_base_env:\n    #            is_mach = False\n    #            for ip_c in self.service_cluster_ip.get(app_name, {}).values():\n    #                if set(ip_c) & set(ip) == set(ip):\n    #                    is_mach = True\n    #            if not is_mach:\n    #                return False, f\"当前依赖{app_name}节点处{ip}在数据库中不存在\"\n    #    return True, \"\"\n\n    # def dumps_scripts_args(self, instance_ls, ser_info, install_args, version, app):\n    #    \"\"\"\n    #    校验成功数据录入\n    #    \"\"\"\n    #    random_str = random.sample('ABCDEFGHIJKLMNQPQRSTUVWXYZ1234567890', 10)\n    #    first_ip = list(instance_ls)[0]\n    #    instance_name = f\"{app}_{first_ip.split('.')[2]}_{first_ip.split('.')[3]}\" if \\\n    #        len(instance_ls) == 1 else f\"{list(ser_info['instance'])[0]}-cluster-{''.join(random_str)}\"\n    #    dependence_name = []\n    #    for a_p, ip in ser_info[\"dependence\"].items():\n    #        app_dc = {a_p: []}\n    #        app_all_dc = self.service_ip_name[1].get(a_p, {})\n    #        for ip_d in ip:\n    #            app_dc[a_p].append(app_all_dc.get(ip_d, \"\"))\n    #        dependence_name.append(app_dc)\n\n    #    # 类似扩容逻辑\n    #    app_ips = set(self.service_ip_name[0].get(list(ser_info['instance'])[0], []))\n    #    finally_ip = list(instance_ls - app_ips)\n    #    self.response_ls.append({\"name\": app,\n    #                             \"ip\": finally_ip,\n    #                             \"error\": \"\"})\n    #    self.redis_save.append({instance_name: list(instance_ls),\n    #                            \"dependence_instance\": dependence_name,\n    #                            \"app_name\": list(ser_info['instance'])[0],\n    #                            \"app_version\": version,\n    #                            \"app_ip\": finally_ip,\n    #                            \"error_msg\": \"\",\n    #                            \"install_args\": install_args})\n\n    # @staticmethod\n    # def check_dump_de(new_de, old_de):\n    #    diff = set(new_de.keys()) & set(old_de.keys())\n    #    if diff != set(new_de.keys()):\n    #        return f\"服务同集群下不同节点采集到的依赖存在不同{','.join(diff)}\"\n    #    for app_name, ip_list in new_de.items():\n    #        # ToDo 临时修改\n    #        if app_name == \"zookeeper\":\n    #            continue\n    #        if set(old_de[app_name]) & set(ip_list) != set(ip_list):\n    #            return f\"相同集群下下不同节点采集到的依赖服务{app_name}地址存在差异\"\n\n    # def check_cluster(self, instance_ls, instance_list_set, dependence):\n    #    error_message = \"\"\n    #    for _ in instance_list_set:\n    #        if instance_ls == _[0]:\n    #            error_message = self.check_dump_de(dependence, _[1])\n    #            return True, error_message\n    #    return False, error_message\n\n    # def explain_scripts_res(self, message, app, install_args, ips, version, ip_list):\n    #    \"\"\"\n    #    zookeeper:[{\"ip1\",\"ip2\"},{\"ip3\",\"ip4\"}]\n    #    message:\n    #    {\"instance\": {\"zookeeper\": [\"10.0.9.33\"]}, \"dependence\": {\"jdk\": [\"10.0.9.33\"]}}\n    #    \"\"\"\n    #    ser_info = json.loads(message)\n    # 代理节点，不存在配置文件中，但发现了安装路径的ip\n    #    instance_ls = set(ser_info[\"instance\"].get(app, [])) | set(ips)\n    # 期望纳管节点不在agent节点中\n    #    app_db_all_ip = set(self.service_ip_name[0].get(app, []))\n    #    if len((set(ip_list) | app_db_all_ip) & instance_ls) != len(instance_ls):\n    #        self.append_error(app, f\"当前{app}存在问题:选择纳管节点{str(ip_list)}\"\n    #                               f\"与期望纳管节点{instance_ls}不一致\", instance_ls)\n    #        return\n    #    instance_list_set = self.cluster_dc.get(app, [])\n    #    error_message = None\n    #    if not instance_list_set:\n    #        instance_list_set = self.cluster_dc[app] = [[set(instance_ls), ser_info[\"dependence\"]]]\n    #        res, message = self.explain_scripts_de(ser_info)\n    #        if not res:\n    #            error_message = f\"当前{app}存在问题:{message}\"\n    #        else:\n    #            self.dumps_scripts_args(instance_ls, ser_info, install_args, version, app)\n    #    # 首先我们查的是其中一个。那么我们应该在收集所有信息之后再进行增减。其余应该只做校验\n    #    for index, instance in enumerate(instance_list_set):\n    #        # 过滤重复 查看重复依赖有无问题\n    #        is_repeat, message = self.check_cluster(instance_ls, instance_list_set, ser_info[\"dependence\"])\n    #        if is_repeat:\n    #            if message:\n    #                error_message = message\n    #            continue\n    # 非重复且无交叉\n    #        intersection = instance_ls & instance[0]\n    #        if len(intersection) == 0:\n    #            instance_list_set.append([set(instance_ls), ser_info[\"dependence\"]])\n    #            res, message = self.explain_scripts_de(ser_info)\n    #            if not res:\n    #                error_message = message\n    #        else:\n    #            error_message = f\"{app}集群存在交叉现象{intersection}\"\n    #        # 已存在正确的要追加错误信息。\n    #        if not error_message:\n    #            self.dumps_scripts_args(instance_ls, ser_info, install_args, version, app)\n    #    if error_message:\n    #        self.append_error(app, f\"当前{app}存在问题:{error_message}\", instance_ls)\n\n    def explain_common_res(self, ips_ls, app, version, install_args):\n        \"\"\"\n        通用自研和lib纳管\n        \"\"\"\n        for ip in ips_ls:\n            # instance_name = f\"{app}_{ip.split('.')[2]}_{ip.split('.')[3]}\"\n            # instance_name: ip,\n            res, de_dc = self.get_dependence(app, ip)\n            error_msg = \"\" if res else f\"{ip}缺少基础组件如jdk，comlib等\"\n            redis_dc = {\n                \"dependence_instance\": de_dc,\n                \"app_name\": app,\n                \"app_version\": version,\n                \"app_ip\": [ip],\n                \"error_msg\": error_msg,\n                \"install_args\": install_args\n            }\n            if app in self.is_common:\n                if self.is_common_dc.get(app):\n                    self.is_common_dc[app][\"app_ip\"].append(ip)\n                else:\n                    self.is_common_dc[app] = redis_dc\n                continue\n\n            self.response_ls.append({\"name\": app,\n                                     \"ip\": [ip],\n                                     \"error\": error_msg,\n                                     \"exist_instance\": [],\n                                     \"is_use_exist\": False})\n            self.redis_save.append(redis_dc)\n\n    def append_error(self, app, message, ip_ls=None):\n        if ip_ls:\n            for k in self.response_ls:\n                if list(ip_ls)[0] in k[\"ip\"]:\n                    k[\"error\"] = message\n                    return\n        self.response_ls.append({\"name\": app,\n                                 \"ip\": [],\n                                 \"error\": message,\n                                 \"exist_instance\": [],\n                                 \"is_use_exist\": False\n                                 })\n\n    def check_component_cmd(self, install_args, app):\n        \"\"\"\n        进一步检查，并获取依赖信息\n        \"\"\"\n        if not install_args.get(\"ip\") and app in self.error_ser:\n            self.append_error(app, f\"当前{app}组件未发现,或所发现的节点都已存在服务列表中\"\n                                   f\"，请检查安装路径是否存在或取消扫描此服务\")\n            # 或需纳管的服务已全部纳管\n            return True, f\"当前{app}组件不支持纳管\"\n        ips_ls = install_args.pop(\"ip\", [])\n        version = install_args.pop(\"version\")\n        # if app in SERVICE_DISCOVERY:\n        #    salt_client = SaltClient()\n        #    for ip in ips_ls:\n        #        cmd = f\"{self.ips[ip]}/omp_salt_agent/env/bin/python3.8 \" \\\n        #              f\"{self.ips[ip]}/omp_salt_agent/scripts/{app}.py \" \\\n        #              f\"--base_dir {install_args.get('base_dir')} --local_ip {ip}\"\n        #        is_success, message = salt_client.cmd(target=ip, command=cmd, timeout=10)\n        # mysql需要每个节点都执行完后再验证。\n        #        if is_success:\n        #            self.explain_scripts_res(message, app, install_args, [ip], version, ips_ls)\n        #        else:\n        #            self.append_error(app, f\"当前{app}组件执行脚本获取参数失败{message}\")\n        #            return False, message\n        # 此处添加结果\n        #    return True, \"\"\n        # elif app in self.is_common:\n        #    self.append_error(app, f\"当前{app}组件不支持纳管\")\n        #    return False, f\"当前{app}组件不支持纳管\"\n        # else:\n        # 查找自研组件依赖,此时的ip一定是我们需要的ip\n        self.explain_common_res(ips_ls, app, version, install_args)\n\n        # 存一下redis 生成key\n        return True, \"\"\n\n    def run(self):\n        \"\"\"\n        入口函数\n        \"\"\"\n        self.fix_service()\n        self.fix_dependence()\n        # 初次校验\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            _check_list_env = []\n            for ip, agent_dir in self.ips.items():\n                for app, install_args in self.app_dc.items():\n                    future_obj = executor.submit(\n                        self.produce_cmd_and_exec, ip, agent_dir, install_args, app)\n                    _check_list_env.append(future_obj)\n            for future in as_completed(_check_list_env):\n                is_success, message = future.result()\n                if not is_success:\n                    self.error_ser.add(message.split(\":\")[1])\n                    logger.info(message)\n\n        # 脚本和格式化校验\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            _check_component_env = []\n            for app, install_args in self.app_dc.items():\n                future_obj = executor.submit(\n                    self.check_component_cmd, install_args, app)\n                _check_component_env.append(future_obj)\n            for future in as_completed(_check_component_env):\n                is_success, message = future.result()\n                if not is_success:\n                    logger.info(message)\n\n        for app, info in self.is_common_dc.items():\n            exist_instance = self.app_ser.get(f\"{app}:{info.get('app_version', '')}\", [])\n\n            self.response_ls.append({\"name\": app,\n                                     \"ip\": info.get(\"app_ip\", []),\n                                     \"error\": info.get(\"error_msg\", \"\"),\n                                     \"exist_instance\": exist_instance,\n                                     \"is_use_exist\": False if len(exist_instance) == 0 else True})\n            self.redis_save.append(info)\n\n        self.redis_obj.set(\n            name=str(int(time.time())),\n            data=self.redis_save\n        )\n        is_continue = True\n        for ser in self.response_ls:\n            if ser.get(\"error\", None):\n                is_continue = False\n        return {\"ser_info\": self.response_ls,\n                \"uuid\": str(int(time.time())),\n                \"is_continue\": is_continue}\n"
  },
  {
    "path": "omp_server/services/app_check/manage_ser_exec.py",
    "content": "import json\nimport os\nimport copy\nimport logging\nimport time\nfrom django.db import transaction\nfrom db_models.models import (\n    MainInstallHistory, DetailInstallHistory,\n    Service, ServiceConnectInfo,\n    ApplicationHub, ClusterInfo, Env,\n    ServiceHistory, Host\n)\nimport random\nfrom app_store.new_install_utils import RedisDB\nfrom app_store.tasks import add_prometheus\n\nlogger = logging.getLogger('server')\n\n\nclass ManagerService:\n    def __init__(self, info, is_extend=False):\n        self.info = info\n        self.is_extend = is_extend\n        self.redis_obj = RedisDB()\n        self.redis_key = \"extend_\" + str(int(time.time())) if is_extend else info.get(\"uuid\", \"\")\n        self.data = self.check_redis()[1]\n        self.service_cluster_ip, self.app_instance_dc = self._get_cluster_ip()\n        self.env = None\n        self.is_base_env = ApplicationHub.objects.filter(\n            is_base_env=True).values_list(\"app_name\", flat=True)\n\n    @staticmethod\n    def _get_cluster_ip():\n        \"\"\"\n        \"jdk:{1:[\"192.168.0.1\",\"192.168.0.2\"]}\"\n        \"\"\"\n        app_dc = {}\n        app_instance_dc = {}\n        service_all = Service.objects.all().values_list(\"ip\", \"cluster\", \"cluster__cluster_service_name\",\n                                                        \"service_instance_name\")\n        for app in service_all:\n            # 新纳管专属逻辑通过服务实例名匹配\n            app_instance_dc[app[3]] = app[1]\n            if app[1]:\n                app_dc.setdefault(app[2], {}).setdefault(app[1], []).append(app[0])\n        return app_dc, app_instance_dc\n\n    def check_redis(self):\n        if self.is_extend:\n            return True, self.info\n        res, redis_data = self.redis_obj.get(self.redis_key)\n        if not res:\n            return False, {\"is_error\": True,\n                           \"message\": \"redis中不存在需要纳管的数据或超时\"}\n        # 兼容新版本纳管\n        for rd in redis_data:\n            app_name = rd[\"app_name\"]\n            for i in self.info[\"ser_info\"]:\n                if i[\"name\"] == app_name:\n                    rd.update({\n                        \"exist_instance\": i[\"exist_instance\"],\n                        \"is_use_exist\": i[\"is_use_exist\"]\n                    })\n        return True, redis_data\n\n    def check_service(self):\n        for i in self.info[\"ser_info\"]:\n            if len(i[\"exist_instance\"]) > 1:\n                return False, {\n                    \"is_error\": True,\n                    \"message\": f\"{i['name']}仅可勾选单个实例\"}\n            if i[\"is_use_exist\"] and len(i[\"exist_instance\"]) == 0:\n                return False, {\n                    \"is_error\": True,\n                    \"message\": f\"{i['name']}选择与现有实例组成集群，但未选择集群实例\"}\n        for data in self.data:\n            if data.get(\"error_msg\"):\n                return False, {\n                    \"is_error\": True,\n                    \"message\": \"存在有扫描异常的服务\"}\n        return True, \"\"\n\n    @staticmethod\n    def update_port(app_obj, app_data):\n        service_ports = json.loads(app_obj.app_port) if app_obj.app_port else []\n        for service_port in service_ports:\n            if service_port.get('key') == 'service_port':\n                service_port['default'] = app_data['install_args'].get('service_port')\n        return json.dumps(service_ports)\n\n    @staticmethod\n    def update_service_controllers(app_obj, app_data):\n        _app_controllers = json.loads(app_obj.app_controllers) if app_obj.app_controllers else []\n        real_home = app_data['install_args'].get('base_dir')\n        _new_controller = dict()\n        for key, value in _app_controllers.items():\n            if not value:\n                continue\n            _new_controller[key] = os.path.join(real_home, value)\n        return _new_controller\n\n    @staticmethod\n    def update_install_args(app_obj, install_detail_args):\n        install_detail_keys = [\"base_dir\", \"data_dir\", \"log_dir\", \"run_user\"]\n        json_install_args = json.loads(app_obj.app_install_args)\n        for install_args in json_install_args:\n            if install_args.get('key') in install_detail_keys:\n                install_args['default'] = install_detail_args.get(install_args.get('key'), \"\")\n        return json_install_args\n\n    def create_install_detail(self, main_obj, install_detail_args, ser_obj, copy_data, app_obj):\n\n        data_dir = Host.objects.filter(ip=ser_obj.ip).first().data_folder\n        cluster_name = \"\"\n        if ser_obj.cluster:\n            cluster_name = ser_obj.cluster.cluster_service_name\n        install_detail = {\n            \"ip\": ser_obj.ip,\n            \"name\": app_obj.app_name,\n            \"ports\": json.loads(copy_data['service_port']),\n            \"version\": app_obj.app_version,\n            \"install_args\": self.update_install_args(app_obj, install_detail_args),\n            \"instance_name\": ser_obj.service_instance_name,\n            \"data_folder\": data_dir,\n            \"cluster_name\": cluster_name\n        }\n        now_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))\n\n        install_status = DetailInstallHistory.INSTALL_STATUS_INSTALLING\n        msg = \"开始扩容\"\n        if not self.is_extend:\n            install_status = DetailInstallHistory.INSTALL_STATUS_SUCCESS\n            ServiceHistory.objects.create(\n                username=\"admin\",\n                description=f\"执行 纳管 操作\",\n                result=\"success\",\n                created=now_time,\n                service=ser_obj\n            )\n            msg = \"纳管成功\"\n\n        DetailInstallHistory.objects.create(\n            service=ser_obj,\n            main_install_history=main_obj,\n            install_step_status=install_status,\n            send_msg=f\"{now_time} {app_obj.app_name} {msg}\",\n            install_detail_args=install_detail\n        )\n\n    def get_service_dependence(self, dependence, ip):\n        dependence_json = []\n        for de in dependence:\n            for app, hosts_name in de.items():\n                ser_obj = Service.objects.filter(service_instance_name=hosts_name[0]).first()\n                # 是集群\n                if ser_obj.cluster:\n                    dependence_json.append({\"name\": app,\n                                            \"cluster_name\": ser_obj.cluster.cluster_name,\n                                            \"instance_name\": None\n                                            })\n                # 基础组件本地\n                elif app in self.is_base_env:\n                    dependence_json.append({\"name\": app,\n                                            \"cluster_name\": None,\n                                            \"instance_name\": f\"{app}-{ip.split('.')[2]}-{ip.split('.')[3]}\"\n                                            })\n                else:\n                    dependence_json.append({\"name\": app,\n                                            \"cluster_name\": None,\n                                            \"instance_name\": hosts_name[0]\n                                            })\n        return json.dumps(dependence_json)\n\n    @staticmethod\n    def get_or_create_service_connect_info(app_name, app_obj):\n        # ToDo 多环境连接信息问题\n        infos = {\"username\", \"password\", \"username_enc\", \"password_enc\"}\n        connect_infos = {}\n        for app_info in json.loads(app_obj.app_install_args):\n            key = app_info.get(\"key\")\n            if key in infos:\n                connect_infos[f\"service_{key}\"] = app_info.get(\"default\")\n        conn_obj = None\n        if connect_infos:\n            # ToDO 判断同同样连接信息时，当前服务归属哪个集群\n            conn_obj = ServiceConnectInfo.objects.filter(\n                service_name=app_name, **connect_infos).first()\n            if not conn_obj:\n                conn_obj = ServiceConnectInfo.objects.create(\n                    service_name=app_name,\n                    **connect_infos\n                )\n        return conn_obj\n\n    def create_cluster_new(self, app_obj, app_data, app_name, real_ip_ls):\n        # real_ip_ls 1 单机不考虑\n        # real_ip_ls 1 exist_instance 1 单机转集群合并 需创建集群\n        # real_ip_ls >1 exist_instance 0 集群\n        # real_ip_ls >1 exist_instance >0 集群合并\n        is_use_exist = app_data.pop(\"is_use_exist\")\n        exist_instance = app_data.pop(\"exist_instance\")\n        if is_use_exist or len(real_ip_ls) > 1:\n            # 判断当前需要融合的实例是否存在集群,无则创建,必返回集群\n            exist_instance = exist_instance[0] if exist_instance else 0\n            cluster_id = self.app_instance_dc.get(exist_instance)\n            # 没有集群需要创建，或者确定集群并且不合并集群的但数量大于1。\n            if not cluster_id or not is_use_exist:\n                cluster_obj = ClusterInfo.objects.create(\n                    cluster_service_name=app_name,\n                    cluster_name=f\"{app_name}-cluster-{''.join(random.sample('ABCDEFGHIJKLMNQPQRSTUVWXYZ1234567890', 10))}\",\n                    service_connect_info=self.get_or_create_service_connect_info(app_name, app_obj)\n                )\n                # 实例存在但不存在集群\n                if exist_instance:\n                    Service.objects.filter(\n                        service_instance_name=exist_instance).update(cluster=cluster_obj)\n            else:\n                cluster_obj = ClusterInfo.objects.filter(id=cluster_id).first()\n            return True, cluster_obj\n        # 看大小返回\n        else:\n            return False, [f\"{app_name}-{real_ip_ls[0].split('.')[2]}-{real_ip_ls[0].split('.')[3]}\"]\n\n    def create_cluster(self, app_obj, app_data, app_name, real_ip_ls):\n        cluster_instance_name = []\n        for key, v in app_data.items():\n            if key.startswith(f'{app_name}'):\n                cluster_instance_name = [key, v]\n        if cluster_instance_name:\n            app_data.pop(cluster_instance_name[0])\n        if not app_data.get(\"is_use_exist\") is None:\n            return self.create_cluster_new(app_obj, app_data, app_name, real_ip_ls)\n        # 此为纳管集群\n        cluster_obj = None\n        if set(cluster_instance_name[1]) & set(real_ip_ls) != set(cluster_instance_name[1]):\n            for c_id, ips in self.service_cluster_ip.get(app_name, {}).items():\n                if list(set(cluster_instance_name[1]) - set(real_ip_ls))[0] in ips:\n                    cluster_obj = ClusterInfo.objects.filter(id=c_id).first()\n        elif len(cluster_instance_name[1]) > 1:\n            cluster_obj = ClusterInfo.objects.create(\n                cluster_service_name=app_name,\n                cluster_name=cluster_instance_name[0],\n                service_connect_info=self.get_or_create_service_connect_info(app_name, app_obj)\n            )\n        else:\n            return False, cluster_instance_name\n        return True, cluster_obj\n\n    def get_or_create_env(self):\n        if self.env:\n            return self.env\n        queryset = Env.objects.filter(id=1)\n        if queryset.exists():\n            return queryset.first()\n        return Env.objects.create(id=1, name=\"default\")\n\n    def create_database_one(self, data, main_obj):\n        app_name = data.pop(\"app_name\")\n        app_version = data.pop(\"app_version\")\n        real_ip_ls = data.pop(\"app_ip\", [])\n        dependence = data.pop(\"dependence_instance\")\n        app_obj = ApplicationHub.objects.filter(\n            app_name=app_name, app_version=app_version).first()\n        data['service_port'] = self.update_port(app_obj, data)\n        data['service_controllers'] = self.update_service_controllers(app_obj, data)\n        data['env'] = self.get_or_create_env()\n        res, cls_obj = self.create_cluster(app_obj, data, app_name, real_ip_ls)\n        if res:\n            data[\"cluster\"] = cls_obj\n        else:\n            data[\"service_instance_name\"] = cls_obj[0]\n        data[\"service_connect_info\"] = self.get_or_create_service_connect_info(app_name, app_obj)\n        data[\"service_status\"] = Service.SERVICE_STATUS_INSTALLING \\\n            if self.is_extend else Service.SERVICE_STATUS_NORMAL\n        for ip in real_ip_ls:\n            # ToDo 未设置vip的选项\n            copy_data = copy.deepcopy(data)\n            if not copy_data.get(\"service_instance_name\"):\n                copy_data[\"service_instance_name\"] = f\"{app_name}-{ip.split('.')[2]}-{ip.split('.')[3]}\"\n            copy_data[\"service_dependence\"] = self.get_service_dependence(dependence, ip)\n            copy_data['service'] = app_obj\n            copy_data[\"ip\"] = ip\n            # ToDo Role 和 vip的\n            copy_data[\"service_role\"] = \"\"\n            install_detail_args = copy_data.pop(\"install_args\")\n            ser_obj = Service.objects.create(\n                **copy_data\n            )\n            # 创建detail表\n            self.create_install_detail(main_obj, install_detail_args, ser_obj, copy_data, app_obj)\n\n    def create_database_all(self):\n        # main表 相关联的服务表 信息集群表，然后detail表\n        with transaction.atomic():\n            main_obj = MainInstallHistory.objects.create(\n                operator=\"admin\",\n                operation_uuid=self.redis_key,\n                install_status=MainInstallHistory.INSTALL_STATUS_INSTALLING\n            )\n            #                \"dependence_instance\": de_dc,\n            #    \"app_name\": app,\n            #    \"app_version\": version,\n            #    \"app_ip\": [ip],\n            #    \"install_args\": install_args\n            for data in self.data:\n                data.pop(\"error_msg\")\n                self.create_database_one(data, main_obj)\n            if not self.is_extend:\n                main_obj.install_status = MainInstallHistory.INSTALL_STATUS_SUCCESS\n            main_obj.save()\n        try:\n            add_prometheus(main_obj.id)\n        except Exception as e:\n            logger.error(f\"纳管服务注册失败:{e}\")\n        return main_obj.id\n\n    def run(self):\n        res, data = self.check_redis()\n        if res:\n            res, data = self.check_service()\n        if not res:\n            return data\n        main_id = self.create_database_all()\n        if self.is_extend:\n            return main_id\n        self.redis_obj.delete_keys(self.redis_key)\n        return {\"is_error\": False,\n                \"message\": \"服务纳管成功\"}\n"
  },
  {
    "path": "omp_server/services/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ServicesConfig(AppConfig):\n    name = 'services'\n"
  },
  {
    "path": "omp_server/services/permission.py",
    "content": "from django.conf import settings\nfrom rest_framework.permissions import BasePermission\n\n\nclass GetDataJsonAuthenticated(BasePermission):\n    \"\"\"\n    Allows access only for users who with secret or authentication .\n    \"\"\"\n\n    def has_permission(self, request, view):\n        query_field = request.query_params.get(\"secret\", \"\")\n        return query_field == settings.DATA_JSON_SECRET or \\\n            bool(request.user and request.user.is_authenticated)\n"
  },
  {
    "path": "omp_server/services/self_heal_filter.py",
    "content": "import time\nimport django_filters\nfrom db_models.models import SelfHealingHistory\nfrom django_filters.rest_framework import FilterSet\nfrom rest_framework.filters import BaseFilterBackend\n\n\nclass SelfHealingHistoryFilter(FilterSet):\n    \"\"\"自愈历史记录过滤类\"\"\"\n    host_ip = django_filters.CharFilter(\n        help_text=\"HOST_IP,模糊匹配\", field_name=\"host_ip\", lookup_expr=\"icontains\"\n    )\n    state = django_filters.CharFilter(help_text=\"STATE,模糊匹配\", field_name=\"state\", lookup_expr=\"icontains\")\n    instance_name = django_filters.CharFilter(help_text=\"INSTANCE_NAME,模糊匹配\", field_name=\"instance_name\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = SelfHealingHistory\n        fields = (\"host_ip\", \"state\", \"instance_name\")\n\n\nclass SelfHealingTimeFilter(BaseFilterBackend):\n    def filter_queryset(self, request, queryset, view):\n        query_start_time = request.GET.get(\"query_start_time\", \"\")\n        query_end_time = request.GET.get(\"query_end_time\", \"\")\n        if query_start_time and query_end_time:\n            try:\n                time.strptime(query_start_time, \"%Y-%m-%d %H:%M:%S\")\n                time.strptime(query_end_time, \"%Y-%m-%d %H:%M:%S\")\n            except ValueError:\n                return queryset.all()\n            return queryset.filter(alert_time__range=(query_start_time, query_end_time))\n        return queryset.all()"
  },
  {
    "path": "omp_server/services/self_heal_serializers.py",
    "content": "import logging\n\nfrom rest_framework import serializers\nfrom rest_framework.serializers import ModelSerializer, Serializer\nfrom db_models.models import SelfHealingSetting, SelfHealingHistory\nfrom rest_framework_bulk import BulkSerializerMixin, BulkListSerializer\nfrom rest_framework.exceptions import ValidationError\n\nlogger = logging.getLogger(\"server\")\n\n\nclass SelfHealingSettingSerializer(BulkSerializerMixin, ModelSerializer):\n    class Meta:\n        model = SelfHealingSetting\n        fields = \"__all__\"\n        list_serializer_class = BulkListSerializer\n\n    def validate(self, attrs):\n        repair_ls = SelfHealingSetting.objects.all().values_list(\"repair_instance\", flat=True)\n        repairs = []\n        for _ in repair_ls:\n            repairs.extend(_)\n        repeat = set(repairs) & set(attrs[\"repair_instance\"])\n        if repeat:\n            raise ValidationError(f\"服务不可重复{repeat}\")\n        if \"all\" in repairs:\n            raise ValidationError(f\"all服务不可再次添加其他服务\")\n        return attrs\n\n\nclass ListSelfHealingHistorySerializer(ModelSerializer):\n    class Meta:\n        model = SelfHealingHistory\n        fields = \"__all__\"\n\n\nclass UpdateSelfHealingHistorySerializer(Serializer):\n    \"\"\"自愈历史记录批量更新\"\"\"\n    ids = serializers.ListField(help_text=\"自愈历史记录ID列表\", required=True, error_messages={\"required\": \"必须包含ID列表字段\"})\n    is_read = serializers.IntegerField(help_text=\"是否已读\", required=True, error_messages={\"required\": \"必须包含是否已读字段\"})\n\n    def create(self, validated_data):\n        SelfHealingHistory.objects.filter(id__in=validated_data.get(\"ids\")).update(\n            is_read=validated_data.get(\"is_read\"))\n        return validated_data\n"
  },
  {
    "path": "omp_server/services/self_heal_util.py",
    "content": "import logging\nimport requests\nimport json\nfrom utils.parse_config import MONITOR_PORT\nfrom promemonitor.prometheus_utils import CW_TOKEN\n\n\nlogger = logging.getLogger(\"server\")\n\n\ndef get_service_status_direct(service_obj_list):\n    \"\"\"\n    直接从monitor_agent获取服务状态\n    param: [{\"ip\": \"127.0.0.1\", \"service_name\": \"mysql\"}, {\"ip\": \"127.0.0.1\", \"service_name\": \"redis\"}]\n    \"\"\"\n    service_obj_result = list()\n    monitor_agent_port = MONITOR_PORT.get('monitorAgent', 19031)\n    headers = {\"Content-Type\": \"application/json\"}.update(CW_TOKEN)\n    ip_item_list = list()\n    ip_list = list()\n    for ele in service_obj_list:\n        ip_list.append(ele.get(\"ip\"))\n    ip_list = list(set(ip_list))\n    for ip in ip_list:\n        ip_service_list = list()\n        for item in service_obj_list:\n            if ip == item.get(\"ip\"):\n                ip_service_list.append(item)\n        ip_item_list.append(ip_service_list)\n\n    try:\n        for ii in ip_item_list:\n            status_url = f\"http://{ii[0].get('ip')}:{monitor_agent_port}/service_status\"  # NOQA\n            response = requests.request(\n                \"POST\", status_url, headers=headers, data=json.dumps(ii))\n            if response.status_code != 200:\n                continue\n            service_obj_result.extend(response.json().get(\"beans\"))\n        return service_obj_result\n    except Exception as e:\n        logger.error(f\"获取制定服务列表状态失败，详情为：{e}\")\n        return service_obj_list\n"
  },
  {
    "path": "omp_server/services/self_heal_view.py",
    "content": "import logging\nimport json\n\nfrom django_filters.rest_framework import DjangoFilterBackend\nfrom rest_framework.filters import OrderingFilter\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.response import Response\nfrom rest_framework.mixins import CreateModelMixin, ListModelMixin, DestroyModelMixin, UpdateModelMixin\nfrom services.self_heal_serializers import SelfHealingSettingSerializer, ListSelfHealingHistorySerializer, \\\n    UpdateSelfHealingHistorySerializer\nfrom services.self_heal_filter import SelfHealingTimeFilter, SelfHealingHistoryFilter\nfrom db_models.models import SelfHealingSetting, SelfHealingHistory\nfrom utils.common.paginations import PageNumberPager\nfrom services.self_healing import get_enable_health\nfrom rest_framework_bulk import BulkUpdateAPIView\n\nlogger = logging.getLogger(\"server\")\n\n\nclass SelfHealingSettingView(GenericViewSet, ListModelMixin, CreateModelMixin, BulkUpdateAPIView, DestroyModelMixin,\n                             UpdateModelMixin):\n    \"\"\"自愈策略\"\"\"\n    queryset = SelfHealingSetting.objects.all()\n    serializer_class = SelfHealingSettingSerializer\n    # 操作信息描述\n    get_description = \"自愈策略\"\n    post_description = \"自愈策略\"\n\n    def list(self, request, *args, **kwargs):\n        query_field = request.query_params.get(\"instance\", False)\n        if query_field:\n            # 查看还有多少服务需要\n            repair_ls = self.get_queryset().values_list(\"repair_instance\", flat=True)\n            repairs = []\n            for _ in repair_ls:\n                repairs.extend(_)\n            if \"all\" in repairs:\n                data = {\"all\": False,\n                        \"service_name\": []}\n            else:\n                data = {\"all\": False if repairs else True,\n                        \"service_name\": get_enable_health(repairs)\n                        }\n            return Response(data)\n        else:\n            return super(SelfHealingSettingView, self\n                         ).list(request, *args, **kwargs)\n\n\nclass ListSelfHealingHistoryView(GenericViewSet, ListModelMixin):\n    \"\"\"自愈历史记录\"\"\"\n    queryset = SelfHealingHistory.objects.all().order_by(\"-alert_time\", \"-end_time\")\n    serializer_class = ListSelfHealingHistorySerializer\n    pagination_class = PageNumberPager\n    filter_backends = (\n        DjangoFilterBackend,\n        OrderingFilter,\n        SelfHealingTimeFilter,\n    )\n    filter_class = SelfHealingHistoryFilter\n    ordering_fields = (\"host_ip\", \"instance_name\", \"state\", \"alert_time\", \"end_time\")\n\n\nclass UpdateSelfHealingHistoryView(CreateModelMixin, GenericViewSet):\n    \"\"\"更新自愈历史记录试图\"\"\"\n    serializer_class = UpdateSelfHealingHistorySerializer\n    queryset = SelfHealingHistory.objects.all().order_by(\"id\")\n    post_description = \"更新自愈历史记录（已读/未读）\"\n"
  },
  {
    "path": "omp_server/services/self_healing.py",
    "content": "import logging\nimport copy\nimport time\nimport paramiko\nimport requests\nimport json\nimport datetime\nfrom db_models.models import Service, SelfHealingSetting, \\\n    SelfHealingHistory, Host, WaitSelfHealing, ApplicationHub\nfrom celery import shared_task\nfrom promemonitor.prometheus_utils import CW_TOKEN\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.plugin.crypto import AESCryptor\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom utils.parse_config import MONITOR_PORT, BASIC_ORDER, \\\n    THREAD_POOL_MAX_WORKERS, HEALTH_REDIS_TIMEOUT, HEALTH_REQUEST_COUNT, HEALTH_REQUEST_SLEEP\nfrom app_store.new_install_utils import RedisDB\nfrom utils.plugin.crontab_utils import maintain\n\nlogger = logging.getLogger('server')\n\n\nclass SelfHealing:\n    def __init__(self, instance_tp, max_healing_count):\n        # 需要起停信息ip等操作\n        self.instance_tp = instance_tp\n        self.max_healing_count = max_healing_count\n        self.redis = RedisDB()\n        self.service_info = set()\n        self.host_info = set()\n\n    def merge_and_filter_ser(self, alert_info):\n        \"\"\"\n        初始化用，过滤服务（主机），及服务初始化信息\n        \"\"\"\n        data_dict = {}\n        host_ls = []\n        for d in alert_info:\n            if d.get(\"alert_service_name\") and d.get(\"alert_instance_name\") and \\\n                    d['alert_instance_name'] not in self.service_info:\n                data_dict.setdefault(d['alert_service_name'], []).append(d)\n                self.service_info.add(d['alert_instance_name'])\n            if not d.get(\"alert_service_name\") and \\\n                    d['alert_host_ip'] not in self.host_info:\n                self.host_info.add(d['alert_host_ip'])\n                host_ls.append(d)\n        # 初始化主机和服务信息\n        self.host_info = dict(Host.objects.filter(\n            ip__in=list(self.host_info)).values_list(\"ip\", \"agent_dir\"))\n        self.service_info = dict(Service.objects.filter(\n            service_instance_name__in=list(self.service_info)\n        ).values_list(\"service_instance_name\", \"service_controllers\"))\n        return data_dict, host_ls\n\n    def sort_service(self, alert_info):\n        \"\"\"\n        初始化用，提供排序\n        \"\"\"\n        sort_dict = copy.copy(BASIC_ORDER)\n        # 赋值并过滤 存在问题 过滤需要过滤会过滤到同服务不同实例名的例\n        data_dict, host_ls = self.merge_and_filter_ser(alert_info)\n        sort_ser = []\n        for key in sort_dict:\n            temp = []\n            for item in sort_dict[key]:\n                temp.extend(data_dict.pop(item, []))\n            if temp:\n                sort_ser.append(temp)\n        other_ser = data_dict.values()\n        if other_ser:\n            other_ser = [service for app in other_ser for service in app]\n            sort_ser.append(other_ser)\n        if host_ls:\n            sort_ser.insert(0, host_ls)\n        return sort_ser\n\n    def exec_salt_cmd(self, ip, command, his_obj):\n        \"\"\"\n        进行启动，请求接口查询状态，日志追加，状态变更\n        \"\"\"\n        salt_obj = SaltClient()\n        cmd_flag, cmd_msg = salt_obj.cmd(\n            target=ip,\n            command=command,\n            timeout=60)\n        healing_log = f\"执行ip:{ip},执行cmd:{command},执行结果:{cmd_flag},执行详情:{cmd_msg},\"\n        if not cmd_flag:\n            his_obj.healing_log = healing_log\n            his_obj.save()\n            return False\n        # 循环检测，超出检测时常后退出\n        for _ in range(HEALTH_REQUEST_COUNT):\n            res = self.check_health(ip, command, his_obj, healing_log)\n            if res:\n                return True\n            time.sleep(HEALTH_REQUEST_SLEEP)\n        return False\n\n    @staticmethod\n    def check_health(ip, command, his_obj, healing_log):\n        request_monitor = {\"service_name\": his_obj.service_name, \"ip\": ip}\n        if \"omp_monitor_agent\" in command:\n            request_monitor[\"service_name\"] = \"node\"\n        try:\n            monitor_agent_res = get_service_status_direct([request_monitor])\n        except Exception as e:\n            his_obj.healing_log = healing_log + \",请求监控报错\"\n            his_obj.save()\n            logger.info(\"monitor_agent_res_error 监控报错信息{}\".format(e))\n            return False\n        if monitor_agent_res[0].get(\"status\") == 1:\n            his_obj.healing_log = healing_log + \"monitor_agent_res 服务状态查看正常更新服务状态\"\n            his_obj.save()\n            return True\n        return False\n\n    def get_command(self, instance_name):\n        \"\"\"\n        获取cmd ，通过策略类型选择启动或重启\n        \"\"\"\n        action_dc = {\n            0: \"start\",\n            1: \"restart\"\n        }\n        action = action_dc[self.instance_tp]\n        service_action = self.service_info.get(instance_name)\n        host_action = self.host_info.get(instance_name)\n        if service_action:\n            command = service_action.get(action) if service_action.get(action) \\\n                else service_action.get(\"start\", \"\").replace(\"start\", action)\n        else:\n            command = f\"bash {host_action}/omp_monitor_agent/monitor_agent.sh {action}\"\n        return command\n\n    @staticmethod\n    def write_db(data, healing_count):\n        \"\"\"\n        合法后创建记录表用\n        \"\"\"\n\n        initial_v = {\n            \"start_time\": datetime.datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\"),\n            \"is_read\": 0,\n            \"state\": 2,\n            \"healing_log\": \"\",\n            \"healing_count\": healing_count\n        }\n        compare_dc = {\n            \"alert_host_ip\": \"host_ip\",\n            \"alert_service_name\": \"service_name\",\n            \"alert_time\": \"alert_time\",\n            # \"fingerprint\": \"fingerprint\",\n            # \"monitor_log\": \"monitor_log\",\n            \"alert_describe\": \"alert_content\",\n            \"alert_instance_name\": \"instance_name\",\n        }\n        write_db_dc = {}\n        for field, value in data.items():\n            compare_v = compare_dc.get(field)\n            if compare_v:\n                write_db_dc[compare_v] = value\n        write_db_dc.update(initial_v)\n        return SelfHealingHistory.objects.create(**write_db_dc)\n\n    def get_redis_count(self, instance_name):\n        \"\"\"\n        缓存校验用，周期内自愈次数最大限制\n        \"\"\"\n        _flag, _data = self.redis.get(f\"heal{instance_name}\")\n        if not _flag:\n            count = 1\n        else:\n            count = int(_data) + 1\n        self.redis.update(\n            name=f\"heal{instance_name}\",\n            data=count,\n            timeout=HEALTH_REDIS_TIMEOUT\n        )\n        return count\n\n    def host(self, hosts_info):\n        identify_des = \"monitor_agent进程丢失\"\n        ip = hosts_info[\"alert_host_ip\"]\n        if identify_des not in hosts_info.get('alert_describe'):\n            # 暂不支持的模式\n            logger.info(f\"暂不支持的模式{ip}\")\n            return True\n        ip = hosts_info[\"alert_host_ip\"]\n        host_ip = \"\".join(ip.split(\".\"))\n        healing_count = self.get_redis_count(host_ip)\n        if healing_count > self.max_healing_count:\n            # 自愈实例超出限制个数\n            return True\n        # 写库\n        his_obj = self.write_db(hosts_info, healing_count)\n        res = self.exec_salt_cmd(ip, self.get_command(ip), his_obj)\n        if not res:\n            return self.exec_salt_cmd(ip, self.get_command(ip), his_obj)\n        his_obj.state = SelfHealingHistory.HEALING_SUCCESS if res else SelfHealingHistory.HEALING_FAIL\n        his_obj.end_time = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n        his_obj.save()\n\n    def service(self, service_info):\n        # 获取service的redis的key HEALTH_REDIS_TIMEOUT\n        identify_des = \"been down for more than a minute\"\n        if identify_des not in service_info.get('alert_describe'):\n            logger.info(f\"暂不支持的模式{service_info['alert_instance_name']}\")\n            # 暂不支持的模式\n            return True\n        healing_count = self.get_redis_count(service_info['alert_instance_name'])\n        if healing_count > self.max_healing_count:\n            # 自愈实例超出限制个数\n            logger.info(f\"自愈实例超出限制个数{service_info['alert_instance_name']}\")\n            return True\n        # 写库\n        his_obj = self.write_db(service_info, healing_count)\n        command = self.get_command(service_info['alert_instance_name'])\n        res = self.exec_salt_cmd(service_info['alert_host_ip'], command, his_obj)\n        if not res:\n            # 二次重复不再进行其余操作\n            res = self.exec_salt_cmd(service_info['alert_host_ip'], command, his_obj)\n        his_obj.state = SelfHealingHistory.HEALING_SUCCESS if res else SelfHealingHistory.HEALING_FAIL\n        his_obj.end_time = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n        his_obj.save()\n\n\ndef get_enable_health(repairs):\n    h_ls = set(Host.objects.all().values_list(\"ip\", flat=True))\n    for app in ApplicationHub.objects.filter(is_base_env=False):\n        if not app.extend_fields.get(\"affinity\", \"\") == \"tengine\":\n            h_ls.add(app.app_name)\n    return list(h_ls - set(repairs))\n\n\n@shared_task\n@maintain\ndef self_healing(task_id):\n    # 校验是否需要自愈\n    self_obj = SelfHealingSetting.objects.get(id=task_id)\n    if not self_obj.used:\n        return \"该策略并未启用\"\n    if \"all\" in self_obj.repair_instance:\n        data = self_obj.get_enable_health(list())\n    else:\n        data = list(self_obj.repair_instance)\n    wait_ser = WaitSelfHealing.objects.filter(service_name__in=data)\n    if not wait_ser or wait_ser.filter(repair_status=1):\n        return \"存在正在自愈的服务或无需自愈的服务\"\n    wait_ser.update(repair_status=1)\n    repair_ser_dc = dict(wait_ser.values_list(\"id\", \"repair_ser\"))\n\n    repair_info = []\n    for ser in repair_ser_dc.values():\n        repair_info.append(ser)\n    logger.info(f\"需要自愈的服务:{repair_info}\")\n    # 排序 先主机 - 基础组件 -自研服务\n    try:\n        health_obj = SelfHealing(self_obj.instance_tp, self_obj.max_healing_count)\n        ser = health_obj.sort_service(repair_info)\n        logger.info(f\"等待自愈的服务信息:{ser}\")\n        # 开启修复\n        for service_ls in ser:\n            with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n                future_list = []\n                for service in service_ls:\n                    future_obj = executor.submit(\n                        getattr(health_obj,\n                                \"service\" if service['alert_type'] == \"component\" else service['alert_type']), service)\n                    future_list.append(future_obj)\n                for future in as_completed(future_list):\n                    future.result()\n    except Exception as e:\n        logger.info(f\"未知异常，需保护释放锁 {e}\")\n        # 释放锁 ,防止惰性查询再次筛选\n    WaitSelfHealing.objects.filter(id__in=list(repair_ser_dc)).delete()\n\n\ndef self_healing_ssh_verification(host_self_healing_list, sudo_check_cmd):\n    \"\"\"\n        先留着吧暂时没啥用。\n        \"\"\"\n\n    host_self_healing_list = host_self_healing_list\n    aes_crypto = AESCryptor()\n    host_list = Host.objects.filter(ip=host_self_healing_list).values_list(\"ip\", \"port\", \"username\", \"password\")\n    for i in range(len(host_list)):\n        try:\n            if len(host_list[i][2]) != 0 and len(host_list[i][3]) != 0:\n                client = paramiko.SSHClient()\n                client.set_missing_host_key_policy(paramiko.AutoAddPolicy())\n                client.connect(hostname=host_list[i][0], port=host_list[i][1],\n                               username=host_list[i][2], password=aes_crypto.decode(host_list[i][3]),\n                               timeout=60)\n                \"\"\" 监控启动脚本 是否需要重启多次？\"\"\"\n                sudo_check_cmd = sudo_check_cmd\n                stdin, stdout, stderr = client.exec_command(sudo_check_cmd)\n                stdout = stdout.read().decode('utf-8')\n                stderr = stderr.read().decode('utf-8')\n                \"\"\" 输出信息需要修改\"\"\"\n                if \"dead\" in stdout:\n                    \"\"\" 监控未 启动 \"\"\"\n                    logger.info(\"monitor_agent 启动失败,输出信息:{} \".format(stdout))\n                    return True, 0\n                if \"running\" in stdout:\n                    \"\"\" 监控启动\"\"\"\n                    return True, 1\n            else:\n                logger.info(\"监控重启失败 无ssh\")\n                \"\"\" 无ssh的情况\"\"\"\n                return False, 0\n        except Exception as e:\n            logger.info(\"监控重启失败,报错信息为：{}\".format(e))\n            \"\"\" ssh 连接超时\"\"\"\n            return False, 1\n    return True, 2\n\n\ndef get_service_status_direct(service_obj_list):\n    \"\"\"\n    直接从monitor_agent获取服务状态\n    param: [{\"ip\": \"127.0.0.1\", \"service_name\": \"mysql\"}, {\"ip\": \"127.0.0.1\", \"service_name\": \"redis\"}]\n    \"\"\"\n    service_obj_result = list()\n    monitor_agent_port = MONITOR_PORT.get('monitorAgent', 19031)\n    headers_type = {\"Content-Type\": \"application/json\"}\n    headers_authentication = CW_TOKEN\n    headers = dict(headers_type, **headers_authentication)\n    ip_item_list = list()\n    ip_list = list()\n    for ele in service_obj_list:\n        ip_list.append(ele.get(\"ip\"))\n    ip_list = list(set(ip_list))\n    for ip in ip_list:\n        ip_service_list = list()\n        for item in service_obj_list:\n            if ip == item.get(\"ip\"):\n                ip_service_list.append(item)\n        ip_item_list.append(ip_service_list)\n    try:\n        for ii in ip_item_list:\n            status_url = f\"http://{ii[0].get('ip')}:{monitor_agent_port}/service_status\"  # NOQA\n            response = requests.request(\n                \"POST\", status_url, headers=headers, data=json.dumps(ii))\n            logger.info(\"interface_monitor_agent监控接口返回数据:{}\".format(response))\n            logger.info(\"interface_monitor_agent请求地址 {}\".format(status_url))\n            if response.status_code != 200:\n                continue\n            logger.info(\"interface_monitor_agent 接口返回:{}\".format(response.json()))\n            service_obj_result.extend(response.json().get(\"beans\"))\n        return service_obj_result\n    except Exception as e:\n        logger.error(f\"interface_monitor_agent 获取制定服务列表状态失败，详情为：{e}\")\n        return service_obj_list\n"
  },
  {
    "path": "omp_server/services/services_filters.py",
    "content": "\"\"\"\n服务相关过滤器\n\"\"\"\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\n\nfrom db_models.models import Service\n\n\nclass ServiceFilter(FilterSet):\n    \"\"\" 服务过滤类 \"\"\"\n    ip = django_filters.CharFilter(\n        help_text=\"IP，模糊匹配\", field_name=\"ip\", lookup_expr=\"icontains\")\n    service_instance_name = django_filters.CharFilter(\n        help_text=\"服务实例名称，模糊匹配\", field_name=\"service_instance_name\", lookup_expr=\"icontains\")\n    label_name = django_filters.CharFilter(\n        help_text=\"功能模块\", field_name=\"service__app_labels__label_name\", lookup_expr=\"icontains\")\n    app_type = django_filters.CharFilter(\n        help_text=\"服务类型: 0-组件 1-应用\", field_name=\"service__app_type\", lookup_expr=\"exact\")\n\n    class Meta:\n        model = Service\n        fields = (\"ip\",)\n"
  },
  {
    "path": "omp_server/services/services_serializers.py",
    "content": "\"\"\"\n服务序列化器\n\"\"\"\nimport json\nfrom rest_framework import serializers\n\nfrom db_models.models import Service, ApplicationHub\nfrom utils.common.serializers import DynamicFieldsModelSerializer\n\n\nclass ServiceStatusSerializer(DynamicFieldsModelSerializer):\n    is_web = serializers.SerializerMethodField()\n    is_base_env = serializers.BooleanField(source=\"service.is_base_env\")\n    service_status = serializers.CharField(source=\"get_service_status_display\")\n    app_version = serializers.CharField(source=\"service.app_version\")\n    app_name = serializers.CharField(source=\"service.app_name\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\n            \"ip\", \"app_name\", \"app_version\", \"service_status\",\n            \"is_base_env\", \"is_web\", \"service_instance_name\"\n        )\n\n    def get_is_web(self, obj):\n        \"\"\" 或是是否为 web 服务 \"\"\"\n        if obj.service.extend_fields.get(\"affinity\", \"\") == \"tengine\":\n            return True\n        return False\n\n\nclass ServiceSerializer(serializers.ModelSerializer):\n    \"\"\" 服务序列化器 \"\"\"\n\n    port = serializers.SerializerMethodField()\n    label_name = serializers.SerializerMethodField()\n    cluster_type = serializers.SerializerMethodField()\n    alert_count = serializers.SerializerMethodField()\n    operable = serializers.SerializerMethodField()\n    is_web = serializers.SerializerMethodField()\n    is_base_env = serializers.BooleanField(source=\"service.is_base_env\")\n    service_status = serializers.CharField(source=\"get_service_status_display\")\n    app_type = serializers.IntegerField(source=\"service.app_type\")\n    app_name = serializers.CharField(source=\"service.app_name\")\n    app_version = serializers.CharField(source=\"service.app_version\")\n    env = serializers.CharField(source=\"env.name\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\n            \"id\", \"service_instance_name\", \"ip\", \"port\", \"label_name\", \"alert_count\",\n            \"operable\", \"app_type\", \"app_name\", \"app_version\", \"cluster_type\",\n            \"service_status\", \"is_base_env\", \"is_web\", \"env\"\n        )\n\n    def get_is_web(self, obj):\n        \"\"\" 或是是否为 web 服务 \"\"\"\n        if obj.service.extend_fields.get(\"affinity\", \"\") == \"tengine\":\n            return True\n        return False\n\n    def get_port(self, obj):\n        \"\"\" 返回服务 service_port \"\"\"\n        service_port = \"-\"\n        if obj.service_port is not None:\n            service_port_ls = json.loads(obj.service_port)\n            if len(service_port_ls) > 0:\n                service_port = service_port_ls[0].get(\"default\", \"\")\n        return service_port\n\n    def get_label_name(self, obj):\n        \"\"\" 拼接返回标签 \"\"\"\n        label_name = \"-\"\n        if obj.service.app_labels.exists():\n            label_name = \", \".join(\n                obj.service.app_labels.values_list(\"label_name\", flat=True))\n        return label_name\n\n    def get_cluster_type(self, obj):\n        \"\"\" 获取集群类型 \"\"\"\n        cluster_type = \"单实例\"\n        if obj.cluster is not None:\n            # cluster_type = obj.cluster.cluster_type\n            cluster_type = \"集群\"\n        return cluster_type\n\n    def get_alert_count(self, obj):\n        \"\"\" 获取告警数量 \"\"\"\n        alert_count = f\"{obj.alert_count}次\"\n        # 服务状态为 '安装中'、'安装失败' 告警数量显示为 '-'\n        if obj.service_status in (\n                Service.SERVICE_STATUS_INSTALLING,\n                Service.SERVICE_STATUS_INSTALL_FAILED):\n            alert_count = \"-\"\n        # '基础环境' 展示为 '-'\n        base_env = obj.service.extend_fields.get(\"base_env\", \"\")\n        if isinstance(base_env, str):\n            base_env = base_env.lower()\n        if base_env in (True, \"true\"):\n            alert_count = \"-\"\n        return alert_count\n\n    def get_operable(self, obj):\n        \"\"\" 服务可操作 (启动、停止、重启) \"\"\"\n        if obj.service_controllers is not None:\n            return obj.service_controllers.get(\"start\", \"\") != \"\"\n        return False\n\n\nclass ServiceDetailSerializer(serializers.ModelSerializer):\n    \"\"\" 服务详情序列化器 \"\"\"\n\n    app_name = serializers.CharField(source=\"service.app_name\")\n    app_version = serializers.CharField(source=\"service.app_version\")\n    label_name = serializers.SerializerMethodField()\n    cluster_type = serializers.SerializerMethodField()\n    install_info = serializers.SerializerMethodField()\n    history = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\n            \"id\", \"service_instance_name\", \"app_name\", \"app_version\", \"label_name\",\n            \"cluster_type\", \"ip\", \"install_info\", \"history\", \"created\",\n        )\n\n    def get_install_info(self, obj):\n        \"\"\" 安装信息 \"\"\"\n        result = {\n            \"service_port\": \"-\",\n            \"base_dir\": \"-\",\n            \"log_dir\": \"-\",\n            \"data_dir\": \"-\",\n            \"username\": \"-\",\n            \"password\": \"-\",\n        }\n        # 获取服务端口号\n        if obj.service_port is not None:\n            service_port_ls = json.loads(obj.service_port)\n            if len(service_port_ls) > 0:\n                result[\"service_port\"] = service_port_ls[0].get(\"default\", \"\")\n        # 应用安装参数\n        app_install_args = []\n        if obj.detailinstallhistory_set.exists():\n            detail_obj = obj.detailinstallhistory_set.first()\n            app_install_args = detail_obj.install_detail_args.get(\n                \"install_args\", [])\n        for app_install_info in app_install_args:\n            key = app_install_info.get(\"key\", \"\")\n            if key in result.keys():\n                result[key] = app_install_info.get(\"default\", \"-\")\n        return result\n\n    def get_label_name(self, obj):\n        \"\"\" 获取拼接后的标签 \"\"\"\n        label_name = \"-\"\n        if obj.service.app_labels.exists():\n            label_name = \", \".join(\n                obj.service.app_labels.values_list(\"label_name\", flat=True))\n        return label_name\n\n    def get_cluster_type(self, obj):\n        \"\"\" 获取集群类型 \"\"\"\n        cluster_type = \"-\"\n        if obj.cluster is not None:\n            cluster_type = obj.cluster.cluster_type\n        return cluster_type\n\n    def get_history(self, obj):\n        \"\"\" 获取历史记录 \"\"\"\n        return list(obj.servicehistory_set.values(\n            \"username\", \"description\", \"result\", \"created\"))\n\n\nclass ServiceActionSerializer(serializers.ModelSerializer):\n    \"\"\" 服务动作序列化类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = '__all__'\n\n\nclass ServiceDeleteSerializer(serializers.ModelSerializer):\n    \"\"\" 服务删除序列化类 \"\"\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = '__all__'\n\n\nclass AppListSerializer(serializers.ModelSerializer):\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ApplicationHub\n        fields = '__all__'\n"
  },
  {
    "path": "omp_server/services/tasks.py",
    "content": "\"\"\"\n服务相关异步任务\n\"\"\"\n\nimport logging\n\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\nfrom db_models.models import (\n    Service, ServiceHistory\n)\nfrom utils.plugin.salt_client import SaltClient\nimport time\nimport json\nfrom promemonitor.prometheus_utils import PrometheusUtils\nfrom db_models.models import (\n    Host, HostOperateLog, ClusterInfo, Product, SelfHealingHistory, Alert\n)\nfrom utils.parse_config import BASIC_ORDER, CLEAR_DB\nfrom django.utils import timezone\n\nfrom django.db.models import F\nfrom django.db import transaction\n\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\ndef delete_action(service_obj):\n    \"\"\"\n    查询删除目录\n    \"\"\"\n    install_detail = service_obj.detailinstallhistory_set.first().install_detail_args\n    dir_list = [\"base_dir\", \"log_dir\", \"data_dir\"]\n    valida_rm = []\n    for args in install_detail.get(\"install_args\"):\n        if args.get(\"key\") in dir_list:\n            dir_name = args.get(\"default\")\n            if dir_name and len(dir_name) >= 5:\n                valida_rm.append(dir_name)\n    result = \" \".join(valida_rm)\n    return result\n\n\ndef get_app_dir(service_obj):\n    \"\"\"获取服务app_dir\"\"\"\n    base_dir = \"\"\n    install_detail_args = service_obj.detailinstallhistory_set.first().install_detail_args\n    base_dir_dict = {\n        \"base_dir\":\n            args.get(\"default\") for args in install_detail_args.get(\"install_args\")\n        if args.get(\"key\") == \"base_dir\"\n    }\n    base_dir = base_dir_dict.get(\"base_dir\", \"\")\n    if base_dir and len(base_dir) >= 5:\n        return base_dir\n\n\ndef delete_file(service_controllers, service_obj):\n    \"\"\"\n    删除文件操作\n    \"\"\"\n    salt_obj = SaltClient()\n    exe_action = service_controllers.get(\"stop\", \"\")\n    if \"hadoop\" in exe_action:\n        scripts_param = exe_action.split()\n        if len(scripts_param) > 3:\n            return True\n        scripts_param[2] = \"all\"\n        exe_action = \" \".join(scripts_param)\n    # 存在stop脚本先执行stop脚本后执行删除\n    if exe_action:\n        for count in range(2):\n            is_success, info = salt_obj.cmd(service_obj.ip, exe_action, 600)\n            time.sleep(count + 1)\n            if is_success is True:\n                break\n            logger.info(f\"执行 [delete] 操作 {is_success}，原因: {info}\")\n    base_dir = delete_action(service_obj)\n    app_dir = get_app_dir(service_obj)\n    # TODO 删除定时任务\n    cron_del_str = f\"crontab -l |grep -v {app_dir} 2>/dev/null | crontab -\"\n    cmd_res, msg = salt_obj.cmd(\n        service_obj.ip, cron_del_str, 600\n    )\n    logger.info(f\"执行 [delete] crontab操作 {cmd_res}, 原因: {msg}\")\n\n    # 删除安装路径\n    if base_dir:\n        is_success, info = salt_obj.cmd(\n            service_obj.ip, f\"/bin/rm -rf {base_dir}\", 600)\n        logger.info(f\"执行 [delete] 操作 {is_success}，原因: {info}\")\n        return cmd_res and is_success\n\n\n@shared_task\ndef exec_action(action, instance, operation_user, del_file=False, need_sleep=True):\n    # edit by vum: 增加服务的目标成功状态、失败状态\n    action_json = {\n        \"1\": [\"start\", 1, 0, 4],\n        \"2\": [\"stop\", 2, 4, 0],\n        \"3\": [\"restart\", 3, 0, 4],\n        \"4\": [\"delete\", 4]\n    }\n    result_json = {\n        True: \"success\",\n        False: \"failure\"\n    }\n    try:\n        service_obj = Service.objects.get(id=instance)\n    except Exception as e:\n        logger.error(f\"service实例id，不存在{instance}:{e}\")\n        return None\n    ip = service_obj.ip\n    # service_controllers 字段为json字段类型\n    service_controllers = service_obj.service_controllers\n    action = action_json.get(str(action))\n    if not action:\n        logger.error(\"action动作不合法\")\n        raise ValueError(\"action动作不合法\")\n    if action[0] == 'delete':\n        service_port = None\n        if service_obj.service_port is not None:\n            service_port_ls = json.loads(service_obj.service_port)\n            if len(service_port_ls) > 0:\n                service_port = service_port_ls[0].get(\"default\", \"\")\n        if service_port is not None:\n            # 端口存在则删除prometheus监控的\n            ser_name = service_obj.service.app_name\n            if ser_name == \"hadoop\":\n                ser_name = service_obj.service_instance_name.split(\"_\", 1)[0]\n            service_data = {\n                \"service_name\": ser_name,\n                \"instance_name\": service_obj.service_instance_name,\n                \"data_path\": None,\n                \"log_path\": None,\n                \"env\": service_obj.env.name,\n                \"ip\": ip,\n                \"listen_port\": service_port\n            }\n            PrometheusUtils().delete_service(service_data)\n        # 删除hosts实例个数\n        service_history_obj = ServiceHistory.objects.filter(\n            service=service_obj)\n        if len(service_history_obj) != 0:\n            service_history_obj.delete()\n        if del_file:\n            is_success = delete_file(service_controllers, service_obj)\n        else:\n            is_success = True\n        host_instances = Host.objects.filter(ip=service_obj.ip)\n        for instance in host_instances:\n            HostOperateLog.objects.create(username=operation_user,\n                                          description=f\"卸载服务 [{service_obj.service.app_name}]\",\n                                          result=\"success\" if is_success else \"failed\",\n                                          host=instance)\n        with transaction.atomic():\n            service_obj.delete()\n            count = Service.objects.filter(ip=service_obj.ip).count()\n            Host.objects.filter(ip=service_obj.ip).update(\n                service_num=count)\n            # 当服务被删除时，应该将其所在的集群都连带删除\n            if service_obj.cluster and Service.objects.filter(\n                    cluster=service_obj.cluster\n            ).count() == 0:\n                ClusterInfo.objects.filter(\n                    id=service_obj.cluster.id\n                ).delete()\n            # 当服务被删除时，如果他所属的产品下已没有其他服务，那么应该删除产品实例\n            if Service.objects.filter(\n                    service__product=service_obj.service.product\n            ).count() == 0:\n                Product.objects.filter(\n                    product=service_obj.service.product\n                ).delete()\n        return None\n\n    exe_action = service_controllers.get(action[0])\n    if exe_action:\n        salt_obj = SaltClient()\n        service_obj.service_status = action[1]\n        service_obj.save()\n        time_array = time.localtime(int(time.time()))\n        time_style = time.strftime(\"%Y-%m-%d %H:%M:%S\", time_array)\n        is_success, info = salt_obj.cmd(ip, exe_action, 600)\n        # TODO 服务状态维护问题，临时解决方案，休眠保持中间态\n        if need_sleep:\n            time.sleep(35)\n        service_obj.service_status = action[2] if is_success else action[3]\n        service_obj.save()\n        logger.info(f\"执行 [{action[0]}] 操作 {is_success}，原因: {info}\")\n        ServiceHistory.objects.create(\n            username=operation_user,\n            description=f\"执行 [{action[0]}] 操作\",\n            result=result_json.get(is_success),\n            created=time_style,\n            service=service_obj\n        )\n        logger.info(f\"服务操作详情:{info}\")\n        return ip, info\n    else:\n        logger.error(f\"数据库无{action[0]}动作\")\n        raise ValueError(f\"数据库无{action[0]}动作\")\n\n@shared_task\ndef clear_db(task_id):\n    \"\"\"\n    # ToDo 懒得写了以后优化\n    \"\"\"\n    days_ago = timezone.now() - timezone.timedelta(\n        days=CLEAR_DB.get('health').get(\"day\", 7)\n    )\n    SelfHealingHistory.objects.filter(end_time__lt=days_ago).delete()\n    days_ago = timezone.now() - timezone.timedelta(\n        days=CLEAR_DB.get('alert').get(\"day\", 7)\n    )\n    Alert.objects.filter(create_time__lt=days_ago).delete()\n"
  },
  {
    "path": "omp_server/services/urls.py",
    "content": "from django.urls import path\nfrom rest_framework.routers import DefaultRouter\n\nfrom services.views import (\n    ServiceListView, ServiceDetailView,\n    ServiceActionView, ServiceDeleteView,\n    ServiceStatusView, ServiceDataJsonView,\n    AppListView, AppConfCheckView,\n)\nfrom services.self_heal_view import (\n    SelfHealingSettingView, ListSelfHealingHistoryView,\n    UpdateSelfHealingHistoryView\n)\n\nrouter = DefaultRouter()\nrouter.register(\"services\", ServiceListView, basename=\"services\")\nrouter.register(\"services\", ServiceDetailView, basename=\"services\")\nrouter.register(\"action\", ServiceActionView, basename=\"action\")\nrouter.register(\"delete\", ServiceDeleteView, basename=\"delete\")\nrouter.register(\"SelfHealingSetting\", SelfHealingSettingView,\n                basename=\"SelfHealingSetting\")\nrouter.register(\"ListSelfHealingHistory\",\n                ListSelfHealingHistoryView, basename=\"ListSelfHealingHistory\")\nrouter.register(\"UpdateSelfHealingHistory\",\n                UpdateSelfHealingHistoryView,\n                basename=\"UpdateSelfHealingHistory\")\nrouter.register(\"serviceStatus\", ServiceStatusView, basename=\"serviceStatus\")\n\n# Accept_manager\nrouter.register(\"appList\", AppListView, basename=\"appList\")\nrouter.register(\"appConfCheck\", AppConfCheckView, basename=\"appConfCheck\")\n\nurlpatterns = [\n    path('data_json', ServiceDataJsonView.as_view(), name=\"serviceDataJson\")\n]\n\nurlpatterns += router.urls\n"
  },
  {
    "path": "omp_server/services/views.py",
    "content": "import json\nimport logging\nimport os\n\nfrom django.conf import settings\nfrom django.http import Http404\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\nfrom rest_framework.views import APIView\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, RetrieveModelMixin,\n    CreateModelMixin\n)\nfrom rest_framework.response import Response\nfrom rest_framework.filters import OrderingFilter\n\nfrom db_models.models import Service, ApplicationHub, MainInstallHistory, Host\nfrom utils.parse_config import BASIC_ORDER\nfrom service_upgrade.update_data_json import DataJsonUpdate\nfrom services.permission import GetDataJsonAuthenticated\nfrom services.tasks import exec_action\nfrom services.services_filters import ServiceFilter\nfrom services.services_serializers import (\n    ServiceSerializer, ServiceDetailSerializer,\n    ServiceActionSerializer, ServiceDeleteSerializer,\n    ServiceStatusSerializer, AppListSerializer\n)\nfrom promemonitor.prometheus import Prometheus\nfrom promemonitor.grafana_url import explain_url\nfrom utils.common.exceptions import OperateError\nfrom utils.common.paginations import PageNumberPager\nfrom operator import itemgetter\nfrom services.app_check import (\n    ConfCheck, ManagerService\n)\n\nlogger = logging.getLogger('server')\n\n\nclass ServiceListView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查询服务列表\n    \"\"\"\n    queryset = Service.objects.all()\n    serializer_class = ServiceSerializer\n    pagination_class = PageNumberPager\n    # 过滤，排序字段\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = ServiceFilter\n    ordering_fields = (\"ip\", \"service_instance_name\")\n    # 动态排序字段\n    dynamic_fields = (\"cpu_usage\", \"mem_usage\")\n    # 操作描述信息\n    get_description = \"查询服务列表\"\n\n    def list(self, request, *args, **kwargs):\n        # 获取序列化数据列表\n        queryset = self.filter_queryset(self.get_queryset())\n        real_query = queryset\n\n        # 实时获取服务动态git\n        prometheus_obj = Prometheus()\n        is_success, prometheus_dict = prometheus_obj.get_all_service_status()\n\n        # 当未指定排序字段且查询成功时\n        query_field = request.query_params.get(\"ordering\", \"\")\n        if is_success and query_field == \"\":\n            stop_ls = []\n            natural_ls = []\n            no_monitor_ls = []\n            ing_ls = []\n            for service in queryset:\n                # 当服务状态为 '正常' 和 '异常' 时\n                if service.service_status in (Service.SERVICE_STATUS_NORMAL, Service.SERVICE_STATUS_STOP):\n                    key_name = f\"{service.ip}_{service.service_instance_name}\"\n                    status = prometheus_dict.get(key_name, None)\n                    if status is None:\n                        no_monitor_ls.append(service)\n                    elif not status:\n                        stop_ls.append(service)\n                    else:\n                        natural_ls.append(service)\n                else:\n                    ing_ls.append(service)\n            real_query = stop_ls + ing_ls + natural_ls + no_monitor_ls\n\n        serializer = self.get_serializer(\n            self.paginate_queryset(real_query), many=True)\n        serializer_data = serializer.data\n\n        # 若获取成功，则动态覆盖服务状态\n        if is_success:\n            status_dict = {\n                True: \"正常\",\n                False: \"停止\",\n                None: \"未监控\",\n            }\n            for service_obj in serializer_data:\n                # 如果服务状态为 '正常' 和 '停止' 的服务，通过 Prometheus 动态更新\n                if service_obj.get(\"service_status\") in (\"正常\", \"停止\"):\n                    # 如果是 web 服务，则状态直接置为正常\n                    if service_obj.get(\"is_web\"):\n                        service_obj[\"service_status\"] = \"正常\"\n                        continue\n                    key_name = f\"{service_obj.get('ip')}_{service_obj.get('service_instance_name')}\"\n                    status = prometheus_dict.get(key_name, None)\n                    service_obj[\"service_status\"] = status_dict.get(status)\n\n        # 获取监控及日志的url\n        serializer_data = explain_url(\n            serializer_data, is_service=True)\n\n        serializer_data = prometheus_obj.get_service_info(serializer_data)\n        reverse_flag = False\n        if query_field.startswith(\"-\"):\n            reverse_flag = True\n            query_field = query_field[1:]\n        # 若排序字段在类视图 dynamic_fields 中，则对根据动态数据进行排序\n        none_ls = list(filter(\n            lambda x: x.get(query_field) is None,\n            serializer_data))\n        exists_ls = list(filter(\n            lambda x: x.get(query_field) is not None,\n            serializer_data))\n        if query_field in self.dynamic_fields:\n            exists_ls = sorted(\n                exists_ls,\n                key=lambda x: x.get(query_field),\n                reverse=reverse_flag)\n        exists_ls.extend(none_ls)\n        return self.get_paginated_response(exists_ls)\n\n\nclass ServiceDetailView(GenericViewSet, RetrieveModelMixin):\n    \"\"\"\n        read:\n        查询服务详情\n    \"\"\"\n    queryset = Service.objects.all()\n    serializer_class = ServiceDetailSerializer\n    # 操作描述信息\n    get_description = \"查询服务详情\"\n\n\nclass ServiceActionView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        服务启停删除\n    \"\"\"\n    queryset = Service.objects.all()\n    serializer_class = ServiceActionSerializer\n    post_description = \"执行启动停止或卸载操作\"\n\n    def create(self, request, *args, **kwargs):\n        many_data = self.request.data.get('data')\n        for data in many_data:\n            action = data.get(\"action\")\n            instance = data.get(\"id\")\n            operation_user = data.get(\"operation_user\")\n            del_file = data.get(\"del_file\", True)\n            service_obj = Service.objects.filter(id=instance).first()\n            need_split = [\"hadoop\"]\n            if service_obj and service_obj.service.app_name in need_split and action == 4:\n                delete_objs = Service.objects.filter(ip=service_obj.ip, service__app_name=\"hadoop\")\n                status = service_obj.service_status\n                delete_objs.update(service_status=Service.SERVICE_STATUS_DELETING)\n                if status != Service.SERVICE_STATUS_DELETING \\\n                        and delete_objs.first().id == service_obj.id:\n                    del_file = True\n                else:\n                    del_file = False\n            if action and instance and operation_user:\n                if action == 4:\n                    try:\n                        service_obj.service_status = Service.SERVICE_STATUS_DELETING\n                        service_obj.save()\n                    except Exception as e:\n                        logger.error(f\"service实例id，不存在{instance}:{e}\")\n                        return Response(\"执行异常\")\n                exec_action.delay(action, instance, operation_user, del_file)\n            else:\n                raise OperateError(\"请输入action或id\")\n        return Response(\"执行成功\")\n\n\nclass ServiceDeleteView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        服务删除校验\n    \"\"\"\n    queryset = Service.objects.all()\n    serializer_class = ServiceDeleteSerializer\n    post_description = \"查看服务删除校验依赖\"\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        检查被依赖关系，包含多服务匹配\n        例如 jdk-1.8和 test-app被同时标记删除\n        test-app依赖jdk-1.8，同时标记则不显示依赖。单选jdk1.8则会显示。\n        \"\"\"\n        many_data = self.request.data.get('data')\n        service_objs = Service.objects.all()\n        app_objs = ApplicationHub.objects.all()\n        service_json = {}\n        dependence_dict = []\n        # 存在的service key\n        for i in service_objs:\n            service_key = f\"{i.service.app_name}-{i.service.app_version}\"\n            service_json[i.id] = service_key\n        # 全量app的dependence反向\n        for app in app_objs:\n            if app.app_dependence:\n                for i in json.loads(app.app_dependence):\n                    dependence_dict.append(\n                        {f\"{i.get('name')}-{i.get('version')}\": f\"{app.app_name}-{app.app_version}\"}\n                    )\n        exist_service = set()\n        # 过滤存在的实例所属app的key\n        for data in many_data:\n            instance = int(data.get(\"id\"))\n            filter_list = service_json.get(instance)\n            exist_service.add(filter_list)\n        # 查看存在的服务有没有被依赖的，做set去重\n        res = set()\n        for i in exist_service:\n            for j in dependence_dict:\n                if j.get(i):\n                    res.add(j.get(i))\n        res = res - exist_service\n        # 查看是否需要被依赖的是否已不存在\n        res = res & set(service_json.values())\n        res = \"存在依赖信息:\" + \",\".join(res) if res else \"无依赖信息\"\n        return Response(res)\n\n\nclass ServiceStatusView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        查询服务列表\n    \"\"\"\n    queryset = Service.objects.filter(\n        service__is_base_env=False)\n    serializer_class = ServiceStatusSerializer\n    authentication_classes = ()\n    permission_classes = ()\n    # 操作描述信息\n    get_description = \"查询服务状态\"\n\n    def list(self, request, *args, **kwargs):\n        # 获取序列化数据列表\n        queryset = self.get_queryset()\n        real_query = queryset\n        # 实时获取服务动态git\n        prometheus_obj = Prometheus()\n        is_success, prometheus_dict = prometheus_obj.get_all_service_status()\n        if is_success:\n            stop_ls = []\n            natural_ls = []\n            no_monitor_ls = []\n            ing_ls = []\n            for service in queryset:\n                # 当服务状态为 '正常' 和 '异常' 时\n                if service.service_status in (Service.SERVICE_STATUS_NORMAL, Service.SERVICE_STATUS_STOP):\n                    key_name = f\"{service.ip}_{service.service_instance_name}\"\n                    status = prometheus_dict.get(key_name, None)\n                    if status is None:\n                        no_monitor_ls.append(service)\n                    elif not status:\n                        stop_ls.append(service)\n                    else:\n                        natural_ls.append(service)\n                else:\n                    ing_ls.append(service)\n            real_query = stop_ls + ing_ls + natural_ls + no_monitor_ls\n\n        serializer = self.get_serializer(real_query, many=True)\n        serializer_data = serializer.data\n\n        # 若获取成功，则动态覆盖服务状态\n        if is_success:\n            for service_obj in serializer_data:\n                # 如果服务状态为 '正常' 和 '停止' 的服务，通过 Prometheus 动态更新\n                if service_obj.get(\"service_status\") in (\"正常\", \"停止\"):\n                    # 如果是 web 服务，则状态直接置为正常\n                    if service_obj.get(\"is_web\"):\n                        service_obj[\"service_status\"] = True\n                        continue\n                    key_name = f\"{service_obj.get('ip')}_{service_obj.get('service_instance_name')}\"\n                    status = prometheus_dict.get(key_name, None)\n                    service_obj[\"service_status\"] = status\n        return Response(serializer_data)\n\n\nclass ServiceDataJsonView(APIView):\n    # for automated testing\n    permission_classes = (GetDataJsonAuthenticated,)\n\n    def get(self, request):\n        main_install = MainInstallHistory.objects.order_by(\"-id\").first()\n        if not main_install:\n            raise Http404('No install history matches the given query.')\n        json_path = os.path.join(\n            settings.PROJECT_DIR,\n            f\"package_hub/data_files/{main_install.operation_uuid}.json\"\n        )\n        if not os.path.exists(json_path):\n            DataJsonUpdate(main_install.operation_uuid).create_json_file()\n        with open(json_path, \"r\") as f:\n            json_data = json.load(f)\n        return Response({\"json_data\": json_data})\n\n\nclass AppListView(GenericViewSet, ListModelMixin, CreateModelMixin):\n    queryset = ApplicationHub.objects.all().exclude(app_name__in=[\"hadoop\", \"doim\"])\n    serializer_class = AppListSerializer\n    get_description = \"应用列表查询\"\n    post_description = \"列表合法性校验\"\n    need_key = ['base_dir', 'run_user', 'service_port', 'data_dir', 'log_dir']\n\n    def list(self, request, *args, **kwargs):\n        queryset = self.get_queryset()\n        app_dc = {}\n        app_ls = []\n        for query in queryset:\n            if query.pro_info:\n                pro_name = query.pro_info[\"pro_name\"]\n                pro_version = query.pro_info[\"pro_version\"]\n                app_dc.setdefault(pro_name, {})\n                app_dc[pro_name].setdefault(\"version\", [])\n                if pro_version not in app_dc[pro_name][\"version\"]:\n                    app_dc[pro_name][\"version\"].append(pro_version)\n                app_dc[pro_name].setdefault(\"child\", {})\n                app_dc[pro_name][\"child\"].setdefault(pro_version, []).append(\n                    {\"name\": query.app_name, \"version\": query.app_version})\n            else:\n                app_dc.setdefault(query.app_name, {})\n                app_dc[query.app_name].setdefault(\"version\", []).append(query.app_version)\n\n        basic_ls = [list() for _ in range(len(BASIC_ORDER))]\n        pro_ls = []\n        for name, info in app_dc.items():\n            tmp_dc = {\n                \"name\": name,\n                \"version\": info[\"version\"]\n            }\n            if info.get(\"child\"):\n                tmp_dc.update({\"child\": info.get(\"child\")})\n                pro_ls.append(tmp_dc)\n            else:\n                for index, name_ls in BASIC_ORDER.items():\n                    if name in name_ls:\n                        basic_ls[index].append(tmp_dc)\n        for i in basic_ls:\n            app_ls.extend(i)\n        app_ls.extend(pro_ls)\n        return Response(app_ls)\n\n    def check_repeat(self, app_data):\n        \"\"\"\n        查询重复\n        \"\"\"\n        for info in app_data:\n            if len(info.get(\"version\", [])) != 1:\n                info.setdefault(\"error\", f\"{info.get('name')}不允许纳管不同版本\")\n                self.error = True\n            if info.get(\"child\"):\n                app_names = []\n                for app_info in list(info[\"child\"].values())[0]:\n                    app_name = app_info.get(\"name\")\n                    if app_name not in app_names:\n                        app_names.append(app_name)\n                    else:\n                        info.setdefault(\"error\", f\"{info.get('name')}产品下{app_name}服务只允许选其中一个\")\n                        self.error = True\n\n    @staticmethod\n    def check_dependence_one(dependence_dc, installed_ser_all):\n        \"\"\"\n        检查单个服务依赖是否存在\n        \"\"\"\n        lost_ser = []\n        for dependence in dependence_dc:\n            dependence_ser = f'{dependence.get(\"name\")}:{dependence.get(\"version\")}'\n            if dependence_ser not in installed_ser_all:\n                lost_ser.append(dependence_ser)\n        return lost_ser\n\n    def check_dependence(self, dependence_dc, app_dc, app_data):\n        \"\"\"\n        查询依赖初次筛选。仅对照应用商店实例，不对照具体实例依赖,并追加参数\n        \"\"\"\n        if self.error:\n            return\n        # 查询当前所有服务的name:version,过滤状态异常的服务\n        ser_name_version = Service.objects.filter(service_status__in=range(0, 5)).values_list(\n            \"service__app_name\", \"service__app_version\")\n        installed_ser = []\n        for ser in ser_name_version:\n            installed_ser.append(f\"{ser[0]}:{ser[1].split('.')[0]}\")\n\n        for info in app_data:\n            lost_ser_set = set()\n            if info.get(\"child\") and list(info[\"child\"].values())[0]:\n                for app in list(info[\"child\"].values())[0]:\n                    app_key = f\"{app['name']}:{app['version']}\"\n                    lost_ser = self.check_dependence_one(dependence_dc.get(app_key), installed_ser)\n                    lost_ser_set.update(set(lost_ser))\n                    has_install_app_args = dict(zip(self.need_key, app_dc.get(app_key))) if app_dc.get(\n                        app_key) else dict(zip(self.need_key, [\"\"] * len(self.need_key)))\n                    app.update(has_install_app_args)\n            else:\n                app_key = f'{info.get(\"name\", \"\")}:{info.get(\"version\", [\"0\"])[0]}'\n                lost_ser = self.check_dependence_one(dependence_dc.get(app_key), installed_ser)\n                lost_ser_set.update(set(lost_ser))\n                has_install_app_args = dict(zip(self.need_key, app_dc.get(app_key))) if app_dc.get(\n                    app_key) else dict(zip(self.need_key, [\"\"] * len(self.need_key)))\n                info.update(has_install_app_args)\n            if lost_ser_set:\n                info.update({\"error\": f'缺失依赖:{\",\".join(list(lost_ser_set))}'})\n                self.error = True\n\n    @staticmethod\n    def explain_json(text):\n        if text:\n            return json.loads(text)\n        return []\n\n    @classmethod\n    def gets_app_list(cls):\n        \"\"\"\n        获取app列表信息\n        \"\"\"\n        app_ls = ApplicationHub.objects.all(). \\\n            values_list(\"app_name\", \"app_version\", \"app_install_args\",\n                        \"app_port\", \"app_dependence\")\n        app_dc = {}\n        dependence_dc = {}\n        for app in app_ls:\n            app_install_port = {}\n            for install in cls.explain_json(app[2]):\n                app_install_port[install.get('key')] = install.get('default')\n            for port in cls.explain_json(app[3]):\n                app_install_port[port.get('key')] = port.get('default')\n            for key in cls.need_key:\n                app_install_port.setdefault(key, \"\")\n            app_dc[f\"{app[0]}:{app[1]}\"] = itemgetter(*cls.need_key)(app_install_port)\n            dependence_dc[f\"{app[0]}:{app[1]}\"] = cls.explain_json(app[4])\n        return app_dc, dependence_dc\n\n    def create(self, request, *args, **kwargs):\n        if not hasattr(self, \"error\"):\n            setattr(self, \"error\", False)\n        app_data = self.request.data.get(\"data\", [])\n        # 查询是否勾选想通服务多版本情况\n        self.check_repeat(app_data)\n        # 查询安装参数及依赖\n        app_dc, dependence_dc = self.gets_app_list()\n        # 检查依赖是否存在\n        self.check_dependence(dependence_dc, app_dc, app_data)\n        res_dc = {\"service\": app_data, \"is_continue\": False if self.error else True}\n        hosts_info = Host.objects.filter(\n            host_agent=Host.AGENT_RUNNING).values_list(\"ip\", \"agent_dir\")\n        res_dc[\"ips\"] = dict(hosts_info)\n        return Response(res_dc)\n\n\nclass AppConfCheckView(GenericViewSet, CreateModelMixin):\n    queryset = ApplicationHub.objects.all()\n    serializer_class = AppListSerializer\n    post_description = \"校验列表问题\"\n\n    def create(self, request, *args, **kwargs):\n        \"\"\"\n        传uuid和不传uuid\n        \"\"\"\n        app_data = self.request.data.get(\"data\")\n        if not app_data.get(\"uuid\"):\n            if not app_data.pop(\"is_continue\"):\n                raise OperateError(\"存在不允许继续纳管的服务\")\n            app_data = ConfCheck(app_data).run()\n        else:\n            app_data = ManagerService(app_data).run()\n        return Response(app_data)\n"
  },
  {
    "path": "omp_server/tests/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/base.py",
    "content": "import json\n\nfrom django.test import TestCase\n\nfrom rest_framework.reverse import reverse\n\nfrom db_models.models import UserProfile, Env\n\n\nclass BaseTest(TestCase):\n    \"\"\" 测试基础类 \"\"\"\n\n    def setUp(self):\n        self.login_url = reverse(\"login\")\n        # 创建默认用户\n        self.default_user = self.create_default_user()\n        self.default_env = self.create_default_env()\n\n    def get(self, url, data=None):\n        return self.client.get(\n            url,\n            data=data if data else None,\n        )\n\n    def post(self, url, data):\n        return self.client.post(\n            url,\n            data=json.dumps(data),\n            content_type=\"application/json; charset=utf-8\",\n        )\n\n    def delete(self, url, data=None):\n        return self.client.delete(\n            url,\n            data=json.dumps(data) if data else None,\n            content_type=\"application/json; charset=utf-8\",\n        )\n\n    def put(self, url, data):\n        return self.client.put(\n            url,\n            data=json.dumps(data),\n            content_type=\"application/json; charset=utf-8\",\n        )\n\n    def patch(self, url, data):\n        return self.client.patch(\n            url,\n            data=json.dumps(data),\n            content_type=\"application/json; charset=utf-8\",\n        )\n\n    def login(self, remember=False):\n        \"\"\" 登录，签发 token 令牌 \"\"\"\n        login_data = {\n            \"username\": self.default_user.username,\n            \"password\": self.default_user.password,\n        }\n        if remember:\n            login_data[\"remember\"] = True\n        resp = self.post(self.login_url, login_data)\n        return resp\n\n    def logout(self):\n        \"\"\" 退出登录，清除 cookies 中的 token 令牌 \"\"\"\n        self.client.cookies.pop(\"jwtToken\")\n\n    @staticmethod\n    def create_default_user():\n        \"\"\" 创建默认用户 \"\"\"\n        queryset = UserProfile.objects.filter(username=\"admin\")\n        if queryset.exists():\n            return queryset.first()\n        user_obj = UserProfile.objects.create_user(\n            username=\"admin\",\n            password=\"adminPassword\",\n            email=\"admin@cloudwise.com\",\n        )\n        user_obj.password = \"adminPassword\"\n        return user_obj\n\n    @staticmethod\n    def create_default_env():\n        \"\"\" 创建默认环境 \"\"\"\n        queryset = Env.objects.filter(id=1)\n        if queryset.exists():\n            return\n        return Env.objects.create(id=1, name=\"default\")\n\n\nclass AutoLoginTest(BaseTest):\n    \"\"\" 自动登录测试基类 \"\"\"\n\n    def setUp(self):\n        super(AutoLoginTest, self).setUp()\n        self.login()\n\n    def tearDown(self):\n        super(AutoLoginTest, self).tearDown()\n        self.logout()\n"
  },
  {
    "path": "omp_server/tests/mixin.py",
    "content": "\"\"\"\n单元测试资源模拟混入类\n\"\"\"\nimport time\nimport json\nimport random\nfrom db_models.models import (\n    Host, Env, Labels, Service, ServiceHistory, GrafanaMainPage,\n    ClusterInfo, ApplicationHub, ProductHub, UploadPackageHistory,\n    MainInstallHistory, DetailInstallHistory\n)\nfrom utils.plugin.crypto import AESCryptor\n\n\nclass HostsResourceMixin:\n    \"\"\" 主机资源混入类 \"\"\"\n\n    INSTANCE_NAME_START = \"t_host\"\n    IP_START = \"127\"\n\n    def get_hosts(self, number=20, env_id=None):\n        \"\"\"\n        获取主机\n        :param number: 创建数量\n        :param env_id: 环境实例\n        \"\"\"\n        # 获取环境信息\n        env = None\n        if env_id:\n            env = Env.objects.filter(id=env_id).first()\n        if not env:\n            env = Env.objects.filter(id=1).first()\n        # 创建主机\n        aes_crypto = AESCryptor()\n        host_ls = []\n        agent_status_ls = list(map(lambda x: x[0], Host.AGENT_STATUS_CHOICES))\n        init_status_ls = list(map(lambda x: x[0], Host.INIT_STATUS_CHOICES))\n        for index in range(number):\n            index += 1\n            host_ls.append(Host(\n                instance_name=f\"{self.INSTANCE_NAME_START}_{index}\",\n                ip=f\"{self.IP_START}.0.0.{index}\",\n                port=36000,\n                username=f\"root{index}\",\n                password=aes_crypto.encode(f\"password_{index}\"),\n                data_folder=\"/data\",\n                operate_system=\"CentOS\",\n                env=env,\n                service_num=random.randint(0, 100),\n                alert_num=random.randint(0, 100),\n                host_agent=random.choice(agent_status_ls),\n                monitor_agent=random.choice(agent_status_ls),\n                init_status=random.choice(init_status_ls),\n            ))\n        Host.objects.bulk_create(host_ls)\n        return Host.objects.filter(\n            instance_name__startswith=self.INSTANCE_NAME_START)\n\n    def destroy_hosts(self):\n        \"\"\" 销毁主机 \"\"\"\n        Host.objects.filter(\n            instance_name__startswith=self.INSTANCE_NAME_START).delete()\n\n\nclass HostBatchRequestMixin:\n    \"\"\" 主机批量请求混入类 \"\"\"\n\n    @staticmethod\n    def get_host_batch_request(number, row=False):\n        \"\"\" 模拟请求信息 \"\"\"\n        host_list = []\n        for i in range(number):\n            data = {\n                \"instance_name\": f\"host_new_{i}\",\n                \"ip\": f\"10.10.10.{i}\",\n                \"port\": 36000,\n                \"username\": \"root\",\n                \"password\": \"root_password\",\n                \"data_folder\": \"/data\",\n                \"operate_system\": random.choice((\"CentOS\", \"RedHat\"))\n            }\n            if row:\n                data[\"row\"] = i + 1\n            host_list.append(data)\n        return {\"host_list\": host_list}\n\n\nclass GrafanaMainPageResourceMixin:\n    \"\"\" Grafana 主面板资源混入类 \"\"\"\n\n    INSTANCE_NAME_TUPLE = (\"node\", \"service\", \"log\", \"mysql\")\n    INSTANCE_URL_CONTAIN = \"t_grafana\"\n\n    def get_grafana_main_pages(self):\n        \"\"\" 获取面板信息 \"\"\"\n        grafana_main_page_ls = []\n        for instance_name in self.INSTANCE_NAME_TUPLE:\n            grafana_main_page_ls.append(GrafanaMainPage(\n                instance_name=instance_name,\n                instance_url=f\"/proxy/v1/{self.INSTANCE_URL_CONTAIN}/d/{instance_name}-url\"\n            ))\n        GrafanaMainPage.objects.bulk_create(grafana_main_page_ls)\n        return GrafanaMainPage.objects.filter(\n            instance_name__in=self.INSTANCE_NAME_TUPLE,\n            instance_url__contains=self.INSTANCE_URL_CONTAIN)\n\n    def destroy_grafana_main_pages(self):\n        \"\"\" 销毁面板信息 \"\"\"\n        GrafanaMainPage.objects.filter(\n            instance_name__in=self.INSTANCE_NAME_TUPLE,\n            instance_url__contains=self.INSTANCE_URL_CONTAIN).delete()\n\n\nclass LabelsResourceMixin:\n    \"\"\" 标签资源混入类 \"\"\"\n\n    LABEL_NAME_START = \"t_label\"\n\n    def get_labels(self, number=10, label_type=None):\n        \"\"\"\n        获取标签\n        :param number: 创建数量\n        :param label_type: 类型\n        \"\"\"\n        label_ls = []\n        for index in range(number):\n            if label_type is None:\n                label_type = random.choice(\n                    Labels.LABELS_CHOICES)[0]\n            index += 1\n            label_ls.append(Labels(\n                label_type=label_type,\n                label_name=f\"{self.LABEL_NAME_START}_{index}\"\n            ))\n        Labels.objects.bulk_create(label_ls)\n        return Labels.objects.filter(\n            label_name__startswith=self.LABEL_NAME_START)\n\n    def destroy_labels(self):\n        \"\"\" 销毁标签 \"\"\"\n        Labels.objects.filter(\n            label_name__startswith=self.LABEL_NAME_START).delete()\n\n\nclass UploadPackageHistoryMixin:\n    \"\"\" 上传安装包记录资源混入类 \"\"\"\n\n    PACKAGE_NAME_START = \"t_pkg\"\n\n    def get_upload_package_history(self, number=20, is_many=True):\n        \"\"\"\n        获取上传安装包记录\n        :param number: 创建数量\n        :param is_many: 是否单次多个上传\n        \"\"\"\n        history_ls = []\n        for index in range(number):\n            index += 1\n            # 短暂休眠，避免毫秒级时间戳重复\n            time.sleep(0.01)\n            opera_uuid = str(int(round(time.time() * 1000)))\n            # 模拟单次多个安装包数量\n            pkg_number = 1\n            if is_many:\n                pkg_number = random.randint(3, 5)\n            for package_number in range(pkg_number):\n                package_number += 1\n                history_ls.append(UploadPackageHistory(\n                    operation_uuid=opera_uuid,\n                    operation_user=\"admin\",\n                    package_name=f\"{self.PACKAGE_NAME_START}_{index}_{package_number}\",\n                    package_md5=f\"{self.PACKAGE_NAME_START}_{index}_{package_number}_md5\",\n                    package_path=f\"/data/app/{package_number}\"\n                ))\n        UploadPackageHistory.objects.bulk_create(history_ls)\n        return UploadPackageHistory.objects.filter(\n            package_name__startswith=self.PACKAGE_NAME_START)\n\n    def destroy_upload_package_history(self):\n        \"\"\" 销毁上传安装包记录 \"\"\"\n        UploadPackageHistory.objects.filter(\n            package_name__startswith=self.PACKAGE_NAME_START).delete()\n\n\nclass ApplicationResourceMixin(LabelsResourceMixin, UploadPackageHistoryMixin):\n    \"\"\" 应用资源混入类 \"\"\"\n    APP_NAME_START = \"t_app\"\n\n    def _mock_install_info(self, index):\n        \"\"\" 模拟应用安装信息 \"\"\"\n        install_info = []\n        install_key_ls = (\"base_dir\", \"log_dir\", \"data_dir\",\n                          \"username\", \"password\")\n        for key in install_key_ls:\n            default = f\"/data/app/{self.APP_NAME_START}{index}\"\n            if key == \"username\":\n                default = \"root\"\n            if key == \"password\":\n                default = \"rootPassword\"\n            install_info.append({\n                \"name\": \"xxx\",\n                \"key\": key,\n                \"default\": default,\n            })\n        return json.dumps(install_info)\n\n    def _create_application(self, index, is_release, app_type, label_ls, app_package, app_version):\n        \"\"\" 创建应用 \"\"\"\n        if app_type is None:\n            app_type = random.choice(\n                ApplicationHub.APP_TYPE_CHOICES)[0]\n        if is_release is None:\n            is_release = random.choice((True, False))\n        # 随机模拟冗余字段\n        extend_fields = {\n            \"base_env\": random.choice((\n                True, False, \"True\", \"False\"\n            )),\n            \"affinity\": random.choice((\n                \"\", \"tengine\"\n            ))\n        }\n        app_obj = ApplicationHub(\n            is_release=is_release,\n            app_type=app_type,\n            app_name=f\"{self.APP_NAME_START}_{index}\",\n            app_version=app_version,\n            app_description=\"应用描述，省略一万字...\",\n            app_logo=\"app log svg data...\",\n            app_install_args=self._mock_install_info(index),\n            extend_fields=extend_fields,\n            app_package=app_package,\n            # is_base_env=random.choice((True, False)),\n            is_base_env=False,\n        )\n        app_obj.save()\n        # 随机模拟属于多种标签情况\n        label_obj_ls = random.sample(\n            list(label_ls), random.randint(1, 2))\n        for label in label_obj_ls:\n            app_obj.app_labels.add(label.id)\n            app_obj.save()\n        return app_obj\n\n    def get_application(self, number=20, app_type=None, is_release=None):\n        \"\"\"\n        获取应用\n        :param number: 创建数量\n        :param app_type: 类型\n        :param is_release: 是否发布\n        \"\"\"\n        label_ls = self.get_labels(\n            label_type=Labels.LABEL_TYPE_COMPONENT)\n        # 创建上传包记录\n        upload_history_ls = self.get_upload_package_history(\n            number=number, is_many=False)\n        for index in range(number):\n            app_package = upload_history_ls[index]\n            index += 1\n            self._create_application(\n                index, is_release, app_type, label_ls,\n                app_package=app_package, app_version=\"1.0\")\n        # 随机模拟多个版本情况\n        random_app_ls = random.sample(\n            list(range(number)),\n            random.randint(0, number // 2 + 1))\n        # 创建上传包记录\n        upload_history_ls = self.get_upload_package_history(\n            number=len(random_app_ls), is_many=False)\n        for index in random_app_ls:\n            app_package = upload_history_ls[index]\n            index += 1\n            self._create_application(\n                index, is_release, app_type, label_ls,\n                app_package=app_package, app_version=\"2.0\")\n        return ApplicationHub.objects.filter(\n            app_name__startswith=self.APP_NAME_START)\n\n    def destroy_application(self):\n        \"\"\" 销毁应用 \"\"\"\n        ApplicationHub.objects.filter(\n            app_name__startswith=self.APP_NAME_START).delete()\n        self.destroy_upload_package_history()\n        self.destroy_labels()\n\n\nclass ProductResourceMixin(LabelsResourceMixin, UploadPackageHistoryMixin):\n    \"\"\" 产品资源混入类 \"\"\"\n    PRO_NAME_START = \"t_pro\"\n\n    def _create_product(self, index, is_release, label_ls, pro_package, pro_version):\n        if is_release is None:\n            is_release = random.choice((True, False))\n        pro_obj = ProductHub(\n            is_release=is_release,\n            pro_name=f\"{self.PRO_NAME_START}_{index}\",\n            pro_version=pro_version,\n            pro_description=\"产品描述，省略一万字...\",\n            pro_logo=\"pro log svg data\",\n            pro_package=pro_package,\n        )\n        pro_obj.save()\n        # 随机模拟属于多种标签情况\n        label_obj_ls = random.sample(\n            list(label_ls), random.randint(1, 2))\n        for label in label_obj_ls:\n            pro_obj.pro_labels.add(label.id)\n            pro_obj.save()\n        return pro_obj\n\n    def get_product(self, number=20, is_release=None):\n        \"\"\"\n        获取产品\n        :param number: 创建数量\n        :param is_release: 是否发布\n        \"\"\"\n        label_ls = self.get_labels(\n            label_type=Labels.LABEL_TYPE_COMPONENT)\n        # 创建上传包记录\n        upload_history_ls = self.get_upload_package_history(\n            number=number, is_many=False)\n        for index in range(number):\n            pro_package = upload_history_ls[index]\n            index += 1\n            self._create_product(\n                index, is_release, label_ls,\n                pro_package=pro_package, pro_version=\"1.0\")\n        # 随机模拟多个版本情况\n        random_pro_ls = random.sample(\n            list(range(number)),\n            random.randint(0, number // 2 + 1))\n        # 创建上传包记录\n        upload_history_ls = self.get_upload_package_history(\n            number=len(random_pro_ls), is_many=False)\n        for index in random_pro_ls:\n            pro_package = upload_history_ls[index]\n            index += 1\n            self._create_product(\n                index, is_release, label_ls,\n                pro_package=pro_package, pro_version=\"2.0\")\n        return ProductHub.objects.filter(\n            pro_name__startswith=self.PRO_NAME_START)\n\n    def destroy_product(self):\n        \"\"\" 销毁应用 \"\"\"\n        ProductHub.objects.filter(\n            pro_name__startswith=self.PRO_NAME_START).delete()\n        self.destroy_labels()\n\n\nclass ClusterResourceMixin:\n    \"\"\" 集群资源混入类 \"\"\"\n    NAME_START = \"t_cluster\"\n\n    def get_cluster(self, number=5, service_name=\"test_service\"):\n        \"\"\"\n        获取集群\n        :param number: 创建数量\n        :param service_name: 集群所属服务\n        \"\"\"\n        cluster_type_ls = (\"单实例\", \"主从\", \"哨兵\", \"集群\")\n        cluster_ls = []\n        for index in range(number):\n            index += 1\n            cluster_ls.append(ClusterInfo(\n                cluster_service_name=service_name,\n                cluster_name=f\"{self.NAME_START}_{index}\",\n                cluster_type=random.choice(cluster_type_ls),\n            ))\n        ClusterInfo.objects.bulk_create(cluster_ls)\n        return ClusterInfo.objects.filter(\n            cluster_name__startswith=self.NAME_START)\n\n    def destroy_cluster(self):\n        \"\"\" 销毁集群 \"\"\"\n        ClusterInfo.objects.filter(\n            cluster_name__startswith=self.NAME_START).delete()\n\n\nclass ServicesResourceMixin(HostsResourceMixin, ClusterResourceMixin,\n                            ApplicationResourceMixin, ProductResourceMixin):\n    \"\"\" 服务资源混入类 \"\"\"\n\n    INSTANCE_NAME_START = \"t_service\"\n\n    def get_services(self, number=20, env_id=None):\n        \"\"\"\n        获取服务\n        :param number: 创建数量\n        :param env_id: 环境实例\n        \"\"\"\n        # 创建主机、应用、集群\n        host_ls = self.get_hosts(env_id=env_id)\n        app_ls = self.get_application(is_release=True)\n        cluster_ls = self.get_cluster()\n        # 获取环境信息\n        env = None\n        if env_id:\n            env = Env.objects.filter(id=env_id).first()\n        if not env:\n            env = Env.objects.filter(id=1).first()\n        # 创建服务\n        service_ls = []\n        for index in range(number):\n            index += 1\n            # 随机构造端口字段\n            service_port_ls = [{\n                \"key\": \"service_port\",\n                \"default\": 18080,\n            }]\n            for port_index in range(random.randint(0, 2)):\n                service_port_ls.append({\n                    \"key\": f\"http_port_{port_index}\",\n                    \"default\": 18090 + port_index,\n                })\n            # 随机分配集群\n            cluster = None\n            if random.choice((True, False)):\n                cluster = random.choice(cluster_ls)\n            service_ls.append(Service(\n                ip=random.choice(host_ls).ip,\n                service_instance_name=f\"{self.INSTANCE_NAME_START}_{index}\",\n                service_port=json.dumps(service_port_ls),\n                service_status=random.choice(\n                    Service.SERVICE_STATUS_CHOICES)[0],\n                alert_count=random.randint(1, 100),\n                self_healing_count=random.randint(1, 100),\n                service=random.choice(app_ls),\n                env=env,\n                cluster=cluster,\n                service_controllers={\n                    \"start\": \"start_path\",\n                    \"stop\": \"stop_path\",\n                    \"restart\": \"restart_path\",\n                    \"init\": \"init_path\",\n                    \"install\": \"install_path\",\n                },\n            ))\n        Service.objects.bulk_create(service_ls)\n\n        service_queryset = Service.objects.filter(\n            service_instance_name__startswith=self.INSTANCE_NAME_START)\n        # 创建服务历史记录\n        history_ls = []\n        for obj in service_queryset:\n            history_ls.append(ServiceHistory(\n                username=\"admin\",\n                description=\"安装实例\",\n                result=\"success\",\n                service=obj,\n            ))\n        ServiceHistory.objects.bulk_create(history_ls)\n        return service_queryset\n\n    def destroy_services(self):\n        \"\"\" 销毁服务 \"\"\"\n        Service.objects.filter(\n            service_instance_name__startswith=self.INSTANCE_NAME_START).delete()\n        self.destroy_application()\n        self.destroy_hosts()\n        self.destroy_cluster()\n\n\nclass InstallHistoryResourceMixin(ServicesResourceMixin):\n    \"\"\" 安装历史记录资源混入类 \"\"\"\n    UUID_START = \"t_main\"\n\n    def get_install_history(self, number=5):\n        \"\"\" 获取安装历史记录 \"\"\"\n        main_obj = MainInstallHistory.objects.create(\n            operation_uuid=f\"{self.UUID_START}_\"\n                           f\"{int(round(time.time() * 1000))}\")\n        service_ls = self.get_services(number=number)\n        detail_ls = []\n        for index in range(number):\n            service = service_ls[index]\n            index += 1\n            detail_ls.append(DetailInstallHistory(\n                service=service,\n                main_install_history=main_obj,\n                install_detail_args={\n                    \"name\": \"t_name\",\n                    \"install_args\": [{\n                        \"key\": key,\n                        \"name\": \"安装目录\",\n                        \"default\": \"/data/t_name\",\n                        \"dir_key\": \"{data_path}\",\n                        \"check_msg\": \"success\",\n                        \"check_flag\": True\n                    } for key in (\"base_dir\", \"data_dir\", \"log_dir\")]\n                }))\n        DetailInstallHistory.objects.bulk_create(detail_ls)\n        detail_obj_ls = DetailInstallHistory.objects.filter(\n            main_install_history=main_obj)\n        return main_obj, detail_obj_ls\n\n    def destroy_install_history(self):\n        \"\"\" 销毁安装历史记录 \"\"\"\n        DetailInstallHistory.objects.filter(\n            main_install_history__operation_uuid__startswith=self.UUID_START).delete()\n        MainInstallHistory.objects.filter(\n            operation_uuid__startswith=self.UUID_START).delete()\n        self.destroy_services()\n"
  },
  {
    "path": "omp_server/tests/test_app_store/__init__.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: __init__.py\n# Author:Times.niu@yunzhihui.com\n# Create time: 2021/10/13 5:12 下午\n"
  },
  {
    "path": "omp_server/tests/test_app_store/install_data_source.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: install_data_source\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-25 15:47\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装过程中的数据\n\"\"\"\n\nimport time\nimport json\nimport random\nimport string\n\nfrom db_models.models import (\n    Host,\n    ProductHub,\n    ApplicationHub,\n    UploadPackageHistory\n)\n\n\ndef create_host(ip=\"127.0.0.1\"):\n    \"\"\"\n    创建主机对象\n    :param ip: ip地址\n    :return:\n    \"\"\"\n    host_dic = {\n        \"instance_name\": f\"host-{ip}\",\n        \"ip\": ip,\n        \"port\": 22,\n        \"username\": \"root\",\n        \"password\": \"fake_password\",\n        \"data_folder\": \"/test\",\n        \"operate_system\": \"CentOS\",\n        \"host_name\": f\"hostname-{ip}\",\n        \"agent_dir\": \"/test\"\n    }\n    host_obj = Host(**host_dic)\n    host_obj.save()\n    return host_obj\n\n\ndef create_product(pro_name=\"test\", pro_version=\"1.0.0\"):\n    \"\"\"\n    创建产品\n    :param pro_name: 产品名称\n    :param pro_version: 产品版本\n    :return:\n    \"\"\"\n    _operation_uuid = str(int(time.time()))\n    # 创建产品安装包数据\n    test_pro_package = {\n        \"operation_uuid\": _operation_uuid,\n        \"operation_user\": \"admin\",\n        \"package_name\": f\"{pro_name}-{pro_version}-20211111-install.tar.gz\",\n        \"package_md5\": ''.join(\n            random.sample(string.ascii_letters + string.digits, 32)),\n        \"package_path\": \"verified\",\n        \"package_status\": 3,\n        \"error_msg\": None,\n        \"package_parent_id\": None,\n        \"is_deleted\": 0\n    }\n    package_obj = UploadPackageHistory(**test_pro_package)\n    package_obj.save()\n\n    # 创建产品数据\n    test_pro_dic = {\n        'is_release': True,\n        'pro_name': pro_name,\n        'pro_version': pro_version,\n        'pro_description': pro_name,\n        'pro_dependence': None,\n        'pro_services': json.dumps([\n            {\"name\": \"ser1\", \"version\": pro_version},\n            {\"name\": \"ser2\", \"version\": pro_version},\n        ]),\n        'pro_logo': None,\n        'extend_fields': {},\n        'pro_package_id': package_obj.id\n    }\n    test_product_obj = ProductHub(**test_pro_dic)\n    test_product_obj.save()\n\n    create_service(pro_obj=test_product_obj)\n\n\ndef create_service_package(pro_obj, ser_name, ser_version):\n    \"\"\"\n    创建服务包\n    :param pro_obj:\n    :param ser_name:\n    :param ser_version:\n    :return:\n    \"\"\"\n    ser_pack_dic = {\n        \"operation_uuid\": pro_obj.pro_package.operation_uuid,\n        \"operation_user\": \"admin\",\n        \"package_name\": f\"{ser_name}-{ser_version}-2021111-ae8557f.tar.gz\",\n        \"package_md5\": ''.join(\n            random.sample(string.ascii_letters + string.digits, 32)),\n        \"package_path\": f\"verified/{pro_obj.pro_name}-{pro_obj.pro_version}\",\n        \"package_parent_id\": pro_obj.pro_package.id,\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    }\n    ser_pack_obj = UploadPackageHistory(**ser_pack_dic)\n    ser_pack_obj.save()\n    return ser_pack_obj\n\n\ndef create_service(pro_obj):\n    \"\"\"\n    创建服务\n    :param pro_obj:\n    :return:\n    \"\"\"\n    ser_lst = json.loads(pro_obj.pro_services)\n    for item in ser_lst:\n        ser_name = item[\"name\"]\n        ser_version = item[\"version\"]\n        ser_pack_obj = create_service_package(\n            pro_obj=pro_obj, ser_name=ser_name, ser_version=ser_version\n        )\n        ser_dic = {\n            \"is_release\": True,\n            \"app_type\": 1,\n            \"app_name\": ser_name,\n            \"app_version\": ser_version,\n            \"app_description\": ser_name,\n            \"app_port\": json.dumps([\n                {\n                    \"name\": \"服务端口\",\n                    \"protocol\": \"TCP\",\n                    \"key\": \"service_port\",\n                    \"default\": \"8080\"\n                }\n            ]),\n            \"app_dependence\": json.dumps([\n                {\n                    \"name\": \"kafka\",\n                    \"version\": \"2.2.2\"\n                }\n            ]),\n            \"app_install_args\": json.dumps([\n                {\n                    \"name\": \"安装目录\",\n                    \"key\": \"base_dir\",\n                    \"default\": \"{data_path}/app/%s\" % ser_name\n                },\n                {\n                    \"name\": \"日志目录\",\n                    \"key\": \"log_dir\",\n                    \"default\": \"{data_path}/logs/%s\" % ser_name\n                },\n                {\n                    \"name\": \"数据目录\",\n                    \"key\": \"data_dir\",\n                    \"default\": \"{data_path}/appData/%s\" % ser_name\n                },\n                {\n                    \"name\": \"启动内存\",\n                    \"key\": \"memory\",\n                    \"default\": \"1g\"\n                },\n                {\n                    \"name\": \"安装用户\",\n                    \"key\": \"run_user\",\n                    \"default\": \"commonuser\"\n                }\n            ]),\n            \"app_controllers\": json.dumps({\n                \"start\": f\"./bin/{ser_name} start\",\n                \"stop\": f\"./bin/{ser_name} stop\",\n                \"restart\": f\"./bin/{ser_name} restart\",\n                \"reload\": \"\",\n                \"install\": \"./scripts/install.py\",\n                \"init\": \"./scripts/init.py\"\n            }),\n            \"app_logo\": None,\n            \"extend_fields\": {\n                \"level\": \"0\",\n                \"deploy\": \"\",\n                \"affinity\": \"\",\n                \"resources\": \"\",\n                \"auto_launch\": \"True\",\n                \"post_action\": \"./scripts/bash/registerService.sh\"\n            },\n            \"is_base_env\": False,\n            \"app_monitor\": {\n                \"type\": \"JavaSpringBoot\",\n                \"metric_port\": \"{service_port}\",\n                \"process_name\": \"\"\n            },\n            \"product\": pro_obj,\n            \"app_package_id\": ser_pack_obj.id\n        }\n        ser_obj = ApplicationHub(**ser_dic)\n        ser_obj.save()\n"
  },
  {
    "path": "omp_server/tests/test_app_store/make_install_fake_data.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: make_fake_data_for_testPro\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-17 14:53\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport os\nimport json\n\nfrom db_models.models import (\n    Host,\n    ProductHub,\n    ApplicationHub,\n    UploadPackageHistory\n)\nfrom omp_server.settings import PROJECT_DIR\n\n# ######################### 测试应用A ################################\n# 创建testPro产品安装包数据\ntestPro_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"testPro-5.3.0-ompopen-20211111-install.tar.gz\",\n    \"package_md5\": \"ca1601ceb5c0682a565120e0d74376f9\",\n    \"package_path\": \"verified\",\n    \"package_status\": 3,\n    \"error_msg\": None,\n    \"package_parent_id\": None,\n    \"is_deleted\": 0\n}\n\n# 创建testPro产品安装包对象\ntestPro_package_obj = UploadPackageHistory(**testPro_package)\ntestPro_package_obj.save()\n\n# 创建testPro下的服务安装包数据\ntestPro_services_packages = [\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProApi-2.3.0-20211019113204-ae8557f.tar.gz\",\n        \"package_md5\": \"b2efb6e605d797e29bb45fc1d7ea376d\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProSso-2.3.0-20211019113204-ae8557f.tar.tar.gz\",\n        \"package_md5\": \"4ec22b75cb963573ef75a80289db32ee\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProDubboRpc-2.3.0-20211019113204-ae8557f.tar.gz\",\n        \"package_md5\": \"bf2aeb1fa188303499a0ec32cc1763b6\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProAdmin-2.3.0-20211019113204-ae8557f.tar.gz\",\n        \"package_md5\": \"49844baddab9c8e610f784c10a500612\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProZabbixApi-2.3.0-20211019113204-ae8557f.tar.gz\",\n        \"package_md5\": \"8404acc041ae783c3798fda1b19321b9\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProWeb-2.3.0-20211018115934-5aee074.tar.gz\",\n        \"package_md5\": \"d924cdc23c7b21eef6d33b9c2572c354\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProAdminWeb-2.3.0-20211009023221-748f711.tar.gz\",\n        \"package_md5\": \"34f6eb46b862701c54e2a8f178afc470\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"gatewayServer-3.1.0-20211016154930-69a1a6c.tar.gz\",\n        \"package_md5\": \"f8fa68eb4acf31294975f817d4159938\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"gatewayServerApi-3.1.0-20211016154930-69a1a6c.tar.gz\",\n        \"package_md5\": \"9e124dd9c0eb2b0ad15e6b87f5c2c894\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"portalWeb-5.3.0-20211017051255-2e0af78.tar.gz\",\n        \"package_md5\": \"ee1a1763e0576e2e496073b4e66fa1c4\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"portalServer-5.3.0-20211014164210-a9f3fba.tar.gz\",\n        \"package_md5\": \"643102cc5cd6373266f9fde42c33f033\",\n        \"package_path\": \"verified/testPro-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    }\n]\n\n# 创建testPro下的服务安装包对象\nfor item in testPro_services_packages:\n    item[\"package_parent_id\"] = testPro_package_obj.id\nUploadPackageHistory.objects.bulk_create(\n    [UploadPackageHistory(**el) for el in testPro_services_packages]\n)\n\n# 创建 testPro ProductHub 对象数据\ntestPro = {\n    'is_release': True,\n    'pro_name': 'testPro',\n    'pro_version': '5.3.0',\n    'pro_description': '用户中心（Digital Operation User Center，简称 DOUC',\n    'pro_dependence': None,\n    'pro_services': json.dumps([\n        {\"name\": \"testProApi\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProSso\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProDubboRpc\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProAdmin\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProZabbixApi\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProWeb\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProAdminWeb\", \"version\": \"2.3.0\"},\n        {\"name\": \"gatewayServer\", \"version\": \"3.1.0\"},\n        {\"name\": \"gatewayServerApi\", \"version\": \"3.1.0\"},\n        {\"name\": \"portalWeb\", \"version\": \"5.3.0\"},\n        {\"name\": \"portalServer\", \"version\": \"5.3.0\"}\n    ]),\n    'pro_logo': None,\n    'extend_fields': {},\n    'pro_package_id': testPro_package_obj.id\n}\ntestPro_product_obj = ProductHub(**testPro)\ntestPro_product_obj.save()\n\ntestPro_services_app = [\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProApi\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18241\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18241\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            },\n            {\n                \"name\": \"rocketmq\",\n                \"version\": \"5.0\"\n            },\n            {\n                \"name\": \"redis\",\n                \"version\": \"5.0.12\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProApi\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProApi\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProApi\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testPro\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProApi start\",\n            \"stop\": \"./bin/testProApi stop\",\n            \"restart\": \"./bin/testProApi restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"./scripts/init.py\"\n        },\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"./scripts/bash/registerService.sh\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": \"{service_port}\",\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProSso\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18256\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18256\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProSso\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProSso\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProSso\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testPro\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProSso start\",\n            \"stop\": \"./bin/testProSso stop\",\n            \"restart\": \"./bin/testProSso restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 8,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProDubboRpc\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18246\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18247\"\n            },\n            {\n                \"name\": \"test端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"test_port\",\n                \"default\": \"18247\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProDubboRpc\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProDubboRpc\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProDubboRpc\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"2g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testPro\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProDubboRpc start\",\n            \"stop\": \"./bin/testProDubboRpc stop\",\n            \"restart\": \"./bin/testProDubboRpc restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 9,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProAdmin\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18266\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18266\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProAdmin\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProAdmin\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProAdmin\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testPro\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProAdmin start\",\n            \"stop\": \"./bin/testProAdmin stop\",\n            \"restart\": \"./bin/testProAdmin restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 10,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProZabbixApi\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18260\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18260\"\n            },\n            {\n                \"name\": \"rpc端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"rpc_port\",\n                \"default\": \"18261\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProZabbixApi\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProZabbixApi\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProZabbixApi\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testPro\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProZabbixApi start\",\n            \"stop\": \"./bin/testProZabbixApi stop\",\n            \"restart\": \"./bin/testProZabbixApi restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 11,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProWeb\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": None,\n        \"app_dependence\": [\n            {\n                \"name\": \"portalWeb\",\n                \"version\": \"5.3.0\"\n            },\n            {\n                \"name\": \"tengine\",\n                \"version\": \"1.20.1\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProWeb\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"\",\n            \"stop\": \"\",\n            \"restart\": \"\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 12,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"1\",\n            \"deploy\": \"\",\n            \"affinity\": \"portalWeb\",\n            \"resources\": \"\",\n            \"auto_launch\": \"False\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": None\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProAdminWeb\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": None,\n        \"app_dependence\": [\n            {\n                \"name\": \"portalWeb\",\n                \"version\": \"5.3.0\"\n            },\n            {\n                \"name\": \"tengine\",\n                \"version\": \"1.20.1\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProAdminWeb\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"\",\n            \"stop\": \"\",\n            \"restart\": \"\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 13,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"1\",\n            \"deploy\": \"\",\n            \"affinity\": \"portalWeb\",\n            \"resources\": \"\",\n            \"auto_launch\": \"False\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": None\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"gatewayServer\",\n        \"app_version\": \"3.1.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18201\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18202\"\n            },\n            {\n                \"name\": \"devtools端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"devtools_port\",\n                \"default\": \"18203\"\n            },\n            {\n                \"name\": \"sentinel端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"sentinel_port\",\n                \"default\": \"18208\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"tengine\",\n                \"version\": \"1.20.1\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/gatewayServer\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/gatewayServer\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/gatewayServer\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/gatewayServer start\",\n            \"stop\": \"./bin/gatewayServer stop\",\n            \"restart\": \"./bin/gatewayServer restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 14,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"./scripts/bash/registerService.sh\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"gatewayServerApi\",\n        \"app_version\": \"3.1.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18204\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18205\"\n            },\n            {\n                \"name\": \"devtools端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"devtools_port\",\n                \"default\": \"18207\"\n            },\n            {\n                \"name\": \"sentinel端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"sentinel_port\",\n                \"default\": \"18209\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"tengine\",\n                \"version\": \"1.20.1\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/gatewayServerApi\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/gatewayServerApi\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/gatewayServerApi\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/gatewayServerApi start\",\n            \"stop\": \"./bin/gatewayServerApi stop\",\n            \"restart\": \"./bin/gatewayServerApi restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 15,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"portalWeb\",\n        \"app_version\": \"5.3.0\",\n        \"app_description\": None,\n        \"app_port\": None,\n        \"app_dependence\": None,\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/portalWeb\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"\",\n            \"stop\": \"\",\n            \"restart\": \"\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 16,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"False\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": None\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"portalServer\",\n        \"app_version\": \"5.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18206\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18206\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"kafka\",\n                \"version\": \"2.2.2\"\n            },\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/portalServer\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/portalServer\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/portalServer\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            },\n            {\n                \"name\": \"kafka_topic名字\",\n                \"key\": \"kafka_topic\",\n                \"default\": \"cw-logs\"\n            },\n            {\n                \"name\": \"数据库名\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_portal\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/portalServer start\",\n            \"stop\": \"./bin/portalServer stop\",\n            \"restart\": \"./bin/portalServer restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"./scripts/init.py\"\n        },\n        \"app_package_id\": 17,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": {\n                \"service_port\": \"\"\n            },\n            \"process_name\": \"\"\n        }\n    }\n]\n\nfor item in testPro_services_app:\n    item[\"product_id\"] = testPro_product_obj.id\n    item[\"app_package_id\"] = UploadPackageHistory.objects.filter(\n        package_name__startswith=f\"{item['app_name']}-{item['app_version']}\"\n    ).last().id\n    if item[\"app_port\"]:\n        item[\"app_port\"] = json.dumps(item[\"app_port\"])\n    if item[\"app_dependence\"]:\n        item[\"app_dependence\"] = json.dumps(item[\"app_dependence\"])\n    if item[\"app_install_args\"]:\n        item[\"app_install_args\"] = json.dumps(item[\"app_install_args\"])\n    if item[\"app_controllers\"]:\n        item[\"app_controllers\"] = json.dumps(item[\"app_controllers\"])\n\nApplicationHub.objects.bulk_create(\n    [ApplicationHub(**el) for el in testPro_services_app]\n)\n\n# ######################### 测试应用A ################################\n\n# ######################### 测试应用B ################################\n\ntestProB_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"testProB-5.3.0-ompopen-20211111-install.tar.gz\",\n    \"package_md5\": \"ca1601ceb5c0682a565220e0d74376f9\",\n    \"package_path\": \"verified\",\n    \"package_status\": 3,\n    \"error_msg\": None,\n    \"package_parent_id\": None,\n    \"is_deleted\": 0\n}\ntestProB_package_obj = UploadPackageHistory(**testProB_package)\ntestProB_package_obj.save()\n\ntestProB_services_packages = [\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProBServer-2.3.0-20211019113204-ae8557f.tar.gz\",\n        \"package_md5\": \"b2efb6e605d797f29bb45fc1d7ea376d\",\n        \"package_path\": \"verified/testProB-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    },\n    {\n        \"operation_uuid\": \"1636770501006\",\n        \"operation_user\": \"admin\",\n        \"package_name\": \"testProBWeb-2.3.0-20211019113204-ae8557f.tar.tar.gz\",\n        \"package_md5\": \"4ec22b75cb9f3573ef75a80289db32ee\",\n        \"package_path\": \"verified/testProB-5.3.0\",\n        \"package_status\": 0,\n        \"error_msg\": None,\n        \"is_deleted\": 0\n    }\n]\n\nfor item in testProB_services_packages:\n    item[\"package_parent_id\"] = testProB_package_obj.id\nUploadPackageHistory.objects.bulk_create(\n    [UploadPackageHistory(**el) for el in testProB_services_packages]\n)\n\ntestProB = {\n    'is_release': True,\n    'pro_name': 'testProB',\n    'pro_version': '5.3.0',\n    'pro_description': '配置中心',\n    'pro_dependence': json.dumps([\n        {\n            \"name\": \"testPro\",\n            \"version\": \"5.3.0\"\n        }\n    ]),\n    'pro_services': json.dumps([\n        {\"name\": \"testProBServer\", \"version\": \"2.3.0\"},\n        {\"name\": \"testProBWeb\", \"version\": \"2.3.0\"}\n    ]),\n    'pro_logo': None,\n    'extend_fields': {},\n    'pro_package_id': testProB_package_obj.id\n}\ntestProB_product_obj = ProductHub(**testProB)\ntestProB_product_obj.save()\n\ntestProB_services_app = [\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProBServer\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": [\n            {\n                \"name\": \"服务端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"service_port\",\n                \"default\": \"18341\"\n            },\n            {\n                \"name\": \"metric端口\",\n                \"protocol\": \"TCP\",\n                \"key\": \"metrics_port\",\n                \"default\": \"18352\"\n            }\n        ],\n        \"app_dependence\": [\n            {\n                \"name\": \"nacos\",\n                \"version\": \"2.0.3\"\n            },\n            {\n                \"name\": \"jdk\",\n                \"version\": \"1.8.0\"\n            },\n            {\n                \"name\": \"mysql\",\n                \"version\": \"5.7.34\"\n            },\n            {\n                \"name\": \"arangodb\",\n                \"version\": \"3.6.5\"\n            },\n            {\n                \"name\": \"redis\",\n                \"version\": \"5.0.12\"\n            }\n        ],\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProBServer\"\n            },\n            {\n                \"name\": \"日志目录\",\n                \"key\": \"log_dir\",\n                \"default\": \"{data_path}/logs/testProBServer\"\n            },\n            {\n                \"name\": \"数据目录\",\n                \"key\": \"data_dir\",\n                \"default\": \"{data_path}/appData/testProBServer\"\n            },\n            {\n                \"name\": \"启动内存\",\n                \"key\": \"memory\",\n                \"default\": \"1g\"\n            },\n            {\n                \"name\": \"数据库\",\n                \"key\": \"dbname\",\n                \"default\": \"cw_testProB\"\n            },\n            {\n                \"name\": \"安装用户\",\n                \"key\": \"run_user\",\n                \"default\": \"commonuser\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"./bin/testProBServer start\",\n            \"stop\": \"./bin/testProBServer stop\",\n            \"restart\": \"./bin/testProBServer restart\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"./scripts/init.py\"\n        },\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"True\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": {\n            \"type\": \"JavaSpringBoot\",\n            \"metric_port\": \"{service_port}\",\n            \"process_name\": \"\"\n        }\n    },\n    {\n        \"is_release\": True,\n        \"app_type\": 1,\n        \"app_name\": \"testProBWeb\",\n        \"app_version\": \"2.3.0\",\n        \"app_description\": None,\n        \"app_port\": None,\n        \"app_dependence\": None,\n        \"app_install_args\": [\n            {\n                \"name\": \"安装目录\",\n                \"key\": \"base_dir\",\n                \"default\": \"{data_path}/app/testProBWeb\"\n            }\n        ],\n        \"app_controllers\": {\n            \"start\": \"\",\n            \"stop\": \"\",\n            \"restart\": \"\",\n            \"reload\": \"\",\n            \"install\": \"./scripts/install.py\",\n            \"init\": \"\"\n        },\n        \"app_package_id\": 16,\n        \"product_id\": 1,\n        \"app_logo\": None,\n        \"extend_fields\": {\n            \"level\": \"0\",\n            \"deploy\": \"\",\n            \"affinity\": \"\",\n            \"resources\": \"\",\n            \"auto_launch\": \"False\",\n            \"post_action\": \"\"\n        },\n        \"is_base_env\": False,\n        \"app_monitor\": None\n    },\n]\n\nfor item in testProB_services_app:\n    item[\"product_id\"] = testProB_product_obj.id\n    item[\"app_package_id\"] = UploadPackageHistory.objects.filter(\n        package_name__startswith=f\"{item['app_name']}-{item['app_version']}\"\n    ).last().id\n    if item[\"app_port\"]:\n        item[\"app_port\"] = json.dumps(item[\"app_port\"])\n    if item[\"app_dependence\"]:\n        item[\"app_dependence\"] = json.dumps(item[\"app_dependence\"])\n    if item[\"app_install_args\"]:\n        item[\"app_install_args\"] = json.dumps(item[\"app_install_args\"])\n    if item[\"app_controllers\"]:\n        item[\"app_controllers\"] = json.dumps(item[\"app_controllers\"])\n\nApplicationHub.objects.bulk_create(\n    [ApplicationHub(**el) for el in testProB_services_app]\n)\n\n# ######################### 测试应用B ################################\n\n# 公共组件部分\njdk_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"jdk-1.8.0-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2efb6e604d797e29bb45fc1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\njdk_package_obj = UploadPackageHistory(**jdk_package)\njdk_package_obj.save()\njdk_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"jdk\",\n    \"app_version\": \"1.8.0\",\n    \"app_description\": \"jdk\",\n    \"app_port\": None,\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/jdk\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"\",\n        \"stop\": \"\",\n        \"restart\": \"\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\",\n        \"is_base_env\": True\n    },\n    \"is_base_env\": True,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": jdk_package_obj.id\n}\nApplicationHub.objects.create(**jdk_app)\n\nmysql_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"mysql-5.7.34-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2efb6e605d797e29bb45fc1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nmysql_package_obj = UploadPackageHistory(**mysql_package)\nmysql_package_obj.save()\nmysql_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"mysql\",\n    \"app_version\": \"5.7.34\",\n    \"app_description\": \"mysql数据库\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18103\"\n        }\n    ]),\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/mysql\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/mysql\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/mysql\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/mysql start\",\n        \"stop\": \"./scripts/mysql stop\",\n        \"restart\": \"./scripts/mysql restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": mysql_package_obj.id\n}\nApplicationHub.objects.create(**mysql_app)\n\nnacos_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"nacos-2.0.3-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2efb6e605d697e29bb45fc1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nnacos_package_obj = UploadPackageHistory(**nacos_package)\nnacos_package_obj.save()\nnacos_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"nacos\",\n    \"app_version\": \"2.0.3\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18117\"\n        }\n    ]),\n    \"app_dependence\": json.dumps([{\n        \"name\": \"jdk\",\n        \"version\": \"1.8.0\"\n    }]),\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/nacos\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/nacos\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/nacos\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/nacos start\",\n        \"stop\": \"./scripts/nacos stop\",\n        \"restart\": \"./scripts/nacos restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": nacos_package_obj.id\n}\nApplicationHub.objects.create(**nacos_app)\n\nkafka_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"kafka-2.2.2-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e605d697e29bb45fc1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nkafka_package_obj = UploadPackageHistory(**kafka_package)\nkafka_package_obj.save()\nkafka_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"kafka\",\n    \"app_version\": \"2.2.2\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18217\"\n        }\n    ]),\n    \"app_dependence\": json.dumps([\n        {\n            \"name\": \"jdk\",\n            \"version\": \"1.8.0\"\n        },\n        {\n            \"name\": \"zookeeper\",\n            \"version\": \"1.2.2\"\n        }\n    ]),\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/kafka\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/kafka\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/kafka\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/kafka start\",\n        \"stop\": \"./scripts/kafka stop\",\n        \"restart\": \"./scripts/kafka restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": kafka_package_obj.id\n}\nApplicationHub.objects.create(**kafka_app)\n\nzookeeper_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"zookeeper-1.2.2-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e605d697f29bb45fc1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nzookeeper_package_obj = UploadPackageHistory(**zookeeper_package)\nzookeeper_package_obj.save()\nzookeeper_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"zookeeper\",\n    \"app_version\": \"1.2.2\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18227\"\n        }\n    ]),\n    \"app_dependence\": json.dumps([{\n        \"name\": \"jdk\",\n        \"version\": \"1.8.0\"\n    }]),\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/zookeeper\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/zookeeper\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/zookeeper\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/zookeeper start\",\n        \"stop\": \"./scripts/zookeeper stop\",\n        \"restart\": \"./scripts/zookeeper restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": zookeeper_package_obj.id\n}\nApplicationHub.objects.create(**zookeeper_app)\n\nredis_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"redis-5.0.12-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e605d697f29bb45fd1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nredis_package_obj = UploadPackageHistory(**redis_package)\nredis_package_obj.save()\nredis_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"redis\",\n    \"app_version\": \"5.0.12\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18137\"\n        }\n    ]),\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/redis\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/redis\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/redis\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/redis start\",\n        \"stop\": \"./scripts/redis stop\",\n        \"restart\": \"./scripts/redis restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": redis_package_obj.id\n}\nApplicationHub.objects.create(**redis_app)\n\nrocketmq_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"rocketmq-5.0-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e605d697f29bb45fd1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\nrocketmq_package_obj = UploadPackageHistory(**rocketmq_package)\nrocketmq_package_obj.save()\nrocketmq_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"rocketmq\",\n    \"app_version\": \"5.0\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18147\"\n        }\n    ]),\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/rocketmq\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/rocketmq\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/rocketmq\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/rocketmq start\",\n        \"stop\": \"./scripts/rocketmq stop\",\n        \"restart\": \"./scripts/rocketmq restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": rocketmq_package_obj.id\n}\nApplicationHub.objects.create(**rocketmq_app)\n\ntengine_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"tengine-1.20.1-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e60dd697f29bb45fd1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\ntengine_package_obj = UploadPackageHistory(**tengine_package)\ntengine_package_obj.save()\ntengine_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"tengine\",\n    \"app_version\": \"1.20.1\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"18080\"\n        }\n    ]),\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/tengine\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/tengine\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/tengine\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/tengine start\",\n        \"stop\": \"./scripts/tengine stop\",\n        \"restart\": \"./scripts/tengine restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": tengine_package_obj.id\n}\nApplicationHub.objects.create(**tengine_app)\n\narangodb_package = {\n    \"operation_uuid\": \"1636770501006\",\n    \"operation_user\": \"admin\",\n    \"package_name\": \"arangodb-5.6.5-20211019113204-ae8557f.tar.gz\",\n    \"package_md5\": \"b2eb6e60dd697f29bb45fd1d7ea376d\",\n    \"package_path\": \"verified\",\n    \"package_status\": 0,\n    \"error_msg\": None,\n    \"is_deleted\": 0\n}\narangodb_package_obj = UploadPackageHistory(**arangodb_package)\narangodb_package_obj.save()\narangodb_app = {\n    \"is_release\": True,\n    \"app_type\": 0,\n    \"app_name\": \"arangodb\",\n    \"app_version\": \"3.6.5\",\n    \"app_description\": \"\",\n    \"app_port\": json.dumps([\n        {\n            \"name\": \"服务端口\",\n            \"protocol\": \"TCP\",\n            \"key\": \"service_port\",\n            \"default\": \"12345\"\n        }\n    ]),\n    \"app_dependence\": None,\n    \"app_install_args\": json.dumps([\n        {\n            \"name\": \"安装目录\",\n            \"key\": \"base_dir\",\n            \"default\": \"{data_path}/app/arangodb\"\n        },\n        {\n            \"name\": \"日志目录\",\n            \"key\": \"log_dir\",\n            \"default\": \"{data_path}/logs/arangodb\"\n        },\n        {\n            \"name\": \"数据目录\",\n            \"key\": \"data_dir\",\n            \"default\": \"{data_path}/appData/arangodb\"\n        },\n        {\n            \"name\": \"安装用户\",\n            \"key\": \"run_user\",\n            \"default\": \"commonuser\"\n        }\n    ]),\n    \"app_controllers\": json.dumps({\n        \"start\": \"./scripts/arangodb start\",\n        \"stop\": \"./scripts/arangodb stop\",\n        \"restart\": \"./scripts/arangodb restart\",\n        \"reload\": \"\",\n        \"install\": \"./scripts/install.py\",\n        \"init\": \"./scripts/init.py\"\n    }),\n    \"app_logo\": None,\n    \"extend_fields\": {\n        \"deploy\": \"\",\n        \"affinity\": \"\",\n        \"resources\": \"\",\n        \"auto_launch\": \"True\",\n        \"post_action\": \"\"\n    },\n    \"is_base_env\": False,\n    \"app_monitor\": {\n        \"type\": \"\",\n        \"metric_port\": \"{service_port}\",\n        \"process_name\": \"\"\n    },\n    \"app_package_id\": arangodb_package_obj.id\n}\nApplicationHub.objects.create(**arangodb_app)\n\n# 全部安装包的创建操作\npackage_lst = UploadPackageHistory.objects.values(\n    \"package_path\", \"package_name\")\nfor item in package_lst:\n    _path = os.path.join(\n        PROJECT_DIR, \"package_hub\",\n        item[\"package_path\"], item[\"package_name\"]\n    )\n    if not os.path.exists(os.path.dirname(_path)):\n        os.system(f\"mkdir {os.path.dirname(_path)}\")\n    a = os.system(f\"touch {_path}\")\n\nhosts = [\n    {\n        \"is_deleted\": 0,\n        \"instance_name\": \"127.0.0.1\",\n        \"ip\": \"127.0.0.1\",\n        \"port\": 36000,\n        \"username\": \"root\",\n        \"password\": \"pMMkpa5jqlJG4A-ROeMlsEHj8YvMTRpMYnNFD2YS7MA\",\n        \"data_folder\": \"/test\",\n        \"service_num\": 0,\n        \"alert_num\": 0,\n        \"operate_system\": \"CentOS\",\n        \"memory\": 32,\n        \"cpu\": 8,\n        \"disk\": {\"/\": 50, \"/data\": 47},\n        \"host_agent\": \"0\",\n        \"monitor_agent\": \"0\",\n        \"is_maintenance\": 0,\n        \"env_id\": 1,\n        \"host_agent_error\": None,\n        \"host_name\": \"localhost\",\n        \"monitor_agent_error\": None,\n        \"agent_dir\": \"/test\"\n    },\n    {\n        \"is_deleted\": 0,\n        \"instance_name\": \"127.0.0.2\",\n        \"ip\": \"127.0.0.2\",\n        \"port\": 36000,\n        \"username\": \"root\",\n        \"password\": \"pMMkpa5jqlJG4A-ROeMlsEHj8YvMTRpMYnNFD2YS7MA\",\n        \"data_folder\": \"/test\",\n        \"service_num\": 0,\n        \"alert_num\": 0,\n        \"operate_system\": \"CentOS\",\n        \"memory\": 32,\n        \"cpu\": 8,\n        \"disk\": {\"/\": 50, \"/data\": 47},\n        \"host_agent\": \"0\",\n        \"monitor_agent\": \"0\",\n        \"is_maintenance\": 0,\n        \"env_id\": 1,\n        \"host_agent_error\": None,\n        \"host_name\": \"localhost\",\n        \"monitor_agent_error\": None,\n        \"agent_dir\": \"/test\"\n    },\n    {\n        \"is_deleted\": 0,\n        \"instance_name\": \"127.0.0.3\",\n        \"ip\": \"127.0.0.3\",\n        \"port\": 36000,\n        \"username\": \"root\",\n        \"password\": \"pMMkpa5jqlJG4A-ROeMlsEHj8YvMTRpMYnNFD2YS7MA\",\n        \"data_folder\": \"/test\",\n        \"service_num\": 0,\n        \"alert_num\": 0,\n        \"operate_system\": \"CentOS\",\n        \"memory\": 32,\n        \"cpu\": 8,\n        \"disk\": {\"/\": 50, \"/data\": 47},\n        \"host_agent\": \"0\",\n        \"monitor_agent\": \"0\",\n        \"is_maintenance\": 0,\n        \"env_id\": 1,\n        \"host_agent_error\": None,\n        \"host_name\": \"localhost\",\n        \"monitor_agent_error\": None,\n        \"agent_dir\": \"/test\"\n    }\n]\nhost_lst = list()\nfor item in hosts:\n    host_lst.append(Host(**item))\nHost.objects.bulk_create(host_lst)\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_app_check.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/test_app_store/test_app_store.py",
    "content": "import random\nfrom django.db.models import Max\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import (\n    ApplicationResourceMixin, ProductResourceMixin\n)\nfrom db_models.models import (\n    Labels, ApplicationHub\n)\n\n\nclass LabelListTest(AutoLoginTest, ApplicationResourceMixin):\n    \"\"\" 标签列表测试类 \"\"\"\n\n    def setUp(self):\n        super(LabelListTest, self).setUp()\n        self.label_list_url = reverse(\"labels-list\")\n        self.get_application()\n\n    def tearDown(self):\n        super(LabelListTest, self).tearDown()\n        self.destroy_application()\n\n    def test_label_list(self):\n        \"\"\" 测试标签列表 \"\"\"\n\n        # 查询标签列表 -> 返回所有标签列表数据\n        resp = self.get(self.label_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        # 查询指定类型标签 -> 返回指定类型标签列表数据\n        choice = random.choice(Labels.LABELS_CHOICES)\n        resp = self.get(self.label_list_url, {\n            \"label_type\": choice[0]\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            set(resp.get(\"data\")),\n            set(Labels.objects.filter(\n                label_type=choice[0],\n                applicationhub__app_type=ApplicationHub.APP_TYPE_COMPONENT\n            ).order_by(\"id\").values_list(\"label_name\", flat=True).distinct())\n        )\n\n\nclass ComponentListTest(AutoLoginTest, ApplicationResourceMixin):\n    \"\"\" 基础组件列表测试类 \"\"\"\n\n    def setUp(self):\n        super(ComponentListTest, self).setUp()\n        self.component_list_url = reverse(\"components-list\")\n        self.app_obj_ls = self.get_application()\n\n    def tearDown(self):\n        super(ComponentListTest, self).tearDown()\n        self.destroy_application()\n\n    def test_component_list_filter(self):\n        \"\"\" 测试基础组件列表过滤 \"\"\"\n\n        # 查询组件列表 -> 按名称合并展示所有已发布组件\n        resp = self.get(self.component_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        comp_set = set(self.app_obj_ls.filter(\n            is_release=True,\n            app_type=ApplicationHub.APP_TYPE_COMPONENT\n        ).values_list(\"app_name\"))\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(comp_set))\n\n        # 组件名过滤 -> 展示组件名模糊匹配项\n        resp = self.get(self.component_list_url, {\n            \"app_name\": \"app_1\"\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        comp_set = set(self.app_obj_ls.filter(\n            is_release=True,\n            app_type=ApplicationHub.APP_TYPE_COMPONENT,\n            app_name__contains=\"app_1\",\n        ).values_list(\"app_name\"))\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(comp_set))\n\n        # 标签类型过滤 -> 展示标签名匹配项\n        label_name = random.choice(Labels.LABELS_CHOICES)[1]\n        resp = self.get(self.component_list_url, {\n            \"type\": label_name\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        comp_set = set(self.app_obj_ls.filter(\n            is_release=True,\n            app_type=ApplicationHub.APP_TYPE_COMPONENT,\n            app_labels__label_name=label_name,\n        ).values_list(\"app_name\"))\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(comp_set))\n\n    def test_component_list_order(self):\n        \"\"\" 测试基础组建列表排序 \"\"\"\n\n        # 查询组件列表 -> 各组件返回最新数据，按照创建时间排序\n        resp = self.get(self.component_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        obj_ls = list(self.app_obj_ls.filter(\n            is_release=True,\n            app_type=ApplicationHub.APP_TYPE_COMPONENT\n        ).values(\"app_name\").annotate(c=Max(\"created\")).order_by(\n            \"-created\").values_list(\"app_name\", flat=True))\n        target_ls = []\n        for obj in obj_ls:\n            if obj not in target_ls:\n                target_ls.append(obj)\n            if len(target_ls) == 10:\n                break\n        result_ls = list(map(\n            lambda x: x.get(\"app_name\"),\n            resp.get(\"data\").get(\"results\")))\n        self.assertEqual(result_ls, target_ls)\n\n\nclass ServiceListTest(AutoLoginTest, ProductResourceMixin):\n    \"\"\" 产品列表测试类 \"\"\"\n\n    def setUp(self):\n        super(ServiceListTest, self).setUp()\n        self.service_list_url = reverse(\"appServices-list\")\n        self.service_obj_ls = self.get_product()\n\n    def tearDown(self):\n        super(ServiceListTest, self).tearDown()\n        self.destroy_product()\n\n    def test_service_list_filter(self):\n        \"\"\" 测试应用服务列表过滤 \"\"\"\n\n        # 查询服务列表 -> 按名称合并展示所有已发布服务\n        resp = self.get(self.service_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        service_set = set(self.service_obj_ls.filter(\n            is_release=True\n        ).values_list(\"pro_name\"))\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(service_set))\n\n        # 服务名过滤 -> 展示服务名模糊匹配项\n        resp = self.get(self.service_list_url, {\n            \"pro_name\": \"pro_1\"\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        service_set = set(self.service_obj_ls.filter(\n            is_release=True,\n            pro_name__contains=\"pro_1\",\n        ).values_list(\"pro_name\"))\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(service_set))\n\n        self.destroy_product()\n\n    def test_service_list_order(self):\n        \"\"\" 测试应用服务列表排序 \"\"\"\n\n        # 查询应用服务列表 -> 各组件返回最新数据，按照创建时间排序\n        resp = self.get(self.service_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        obj_ls = list(self.service_obj_ls.filter(\n            is_release=True,\n        ).values(\"pro_name\").annotate(c=Max(\"created\")).order_by(\n            \"-created\").values_list(\"pro_name\", flat=True))\n        target_ls = []\n        for obj in obj_ls:\n            if obj not in target_ls:\n                target_ls.append(obj)\n        result_ls = list(map(\n            lambda x: x.get(\"pro_name\"),\n            resp.get(\"data\").get(\"results\")))\n        self.assertEqual(result_ls, target_ls[:10])\n\n\nclass AppStoreDetailTest(AutoLoginTest, ApplicationResourceMixin, ProductResourceMixin):\n    \"\"\" 应用商店组件和产品测试类 \"\"\"\n\n    def setUp(self):\n        super(AppStoreDetailTest, self).setUp()\n        self.application_detail_url = reverse(\"componentDetail-list\")\n        self.product_detail_url = reverse(\"appServiceDetail-list\")\n\n    def test_application_detail(self):\n        \"\"\" 测试应用详情 \"\"\"\n        app_ls = self.get_application()\n\n        # 查询应用表 -> 返回所指定应用名符合的数据\n        resp = self.get(self.application_detail_url, {\n            \"app_name\": random.choice(app_ls).app_name\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get('data'))\n\n        self.destroy_application()\n\n    def test_product_detail(self):\n        \"\"\"\n        测试产品详情\n        \"\"\"\n        pro_ls = self.get_product()\n\n        resp = self.get(self.product_detail_url, {\n            \"pro_name\": random.choice(pro_ls).pro_name\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get('data'))\n\n        self.destroy_product()\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_app_store_install.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_app_store_install\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-23 09:05\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n组件、应用安装入口使用的解析方法测试\n\"\"\"\n\nimport json\nimport datetime\nimport os\nfrom unittest import mock\n\nfrom rest_framework.reverse import reverse\nfrom rest_framework.test import APIClient\nfrom django.test import TransactionTestCase\n\nfrom db_models.models import (\n    ApplicationHub, ProductHub, UploadPackageHistory, Host,\n    Env, UserProfile\n)\nfrom omp_server.settings import PROJECT_DIR\nfrom app_store.tasks import install_service\nfrom utils.plugin.salt_client import SaltClient\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import (\n    ApplicationResourceMixin,\n    ProductResourceMixin\n)\n\n\nclass ComponentEntranceTest(AutoLoginTest, ApplicationResourceMixin):\n    def setUp(self):\n        super(ComponentEntranceTest, self).setUp()\n        self.componentEntrance_url = reverse(\n            \"componentEntrance-list\")\n\n    def test_null_ret_success(self):\n        res = self.get(self.componentEntrance_url).json()\n        self.assertDictEqual(\n            res,\n            {'code': 0, 'message': 'success', 'data': []}\n        )\n\n    def test_normal_res(self):\n        self.get_application()\n        res = self.get(self.componentEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        self.assertEqual(len(res.get(\"data\", [])) != 0, True)\n        self.destroy_application()\n        self.destroy_labels()\n\n    def make_unique_app_data(self, dic):  # NOQA\n        obj_dic = {\n            \"is_release\": 1,\n            \"app_type\": 0,\n            \"app_logo\": \"app log svg data...\",\n            \"app_description\": \"应用描述，省略一万字...\",\n            \"app_port\": json.dumps(\n                [{\"default\": 18080, \"key\": \"http_port\", \"name\": \"服务端口\"}]\n            ),\n            \"app_install_args\": json.dumps(\n                [\n                    {\"name\": \"安装目录\", \"key\": \"install_dir\",\n                     \"default\": \"{data_path}/abc\"},\n                    {\"name\": \"数据目录\", \"key\": \"data_dir\",\n                     \"default\": \"{data_path}/data/test_app1\"}\n                ]\n            )\n        }\n        obj_dic.update(dic)\n        ApplicationHub(**obj_dic).save()\n\n    def destroy_app(self):  # NOQA\n        ApplicationHub.objects.filter(app_name__icontains=\"test_app\").delete()\n\n    def make_base_app_1(self):  # NOQA\n        app_base_1 = {\n            \"app_name\": \"test_app1\",\n            \"app_version\": \"8u211\",\n            \"extend_fields\": {\"base_env\": True}\n        }\n        self.make_unique_app_data(app_base_1)\n\n    def make_base_app_2(self):  # NOQA\n        app_base_2 = {\n            \"app_name\": \"test_app2\",\n            \"app_version\": \"1.0\",\n            \"app_dependence\": json.dumps(\n                [{\"name\": \"test_app1\", \"version\": \"8u211\"}]\n            ),\n            \"extend_fields\": {\n                \"deploy\": {\n                    \"single\": [{\"key\": \"single\", \"name\": \"单实例\"}],\n                    \"complex\": [{\"key\": \"master_slave\", \"name\": \"主从模式\",\n                                 \"nodes\": {\"step\": 1, \"start\": 2}}]\n                },\n            }\n        }\n        self.make_unique_app_data(app_base_2)\n\n    def make_base_app_3(self):  # NOQA\n        app_base_3 = {\n            \"app_name\": \"test_app3\",\n            \"app_version\": \"1.0\",\n            \"app_dependence\": json.dumps(\n                [\n                    {\"name\": \"test_app2\", \"version\": \"1.0\"},\n                    {\"name\": \"test_app4\", \"version\": \"1.0\"}\n                ]\n            ),\n            \"extend_fields\": {}\n        }\n        self.make_unique_app_data(app_base_3)\n\n    def make_unrelease_app(self):  # NOQA\n        app_base_4 = {\n            \"app_name\": \"test_app4\",\n            \"app_version\": \"1.0\",\n            \"is_release\": \"0\",\n            \"app_dependence\": json.dumps(\n                [{\"name\": \"test_app1\", \"version\": \"1.0\"}]\n            ),\n            \"extend_fields\": {}\n        }\n        self.make_unique_app_data(app_base_4)\n\n    def test_dependence_one_level(self):\n        \"\"\" 一层依赖信息单元测试 \"\"\"\n        self.make_base_app_1()\n        self.make_base_app_2()\n        res = self.get(self.componentEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        self.assertEqual(len(res.get(\"data\")), 2)\n        for item in res.get(\"data\"):\n            if item.get(\"app_name\") == \"test_app2\":\n                self.assertEqual(len(item.get(\"app_dependence\")), 1)\n        self.destroy_app()\n\n    def test_dependence_two_level(self):\n        \"\"\" 二层依赖信息单元测试 \"\"\"\n        self.make_base_app_1()\n        self.make_base_app_2()\n        self.make_base_app_3()\n        res = self.get(self.componentEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        self.assertEqual(len(res.get(\"data\")), 3)\n        for item in res.get(\"data\"):\n            if item.get(\"app_name\") == \"test_app3\":\n                self.assertEqual(len(item.get(\"app_dependence\")), 3)\n        self.destroy_app()\n\n    def test_no_release_app_dependence(self):\n        \"\"\" 测试缺少服务依赖信息场景 \"\"\"\n        self.make_base_app_1()\n        self.make_base_app_2()\n        self.make_base_app_3()\n        self.make_unrelease_app()\n        res = self.get(self.componentEntrance_url).json()\n        process_continue = True\n        for item in res.get(\"data\"):\n            for el in item.get(\"app_dependence\"):\n                if not el.get(\"process_continue\"):\n                    process_continue = False\n        self.assertEqual(process_continue, False)\n\n\nclass ProductEntranceTest(ComponentEntranceTest, ProductResourceMixin):\n\n    def setUp(self):\n        super(ProductEntranceTest, self).setUp()\n        self.productEntrance_url = reverse(\n            \"productEntrance-list\")\n\n    def test_normal_res(self):\n        self.get_application()\n        self.get_product()\n        res = self.get(self.productEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        self.assertEqual(len(res.get(\"data\", [])) != 0, True)\n        self.destroy_application()\n        self.destroy_labels()\n        self.destroy_product()\n\n    def make_pro_1(self):  # NOQA\n        \"\"\"\n        创建不依赖其他应用的应用，应用下具备2个服务\n        :return:\n        \"\"\"\n        # 创建产品下的服务\n        test_pro_ser_1 = {\n            \"app_name\": \"test_pro_ser_1\",\n            \"app_version\": \"1.0\",\n            \"app_dependence\": json.dumps(\n                [{\"name\": \"test_app1\", \"version\": \"8u211\"}]\n            )\n        }\n        self.make_unique_app_data(test_pro_ser_1)\n        test_pro_ser_2 = {\n            \"app_name\": \"test_pro_ser_2\",\n            \"app_version\": \"1.0\",\n            \"app_dependence\": json.dumps(\n                [{\"name\": \"test_app1\", \"version\": \"8u211\"}]\n            )\n        }\n        self.make_unique_app_data(test_pro_ser_2)\n        pro_dic = {\n            \"is_release\": 1,\n            \"pro_name\": \"test_pro_1\",\n            \"pro_version\": \"1.0\",\n            \"pro_dependence\": json.dumps([\n                {\"name\": \"test_pro_2\", \"version\": \"1.0\"},\n                {\"name\": \"test_pro_30\", \"version\": \"1.0\"},\n            ]),\n            \"pro_services\": json.dumps([\n                {\"name\": \"test_pro_ser_1\", \"version\": \"1.0\"},\n                {\"name\": \"test_pro_ser_2\", \"version\": \"1.0\"}\n            ])\n        }\n        ProductHub(**pro_dic).save()\n\n    def make_pro_2(self):  # NOQA\n        pro_dic = {\n            \"is_release\": 1,\n            \"pro_name\": \"test_pro_2\",\n            \"pro_version\": \"1.2\",\n            \"pro_dependence\": json.dumps([\n                {\"name\": \"test_pro_1\", \"version\": \"1.0\"},\n                {\"name\": \"test_pro_2\", \"version\": \"1.0\"},\n            ]),\n            \"pro_services\": json.dumps([\n                {\"name\": \"test_pro_ser_3\", \"version\": \"2.0\"},\n                {\"name\": \"test_pro_ser_4\", \"version\": \"2.0\"},\n                {\"name\": \"test_pro_ser_5\", \"version\": \"3.0\"},\n            ])\n        }\n        ProductHub(**pro_dic).save()\n\n    def make_pro_3(self):  # NOQA\n        pro_dic = {\n            \"is_release\": 1,\n            \"pro_name\": \"test_pro_3\",\n            \"pro_version\": \"1.2\",\n            \"pro_dependence\": json.dumps([\n                {\"name\": \"test_pro_1\", \"version\": \"1.0\"},\n            ]),\n            \"pro_services\": json.dumps([\n                {\"name\": \"test_pro_ser_3\", \"version\": \"2.0\"},\n                {\"name\": \"test_pro_ser_4\", \"version\": \"2.0\"},\n                {\"name\": \"test_pro_ser_5\", \"version\": \"3.0\"},\n            ])\n        }\n        ProductHub(**pro_dic).save()\n\n    def test_pro_component_dependence(self):\n        self.make_pro_1()\n        res = self.get(self.productEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n\n    def test_pro_pro_dependence(self):\n        \"\"\" 测试产品间有依赖场景 \"\"\"\n        self.make_pro_1()\n        self.make_pro_2()\n        self.make_pro_3()\n        res = self.get(self.productEntrance_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n\n\ndef create_host():\n    \"\"\"\n    创建测试使用主机\n    :return:\n    \"\"\"\n    env = Env(name=\"default\")\n    env.save()\n    test_host = {\n        'created': datetime.datetime(2021, 10, 26, 17, 59, 45, 248976),\n        'modified': datetime.datetime(2021, 10, 26, 17, 59, 45, 553447),\n        'is_deleted': False,\n        'instance_name': '127.0.0.1',\n        'ip': '127.0.0.1',\n        'port': 22,\n        'username': 'root',\n        'password': 'lEJBI-Pt8Ih321eaawzf1kHj8YvMTRpMYnNFD2YS7MA',\n        'data_folder': '/data',\n        'service_num': 0,\n        'alert_num': 0,\n        'operate_system': 'CentOS',\n        'host_name': None,\n        'memory': None,\n        'cpu': None,\n        'disk': None,\n        'host_agent': '0',\n        'monitor_agent': '4',\n        'host_agent_error': None,\n        'monitor_agent_error': '',\n        'is_maintenance': False,\n        'agent_dir': '/data',\n        'env': env\n    }\n    Host(**test_host).save()\n\n\ndef create_base_app():\n    \"\"\"\n    创建安装过程中使用的app\n    :return:\n    \"\"\"\n    test_app_1_upload_history = {\n        'created': datetime.datetime(2021, 10, 25, 10, 39, 59, 239807),\n        'modified': datetime.datetime(2021, 10, 25, 10, 40, 7, 191114),\n        'is_deleted': False,\n        'operation_uuid': '1635129600184',\n        'operation_user': 'admin',\n        'package_name': 'testApp-1.0.1-35144e57a59d774869ccc218539db8c7.tar.gz',\n        'package_md5': '35144e57a59d774869ccc218539db8c7',\n        'package_path': 'verified',\n        'package_status': 3,\n        'error_msg': None,\n        'package_parent_id': None\n    }\n\n    test_app_1_application_hub = {\n        'created': datetime.datetime(2021, 10, 25, 10, 40, 29, 981060),\n        'modified': datetime.datetime(2021, 10, 25, 10, 40, 29, 988651),\n        'is_release': True,\n        'app_type': 0,\n        'app_name': 'testApp',\n        'app_version': '1.0.1',\n        'app_description': 'Java Development Kit (JDK) 是Sun公司（已被Oracle收购）'\n                           '针对Java开发员的软件开发工具包'\n                           'Java SDK（Software development kit）。',\n        'app_port': json.dumps([\n            {\"default\": 8080, \"key\": \"http_port\", \"name\": \"业务端口\"},\n            {\"default\": 8081, \"key\": \"metric_port\", \"name\": \"监控端口\"}\n        ]),\n        'app_dependence': None,\n        'app_install_args': json.dumps([\n            {\"name\": \"安装目录\", \"key\": \"base_dir\", \"default\": \"{data_path}/jdk\"},\n            {\"name\": \"数据目录\", \"key\": \"data_dir\",\n             \"default\": \"{data_path}/data/jdk\"},\n            {\"name\": \"日志目录\", \"key\": \"log_dir\",\n             \"default\": \"{data_path}/log/jdk\"},\n            {\"name\": \"用户名\", \"key\": \"username\", \"default\": \"jon\"},\n            {\"name\": \"密码\", \"key\": \"password\", \"default\": \"jon_password\"},\n        ]),\n        'app_controllers': json.dumps(\n            {\n                \"start\": \"./bin/testApp start\",\n                \"stop\": \"./bin/testApp stop\",\n                \"restart\": \"./bin/testApp restart\",\n                \"reload\": \"./bin/testApp reload\",\n                \"install\": \"./scripts/install.py\",\n                \"init\": \"\"\n            }\n        ),\n        'app_package_id': 1,\n        'product_id': None,\n        'app_logo': None,\n        'extend_fields': {\n            'deploy': {\n                \"single\": [{\"name\": \"单实例\", \"key\": \"single\"}],\n                \"complex\": [{\"name\": \"主从模式\", \"key\": \"master_slave\"}]\n            },\n            'monitor': None,\n            'base_env': True,\n            'resources': None,\n            'auto_launch': False\n        }\n    }\n\n    up_obj = UploadPackageHistory(**test_app_1_upload_history)\n    up_obj.save()\n\n    test_app_1_application_hub[\"app_package_id\"] = up_obj.id\n    app_obj = ApplicationHub(**test_app_1_application_hub)\n    app_obj.save()\n\n\nclass ExecuteInstallTest(TransactionTestCase):\n    def setUp(self):\n        super(ExecuteInstallTest, self).setUp()\n        self.executeInstall_url = reverse(\n            \"executeInstall-list\")\n        create_host()\n        create_base_app()\n        user = UserProfile.objects.create(username=\"admin\")\n        self.client = APIClient()\n        self.client.force_authenticate(user)\n        self.test_data = {\n            \"install_type\": 0,\n            \"use_exist_services\": [\n            ],\n            \"install_services\": [\n                {\n                    \"name\": \"testApp\",\n                    \"version\": \"1.0.1\",\n                    \"ip\": \"127.0.0.1\",\n                    \"install_args\": [\n                        {\"name\": \"安装目录\", \"key\": \"base_dir\",\n                         \"dir_key\": \"{data_path}\", \"default\": \"/jdk\"},\n                        {\"name\": \"数据目录\", \"key\": \"data_dir\",\n                         \"dir_key\": \"{data_path}\", \"default\": \"/data/jdk\"},\n                        {\"name\": \"日志目录\", \"key\": \"log_dir\",\n                         \"dir_key\": \"{data_path}\", \"default\": \"/log/jdk\"},\n                        {\"name\": \"用户名\", \"key\": \"username\",\n                         \"default\": \"jon\"},\n                        {\"name\": \"密码\", \"key\": \"password\",\n                         \"default\": \"jon_password\"},\n                    ],\n                    \"app_port\": [\n                        {\"default\": 8000, \"key\": \"http_port\", \"name\": \"业务端口\"},\n                        {\"default\": 8081, \"key\": \"metric_port\", \"name\": \"监控端口\"}\n                    ],\n                    \"service_instance_name\": \"testApp-jon\",\n                    \"deploy_mode\": {\n                        \"key\": \"single\",\n                        \"name\": \"单实例\"\n                    }\n                }\n            ]\n        }\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"OK\"))\n    @mock.patch.object(install_service, \"delay\", return_value=None)\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(False, \"OK\"))\n    # @mock.patch(\n    #     \"utils.plugin.public_utils.check_ip_port\", return_value=(False, \"\"))\n    def test_main_success(self, *args, **kwargs):\n\n        res = self.client.post(\n            self.executeInstall_url,\n            data=json.dumps(self.test_data),\n            content_type=\"application/json\"\n        ).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        # operation_uuid = res.get(\"data\", {}).get(\"operation_uuid\")\n        # self.assertTrue(operation_uuid)\n        # 删除json文件\n        os.system(f\"rm -rf {PROJECT_DIR}/package_hub/data_files/*.json\")\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(False, \"\"))\n    @mock.patch.object(install_service, \"delay\", return_value=None)\n    # @mock.patch(\n    #     \"utils.plugin.public_utils.check_ip_port\", return_value=(False, \"\"))\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(False, \"OK\"))\n    def test_failed_path_exist(self, *args, **kwargs):\n        res = self.client.post(\n            self.executeInstall_url,\n            data=json.dumps(self.test_data),\n            content_type=\"application/json\"\n        ).json()\n        self.assertEqual(res.get(\"code\"), 0)\n        # 删除json文件\n        os.system(f\"rm -rf {PROJECT_DIR}/package_hub/data_files/*.json\")\n\n\nclass InstallHistoryTest(ExecuteInstallTest):\n    def setUp(self):\n        super(InstallHistoryTest, self).setUp()\n        self.installHistory_url = reverse(\n            \"installHistory-list\")\n\n    def test_success(self):\n        self.test_main_success()\n        res = self.client.get(self.installHistory_url).json()\n        self.assertEqual(res.get(\"code\"), 0)\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_app_store_upload.py",
    "content": "from rest_framework.reverse import reverse\n\nfrom db_models.models import (\n    ApplicationHub, ProductHub,\n    UploadPackageHistory, Labels\n)\nfrom tests.base import AutoLoginTest\nfrom unittest import mock\nfrom app_store.tasks import front_end_verified\nfrom utils.plugin import public_utils\nfrom app_store.tasks import (\n    ExplainYml, PublicAction,\n    publish_bak_end, publish_entry,\n    exec_clear\n)\nfrom unittest.mock import patch, mock_open\nfrom app_store.tmp_exec_back_task import back_end_verified_init\nimport os\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nproject_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))\n\ntest_product = {\n    \"kind\": \"product\",\n    \"name\": \"jenkins\",\n    \"version\": \"5.2.0\",\n    \"description\": \"这是jenkins\",\n    \"labels\": [\"CI&CD\"],\n    \"dependencies\": None,\n    \"service\": [{\"name\": \"jenkins\"}],\n    \"extend_fields\": {}\n}\n\ntest_service = {\n    \"kind\": \"service\",\n    \"name\": \"jenkins\",\n    \"version\": \"2.303.2\",\n    \"ports\": [{\"name\": \"服务端口\", \"protocol\": \"TCP\", \"key\": \"service_port\", \"default\": 8080}],\n    \"dependencies\": [{\"name\": \"jdk\", \"version\": \"1.8.0\"}],\n    \"install\": [{\"name\": \"安装目录\", \"key\": \"base_dir\", \"default\": \"path/jenkins\"}],\n    \"control\": [\n        {\n            \"start\": \"./bin/start.sh\",\n            \"stop\": \"./bin/stop.sh\",\n            \"restart\": \"./bin/restart.sh\",\n            \"reload\": \"./bin/reload.sh\",\n            \"install\": \"./scripts/install.sh\",\n            \"init\": \"./scripts/init.sh\"\n        }\n    ],\n    \"base_env\": False,\n    \"monitor\": {\n        \"process_name\": \"jenkins\",\n        \"metric_port\": \"service_port\",\n        \"type\": \"JavaSpringBoot\"\n    },\n    \"extend_fields\": {\n        \"auto_launch\": True,\n        \"affinity:\": None,\n        \"level\": \"1\",\n        \"deploy\": None,\n        \"resources\": {\"cpu\": \"2c\", \"memory\": \"2g\"}\n    }\n}\n\nservice_yml = \"\"\"\nkind: service\nname: jenkins\nversion: 2.303.2\nauto_launch: True\nbase_env: False\nlevel: 1\nmonitor:\n  process_name: \"jenkins\"\n  metric_port: {service_port}\n  type: \"JavaSpringBoot\"\nports:\n  - name: 服务端\n    protocol: TCP\n    default: 8080\n    key: service_port\ndependencies:\n  - name: jdk\n    version: 1.8\nresources:\n  cpu: 1000m\n  memory: 2000m\ninstall:\n  - name: \"安装目录\"\n    key: base_dir\n    default: \"{data_path}/jeknins\"\naffinity:\ndeploy:\ncontrol:\n  start: \"./bin/start.sh\"\n  stop: \"./bin/stop.sh\"\n  restart: \"./bin/restart.sh\"\n  reload: \"./bin/reload.sh\"\n  install: \"./scripts/install.sh\"\n  init: \"./scripts/init.sh\"\npost_action:\n\"\"\"\n\ncomponent_yml = \"\"\"\nkind: component\nname: tengine\nversion: 2.3.3\ndescription: \"服务tengine\"\nlabels:\n  - WEB服务\nauto_launch: True\nbase_env: False\nmonitor:\n  process_name: \"nginx\"\n  metric_port: {service_port}\n  type: \"JavaSpringBoot\"\nports:\n  - name: 服务端口\n    protocol: TCP\n    key: service_port\n    default: 80\ndeploy:\naffinity:\ndependencies:\nresources:\n  cpu: 1000m\n  memory: 500m\ninstall:\n  - name: \"安装目录\"\n    key: base_dir\n    default: \"{data_path}/tengine\"\n  - name: \"日志目录\"\n    key: log_dir\n    default: \"{data_path}/tengine/logs\"\n  - name: \"vhosts\"\n    key: vhosts_dir\n    default: \"{data_path}/tengine/vhosts\"\ncontrol:\n  start: \"./bin/start.sh\"\n  stop: \"./bin/stop.sh\"\n  restart: \"./bin/restart.sh\"\n  reload: \"./bin/reload.sh\"\n  install: \"./scripts/install.sh\"\n  init:  \"./scripts/init.sh\"\npost_action:\n\"\"\"\n\nproduct_yml = \"\"\"\nkind: product\nname: jenkins\nversion: 5.2.0\ndescription: \"Jenkins开源\"\nlabels:\n  - CI&CD\ndependencies:\nservice:\n  - name: jenkins\n\"\"\"\n\n\nclass PackageUploadTest(AutoLoginTest):\n    # 上传逻辑\n    def setUp(self):\n        super(PackageUploadTest, self).setUp()\n        self.upload_url = reverse(\n            \"upload-list\")\n        UploadPackageHistory(\n            operation_uuid='test-uuid',\n            operation_user='admin',\n            package_name='jenkins-1.0.0-test-md5.tar.gz',\n            package_md5='test-md5',\n            package_path=\"verified\"\n        ).save()\n\n    @mock.patch.object(public_utils, \"local_cmd\", return_value=\"\")\n    @mock.patch(\n        \"os.mkdir\",\n        return_value=None)\n    @mock.patch(\n        \"os.path.exists\",\n        return_value=\"\")\n    @mock.patch(\n        \"app_store.tasks.ExplainYml.explain_yml\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"os.listdir\",\n        return_value=[\"jenkins-2.303.2.tar.gz\"]\n    )\n    @mock.patch(\n        \"os.path.isfile\",\n        return_value=True\n    )\n    def test_app_store_upload(self, isfile, listdir, explain, exists, mkdir, local_cmd):\n        # 正向前端发布\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        local_cmd.side_effect = [\n            (\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 0),\n            (\"success\", \"\", 0),\n            (\"test-md6 jenkins01-1.0.0.tar.gz\", \"\", 0)\n        ]\n        exists.side_effect = [\n            True, False, True\n        ]\n        explain.side_effect = [(True, test_product), (True, test_service)]\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        clear_file = os.path.join(\n            project_dir, 'data', \"middle_data-test-uuid.json\")\n        os.remove(clear_file)\n        res = upload_obj.package_status\n        self.assertEqual(res, 0)\n\n    @mock.patch.object(public_utils, \"local_cmd\",\n                       return_value=(\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 1))\n    def test_app_store_upload_md5(self, local_cmd):\n        # 反向md5校验失败\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        res = upload_obj.package_status\n        self.assertEqual(res, 1)\n\n    @mock.patch.object(public_utils, \"local_cmd\",\n                       return_value=\"\")\n    @mock.patch(\n        \"os.mkdir\",\n        return_value=None)\n    def test_app_store_upload_tar(self, mkdir, local_cmd):\n        # 反向tar解压失败\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        local_cmd.side_effect = [\n            (\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 0),\n            (\"false\", \"\", 1)\n        ]\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        res = upload_obj.package_status\n        self.assertEqual(res, 1)\n\n    @mock.patch.object(\n        public_utils,\n        \"local_cmd\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"os.path.exists\",\n        return_value=False)\n    @mock.patch(\n        \"os.mkdir\",\n        return_value=None)\n    def test_app_store_upload_file_check(self, mkdir, exists, local_cmd):\n        # 产品或组建yaml文件检测文件存在\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        local_cmd.side_effect = [\n            (\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 0),\n            (\"false\", \"\", 0)\n        ]\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        res = upload_obj.package_status\n        self.assertEqual(res, 1)\n\n    @mock.patch.object(\n        public_utils,\n        \"local_cmd\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"os.path.exists\",\n        return_value=\"\")\n    @patch(\n        \"builtins.open\",\n        new_callable=mock_open,\n        read_data=\"this-is-image\"\n    )\n    @mock.patch(\n        \"app_store.tasks.ExplainYml.explain_yml\",\n        return_value=(True, test_product)\n    )\n    @mock.patch(\n        \"os.listdir\",\n        return_value=[\"jenkins-2.303.2.tar.gz\"]\n    )\n    @mock.patch(\n        \"os.path.isfile\",\n        return_value=True\n    )\n    @mock.patch(\n        \"os.mkdir\",\n        return_value=None)\n    def test_app_store_upload_file_service(self, mkdir, isfile, listdir, explain, image, exists, local_cmd):\n        # 服务yaml文件检测文件存在\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        local_cmd.side_effect = [\n            (\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 0),\n            (\"false\", \"\", 0),\n            (\"false\", \"\", 1)\n        ]\n        exists.side_effect = [\n            True, True, False\n        ]\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        res = upload_obj.package_status\n        self.assertEqual(res, 1)\n\n    @mock.patch.object(\n        public_utils,\n        \"local_cmd\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"os.path.exists\",\n        return_value=\"\")\n    @patch(\n        \"builtins.open\",\n        new_callable=mock_open,\n        read_data=\"this-is-image\"\n    )\n    @mock.patch(\n        \"app_store.tasks.ExplainYml.explain_yml\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"os.listdir\",\n        return_value=[\"jenkins-2.303.2.tar.gz\"]\n    )\n    @mock.patch(\n        \"os.path.isfile\",\n        return_value=True\n    )\n    @mock.patch(\n        \"os.mkdir\",\n        return_value=None)\n    def test_app_store_upload_file_md5(self, mkdir, isfile, listdir, explain, image, exists, local_cmd):\n        # 服务md5sum校验失败\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        local_cmd.side_effect = [\n            (\"test-md5 jenkins-1.0.0-test-md5.tar.gz\", \"\", 0),\n            (\"false\", \"\", 0)\n        ]\n        exists.side_effect = [\n            True, True, False\n        ]\n        explain.side_effect = [\n            (True, test_product),\n            (True, test_service)\n        ]\n        front_end_verified(upload_obj.operation_uuid,\n                           upload_obj.operation_user,\n                           upload_obj.package_name,\n                           \"RandomStr\",\n                           \"front_end_verified\",\n                           upload_obj.id)\n        upload_obj.refresh_from_db()\n        res = upload_obj.package_status\n        self.assertEqual(res, 1)\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=service_yml)\n    def test_app_store_explain_service(self, with_open):\n        # 正向解析服务\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        public_action = PublicAction(upload_obj.package_md5)\n        explain = ExplainYml(public_action, '/data/test').explain_yml()\n        upload_obj.refresh_from_db()\n        self.assertEqual(explain[0], True)\n        self.assertEqual(explain[1].get('version'), \"2.303.2\")\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=component_yml)\n    def test_app_store_explain_component(self, with_open):\n        # 正向解析组件\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        public_action = PublicAction(upload_obj.package_md5)\n        explain = ExplainYml(public_action, '/data/test').explain_yml()\n        upload_obj.refresh_from_db()\n        self.assertEqual(explain[0], True)\n        self.assertEqual(explain[1].get('version'), \"2.3.3\")\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=product_yml)\n    def test_app_store_explain_product(self, with_open):\n        # 正向解析产品\n        upload_obj = UploadPackageHistory.objects.get(\n            operation_uuid='test-uuid')\n        public_action = PublicAction(upload_obj.package_md5)\n        explain = ExplainYml(public_action, '/data/test').explain_yml()\n        upload_obj.refresh_from_db()\n        self.assertEqual(explain[0], True)\n        self.assertEqual(explain[1].get('version'), \"5.2.0\")\n\n\nclass SimulationRedis(object):\n    def exists(self, key):\n        return 0\n\n    def lpush(self, key, *args):\n        return True\n\n    def expire(self, key, expire):\n        return True\n\n    def lindex(self, key, index):\n        return 'test_redis'\n\n\npublish_info = \"\"\"\\\n{\"kind\": \"product\", \"name\": \"jenkins\", \"version\": \"1.0.0\", \"description\": \"描述\",\\\n\"dependencies\": [{\"name\": \"jdk1.88\", \"version\": \"1.88\"},\\\n{\"name\": \"tomcat1.88\", \"version\": \"1.88\"},\\\n{\"name\": \"home1.99\", \"version\": \"1.99\"}],\\\n\"extend_fields\": {},\\\n\"service\": [{\"name\": \"jenkinss01\"},\\\n{\"name\": \"jenkinsss01\"},\\\n{\"name\": \"jenkinssss01\"},\\\n{\"name\": \"qqqqqqqqqq01\"},\\\n{\"name\": \"jenkins\"}],\\\n\"labels\": [\"mysql_db\"],\\\n\"product_service\":\\\n[{\"kind\": \"service\", \"name\": \"jenkins\", \"version\": \"123\",\\\n\"dependencies\": [{\"name\": \"jdk\", \"version\": \"8u211\"}],\\\n\"base_env\": \"False\",\\\n\"extend_fields\": {\"auto_launch\": \"true\",\"affinity\":\"\",\"level\":\"1\",\\\n\"resources\": {\"cpu\": \"1000m\", \"memory\": \"2000m\"}},\\\n\"ports\": [{\"name\": \"服务端口\", \"protocol\": \"TCP\", \"key\": \"service_port\", \"port\": \"8080\"}],\\\n\"monitor\": {\"process_name\": \"jenkins\"},\\\n\"control\": {\"start\": \"./bin/start.sh\", \"stop\": \"./bin/stop.sh\",\\\n\"restart\": \"./bin/restart.sh\", \"reload\": \"./bin/reload.sh\", \"install\": \"./scripts/install.sh\",\\\n\"init\": \"./scripts/init.sh\"},\\\n\"install\": [{\"name\": \"安装目录\", \"key\": \"base_dir\", \"default\": \"{data_path}/jeknins\"}],\\\n\"package_name\": \"jenkins-1.0.0-service.tar.gz\"}],\\\n\"image\": null, \"package_name\": \"jenkins-1.0.0-test-md5.tar.gz\",\\\n\"tmp_dir\": [\"/data/omp/package_hub/front_end_verified/jenkins-test40yp18cfwbz/jenkins\", \"00011123\"]}\\\n\"\"\"\n\n\nclass PackagePublishTest(AutoLoginTest):\n    # 发布逻辑\n    def setUp(self):\n        super(PackagePublishTest, self).setUp()\n        self.publish_url = reverse(\n            \"publish-list\")\n        upload_obj = UploadPackageHistory(\n            operation_uuid='test-uuid',\n            operation_user='admin',\n            package_name='jenkins-1.0.0-test-md5.tar.gz',\n            package_md5='test-md5',\n            package_path=\"verified\",\n            package_status=0\n        )\n        upload_obj.save()\n        UploadPackageHistory.objects.create(\n            operation_uuid='test-uuid',\n            operation_user='admin',\n            package_name='jenkins-1.0.0-service.tar.gz',\n            package_md5='test-md5-service',\n            package_path=\"verified/jenkins-1.0.0\",\n            package_status=0,\n            package_parent=upload_obj\n        )\n\n    @patch(\"builtins.open\", new_callable=mock_open, read_data=publish_info)\n    @mock.patch(\n        \"app_store.tasks.exec_clear\",\n        return_value=\"\"\n    )\n    @mock.patch.object(\n        public_utils,\n        \"local_cmd\",\n        return_value=(\"\", \"\", 0)\n    )\n    @mock.patch(\n        \"os.path.isfile\",\n        return_value=False\n    )\n    def test_app_store_publish(self, isfile, local_cmd, exe_clear, with_open):\n        # 正向发布产品，包含服务，前端\n        upload_obj = UploadPackageHistory.objects.get(operation_uuid='test-uuid',\n                                                      package_parent__isnull=True\n                                                      )\n        publish_entry(upload_obj.operation_uuid)\n        upload_obj.refresh_from_db()\n        app_count = ApplicationHub.objects.filter(\n            app_name=\"jenkins\",\n            app_version=\"123\"\n        ).count()\n        pro_count = ProductHub.objects.filter(\n            pro_name=\"jenkins\",\n            pro_version=\"1.0.0\"\n        ).count()\n        label_count = Labels.objects.filter(\n            label_name=\"mysql_db\"\n        ).count()\n        self.assertEqual(app_count, 1)\n        self.assertEqual(pro_count, 1)\n        self.assertEqual(label_count, 1)\n\n    @mock.patch(\n        \"redis.Redis.delete\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"app_store.tasks.publish_entry\",\n        return_value=\"\"\n    )\n    def test_app_store_publish_back_true(self, publish, redis):\n        # 正向后端发布等待\n\n        res = publish_bak_end('test-uuid', 1)\n        self.assertEqual(res, None)\n\n    @mock.patch(\n        \"app_store.tasks.exec_clear\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"redis.Redis.delete\",\n        return_value=\"\"\n    )\n    def test_app_store_publish_back(self, redis, exe_clear):\n        # 反向后端发布等待\n        upload_obj = UploadPackageHistory.objects.get(operation_uuid='test-uuid',\n                                                      package_parent__isnull=True\n                                                      )\n        upload_obj.package_status = 1\n        upload_obj.save()\n        res = publish_bak_end('test-uuid', 1)\n        self.assertEqual(res, None)\n\n    @mock.patch(\n        \"app_store.tasks.publish_entry.delay\",\n        return_value=\"\"\n    )\n    def test_app_store_publish_api(self, delay):\n        # 正向post发布接口\n        resp = self.post(self.publish_url, {\n            \"uuid\": 'test-uuid',\n        }).json()\n        self.assertDictEqual(resp.get('data'), {\n            \"status\": \"发布任务下发成功\"\n        })\n\n        # 正向get请求,过滤状态3，4，5\n        upload_obj1 = UploadPackageHistory.objects.get(operation_uuid='test-uuid',\n                                                       package_parent__isnull=True\n                                                       )\n        upload_obj1.package_status = 0\n        upload_obj1.save()\n        resp = self.get(self.publish_url, data={\n            \"operation_uuid\": 'test-uuid',\n        }).json()\n        self.assertDictEqual(\n            resp, {\n                'code': 0,\n                'message': 'success',\n                'data': []\n            }\n        )\n\n    @mock.patch.object(\n        public_utils,\n        \"local_cmd\",\n        return_value=(\"\", \"\", 0)\n    )\n    def test_app_store_publish_clear(self, local_cmd):\n        # 正向删除\n        upload_obj1 = UploadPackageHistory.objects.get(operation_uuid='test-uuid',\n                                                       package_parent__isnull=True\n                                                       )\n        upload_obj1.package_status = 3\n        upload_obj1.save()\n        resp = exec_clear('/data/omp/package_hub/front_end_verified')\n        self.assertEqual(resp, None)\n\n    @mock.patch(\n        \"redis.Redis\",\n        return_value=SimulationRedis())\n    @mock.patch(\n        \"os.listdir\",\n        return_value=[\"jdk-1.8.1.tar.gz\"])\n    @mock.patch(\n        \"os.path.isfile\",\n        return_value=True)\n    @mock.patch(\n        \"app_store.tasks.front_end_verified.delay\",\n        return_value=\"\")\n    @mock.patch(\n        \"app_store.tasks.publish_bak_end.delay\",\n        return_value=\"\")\n    def test_app_store_scan(self, bak, front, isfile, listdir, redis):\n        uuid, exec_name = back_end_verified_init('admin')\n        count = UploadPackageHistory.objects.filter(\n            operation_uuid=uuid).count()\n        self.assertEqual(count, 1)\n        self.assertEqual(exec_name[0], \"jdk-1.8.1.tar.gz\")\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_execute_package_scan.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_execute_package_scan\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-19 21:04\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n服务端本地扫描测试方法\n\"\"\"\nimport uuid\nfrom unittest import mock\n\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom db_models.models import UploadPackageHistory\n\n\nclass ExecutePackageScanTest(AutoLoginTest):\n    def setUp(self):\n        super(ExecutePackageScanTest, self).setUp()\n        self.executeLocalPackageScan_url = reverse(\n            \"executeLocalPackageScan-list\")\n\n    @mock.patch(\n        \"app_store.tmp_exec_back_task.back_end_verified_init\",\n        return_value=(\"uuid\", []))\n    def test_request_success(self, mock_obj):\n        resp = self.post(\n            url=self.executeLocalPackageScan_url, data=None).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": {\n                \"uuid\": \"uuid\",\n                \"package_names\": []\n            }\n        })\n\n\nclass LocalPackageScanResultTest(AutoLoginTest):\n    def setUp(self):\n        super(LocalPackageScanResultTest, self).setUp()\n        self.localPackageScanResult_url = reverse(\n            \"localPackageScanResult-list\")\n        self.operation_uuid = str(uuid.uuid4())\n        self.operation_user = \"admin\"\n        self.package_name_pre = \"test_\"\n        self.package_path = \"/tmp\"\n        self.package_names_lst = list()\n        upload_package_history = list()\n        for i in range(10):\n            _obj = UploadPackageHistory(\n                operation_uuid=self.operation_uuid,\n                operation_user=self.operation_user,\n                package_name=self.package_name_pre + f\"{str(i)}.tar.gz\",\n                package_md5=str(uuid.uuid4())\n            )\n            self.package_names_lst.append(\n                self.package_name_pre + f\"{str(i)}.tar.gz\")\n            upload_package_history.append(_obj)\n        UploadPackageHistory.objects.bulk_create(upload_package_history)\n\n    def test_get_failed_1(self):\n        resp = self.get(url=self.localPackageScanResult_url)\n        self.assertEqual(resp.json().get(\"code\"), 1)\n        self.assertEqual(\n            resp.json().get(\"message\"), \"请求参数中必须包含 [uuid] 字段\")\n\n    def test_get_failed_2(self):\n        resp = self.get(\n            url=self.localPackageScanResult_url,\n            data={\n                \"uuid\": self.operation_uuid\n            }\n        )\n        self.assertEqual(\n            resp.json().get(\"message\"), \"请求参数中必须包含 [package_names] 字段\")\n\n    def get_success_resp(self):\n        \"\"\"\n        获取响应值\n        :return:\n        \"\"\"\n        return self.get(\n            url=self.localPackageScanResult_url,\n            data={\n                \"uuid\": self.operation_uuid,\n                \"package_names\": \",\".join(self.package_names_lst)\n            }\n        )\n\n    def test_get_success(self):\n        self.assertEqual(self.get_success_resp().json().get(\"code\"), 0)\n\n    def test_checking_status(self):\n        res_dic = self.get_success_resp().json()\n        status = res_dic.get(\"data\", {}).get(\"stage_status\")\n        self.assertEqual(status, \"checking\")\n\n    def test_check_all_failed_status(self):\n        UploadPackageHistory.objects.filter(\n            operation_uuid=self.operation_uuid).update(\n            package_status=1)\n        res_dic = self.get_success_resp().json()\n        status = res_dic.get(\"data\", {}).get(\"stage_status\")\n        self.assertEqual(status, \"check_all_failed\")\n\n    def test_published_status(self):\n        UploadPackageHistory.objects.filter(\n            operation_uuid=self.operation_uuid).update(\n            package_status=3)\n        res_dic = self.get_success_resp().json()\n        status = res_dic.get(\"data\", {}).get(\"stage_status\")\n        self.assertEqual(status, \"published\")\n\n    def test_publishing_status(self):\n        UploadPackageHistory.objects.filter(\n            operation_uuid=self.operation_uuid).update(\n            package_status=5)\n        res_dic = self.get_success_resp().json()\n        status = res_dic.get(\"data\", {}).get(\"stage_status\")\n        self.assertEqual(status, \"publishing\")\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_get_application_template.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_get_application_template\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-26 11:33\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom rest_framework.reverse import reverse\nfrom django.http.response import FileResponse\n\nfrom tests.base import AutoLoginTest\n\n\nclass ApplicationTemplateTest(AutoLoginTest):\n    \"\"\" 主机批量校验测试类 \"\"\"\n\n    def setUp(self):\n        super(ApplicationTemplateTest, self).setUp()\n        self.get_template_url = reverse(\"applicationTemplate-list\")\n\n    def test_get_application_template(self):\n        \"\"\" 获取应用商店导入模板 \"\"\"\n\n        # 获取应用商店导入模板 -> 返回文件\n        resp = self.get(self.get_template_url)\n        self.assertEqual(resp.status_code, 200)\n        self.assertTrue(isinstance(resp, FileResponse))\n        self.assertTrue(resp.streaming)\n        self.assertIsNotNone(resp.streaming_content)\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_install_executor.py",
    "content": "import random\nfrom unittest import mock\nfrom django.test import TestCase\n\nfrom app_store.install_executor import InstallServiceExecutor\nfrom db_models.models import MainInstallHistory\nfrom utils.plugin.salt_client import SaltClient\nfrom tests.mixin import InstallHistoryResourceMixin\n\n\nclass TestInstallExecutor(TestCase, InstallHistoryResourceMixin):\n    \"\"\" 安装执行器测试类 \"\"\"\n\n    def test_action(self):\n        \"\"\" 测试动作 \"\"\"\n        main_obj, detail_obj_ls = self.get_install_history()\n        executor = InstallServiceExecutor(main_obj.id, \"admin\")\n\n        # 发送执行正常\n        with mock.patch.object(SaltClient, \"cp_file\") as mock_cp_file:\n            mock_cp_file.return_value = True, \"success\"\n            # 发送服务包\n            detail_obj = random.choice(detail_obj_ls)\n            is_success, _ = executor.send(detail_obj)\n            self.assertTrue(is_success)\n            self.assertEqual(detail_obj.send_flag, 2)\n\n        # 发送执行异常\n        with mock.patch.object(SaltClient, \"cp_file\") as mock_cp_file:\n            mock_cp_file.return_value = False, \"failed\"\n            # 发送服务包\n            detail_obj = random.choice(detail_obj_ls)\n            is_success, _ = executor.send(detail_obj)\n            self.assertFalse(is_success)\n            self.assertEqual(detail_obj.send_flag, 3)\n\n        # 命令执行正常\n        with mock.patch.object(SaltClient, \"cmd\") as mock_cmd:\n            mock_cmd.return_value = True, \"success\"\n            detail_obj = random.choice(detail_obj_ls)\n            # 解压服务包\n            is_success, _ = executor.unzip(detail_obj)\n            self.assertTrue(is_success)\n            self.assertEqual(detail_obj.unzip_flag, 2)\n            # 安装\n            is_success, _ = executor.install(detail_obj)\n            self.assertTrue(is_success)\n            self.assertEqual(detail_obj.install_flag, 2)\n            # 初始化\n            is_success, _ = executor.init(detail_obj)\n            self.assertTrue(is_success)\n            self.assertEqual(detail_obj.init_flag, 2)\n            # 启动\n            is_success, _ = executor.start(detail_obj)\n            self.assertTrue(is_success)\n            self.assertEqual(detail_obj.start_flag, 2)\n\n        # 命令执行异常\n        with mock.patch.object(SaltClient, \"cmd\") as mock_cmd:\n            mock_cmd.return_value = False, \"failed\"\n            detail_obj = random.choice(detail_obj_ls)\n            # 解压服务包\n            is_success, _ = executor.unzip(detail_obj)\n            self.assertFalse(is_success)\n            self.assertEqual(detail_obj.unzip_flag, 3)\n            # 安装\n            is_success, _ = executor.install(detail_obj)\n            self.assertFalse(is_success)\n            self.assertEqual(detail_obj.install_flag, 3)\n            # 初始化\n            is_success, _ = executor.init(detail_obj)\n            self.assertFalse(is_success)\n            self.assertEqual(detail_obj.init_flag, 3)\n            # 启动\n            is_success, _ = executor.start(detail_obj)\n            self.assertFalse(is_success)\n            self.assertEqual(detail_obj.start_flag, 3)\n\n        # 服务无 init、start 脚本\n        detail_obj = random.choice(detail_obj_ls)\n        detail_obj.service.service_controllers.pop(\"init\")\n        detail_obj.service.service_controllers.pop(\"start\")\n        detail_obj.service.save()\n        # 初始化 -> 成功 (跳过)\n        is_success, _ = executor.init(detail_obj)\n        self.assertTrue(is_success)\n        self.assertEqual(detail_obj.init_flag, 2)\n        # 启动 -> 成功 (跳过)\n        is_success, _ = executor.start(detail_obj)\n        self.assertTrue(is_success)\n        self.assertEqual(detail_obj.start_flag, 2)\n\n    @mock.patch.object(InstallServiceExecutor, \"start\", return_value=(True, \"\"))\n    @mock.patch.object(InstallServiceExecutor, \"init\", return_value=(True, \"\"))\n    @mock.patch.object(InstallServiceExecutor, \"install\", return_value=(True, \"\"))\n    @mock.patch.object(InstallServiceExecutor, \"unzip\", return_value=(True, \"\"))\n    @mock.patch.object(InstallServiceExecutor, \"send\", return_value=(True, \"\"))\n    def test_main(self, mock_send, mock_unzip, mock_install, mock_init, mock_start):\n        \"\"\" 测试主流程函数 \"\"\"\n        main_obj, detail_obj_ls = self.get_install_history()\n        action_ls = (mock_send, mock_unzip,\n                     mock_install, mock_init, mock_start)\n\n        executor = InstallServiceExecutor(main_obj.id, \"admin\")\n        # 所有动作执行成功 -> 主流程执行成功\n        executor.main()\n        main_obj.refresh_from_db()\n        self.assertEqual(\n            main_obj.install_status,\n            MainInstallHistory.INSTALL_STATUS_SUCCESS)\n\n        # 任意动作执行失败 -> 主流程执行失败\n        action = random.choice(action_ls)\n        action.return_value = False, \"failed\"\n        executor.main()\n        main_obj.refresh_from_db()\n        self.assertEqual(\n            main_obj.install_status,\n            MainInstallHistory.INSTALL_STATUS_FAILED)\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_new_install.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_new_install\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-11-25 16:37\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n# import json\n#\n# from rest_framework.reverse import reverse\n# from rest_framework.test import APIClient\n#\n# from db_models.models import (\n#     UserProfile\n# )\n#\n# from tests.base import BaseTest\n# from tests.test_app_store.install_data_source import (\n#     create_product, create_host\n# )\n#\n#\n# class BatchInstallEntranceTest(BaseTest):\n#     def setUp(self):\n#         create_host()\n#         create_product()\n#         user = UserProfile.objects.create(username=\"admin\")\n#         self.client = APIClient()\n#         self.client.force_authenticate(user)\n#         self.batchInstallEntrance_url = reverse(\"batchInstallEntrance-list\")\n#\n#     def test_success_1(self, *args, **kwargs):\n#         res = self.client.get(\n#             path=self.batchInstallEntrance_url\n#         )\n#         data = res.json().get(\"data\")\n#         self.assertTrue(len(data) != 0)\n#         self.client.get(\n#             path=self.batchInstallEntrance_url,\n#             data={\"product_name\": \"test\"}\n#         )\n#\n#\n# class CreateInstallInfoTest(BaseTest):\n#     def setUp(self):\n#         create_host()\n#         create_product()\n#         user = UserProfile.objects.create(username=\"admin\")\n#         self.client = APIClient()\n#         self.client.force_authenticate(user)\n#         self.createInstallInfo_url = reverse(\"createInstallInfo-list\")\n#\n#     def test_success_1(self, *args, **kwargs):\n#         res = self.client.get(\n#             path=reverse(\"batchInstallEntrance-list\")\n#         )\n#         data = res.json().get(\"data\")\n#         unique_key = data[\"unique_key\"]\n#         res = self.client.post(\n#             path=self.createInstallInfo_url,\n#             data=json.dumps({\n#                 \"high_availability\": False,\n#                 \"install_product\": [\n#                     {\n#                         \"name\": \"test\",\n#                         \"version\": \"1.0.0\"\n#                     }\n#                 ],\n#                 \"unique_key\": unique_key\n#             }),\n#             content_type=\"application/json\"\n#         )\n#         data = res.json().get(\"data\")\n#         is_continue = data.get(\"is_continue\")\n#         self.assertFalse(is_continue)\n"
  },
  {
    "path": "omp_server/tests/test_app_store/test_upload_package.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: test_upload_package\n# Author:Times.niu@yunzhihui.com\n# Create time: 2021/10/13 5:13 下午\n\nimport os\nimport time\n\nfrom django.conf import settings\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import UploadPackageHistoryMixin\nfrom db_models.models import UploadPackageHistory\n\n\nclass UploadPackageTest(AutoLoginTest):\n    \"\"\" 创建上传文件测试类 \"\"\"\n\n    def setUp(self):\n        super(UploadPackageTest, self).setUp()\n        self.upload_url = reverse(\"upload-list\")\n\n    def create_fake_file(self, file_end):\n        \"\"\" 根据传入的文件后缀（file_end）创建 \"\"\"\n        fake_data = \"hello world\"\n        file_name = \"test\" + file_end\n        file_path = os.path.join(settings.PROJECT_DIR,\n                                 \"package_hub\", file_name)\n        with open(file_path, \"w+\") as f:\n            f.write(fake_data)\n        return file_path\n\n    def test_error_field(self):\n        # 不提供uuid\n        file_path = self.create_fake_file(\".tar.gz\")\n        with open(file_path, \"rb\") as f:\n            resp = self.client.post(\n                self.upload_url,\n                data={\"operation_user\": \"admin\", \"file\": f,\n                      \"md5\": \"dfasdfafadfadfagagate\"}\n            ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[uuid]字段\",\n            \"data\": None\n        })\n\n        # 不提供operation_user\n        with open(file_path, \"rb\") as f:\n            resp = self.client.post(\n                self.upload_url,\n                data={\"uuid\": \"63ece2802559e7a37d01daa686d10c4b\",\n                      \"file\": f, \"md5\": \"dfasdfafadfadfagagate\"}\n            ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[operation_user]字段\",\n            \"data\": None\n        })\n\n        # 不提供md5\n        with open(file_path, \"rb\") as f:\n            resp = self.client.post(\n                self.upload_url,\n                data={\"operation_user\": \"admin\",\n                      \"uuid\": \"63ece2802559e7a37d01daa686d10c4b\", \"file\": f}\n            ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[md5]字段\",\n            \"data\": None\n        })\n\n        # 不提供file\n        resp = self.post(\n            self.upload_url,\n            data={\"uuid\": \"63ece2802559e7a37d01daa686d10c4b\",\n                  \"operation_user\": \"admin\", \"md5\": \"dfasdfafadfadfagagate\"}\n        ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[file]字段\",\n            \"data\": None\n        })\n\n        # 提供非tar tar.gz文件\n        file_path_err = self.create_fake_file(\".dmg\")\n        with open(file_path_err, \"rb\") as f:\n            resp = self.client.post(\n                self.upload_url,\n                data={\"uuid\": \"63ece2802559e7a37d01daa686d10c4b\",\n                      \"operation_user\": \"admin\", \"file\": f, \"md5\": \"dfasdfafadfadfagagate\"}\n            ).json()\n            self.assertDictEqual(resp, {\n                \"code\": 1,\n                \"message\": \"上传文件名仅支持.tar或.tar.gz\",\n                \"data\": None\n            })\n\n    def test_correct_field(self):\n        # file_path = self.create_fake_file(\".tar.gz\")\n        # with open(file_path, \"rb\") as f:\n        #     resp = self.client.post(\n        #         self.upload_url,\n        #         data={\"uuid\": \"63ece2802559e7a37d01daa686d10c4b\", \"operation_user\": \"admin\", \"file\": f}\n        #     ).json()\n        #     self.assertDictEqual(resp, {\n        #         \"code\": 0,\n        #         \"message\": \"success\",\n        #         \"data\": {\n        #             \"uuid\": \"63ece280-2559-e7a3-7d01-daa686d10c4b\",\n        #             \"operation_user\": \"admin\",\n        #             \"file\": None\n        #         }\n        #     })\n        pass\n\n    def tearDown(self):\n        super(UploadPackageTest, self).tearDown()\n        try:\n            os.remove(os.path.join(\n                settings.PROJECT_DIR, \"package_hub\", \"test.tar.gz\"))\n            os.remove(os.path.join(\n                settings.PROJECT_DIR, \"package_hub\", \"test.dmg\"))\n        except Exception:\n            pass\n\n\nclass RemovePackageTest(AutoLoginTest, UploadPackageHistoryMixin):\n    \"\"\" 移除安装包测试类 \"\"\"\n\n    def setUp(self):\n        super(RemovePackageTest, self).setUp()\n        self.remove_package_url = reverse(\"remove-list\")\n\n    def test_error_field(self):\n        \"\"\" 测试错误字段 \"\"\"\n\n        history_objs = self.get_upload_package_history(number=1)\n        operation_uuid = history_objs[0].operation_uuid\n        package_name_ls = list(\n            history_objs.values_list(\"package_name\", flat=True))\n\n        # 不提供 uuid -> 移除失败\n        resp = self.post(self.remove_package_url, {\n            \"package_names\": package_name_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[uuid]字段\",\n            \"data\": None\n        })\n\n        # 提供无效 uuid -> 移除失败\n        resp = self.post(self.remove_package_url, {\n            \"uuid\": str(int(round(time.time() * 1000))),\n            \"package_names\": package_name_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"该 uuid 未找到有效的操作记录\",\n            \"data\": None\n        })\n\n        # 不提供 package_names -> 移除失败\n        resp = self.post(self.remove_package_url, {\n            \"uuid\": operation_uuid,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[package_names]字段\",\n            \"data\": None\n        })\n\n        # 提供无效 package_names -> 移除失败\n        resp = self.post(self.remove_package_url, {\n            \"uuid\": operation_uuid,\n            \"package_names\": [\"111\", \"222\", \"333\"]\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"该 uuid 未找到有效的操作记录\",\n            \"data\": None\n        })\n\n        self.destroy_upload_package_history()\n\n    def test_correct_field(self):\n        \"\"\" 测试正确字段 \"\"\"\n\n        history_objs = self.get_upload_package_history(number=1)\n        operation_uuid = history_objs[0].operation_uuid\n        package_name_ls = list(\n            history_objs.values_list(\"package_name\", flat=True))\n\n        # 正确参数 -> 移除成功\n        resp = self.post(self.remove_package_url, {\n            \"uuid\": operation_uuid,\n            \"package_names\": package_name_ls\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        # 安装包历史记录标记软删除\n        queryset = UploadPackageHistory.objects.filter(\n            operation_uuid=operation_uuid, package_name__in=package_name_ls)\n        for history in queryset:\n            self.assertTrue(history.is_deleted)\n\n        self.destroy_upload_package_history()\n"
  },
  {
    "path": "omp_server/tests/test_hosts/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/test_hosts/test_celery_tasks.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_celery_tasks\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 20:11\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n主机相关异步任务单元测试\n\"\"\"\n\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom utils.plugin.agent_util import Agent\nfrom utils.plugin.ssh import SSH\nfrom utils.plugin.monitor_agent import MonitorAgentManager\nfrom hosts import tasks\nfrom hosts.tasks import deploy_agent\nfrom hosts.tasks import host_agent_restart\nfrom hosts.tasks import real_deploy_agent\nfrom hosts.tasks import real_host_agent_restart\nfrom hosts.tasks import deploy_monitor_agent\nfrom db_models.models import Host\n\n\nclass HostCeleryTaskTest(BaseTest):\n    \"\"\" 主机Agent的测试类 \"\"\"\n\n    def setUp(self):\n        super(HostCeleryTaskTest, self).setUp()\n        self.correct_host_data = {\n            \"instance_name\": \"mysql_instance_1\",\n            \"ip\": \"127.0.0.10\",\n            \"port\": 36000,\n            \"username\": \"root\",\n            \"password\": \"uea_xeU_d_6YHCCY7Q-e2xZolSw2z2C3KGhLY6iMdnI\",\n            \"data_folder\": \"/data\",\n            \"operate_system\": \"CentOS\",\n        }\n        self.host = Host(**self.correct_host_data)\n        self.host.save()\n\n    @mock.patch.object(Agent, \"agent_deploy\", return_value=(True, \"success\"))\n    @mock.patch.object(tasks, \"deploy_monitor_agent\", return_value=None)\n    def test_deploy_agent_success(self, *args, **kwargs):\n        \"\"\"\n        测试部署Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_agent(self.host.id), None)\n\n    @mock.patch.object(\n        Agent, \"agent_deploy\", return_value=(False, \"error_message\"))\n    @mock.patch.object(tasks, \"deploy_monitor_agent\", return_value=None)\n    def test_deploy_agent_failed(self, *args, **kwargs):\n        \"\"\"\n        测试部署Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_agent(self.host.id), None)\n\n    @mock.patch.object(\n        Agent, \"agent_deploy\", return_value=(False, \"error_message\"))\n    @mock.patch.object(tasks, \"deploy_monitor_agent\", return_value=None)\n    def test_deploy_agent_failed_with_wrong_id(self, *args, **kwargs):\n        \"\"\"\n        测试部署Agent失败，主机id错误\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_agent(1000), None)\n\n    @mock.patch.object(Agent, \"agent_deploy\", return_value=(True, \"success\"))\n    @mock.patch.object(tasks, \"deploy_monitor_agent\", return_value=None)\n    def test_real_deploy_agent_success(self, *args, **kwargs):\n        \"\"\"\n        测试部署Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(real_deploy_agent(self.host), None)\n\n    @mock.patch.object(\n        Agent, \"agent_deploy\", return_value=(False, \"error_message\"))\n    @mock.patch.object(tasks, \"deploy_monitor_agent\", return_value=None)\n    def test_real_deploy_agent_failed(self, *args, **kwargs):\n        \"\"\"\n        测试部署Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(real_deploy_agent(self.host), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_restart_agent_success(self, agent_deploy):\n        \"\"\"\n        测试重启主机Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(host_agent_restart(self.host.id), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"error_message\"))\n    def test_restart_agent_failed(self, agent_deploy):\n        \"\"\"\n        测试重启主机Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(host_agent_restart(self.host.id), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"error_message\"))\n    def test_restart_agent_failed_with_wrong_id(self, agent_deploy):\n        \"\"\"\n        测试重启主机Agent失败，主机id错误\n        :return:\n        \"\"\"\n        self.assertEqual(host_agent_restart(1000), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_real_restart_agent_success(self, agent_deploy):\n        \"\"\"\n        测试重启主机Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(real_host_agent_restart(self.host), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"error_message\"))\n    def test_real_restart_agent_failed(self, agent_deploy):\n        \"\"\"\n        测试重启主机Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(real_host_agent_restart(self.host), None)\n\n    @mock.patch.object(\n        MonitorAgentManager, \"install\", return_value=(True, \"error_message\"))\n    def test_deploy_monitor_agent_success(self, *args, **kwargs):\n        \"\"\"\n        测试部署监控Agent函数 成功情况\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_monitor_agent(self.host, True), None)\n\n    @mock.patch.object(\n        MonitorAgentManager, \"install\", return_value=(True, \"error_message\"))\n    def test_deploy_monitor_agent_false(self, *args, **kwargs):\n        \"\"\"\n        测试部署监控Agent函数 成功情况\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_monitor_agent(self.host, False), None)\n\n    @mock.patch.object(\n        MonitorAgentManager, \"install\", return_value=(False, \"error_message\"))\n    def test_deploy_monitor_agent_failed(self, *args, **kwargs):\n        \"\"\"\n        测试部署监控Agent函数 成功情况\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        self.assertEqual(deploy_monitor_agent(self.host, True), None)\n"
  },
  {
    "path": "omp_server/tests/test_hosts/test_hosts.py",
    "content": "import random\nimport string\nfrom datetime import datetime\nfrom unittest import mock\n\nfrom django.http.response import FileResponse\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import (\n    HostsResourceMixin, HostBatchRequestMixin,\n    GrafanaMainPageResourceMixin\n)\nfrom hosts.views import HostListView\nfrom hosts.tasks import (\n    host_agent_restart, insert_host_celery_task\n)\nfrom hosts.hosts_serializers import HostSerializer\nfrom db_models.models import (\n    Host, HostOperateLog\n)\nfrom utils.plugin.ssh import SSH\nfrom utils.plugin.crypto import AESCryptor\nfrom promemonitor.prometheus import Prometheus\nfrom promemonitor.alertmanager import Alertmanager\n\n\nclass CreateHostTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 创建主机测试类 \"\"\"\n\n    def setUp(self):\n        super(CreateHostTest, self).setUp()\n        self.create_host_url = reverse(\"hosts-list\")\n        # 正确主机数据\n        self.correct_host_data = {\n            \"instance_name\": \"mysql_instance_1\",\n            \"ip\": \"127.0.0.10\",\n            \"port\": 36000,\n            \"username\": \"root\",\n            \"password\": \"root_password\",\n            \"data_folder\": \"/data\",\n            \"operate_system\": \"CentOS\",\n        }\n\n    def test_error_field_instance_name(self):\n        \"\"\" 测试错误字段校验，instance_name \"\"\"\n\n        # 不提供 instance_name -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"instance_name\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[instance_name]字段\",\n            \"data\": None\n        })\n\n        # instance_name 超过长度 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update(\n            {\"instance_name\": \"north_host_instance_name_mysql_node_one\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名长度需小于16\",\n            \"data\": None\n        })\n\n        # instance_name 含中文 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"instance_name\": \"mysql实例节点1\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名不可含有中文\",\n            \"data\": None\n        })\n\n        # instance_name 含有表情 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"instance_name\": \"mysql😃1\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名不可含有表情\",\n            \"data\": None\n        })\n\n        # instance_name 不以字母、数字、- 开头 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"instance_name\": \"$mysql-01\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名格式不合法\",\n            \"data\": None\n        })\n\n        # instance_name 已存在 -> 创建失败\n        host_obj = self.get_hosts(1)[0]\n        data = self.correct_host_data.copy()\n        data.update({\"instance_name\": host_obj.instance_name})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名已经存在\",\n            \"data\": None\n        })\n        self.destroy_hosts()\n\n    def test_error_field_ip(self):\n        \"\"\" 测试错误字段校验，ip \"\"\"\n\n        # 不提供 ip -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"ip\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[ip]字段\",\n            \"data\": None\n        })\n\n        # ip 格式不规范 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"ip\": \"120.100.80\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"IP格式不合法\",\n            \"data\": None\n        })\n\n        # ip 已存在 -> 创建失败\n        host_obj = self.get_hosts(1)[0]\n        data = self.correct_host_data.copy()\n        data.update({\"ip\": host_obj.ip})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"IP已经存在\",\n            \"data\": None\n        })\n        self.destroy_hosts()\n\n    def test_error_field_port(self):\n        \"\"\" 测试错误字段校验，port \"\"\"\n\n        # 不提供 port -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"port\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[port]字段\",\n            \"data\": None\n        })\n\n        # port 超过范围 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"port\": 66666})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"端口超出指定范围\",\n            \"data\": None\n        })\n\n    def test_error_field_username(self):\n        \"\"\" 测试错误字段校验，username \"\"\"\n\n        # 不提供 username -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"username\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[username]字段\",\n            \"data\": None\n        })\n\n        # username 超过指定长度 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"username\": \"this_is_a_too_lang_username\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"用户名长度需小于16\",\n            \"data\": None\n        })\n\n        # username 不以数字、字母、_ 开头 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"username\": \"$my_username\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"用户名格式不合法\",\n            \"data\": None\n        })\n\n    def test_error_field_password(self):\n        \"\"\" 测试错误字段校验，password \"\"\"\n\n        # 不提供 password -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"password\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[password]字段\",\n            \"data\": None\n        })\n\n        # password 小于指定长度 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"password\": \"pass11\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"密码长度需大于8\",\n            \"data\": None\n        })\n\n        # password 超过指定长度 -> 创建失败\n        data = self.correct_host_data.copy()\n        to_long_password = ''.join(random.choice(\n            string.ascii_letters) for _ in range(70))\n        data.update({\"password\": to_long_password})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"密码长度需小于64\",\n            \"data\": None\n        })\n\n        # password 含有中文 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"password\": \"mysql节点密码\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"密码不可含有中文\",\n            \"data\": None\n        })\n\n        # password 含有表情 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"password\": \"password😊mysql\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"密码不可含有表情\",\n            \"data\": None\n        })\n\n    def test_error_field_data_folder(self):\n        \"\"\" 测试错误字段校验，data_folder \"\"\"\n\n        # 不提供 data_folder -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"data_folder\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[data_folder]字段\",\n            \"data\": None\n        })\n\n        # data_folder 不以 '/' 开头 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"data_folder\": \"data\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"数据分区格式不合法\",\n            \"data\": None\n        })\n\n        # data_folder 目录以 '-' 开头 -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"data_folder\": \"/data/-myDir\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"数据分区目录不能以'-'开头\",\n            \"data\": None\n        })\n\n    def test_error_field_operate_system(self):\n        \"\"\" 测试错误字段校验，operate_system \"\"\"\n\n        # 不提供 operate_system -> 创建失败\n        data = self.correct_host_data.copy()\n        data.pop(\"operate_system\")\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"必须包含[operate_system]字段\",\n            \"data\": None\n        })\n\n        # 不支持的 operate_system -> 创建失败\n        data = self.correct_host_data.copy()\n        data.update({\"operate_system\": \"SUSE\"})\n        resp = self.post(self.create_host_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"操作系统支持CentOS/RedHat\",\n            \"data\": None\n        })\n\n    @mock.patch.object(SSH, \"check\", return_value=(False, \"error message\"))\n    def test_wrong_ssh(self, ssh_mock):\n        \"\"\" 测试创建主机，SSH 校验未通过\"\"\"\n\n        # 正确字段，ssh 校验未通过 -> 创建失败\n        resp = self.post(self.create_host_url, self.correct_host_data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"SSH登录失败\",\n            \"data\": None\n        })\n\n    # @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    # @mock.patch.object(SSH, \"is_sudo\", return_value=(False, \"is sudo\"))\n    # def test_wrong_username(self, si_sudo, ssh_mock):\n    #     \"\"\" 测试创建主机，SSH 用户 sudo 权限未通过 \"\"\"\n    #\n    #     # 正确字段，ssh 校验未通过 -> 创建失败\n    #     resp = self.post(self.create_host_url, self.correct_host_data).json()\n    #     self.assertDictEqual(resp, {\n    #         \"code\": 1,\n    #         \"message\": \"用户权限错误，请使用root或具备sudo免密用户\",\n    #         \"data\": None\n    #     })\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_correct_field(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试正确字段 \"\"\"\n\n        # 正确字段 -> 创建成功\n        resp = self.post(self.create_host_url, self.correct_host_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        host_info = resp.get(\"data\")\n        self.assertIsNotNone(host_info)\n        for k, v in self.correct_host_data.items():\n            # 密码字段加密处理，不相等\n            if k == \"password\":\n                self.assertNotEqual(host_info.get(k), v)\n                continue\n            # 各字段值相等\n            self.assertEqual(host_info.get(k), v)\n        # 服务数和告警为 0\n        self.assertEqual(host_info.get(\"service_num\"), 0)\n        self.assertEqual(host_info.get(\"alert_num\"), 0)\n        # 主机 Agent 和监控 Agent 默认为部署中\n        self.assertEqual(\n            host_info.get(\"host_agent\"),\n            Host.AGENT_DEPLOY_ING)\n        self.assertEqual(\n            host_info.get(\"monitor_agent\"),\n            Host.AGENT_DEPLOY_ING)\n        # 维护模式默认不开启\n        self.assertEqual(host_info.get(\"is_maintenance\"), False)\n\n        # 数据库 -> 主机存在\n        host_obj = Host.objects.filter(id=host_info.get(\"id\")).first()\n        self.assertIsNotNone(host_obj)\n        # 密码字段 -> 加密处理\n        self.assertNotEqual(\n            host_obj.password,\n            self.correct_host_data.get(\"password\")\n        )\n        aes = AESCryptor()\n        self.assertEqual(\n            aes.decode(host_obj.password),\n            self.correct_host_data.get(\"password\")\n        )\n\n        # 软删除字段 -> False\n        self.assertEqual(host_obj.is_deleted, False)\n\n        # 删除主机\n        host_obj.delete(soft=False)\n\n\nclass ListHostTest(AutoLoginTest, HostsResourceMixin, GrafanaMainPageResourceMixin):\n    \"\"\" 主机列表测试类 \"\"\"\n\n    def setUp(self):\n        super(ListHostTest, self).setUp()\n        self.create_host_url = reverse(\"hosts-list\")\n        self.list_host_url = reverse(\"hosts-list\")\n        self.get_grafana_main_pages()\n        self.host_obj_ls = self.get_hosts(50)\n\n    def tearDown(self):\n        super(ListHostTest, self).tearDown()\n        self.destroy_hosts()\n\n    @staticmethod\n    def mock_prometheus_info(host_obj_ls):\n        \"\"\" 模拟 prometheus 返回数据 \"\"\"\n        for host in host_obj_ls:\n            host.update({\n                \"cpu_usage\": random.choice(\n                    [None, random.randint(0, 100)]),\n                \"mem_usage\": random.choice(\n                    [None, random.randint(0, 100)]),\n                \"root_disk_usage\": random.choice(\n                    [None, random.randint(0, 100)]),\n                \"data_disk_status\": random.choice(\n                    [None, random.randint(0, 100)]),\n                \"cpu_status\": random.choice(\n                    [None, random.choice(Prometheus.STATUS)]),\n                \"mem_status\": random.choice(\n                    [None, random.choice(Prometheus.STATUS)]),\n                \"data_disk_usage\": random.choice(\n                    [None, random.choice(Prometheus.STATUS)]),\n                \"root_disk_status\": random.choice(\n                    [None, random.choice(Prometheus.STATUS)]),\n            })\n        return host_obj_ls\n\n    def test_hosts_list_filter(self):\n        \"\"\" 测试主机列表过滤 \"\"\"\n\n        # 查询主机列表 -> 展示所有主机\n        resp = self.get(self.list_host_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get(\"data\"))\n        # 数据总量为所有主机数\n        self.assertEqual(resp.get(\"data\").get(\"count\"), len(self.host_obj_ls))\n\n        # IP 过滤主机 -> 展示 IP 模糊匹配项\n        ip_field = str(random.randint(1, 50))\n        resp = self.get(self.list_host_url, {\n            \"ip\": ip_field\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get(\"data\"))\n        count_number = Host.objects.filter(ip__contains=ip_field).count()\n        self.assertEqual(resp.get(\"data\").get(\"count\"), count_number)\n\n    def test_hosts_list_order(self):\n        \"\"\" 测试主机列表排序 \"\"\"\n\n        # 不传递排序字段 -> 默认按照主机创建时间排序\n        resp = self.get(self.list_host_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        res_ls = resp.get(\"data\").get(\"results\")\n        sorted_res_ls = res_ls[:]\n        random.shuffle(sorted_res_ls)\n        sorted_res_ls = sorted(\n            sorted_res_ls,\n            key=lambda x: datetime.strptime(\n                x.get(\"created\"), \"%Y-%m-%dT%H:%M:%S.%f\"),\n            reverse=True)\n        self.assertEqual(res_ls, sorted_res_ls)\n\n        # 指定字段排序 -> 返回排序后的列表\n        reverse_flag = random.choice((\"\", \"-\"))\n        order_field = random.choice(HostListView.ordering_fields)\n        resp = self.get(self.list_host_url, {\n            \"ordering\": f\"{reverse_flag}{order_field}\"\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        res_ls = list(map(lambda x: x.get(order_field),\n                          resp.get(\"data\").get(\"results\")))\n        sorted_res_ls = res_ls[:]\n        random.shuffle(sorted_res_ls)\n        sorted_res_ls = sorted(\n            sorted_res_ls,\n            reverse=True if reverse_flag else False)\n        self.assertEqual(res_ls, sorted_res_ls)\n\n        # 指定动态排序字段 -> 返回值为None的不参与排序\n        reverse_flag = random.choice((\"\", \"-\"))\n        order_field = random.choice(HostListView.dynamic_fields)\n        host_obj_ls = HostSerializer(Host.objects.all(), many=True).data\n        with mock.patch.object(Prometheus, \"get_host_info\") as mock_prometheus_info:\n            mock_prometheus_info.return_value = self.mock_prometheus_info(\n                host_obj_ls)\n            resp = self.get(self.list_host_url, {\n                \"ordering\": f\"{reverse_flag}{order_field}\"\n            }).json()\n            # 返回值为 None 的数据不参与排序，排在末尾位置\n            res_ls = list(map(lambda x: x.get(order_field),\n                              resp.get(\"data\").get(\"results\")))\n            none_number = res_ls.count(None)\n            self.assertTrue(not any(res_ls[-none_number:]))\n            res_ls = list(filter(lambda x: x is not None, res_ls))\n            sorted_res_ls = res_ls[:]\n            random.shuffle(sorted_res_ls)\n            sorted_res_ls = sorted(\n                sorted_res_ls,\n                reverse=True if reverse_flag else False)\n            self.assertEqual(res_ls, sorted_res_ls)\n\n\nclass HostDetailTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 主机详情测试类 \"\"\"\n\n    def setUp(self):\n        super(HostDetailTest, self).setUp()\n        self.host_obj_ls = self.get_hosts()\n\n    def tearDown(self):\n        super(HostDetailTest, self).tearDown()\n        self.destroy_hosts()\n\n    def test_host_detail(self):\n        \"\"\" 测试主机详情 \"\"\"\n        resp = self.get(reverse(\"hostsDetail-detail\", [\n            random.choice(self.host_obj_ls).id\n        ])).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get(\"data\"))\n\n\nclass UpdateHostTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 更新主机测试类 \"\"\"\n\n    def setUp(self):\n        super(UpdateHostTest, self).setUp()\n        self.host_obj_ls = self.get_hosts()\n\n    def tearDown(self):\n        super(UpdateHostTest, self).tearDown()\n        self.destroy_hosts()\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    def test_update_host(self, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试更新一个主机 \"\"\"\n        # 更新不存在主机 -> 更新失败\n        resp = self.put(reverse(\"hosts-detail\", [9999]), {\n            \"instance_name\": \"mysql_instance_1\",\n            \"ip\": \"127.0.0.255\",\n            \"port\": 36000,\n            \"username\": \"root\",\n            \"password\": \"root_password\",\n            \"data_folder\": \"/data\",\n            \"operate_system\": \"CentOS\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"未找到\",\n            \"data\": None\n        })\n\n        # 更新已存在主机，修改主机 IP -> 更新失败\n        host_obj = random.choice(self.host_obj_ls)\n        resp = self.put(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"instance_name\": host_obj.instance_name,\n            \"ip\": \"127.0.0.255\",\n            \"port\": host_obj.port,\n            \"username\": host_obj.username,\n            \"password\": AESCryptor().decode(host_obj.password),\n            \"data_folder\": host_obj.data_folder,\n            \"operate_system\": host_obj.operate_system,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"IP不可修改\",\n            \"data\": None\n        })\n\n        # 更新已存在主机，修改实例名为已存在 -> 更新失败\n        host_obj = random.choice(self.host_obj_ls)\n        host_queryset = Host.objects.exclude(\n            instance_name=host_obj.instance_name)\n        exists_name = random.choice(host_queryset).instance_name\n        resp = self.put(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"instance_name\": exists_name,\n            \"ip\": host_obj.ip,\n            \"port\": host_obj.port,\n            \"username\": host_obj.username,\n            \"password\": AESCryptor().decode(host_obj.password),\n            \"data_folder\": host_obj.data_folder,\n            \"operate_system\": host_obj.operate_system,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名已经存在\",\n            \"data\": None\n        })\n\n        # 正确修改数据 -> 修改成功\n        host_obj = random.choice(self.host_obj_ls)\n        resp = self.put(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"instance_name\": \"new_host_name\",\n            \"ip\": host_obj.ip,\n            \"port\": host_obj.port,\n            \"username\": \"new_username\",\n            \"password\": \"new_password\",\n            \"data_folder\": host_obj.data_folder,\n            \"operate_system\": host_obj.operate_system,\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        new_host_info = resp.get(\"data\")\n        # 数据已更新\n        self.assertEqual(new_host_info.get(\"instance_name\"), \"new_host_name\")\n        # 更新时间变化\n        self.assertNotEqual(\n            host_obj.modified,\n            Host.objects.filter(id=host_obj.id).first().modified)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    def test_partial_update_host(self, cmd, is_sudo, ssh_mock):\n        \"\"\" 更新一个现有主机的一个或多个字段 \"\"\"\n\n        # 更新不存在主机 -> 更新失败\n        resp = self.patch(reverse(\"hosts-detail\", [9999]), {\n            \"instance_name\": \"new_host_name\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"未找到\",\n            \"data\": None\n        })\n\n        # 更新已存在主机，修改主机 IP -> 更新失败\n        host_obj = random.choice(self.host_obj_ls)\n        resp = self.patch(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"ip\": \"120.100.80.60\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"IP不可修改\",\n            \"data\": None\n        })\n\n        # 更新已存在主机，修改实例名为已存在 -> 更新失败\n        host_obj = random.choice(self.host_obj_ls)\n        host_queryset = Host.objects.exclude(\n            instance_name=host_obj.instance_name)\n        exists_name = random.choice(host_queryset).instance_name\n        resp = self.patch(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"instance_name\": exists_name,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"实例名已经存在\",\n            \"data\": None\n        })\n\n        # 正确修改数据 -> 修改成功\n        host_obj = random.choice(self.host_obj_ls)\n        resp = self.patch(reverse(\"hosts-detail\", [host_obj.id]), {\n            \"instance_name\": \"new_host_name\",\n            \"username\": \"new_username\",\n            \"password\": \"new_password\",\n        }).json()\n        # self.assertEqual(resp.get(\"code\"), 0)\n        # self.assertEqual(resp.get(\"message\"), \"success\")\n        # new_host_obj = resp.get(\"data\")\n        # self.assertIsNotNone(new_host_obj)\n        # # 数据已更新\n        # self.assertEqual(new_host_obj.get(\"instance_name\"), \"new_host_name\")\n        # # 更新时间变化\n        # self.assertNotEqual(\n        #     host_obj.modified,\n        #     Host.objects.filter(id=host_obj.id).first().modified)\n\n\nclass HostFieldCheckTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 主机字段校验测试类 \"\"\"\n\n    def setUp(self):\n        super(HostFieldCheckTest, self).setUp()\n        self.field_check_url = reverse(\"fields-list\")\n        self.host_obj_ls = self.get_hosts()\n\n    def tearDown(self):\n        self.destroy_hosts()\n\n    def test_create_host_check(self):\n        \"\"\" 测试创建主机场景 \"\"\"\n\n        # instance_name 重复 -> 验证结果 False\n        resp = self.post(self.field_check_url, {\n            \"instance_name\": random.choice(self.host_obj_ls).instance_name\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": False\n        })\n\n        # instance_name 不重复 -> 验证结果 True\n        resp = self.post(self.field_check_url, {\n            \"instance_name\": \"my_host_name\"\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": True\n        })\n\n        # ip 重复 -> 验证结果 False\n        resp = self.post(self.field_check_url, {\n            \"ip\": random.choice(self.host_obj_ls).ip\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": False\n        })\n\n        # ip 不重复 -> 验证结果 True\n        resp = self.post(self.field_check_url, {\n            \"ip\": \"123.1.2.3\"\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": True\n        })\n\n    def test_error_host_check(self):\n        \"\"\" 测试更新主机场景 \"\"\"\n        host_obj_one = random.choice(self.host_obj_ls)\n        host_queryset = Host.objects.exclude(\n            instance_name=host_obj_one.instance_name)\n        host_obj_two = random.choice(host_queryset)\n\n        # instance_name 重复 (为主机自身 instance_name) -> 验证结果 True\n        resp = self.post(self.field_check_url, {\n            \"id\": host_obj_one.id,\n            \"instance_name\": host_obj_one.instance_name\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": True\n        })\n\n        # instance_name 重复 (为其他主机 instance_name) -> 验证结果 False\n        resp = self.post(self.field_check_url, {\n            \"id\": host_obj_one.id,\n            \"instance_name\": host_obj_two.instance_name\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": False\n        })\n\n        # ip 重复 (为主机自身 ip) -> 验证结果 True\n        resp = self.post(self.field_check_url, {\n            \"id\": host_obj_one.id,\n            \"ip\": host_obj_one.ip\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": True\n        })\n\n        # ip 重复 (为其他主机 ip) -> 验证结果 False\n        resp = self.post(self.field_check_url, {\n            \"id\": host_obj_one.id,\n            \"ip\": host_obj_two.ip\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": False\n        })\n\n\nclass ListIPTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" IP 列表测试类 \"\"\"\n\n    def setUp(self):\n        super(ListIPTest, self).setUp()\n        self.ip_list_url = reverse(\"ips-list\")\n        self.get_hosts()\n\n    def tearDown(self):\n        super(ListIPTest, self).tearDown()\n        self.destroy_hosts()\n\n    def test_ip_list(self):\n        \"\"\" 测试 IP 列表 \"\"\"\n\n        # 查询主机列表 -> 返回所有主机列表数据\n        resp = self.get(self.ip_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            set(resp.get(\"data\")),\n            set(Host.objects.all().values_list(\"ip\", flat=True)))\n\n\nclass HostMaintainTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 主机维护模式测试类 \"\"\"\n\n    def setUp(self):\n        super(HostMaintainTest, self).setUp()\n        self.host_maintain_url = reverse(\"maintain-list\")\n        self.host_obj_ls = self.get_hosts()\n\n    def tearDown(self):\n        super(HostMaintainTest, self).tearDown()\n        self.destroy_hosts()\n\n    def test_error_field(self):\n        \"\"\" 测试错误字段校验 \"\"\"\n        host_obj_id_ls = list(map(lambda x: x.id, self.host_obj_ls))\n\n        # host_ids 中含不存在的 ID -> 修改失败\n        not_exists_id = 9999\n        random_host_ls = random.sample(host_obj_id_ls, 5)\n        random_host_ls.append(not_exists_id)\n        resp = self.post(self.host_maintain_url, {\n            \"is_maintenance\": True,\n            \"host_ids\": random_host_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": f\"主机列表中有不存在的ID [{not_exists_id}]\",\n            \"data\": None\n        })\n\n        # host_ids 中存在已经处于 type 类型的主机 -> 创建失败\n        random_host_ls = random.sample(host_obj_id_ls, 5)\n        resp = self.post(self.host_maintain_url, {\n            \"is_maintenance\": False,\n            \"host_ids\": random_host_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"主机列表中存在已 '关闭' 维护模式的主机\",\n            \"data\": None\n        })\n\n    @mock.patch.object(Alertmanager, \"set_maintain_by_host_list\", return_value=[1, 2, 3])\n    @mock.patch.object(Alertmanager, \"revoke_maintain_by_host_list\", return_value=[1, 2, 3])\n    def test_correct_field(self, mock_down, mock_up):\n        \"\"\" 正确字段校验 \"\"\"\n        random_host_ls = random.sample(list(self.host_obj_ls), 5)\n        random_host_id_ls = list(map(lambda x: x.id, random_host_ls))\n\n        # 开启维护模式 -> 开启成功，记录操作\n        data = {\n            \"is_maintenance\": True,\n            \"host_ids\": random_host_id_ls\n        }\n        resp = self.post(self.host_maintain_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": data\n        })\n        # host_ids中主机，is_maintenance 状态均为 True\n        is_maintenance_ls = Host.objects.filter(\n            id__in=random_host_id_ls\n        ).values_list(\"is_maintenance\", flat=True)\n        self.assertTrue(all(is_maintenance_ls))\n        # 主机操作日志含有操作记录\n        operate_log_ls = HostOperateLog.objects.filter(\n            host__in=random_host_ls,\n            description=\"开启[维护模式]\")\n        self.assertEqual(len(random_host_id_ls), len(operate_log_ls))\n        self.assertEqual(\n            len(operate_log_ls),\n            len(operate_log_ls.filter(result=\"success\")))\n\n        # 关闭维护模式\n        data = {\n            \"is_maintenance\": False,\n            \"host_ids\": random_host_id_ls\n        }\n        resp = self.post(self.host_maintain_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": data\n        })\n        # host_ids中主机，is_maintenance 状态均为 False\n        is_maintenance_ls = Host.objects.filter(\n            id__in=random_host_id_ls\n        ).values_list(\"is_maintenance\", flat=True)\n        self.assertTrue(not any(is_maintenance_ls))\n        # 主机操作日志含有操作记录\n        operate_log_ls = HostOperateLog.objects.filter(\n            host__in=random_host_ls,\n            description=\"关闭[维护模式]\")\n        self.assertEqual(len(random_host_id_ls), len(operate_log_ls))\n        self.assertEqual(\n            len(operate_log_ls),\n            len(operate_log_ls.filter(result=\"success\")))\n\n    @mock.patch.object(Alertmanager, \"set_maintain_by_host_list\", return_value=None)\n    @mock.patch.object(Alertmanager, \"revoke_maintain_by_host_list\", return_value=None)\n    def test_alert_manager_error(self, mock_down, mock_up):\n        \"\"\" alert manage 返回值异常 \"\"\"\n\n        random_host_ls = random.sample(list(self.host_obj_ls), 5)\n        random_host_id_ls = list(map(lambda x: x.id, random_host_ls))\n\n        # 开始维护模式 -> 开启失败，记录操作\n        resp = self.post(self.host_maintain_url, {\n            \"is_maintenance\": True,\n            \"host_ids\": random_host_id_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"主机'开启'维护模式失败\",\n            \"data\": None\n        })\n        # host_ids中主机，is_maintenance 状态均为 False\n        is_maintenance_ls = Host.objects.filter(\n            id__in=random_host_id_ls\n        ).values_list(\"is_maintenance\", flat=True)\n        self.assertTrue(not any(is_maintenance_ls))\n        # 主机操作日志含有操作记录\n        operate_log_ls = HostOperateLog.objects.filter(\n            host__in=random_host_ls,\n            description=\"开启[维护模式]\")\n        self.assertEqual(len(random_host_id_ls), len(operate_log_ls))\n        self.assertEqual(\n            len(operate_log_ls),\n            len(operate_log_ls.filter(result=\"failed\")))\n\n        # 关闭维护模式 -> 关闭失败，记录操作\n        random_host_ls = random.sample(list(self.host_obj_ls), 5)\n        random_host_id_ls = list(map(lambda x: x.id, random_host_ls))\n        Host.objects.filter(\n            id__in=random_host_id_ls\n        ).update(is_maintenance=True)\n        resp = self.post(self.host_maintain_url, {\n            \"is_maintenance\": False,\n            \"host_ids\": random_host_id_ls\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"主机'关闭'维护模式失败\",\n            \"data\": None\n        })\n        # host_ids中主机，is_maintenance 状态均为 True\n        is_maintenance_ls = Host.objects.filter(\n            id__in=random_host_id_ls\n        ).values_list(\"is_maintenance\", flat=True)\n        self.assertTrue(all(is_maintenance_ls))\n        # 主机操作日志含有操作记录\n        operate_log_ls = HostOperateLog.objects.filter(\n            host__in=random_host_ls,\n            description=\"关闭[维护模式]\")\n        self.assertEqual(len(random_host_id_ls), len(operate_log_ls))\n        self.assertEqual(\n            len(operate_log_ls),\n            len(operate_log_ls.filter(result=\"failed\")))\n\n\nclass HostAgentRestartTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 主机 agent 重启测试类 \"\"\"\n\n    def setUp(self):\n        super(HostAgentRestartTest, self).setUp()\n        self.host_restartHostAgent_url = reverse(\"restartHostAgent-list\")\n        self.host_obj_ls = self.get_hosts(2)\n\n    def tearDown(self):\n        super(HostAgentRestartTest, self).tearDown()\n        self.destroy_hosts()\n\n    @mock.patch.object(host_agent_restart, \"delay\", return_value=None)\n    def test_success(self, host_agent_restart_mock):\n        \"\"\" 请求成功测试 \"\"\"\n\n        host_obj_id_ls = list(map(lambda x: x.id, self.host_obj_ls))\n        resp = self.post(\n            self.host_restartHostAgent_url,\n            data={\"host_ids\": host_obj_id_ls}\n        ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": {\n                \"host_ids\": host_obj_id_ls\n            }\n        })\n\n    @mock.patch.object(host_agent_restart, \"delay\", return_value=None)\n    def test_failed(self, host_agent_restart_mock):\n        \"\"\" 请求失败测试 \"\"\"\n\n        resp = self.post(\n            self.host_restartHostAgent_url,\n            data={\"host_ids\": [random.randint(10000, 20000)]}\n        ).json()\n        self.assertEqual(resp.get(\"code\"), 1)\n\n\nclass HostBatchValidateTest(AutoLoginTest, HostsResourceMixin, HostBatchRequestMixin):\n    \"\"\" 主机批量校验测试类 \"\"\"\n\n    def setUp(self):\n        super(HostBatchValidateTest, self).setUp()\n        self.get_template_url = reverse(\"batchValidate-list\")\n        self.batch_validate_url = reverse(\"batchValidate-list\")\n\n    @staticmethod\n    def create_repeat_data(host_list, field_name):\n        \"\"\" 创建重复数据 \"\"\"\n        instance_name = \"mysql_{}\"\n        ip = \"10.0.0.{}\"\n        repeat_number = random.randint(2, 5)\n        if field_name == \"instance_name\" or field_name == \"all\":\n            instance_name = host_list[repeat_number].get(\"instance_name\")\n        if field_name == \"ip\" or field_name == \"all\":\n            ip = host_list[repeat_number].get(\"ip\")\n        for i in range(repeat_number):\n            host_list.append({\n                \"instance_name\": instance_name.format(i),\n                \"ip\": ip.format(i),\n                \"port\": 36000,\n                \"username\": \"root\",\n                \"password\": \"root_password\",\n                \"data_folder\": \"/data\",\n                \"operate_system\": random.choice((\"CentOS\", \"RedHat\")),\n                # \"row\": i * 100\n            })\n        return host_list, repeat_number\n\n    def test_get_host_batch_template(self):\n        \"\"\" 获取主机批量导入模板 \"\"\"\n\n        # 获取主机批量导入模板 -> 返回文件\n        resp = self.get(self.get_template_url)\n        self.assertEqual(resp.status_code, 200)\n        self.assertTrue(isinstance(resp, FileResponse))\n        self.assertTrue(resp.streaming)\n        self.assertIsNotNone(resp.streaming_content)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_error_format(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试错误格式 \"\"\"\n\n        # 格式错误 -> 添加失败\n        data = self.get_host_batch_request(10, row=True)\n        data[\"host_list\"].append(12345)\n        resp = self.post(self.batch_validate_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"数据格式错误\",\n            \"data\": None\n        })\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_batch_validate_error_field(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试批量校验错误字段 \"\"\"\n\n        host_number = 10\n        # 存在实例名重复 -> 返回值 error 中包含错误信息\n        data = self.get_host_batch_request(host_number, row=True)\n        data[\"host_list\"], repeat_number = self.create_repeat_data(\n            data.get(\"host_list\"), \"instance_name\")\n        resp = self.post(self.batch_validate_url, data).json()\n        error_ls = resp.get(\"data\").get(\"error\", [])\n        self.assertEqual(len(error_ls), repeat_number + 1)\n        for error_host_info in error_ls:\n            self.assertEqual(\n                error_host_info.get(\"validate_error\"),\n                \"实例名在表格中重复\"\n            )\n\n        #  存在IP重复 -> 返回值 error 中包含错误信息\n        data = self.get_host_batch_request(host_number, row=True)\n        data[\"host_list\"], repeat_number = self.create_repeat_data(\n            data.get(\"host_list\"), \"ip\")\n        resp = self.post(self.batch_validate_url, data).json()\n        error_ls = resp.get(\"data\").get(\"error\", [])\n        self.assertEqual(len(error_ls), repeat_number + 1)\n        for error_host_info in error_ls:\n            self.assertEqual(\n                error_host_info.get(\"validate_error\"),\n                \"IP在表格中重复\"\n            )\n\n        # 存在实例名、IP混合重复 -> 返回值 error 中包含错误信息\n        data = self.get_host_batch_request(host_number, row=True)\n        data[\"host_list\"], repeat_number = self.create_repeat_data(\n            data.get(\"host_list\"), \"all\")\n        resp = self.post(self.batch_validate_url, data).json()\n        error_ls = resp.get(\"data\").get(\"error\", [])\n        self.assertEqual(len(error_ls), repeat_number + 1)\n        for error_host_info in error_ls:\n            self.assertEqual(\n                error_host_info.get(\"validate_error\"),\n                \"实例名、IP在表格中重复\"\n            )\n\n        # 测试主机数据信息不合法 -> 返回值 error 中包含错误信息\n        data = self.get_host_batch_request(host_number, row=True)\n        error_index = random.randint(0, host_number - 1)\n        data.get(\"host_list\")[error_index][\"instance_name\"] = \"中文实例名\"\n        resp = self.post(self.batch_validate_url, data).json()\n        error_ls = resp.get(\"data\").get(\"error\", [])\n        self.assertEqual(len(error_ls), 1)\n        self.assertEqual(\n            error_ls[0].get(\"validate_error\"),\n            \"实例名不可含有中文; 实例名格式不合法\")\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_batch_validate_correct_field(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试批量校验正确字段 \"\"\"\n\n        # 正确字段 -> 返回值全部包含于 correct ，error 中无数据\n        host_number = 10\n        data = self.get_host_batch_request(host_number, row=True)\n        resp = self.post(self.batch_validate_url, data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        correct_ls = resp.get(\"data\").get(\"correct\", [])\n        error_ls = resp.get(\"data\").get(\"error\", [])\n        self.assertEqual(len(correct_ls), host_number)\n        self.assertEqual(len(error_ls), 0)\n        # 返回结果按照 row 进行排序\n        self.assertEqual(\n            correct_ls,\n            list(sorted(correct_ls, key=lambda x: x.get(\"row\")))\n        )\n\n\nclass HostBatchImportTest(AutoLoginTest, HostsResourceMixin, HostBatchRequestMixin):\n    \"\"\" 主机批量导入测试类 \"\"\"\n\n    def setUp(self):\n        super(HostBatchImportTest, self).setUp()\n        self.batch_import_url = reverse(\"batchImport-list\")\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_error_format(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试错误格式 \"\"\"\n\n        # 格式错误 -> 添加失败\n        data = self.get_host_batch_request(10, row=True)\n        data[\"host_list\"].append(12345)\n        resp = self.post(self.batch_import_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"数据格式错误\",\n            \"data\": None\n        })\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"\"))\n    @mock.patch.object(SSH, \"is_sudo\", return_value=(True, \"is sudo\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"\"))\n    @mock.patch.object(insert_host_celery_task, \"delay\", return_value=None)\n    def test_batch_import(self, celery_task_mock, cmd_mock, is_sudo, ssh_mock):\n        \"\"\" 测试批量添加主机 \"\"\"\n\n        # 批量添加主机 -> 添加成功\n        data = self.get_host_batch_request(10, row=True)\n        resp = self.post(self.batch_import_url, data).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": \"添加成功\"\n        })\n"
  },
  {
    "path": "omp_server/tests/test_inspection/__init__.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/28 4:58 下午\n# Description:\n"
  },
  {
    "path": "omp_server/tests/test_inspection/inspection_mixin.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/29 10:26 上午\n# Description:\nimport random\nfrom db_models.models import (\n    InspectionHistory, InspectionCrontab, InspectionReport)\n\n\nclass InspectionHistoryMixin:\n    @staticmethod\n    def get_inspection_history(env):\n        h_bulk = list()\n        inspection_type = [\"host\", \"deep\", \"service\"]\n        inspection_status = [1, 2, 3]\n        for i in range(12):\n            h_dict = {'inspection_name': '深度巡检202110271',\n                      'inspection_type': random.choices(inspection_type)[0],\n                      'inspection_status': random.choices(inspection_status)[0],\n                      'execute_type': 'man', 'inspection_operator': 'admin',\n                      'hosts': [\"10.0.7.146\"], 'env': env}\n            h_bulk.append(InspectionHistory(**h_dict))\n\n        InspectionHistory.objects.bulk_create(h_bulk)\n        return h_bulk\n\n    @staticmethod\n    def create_inspection_crontab(env):\n        _ = {'crontab_detail': {'hour': \"09\", 'minute': \"41\", 'month': \"*\",\n                                'day_of_week': \"*\", 'day': \"*\"},\n             'env': env,\n             'is_start_crontab': 0,\n             'job_name': \"深度分析\",\n             'job_type': 0}\n        return InspectionCrontab.objects.create(**_)\n\n    @staticmethod\n    def update_inspection_crontab():\n        _ = {'crontab_detail': {'hour': \"09\", 'minute': \"41\", 'month': \"*\",\n                                'day_of_week': \"*\", 'day': \"*\"},\n             'env': 1,\n             'is_start_crontab': 0,\n             'job_name': \"深度分析\",\n             'job_type': 0}\n        InspectionCrontab.objects.filter(job_type=_.get('job_type')).update(**_)\n\n    @staticmethod\n    def get_inspection_crontab():\n        _ = InspectionCrontab.objects.filter(job_type=0).first()\n        return _\n\n\nclass InspectionReportMixin:\n    @staticmethod\n    def create_inspection_report(env):\n        h_dict = {'inspection_name': '深度巡检202110271',\n                  'inspection_type': 'deep',\n                  'inspection_status': '2',\n                  'execute_type': 'man', 'inspection_operator': 'admin',\n                  'hosts': [\"10.0.7.146\"], 'env': env}\n        _obj = InspectionHistory.objects.create(**h_dict)\n        InspectionReport(inst_id=_obj).save()\n        return _obj.id\n\n    @staticmethod\n    def update_inspection_report(inst_id):\n        InspectionReport.objects.filter(inst_id=inst_id).update(\n            risk_data={'host_list': [], 'service': []}\n        )\n"
  },
  {
    "path": "omp_server/tests/test_inspection/test_crontab.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/28 4:59 下午\n# Description:\nfrom tests.base import AutoLoginTest\nfrom rest_framework.reverse import reverse\nfrom tests.test_inspection.inspection_mixin import InspectionHistoryMixin\n\n\nclass TestInspectionCrontabList(AutoLoginTest, InspectionHistoryMixin):\n    def setUp(self):\n        super(TestInspectionCrontabList, self).setUp()\n        self.env = self.create_default_env()\n\n    def tearDown(self):\n        super(TestInspectionCrontabList, self).tearDown()\n\n    def test_crontab_read(self):\n        _ = self.create_inspection_crontab(self.env)\n        resp = self.get(\n            f\"{reverse('crontab-list')}0/\", data={'job_type': 0}).json()\n        self.assertEqual(_.id, resp.get('data').get('id'))\n\n    def test_crontab_create(self):\n        _ = {'crontab_detail': {'hour': \"09\", 'minute': \"41\", 'month': \"*\",\n                                'day_of_week': \"1\", 'day': \"*\"},\n             'env': self.env,\n             'is_start_crontab': 0,\n             'job_name': \"深度分析\",\n             'job_type': 0}\n        resp = self.post(reverse(\"crontab-list\"), data=_).json()\n        _obj = self.get_inspection_crontab()\n        self.assertEqual(_obj.id, resp.get('data').get('id'))\n\n    def test_crontab_update(self):\n        _ = {'env': self.env, 'is_start_crontab': 0, 'job_name': \"深度分析\",\n             'job_type': 0,\n             'crontab_detail': {'hour': \"09\", 'minute': \"41\", 'month': \"*\",\n                                'day_of_week': \"1\", 'day': \"*\"}\n             }\n        resp_add = self.post(reverse(\"crontab-list\"), data=_).json()\n        self.assertEqual(resp_add.get(\"code\"), 0)\n        self.assertEqual(resp_add.get(\"message\"), \"success\")\n        self.assertTrue(resp_add.get(\"data\") is not None)\n\n        _ = {'crontab_detail': {'hour': \"10\", 'minute': \"41\", 'month': \"*\",\n                                'day_of_week': \"1\", 'day': \"*\"},\n             'env': 1,\n             'is_start_crontab': 0,\n             'job_name': \"深度分析2\",\n             'job_type': 0}\n        resp_upd = self.put(\n            f\"{reverse('crontab-list')}0/?job_type=0\", data=_).json()\n        self.assertEqual(resp_upd.get('data').get('job_name'), \"深度分析2\")\n"
  },
  {
    "path": "omp_server/tests/test_inspection/test_history.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/28 4:59 下午\n# Description:\nfrom unittest import mock\nfrom tests.base import AutoLoginTest\nfrom rest_framework.reverse import reverse\nfrom tests.test_inspection.inspection_mixin import InspectionHistoryMixin\n\n\nclass TestInspectionHistoryList(AutoLoginTest, InspectionHistoryMixin):\n    def setUp(self):\n        super(TestInspectionHistoryList, self).setUp()\n        self.env = self.create_default_env()\n\n    def tearDown(self):\n        super(TestInspectionHistoryList, self).tearDown()\n\n    def test_history_list(self):\n        # 未找到\n        resp = self.get(reverse(\"history-list\")).json()\n        self.assertDictEqual(resp, {\n            'code': 0,\n            'message': 'success',\n            'data': {'count': 0, 'next': None, 'previous': None, 'results': []}\n        })\n\n        # 查询成功\n        self.get_inspection_history(self.env)\n        resp = self.get(reverse(\"history-list\")).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch(\"inspection.tasks.get_prometheus_data.delay\", return_value=True)\n    def test_history_post(self, tasks):\n        # 深度巡检\n        data = {'env': 1,\n                'execute_type': \"man\",\n                'hosts': {},\n                'inspection_name': \"深度巡检\",\n                'inspection_operator': \"admin\",\n                'inspection_status': '1',\n                'inspection_type': \"deep\",\n                'services': {}}\n        resp = self.post(reverse(\"history-list\"), data=data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n        # 主机巡检\n        data = {'env': 1,\n                'execute_type': \"man\",\n                'hosts': [\"10.0.9.67\"],\n                'inspection_name': \"主机巡检\",\n                'inspection_operator': \"admin\",\n                'inspection_status': '1',\n                'inspection_type': \"host\",\n                'services': {}}\n        resp = self.post(reverse(\"history-list\"), data=data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n        # 组件巡检\n        data = {'env': 1,\n                'execute_type': \"man\",\n                'hosts': {},\n                'inspection_name': \"组件巡检\",\n                'inspection_operator': \"admin\",\n                'inspection_status': '1',\n                'inspection_type': \"service\",\n                'services': [8]}\n        resp = self.post(reverse(\"history-list\"), data=data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_inspection/test_inspection_email.py",
    "content": "import json\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom tests.test_inspection.inspection_mixin import InspectionHistoryMixin\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n        self.status_code = self.status\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass InspectionEmail(AutoLoginTest, InspectionHistoryMixin):\n\n    def setUp(self):\n        super(InspectionEmail, self).setUp()\n        self.inspection_email_config_url = reverse(\n            \"inspectionSendEmailSetting-list\")\n        self.inspection_send_email_url = reverse(\"inspectionSendEmail-list\")\n\n    def tearDown(self):\n        super(InspectionEmail, self).tearDown()\n\n    def test_get_inspection_email_config(self):\n        resp = self.get(self.inspection_email_config_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    def test_update_inspection_email_config(self):\n        post_data = {\n            \"env_id\": 1,\n            \"send_email\": True,\n            \"to_users\": \"123@qq.com\"\n        }\n        resp = self.post(self.inspection_email_config_url,\n                         data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    # def test_inspection_send_email(self):\n    #     from db_models.models import Env\n    #     Env.objects.get_or_create(name=\"default\")\n    #     env = Env.objects.get(id=1)\n    #     inspection_history_objs = InspectionHistoryMixin.get_inspection_history(\n    #         env=env)\n    #     inspection_report_objs = InspectionReportMixin.create_inspection_report(\n    #         env=env)\n    #     post_data = {\n    #         \"id\": 1,\n    #         \"module\": \"deep\",\n    #         \"to_users\": \"123@qq.com\"\n    #     }\n    #     resp = self.post(self.inspection_send_email_url, data=post_data).json()\n    #     print(resp)\n    #     self.assertEqual(resp.get(\"code\"), 0)\n    #     self.assertEqual(resp.get(\"message\"), \"success\")\n    #     self.assertTrue(resp.get(\"data\") is not None)\n    #     for ih_obj in inspection_history_objs:\n    #         ih_obj.delete()\n    #     Env.objects.filter(name=\"default\").delete()\n"
  },
  {
    "path": "omp_server/tests/test_inspection/test_report.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/28 4:59 下午\n# Description:\nfrom tests.base import AutoLoginTest\nfrom rest_framework.reverse import reverse\nfrom tests.test_inspection.inspection_mixin import InspectionReportMixin\n\n\nclass TestInspectionReportList(AutoLoginTest, InspectionReportMixin):\n    def setUp(self):\n        super(TestInspectionReportList, self).setUp()\n        self.env = self.create_default_env()\n\n    def tearDown(self):\n        super(TestInspectionReportList, self).tearDown()\n\n    def test_crontab_read(self):\n        inst_id = self.create_inspection_report(self.env)\n        self.update_inspection_report(inst_id=inst_id)\n        resp = self.get(\n            f\"/api/inspection/report/{inst_id}/\", data={'inst_id': inst_id}\n        ).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_alert.py",
    "content": "import json\n\nfrom rest_framework.reverse import reverse\nfrom unittest import mock\n\nfrom tests.base import AutoLoginTest, BaseTest\nfrom db_models.models import Alert\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status_code = 0\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass AlertTest(AutoLoginTest):\n    \"\"\" 告警测试类 \"\"\"\n\n    def setUp(self):\n        super(AlertTest, self).setUp()\n        self.list_alert_url = reverse(\"listAlert-list\")\n        self.update_alert_url = reverse(\"updateAlert-list\")\n        # 正确请求数据\n        self.correct_request_data = {'is_read': 1}\n        Alert.objects.create(\n            is_read=0,\n            alert_type='host',\n            alert_host_ip='10.0.9.61',\n            alert_service_name='',\n            alert_instance_name='doim',\n            alert_service_type='',\n            alert_level='critical',\n            alert_describe='zsh',\n            alert_receiver='test',\n            alert_resolve='',\n            alert_time='2021-06-28 12:00:01',\n            create_time='2021-06-28 12:00:01',\n            monitor_path='-',\n            monitor_log='-',\n            fingerprint='',\n            # env='default'  # TODO 此版本默认不赋值\n        )\n\n    def test_get_alerts(self):\n        \"\"\" 测试获取告警记录 \"\"\"\n        resp = self.get(self.list_alert_url,\n                        data={\"start_alert_time\": \"2021-09-11 12:34:56\",\n                              \"end_alert_time\": \"2021-09-11 12:34:57\"}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        resp = self.get(self.list_alert_url,\n                        data={\"start_alert_time\": \"2021-09-11 12:34:56\"}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        error_time_resp = self.get(self.list_alert_url,\n                                   data={\"start_alert_time\": \"2021-09-11 12:34:56\", \"end_alert_time\": \"123456\"}).json()\n        self.assertEqual(error_time_resp.get(\"code\"), 0)\n        self.assertEqual(error_time_resp.get(\"message\"), \"success\")\n\n    request_post_response = {\n        \"code\": 0,\n        \"message\": \"success\",\n        \"data\": {\n            \"ids\": [\n                8,\n                9\n            ],\n            \"is_read\": 1\n        }\n    }\n\n    @mock.patch.object(BaseTest, 'post', return_value='')\n    def test_update_is_read(self, mock_post):\n        \"\"\"\n        修改已读/未读\n        \"\"\"\n        mock_post.return_value = MockResponse(self.request_post_response)\n\n        resp = self.post(self.update_alert_url,\n                         self.correct_request_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get('data'))\n\n    def tearDown(self):\n        Alert.objects.filter(alert_host_ip='10.0.9.61').delete()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_alertmanager.py",
    "content": "import json\nfrom unittest import mock\n\nimport requests\nfrom django.test import TestCase\n\nfrom promemonitor.alertmanager import Alertmanager\nfrom db_models.models import MonitorUrl, Maintain\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass TestAlertmanager(TestCase):\n    \"\"\"\n    alertmanager功能测试类\n    \"\"\"\n\n    def setUp(self):\n        MonitorUrl.objects.create(\n            name='alertmanager', monitor_url='127.0.0.1:19013')\n\n    @staticmethod\n    def return_host_list():\n        host_list = [\n            {\n                'ip': '10.0.3.71',\n                'data_folder': '/boot',\n                'cpu_usage': 0,\n                'mem_usage': 0,\n                'root_disk_usage': 0,\n                'data_disk_usage': 0,\n            },\n            {\n                'ip': '10.0.3.72',\n                'data_folder': '/boot',\n                'cpu_usage': 0,\n                'mem_usage': 0,\n                'root_disk_usage': 0,\n                'data_disk_usage': 0,\n            }\n        ]\n        return host_list\n\n    @staticmethod\n    def return_set_alertmanager_maintain_response():\n        maintain_info = MockResponse(\n            {'status': 'success', 'data': {'silenceId': 'f9aed355-8a99-42ba-9fe6-877633ea3e4a'}})\n        return maintain_info\n\n    @staticmethod\n    def return_revoke_alertmanager_maintain_response():\n        revoke_maintain_info = MockResponse('')\n        return revoke_maintain_info\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_set_maintain_by_host_list(self, mock_post):\n        mock_post.return_value = self.return_set_alertmanager_maintain_response()\n        alertmanager = Alertmanager()\n        maintain_ids = alertmanager.set_maintain_by_host_list(\n            self.return_host_list())\n        TestCase.assertIsNotNone(maintain_ids, '添加维护失败')\n        return maintain_ids\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_set_maintain_by_env_name(self, mock_post):\n        mock_post.return_value = self.return_set_alertmanager_maintain_response()\n        alertmanager = Alertmanager()\n        maintain_ids = alertmanager.set_maintain_by_env_name('default')\n        TestCase.assertIsNotNone(maintain_ids, '添加维护失败')\n        return maintain_ids\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_revoke_alertmanager_maintain_by_host_list(self, mock_post):\n        mock_post.return_value = self.return_revoke_alertmanager_maintain_response()\n        alertmanager = Alertmanager()\n        m1 = Maintain.objects.create(\n            matcher_name=\"instance\", matcher_value=\"10.0.3.71\", maintain_id=1)\n        m2 = Maintain.objects.create(\n            matcher_name=\"instance\", matcher_value=\"10.0.3.71\", maintain_id=2)\n        revoke_result = alertmanager.revoke_maintain_by_host_list(\n            self.return_host_list())\n        TestCase.assertIsNotNone(revoke_result, '删除维护失败')\n        m1.delete()\n        m2.delete()\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_revoke_alertmanager_maintain_by_env_name(self, mock_post):\n        mock_post.return_value = self.return_revoke_alertmanager_maintain_response()\n        alertmanager = Alertmanager()\n        revoke_result = alertmanager.revoke_maintain_by_env_name('default')\n        TestCase.assertIsNotNone(revoke_result, '删除维护失败')\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_error_alertmanager_func1(self, mock_post):\n        alertmanager = Alertmanager()\n        result_format_time = alertmanager.format_time(1)\n        self.assertIsNone(result_format_time)\n\n        result_add_setting = alertmanager.add_setting(\n            value=1, name='env', start_time=1, ends_time=2)\n        self.assertIsNone(result_add_setting)\n\n        result_add_setting = alertmanager.add_setting(value=1, name='env', start_time='2021-09-11 12:34:56',\n                                                      ends_time=2)\n        self.assertIsNone(result_add_setting)\n\n        mock_post.return_value = self.return_revoke_alertmanager_maintain_response()\n\n        MonitorUrl.objects.filter(name='alertmanager').delete()\n        alertmanager.get_alertmanager_config()\n\n    def test_error_alertmanager_func2(self):\n        alertmanager = Alertmanager()\n        result_set_maintain_by_env_name = alertmanager.set_maintain_by_env_name(\n            'aaa')\n        self.assertIsNone(result_set_maintain_by_env_name)\n\n        result_revoke_maintain_by_host_list = alertmanager.revoke_maintain_by_host_list([\n            {\n                'ip': '10.0.3.73',\n                'data_folder': '/boot',\n                'cpu_usage': 0,\n                'mem_usage': 0,\n                'root_disk_usage': 0,\n                'data_disk_usage': 0,\n            }])\n        self.assertEqual(result_revoke_maintain_by_host_list, False)\n\n    def tearDown(self):\n        MonitorUrl.objects.filter(name='alertmanager').delete()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_celery_tasks.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_celery_tasks\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 20:11\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom db_models.models import Host\nfrom utils.plugin.salt_client import SaltClient\nfrom promemonitor.tasks import monitor_agent_restart\nfrom promemonitor.tasks import real_monitor_agent_restart\n\n\nclass MonitorAgentRestartCeleryTaskTest(BaseTest):\n    \"\"\" 主机Agent的测试类 \"\"\"\n\n    def setUp(self):\n        super(MonitorAgentRestartCeleryTaskTest, self).setUp()\n        self.correct_host_data = {\n            \"instance_name\": \"mysql_instance_1\",\n            \"ip\": \"127.0.0.10\",\n            \"port\": 36000,\n            \"username\": \"root\",\n            \"password\": \"uea_xeU_d_6YHCCY7Q-e2xZolSw2z2C3KGhLY6iMdnI\",\n            \"data_folder\": \"/data\",\n            \"operate_system\": \"CentOS\",\n        }\n        self.host = Host(**self.correct_host_data)\n        self.host.save()\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_restart_monitor_agent_success(self, *args, **kwargs):\n        \"\"\"\n        测试重启主机Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(monitor_agent_restart(self.host.id), None)\n\n    @mock.patch.object(\n        SaltClient, \"cmd\", return_value=(False, \"error_message\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_restart_agent_failed(self, *args, **kwargs):\n        \"\"\"\n        测试重启主机Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(monitor_agent_restart(self.host.id), None)\n\n    @mock.patch.object(\n        SaltClient, \"cmd\", return_value=(False, \"error_message\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_restart_agent_failed_with_wrong_id(self, *args, **kwargs):\n        \"\"\"\n        测试重启主机Agent失败，主机id错误\n        :return:\n        \"\"\"\n        self.assertEqual(monitor_agent_restart(1000), None)\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_real_restart_monitor_agent_success(self, *args, **kwargs):\n        \"\"\"\n        测试重启主机Agent成功\n        :return:\n        \"\"\"\n        self.assertEqual(real_monitor_agent_restart(self.host), None)\n\n    @mock.patch.object(\n        SaltClient, \"cmd\", return_value=(False, \"error_message\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_real_restart_monitor_agent_failed(self, *args, **kwargs):\n        \"\"\"\n        测试重启主机Agent失败\n        :return:\n        \"\"\"\n        self.assertEqual(real_monitor_agent_restart(self.host), None)\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_email_config.py",
    "content": "import json\nimport os\nimport shutil\n\nimport requests\nimport yaml\nfrom rest_framework.reverse import reverse\nfrom unittest import mock\n\nfrom tests.base import AutoLoginTest\nfrom db_models.models import EmailSMTPSetting, AlertSendWaySetting\nfrom omp_server.settings import PROJECT_DIR\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n        self.status_code = self.status\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass EmailConfig(AutoLoginTest):\n\n    @staticmethod\n    def delete_conf_dir():\n        \"\"\"\n        删除alertmanager文件\n        :return:\n        \"\"\"\n        alertmanager_conf_dir = os.path.join(\n            PROJECT_DIR, \"component/alertmanager/conf\")\n        if os.path.exists(alertmanager_conf_dir):\n            shutil.rmtree(alertmanager_conf_dir)\n\n    def setUp(self):\n        super(EmailConfig, self).setUp()\n        self.delete_conf_dir()\n        alertmanager_conf_dir = os.path.join(\n            PROJECT_DIR, \"component/alertmanager/conf\")\n        if not os.path.exists(alertmanager_conf_dir):\n            os.makedirs(alertmanager_conf_dir)\n        alertmanager_conf_dict = {\"global\": {\"resolve_timeout\": \"5m\", \"smtp_from\": \"1jayden.liu@cloudwise.com\",\n                                             \"smtp_smarthost\": \"smtp.feishu.cn:465\",\n                                             \"smtp_auth_username\": \"jayden.liu@cloudwise.com\",\n                                             \"smtp_auth_password\": \"Pc6qjfofl0TaqTlf\", \"smtp_require_tls\": False,\n                                             \"smtp_hello\": \"qq.com\"},\n                                  \"templates\": [\"/data/omp/omp_monitor/promemonitor/alertmanager/templates/*tmpl\"],\n                                  \"route\": {\"group_by\": [\"instance\"], \"group_wait\": \"10s\", \"group_interval\": \"10s\",\n                                            \"repeat_interval\": \"1m\", \"receiver\": \"commonuser\"}, \"receivers\": [\n            {\"name\": \"commonuser\", \"email_configs\": [\n                {\"to\": \"lingyang.guo@cloudwise.com\", \"headers\": {\"Subject\": \"OMP ALERT\"},\n                     \"html\": \"{{ template \\\"email.to.html\\\" . }}\"}]}], \"inhibit_rules\": [\n            {\"source_match\": {\"severity\": \"critical\"}, \"target_match\": {\"severity\": \"warning\"},\n             \"equal\": [\"instance\", \"job\", \"alertname\"]}]}\n        with open(os.path.join(alertmanager_conf_dir, \"alertmanager.yml\"), \"w\", encoding=\"utf8\") as ay_fp:\n            yaml.dump(alertmanager_conf_dict, ay_fp, allow_unicode=True)\n\n        self.esc_get_url = reverse(\"getSendEmailConfig-list\")\n        self.esc_update_url = reverse(\"updateSendEmailConfig-list\")\n        self.rsc_get_url = reverse(\"getSendAlertSetting-list\")\n        self.rsc_update_url = reverse(\"updateSendAlertSetting-list\")\n        self.ess = EmailSMTPSetting.objects.create(\n            email_host=\"123@qq.com\",\n            email_port=\"465\",\n            email_host_user=\"test_user\",\n            email_host_password=\"test_password\"\n        )\n        self.asws = AlertSendWaySetting.objects.create(\n            used=True,\n            env_id=1,\n            way_name=\"email\",\n            server_url=\"123@qq.com\",\n            way_token=\"123\",\n            extra_info=\"\"\n        )\n\n    def tearDown(self):\n        super(EmailConfig, self).tearDown()\n        self.ess.delete()\n        self.asws.delete()\n        self.delete_conf_dir()\n\n    def test_get_email_smtp_config(self):\n        resp = self.get(self.esc_get_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch.object(requests, 'post', return_value=None)\n    def test_update_smtp_config(self, mock_post=None):\n        mock_post.return_value = MockResponse(\n            {\"status\": \"success\", \"status_code\": 200})\n        post_data = {\n            \"host\": \"smtp.163.com\",\n            \"port\": 465,\n            \"username\": \"123456789@qq.com\",\n            \"password\": \"12345\"\n        }\n        resp = self.post(self.esc_update_url, data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    def test_get_send_alert_config(self):\n        resp = self.get(self.rsc_get_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch.object(requests, 'post', return_value=None)\n    def test_update_send_alert_config(self, mock_post=None):\n        mock_post.return_value = MockResponse(\n            {\"status\": \"success\", \"status_code\": 200})\n        post_data = {\"way_name\": \"email\",\n                     \"server_url\": \"98765432dd2@qq.com\", \"used\": True, \"env_id\": 0}\n        resp = self.post(self.rsc_update_url, data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_global_maintain.py",
    "content": "import json\n\nimport requests\nfrom rest_framework.reverse import reverse\nfrom unittest import mock\n\nfrom tests.base import AutoLoginTest\nfrom promemonitor.alertmanager import Alertmanager\nfrom db_models.models import MonitorUrl\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass GlobalMaintainTest(AutoLoginTest):\n    \"\"\" 全局维护测试类 \"\"\"\n\n    def setUp(self):\n        super(GlobalMaintainTest, self).setUp()\n        MonitorUrl.objects.create(\n            name='alertmanager', monitor_url='127.0.0.1:19013')\n        self.global_maintain_url = reverse(\"globalMaintain-list\")\n        # 正确数据\n        self.correct_maintain_data = {\n            \"matcher_name\": \"env\",\n            \"matcher_value\": \"default\"\n        }\n\n    @staticmethod\n    def return_set_alertmanager_maintain_response():\n        maintain_info = MockResponse(\n            {'status': 'success', 'data': {'silenceId': 'f9aed355-8a99-42ba-9fe6-877633ea3e4a'}})\n        return maintain_info\n\n    @staticmethod\n    def return_revoke_alertmanager_maintain_response():\n        revoke_maintain_info = MockResponse('')\n        return revoke_maintain_info\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_set_global_maintain(self, mock_post):\n        \"\"\" 测试设置全局维护 \"\"\"\n        mock_post.return_value = self.return_set_alertmanager_maintain_response()\n        resp = self.post(self.global_maintain_url,\n                         self.correct_maintain_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n        # print('已设置全局维护:', resp)\n\n        # 删除主机\n        self.revoke_global_maintain(self.correct_maintain_data)\n\n    @mock.patch.object(requests, 'delete', return_value='')\n    def revoke_global_maintain(self, maintain_data, mock_post=''):\n        mock_post.return_value = self.return_set_alertmanager_maintain_response()\n        alertmanager = Alertmanager()\n        alertmanager.revoke_maintain_by_env_name(\n            env_name=maintain_data.get(\"matcher_value\"))\n\n    def tearDown(self):\n        MonitorUrl.objects.filter(name='alertmanager').delete()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_grafana_url.py",
    "content": "from unittest import mock\n\nfrom rest_framework.reverse import reverse\n\nfrom promemonitor.grafana_url import CurlPrometheus\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import GrafanaMainPageResourceMixin\n\n# 正确prometheus数据\ncorrect_prometheus_data = {\n    \"status\": \"success\",\n    \"data\": {\n        \"alerts\": [\n            {\n                \"labels\": {\n                    \"alertname\": \"实例宕机\",\n                    \"instance\": \"10.0.3.72\",\n                    \"job\": \"nodeExporter\",\n                    \"severity\": \"critical\"\n                },\n                \"annotations\": {\n                    \"consignee\": \"987654321@qq.com\",\n                    \"description\": \"实例 10.0.3.72 已宕机超过1分钟\",\n                    \"summary\": \"实例宕机(10.0.3.72)\"\n                },\n                \"state\": \"firing\",\n                \"activeAt\": \"2021-09-27T07:08:05.68330499Z\",\n                \"value\": \"0e+00\"\n            },\n            {\n                \"labels\": {\n                    \"alertname\": \"实例宕机\",\n                    \"instance\": \"10.0.3.73\",\n                    \"job\": \"nodeExporter\",\n                    \"severity\": \"critical\"\n                },\n                \"annotations\": {\n                    \"consignee\": \"987654321@qq.com\",\n                    \"description\": \"实例 10.0.3.71 已宕机超过1分钟\",\n                    \"summary\": \"实例宕机(10.0.3.71)\"\n                },\n                \"state\": \"firing\",\n                \"activeAt\": \"2021-09-27T07:07:24.495164774Z\",\n                \"value\": \"0e+00\"\n            },\n            {'status': 'firing',\n             'labels': {\n                 'alertname': 'app state',\n                 'app': 'dolaLogMonitorServer',\n                 'env': '118',\n                 'instance': '10.0.7.164',\n                 'job': 'dolaLogMonitorServerExporter',\n                 'severity': 'critical'\n             },\n             'annotations': {\n                 'consignee': 'cw-email-address',\n                 'description': '主机 10.0.7.164 中的 服务 dolaLogMonitorServer 已经down掉超过一分钟.',\n                 'summary': 'app state(instance 10.0.7.164)'},\n             'startsAt': '2021-06-26T07:23:42.479972051Z',\n             'endsAt': '2021-06-26T07:38:01.343952065Z',\n             'generatorURL': 'https://centos7:19011/graph?g0.expr=probe_success+%3D%3D+0&g0.tab=1',\n             'fingerprint': '29a070a620efa300'}\n        ]\n    }\n}\n\n\nclass GrafanaUrlTest(AutoLoginTest, GrafanaMainPageResourceMixin):\n    def setUp(self):\n        super(GrafanaUrlTest, self).setUp()\n        self.list_grafana_url = reverse(\"grafanaurl-list\")\n        self.get_grafana_main_pages()\n\n    @mock.patch.object(CurlPrometheus, \"curl_prometheus\", return_value=correct_prometheus_data)\n    def test_exception_list(self, curl_prometheus):\n        \"\"\"请求全列表\"\"\"\n        resp = self.get(self.list_grafana_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n    @mock.patch.object(CurlPrometheus, \"curl_prometheus\", return_value=correct_prometheus_data)\n    def test_exception_list_filter(self, curl_prometheus):\n        \"\"\"\"多字段筛选并以单一字段排序\"\"\"\n        data = {\"ip\": \"10.0\", \"instance_name\": \"dola\", \"ordering\": \"ip\"}\n        resp = self.get(self.list_grafana_url, data=data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_grafana_views.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_grafana_views\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-12 10:55\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\ngrafana views测试类\n\"\"\"\n\nimport requests\n\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom db_models.models import MonitorUrl\n\n\nclass MockResponse(object):\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n\n    def __init__(self, headers=None):\n        self.content = \"success\"\n        self.headers = headers\n        self.status_code = 200\n\n\nclass TestGrafanaViews(BaseTest):\n\n    def setUp(self):\n        super(TestGrafanaViews, self).setUp()\n        MonitorUrl.objects.create(\n            name='grafana', monitor_url='127.0.0.1:19014')\n\n    @mock.patch.object(\n        requests, \"request\", return_value=MockResponse(headers=dict()))\n    def test_success(self, request):\n        res = self.get(url=\"/proxy/v1/grafana/\")\n        self.assertEqual(res.status_code, 200)\n\n    @mock.patch.object(\n        requests, \"request\", return_value=MockResponse(headers=None))\n    def test_failed_connected(self, request):\n        res = self.get(url=\"/proxy/v1/grafana/\")\n        self.assertEqual(res.status_code, 200)\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_instance_name_list.py",
    "content": "from rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\n\n\nclass GetInstanceNameListTest(AutoLoginTest):\n    \"\"\" 获取主机和应用实例名测试类 \"\"\"\n\n    def setUp(self):\n        super(GetInstanceNameListTest, self).setUp()\n        self.instance_name_list_url = reverse(\"instanceNameList-list\")\n\n    def test_get_instance_name_list(self):\n        \"\"\" 测试获取主机和应用实例名 \"\"\"\n        resp = self.get(self.instance_name_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_instrument_panel.py",
    "content": "import json\nfrom unittest import mock\n\nimport requests\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom db_models.models import MonitorUrl\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status_code = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass InstrumentPanelTest(AutoLoginTest):\n\n    def setUp(self):\n        super(InstrumentPanelTest, self).setUp()\n        MonitorUrl.objects.create(\n            name='prometheus', monitor_url='127.0.0.1:19011')\n        self.instrument_panel_url = reverse(\"instrumentPanel-list\")\n\n    @staticmethod\n    def return_prometheus_alerts_response():\n        prometheus_alerts_response = MockResponse(\n            {\n                \"status\": \"success\",\n                \"data\": {\n                    \"alerts\": [\n                        {\n                            \"labels\": {\n                                \"alertname\": \"实例宕机\",\n                                \"instance\": \"10.0.3.71\",\n                                \"service_type\": \"host\",\n                                \"service_name\": \"node\",\n                                \"instance_name\": \"dosm1\",\n                                \"job\": \"nodeExporter\",\n                                \"severity\": \"critical\"\n                            },\n                            \"annotations\": {\n                                \"consignee\": \"987654321@qq.com\",\n                                \"description\": \"实例 10.0.3.71 已宕机超过1分钟\",\n                                \"summary\": \"实例宕机(10.0.3.71)\"\n                            },\n                            \"state\": \"firing\",\n                            \"activeAt\": \"2021-10-16T08:16:24.495164774Z\",\n                            \"value\": \"0e+00\"\n                        },\n                        {\n                            \"labels\": {\n                                \"alertname\": \"mysql down\",\n                                \"instance\": \"10.0.3.72\",\n                                \"service_type\": \"database\",\n                                \"service_name\": \"mysql\",\n                                \"instance_name\": \"mysql1\",\n                                \"job\": \"mysqlExporter\",\n                                \"severity\": \"critical\"\n                            },\n                            \"annotations\": {\n                                \"consignee\": \"987654321@qq.com\",\n                                \"description\": \"实例 10.0.3.72 已宕机超过1分钟\",\n                                \"summary\": \"实例宕机(10.0.3.72)\"\n                            },\n                            \"state\": \"firing\",\n                            \"activeAt\": \"2021-10-16T08:16:05.68330499Z\",\n                            \"value\": \"0e+00\"\n                        },\n                        {\n                            \"labels\": {\n                                \"alertname\": \"alertChanel down\",\n                                \"instance\": \"10.0.3.72\",\n                                \"service_type\": \"service\",\n                                \"service_name\": \"alertChanel\",\n                                \"instance_name\": \"alertChanel\",\n                                \"job\": \"alertChanelExporter\",\n                                \"severity\": \"critical\"\n                            },\n                            \"annotations\": {\n                                \"consignee\": \"987654321@qq.com\",\n                                \"description\": \"实例 10.0.3.72 已宕机超过1分钟\",\n                                \"summary\": \"实例宕机(10.0.3.72)\"\n                            },\n                            \"state\": \"firing\",\n                            \"activeAt\": \"2021-10-16T08:16:05.68330499Z\",\n                            \"value\": \"0e+00\"\n                        },\n                        {\n                            \"labels\": {\n                                \"alertname\": \"zookeeper down\",\n                                \"instance\": \"10.0.3.72\",\n                                \"service_type\": \"component\",\n                                \"service_name\": \"zookeeper\",\n                                \"instance_name\": \"alertChanel\",\n                                \"job\": \"zookeeperExporter\",\n                                \"severity\": \"critical\"\n                            },\n                            \"annotations\": {\n                                \"consignee\": \"987654321@qq.com\",\n                                \"description\": \"实例 10.0.3.72 已宕机超过1分钟\",\n                                \"summary\": \"实例宕机(10.0.3.72)\"\n                            },\n                            \"state\": \"firing\",\n                            \"activeAt\": \"2021-10-16T08:16:05.68330499Z\",\n                            \"value\": \"0e+00\"\n                        },\n                        {\n                            \"labels\": {\n                                \"alertname\": \"custom_kafka down\",\n                                \"instance\": \"10.0.3.75\",\n                                \"service_type\": \"third\",\n                                \"service_name\": \"kafka\",\n                                \"instance_name\": \"custom_kafka\",\n                                \"job\": \"kafkaExporter\",\n                                \"severity\": \"critical\"\n                            },\n                            \"annotations\": {\n                                \"consignee\": \"987654321@qq.com\",\n                                \"description\": \"实例 10.0.3.72 已宕机超过1分钟\",\n                                \"summary\": \"实例宕机(10.0.3.72)\"\n                            },\n                            \"state\": \"firing\",\n                            \"activeAt\": \"2021-10-16T08:16:05.68330499Z\",\n                            \"value\": \"0e+00\"\n                        }\n                    ]\n                }\n            })\n        return prometheus_alerts_response\n\n    @mock.patch.object(requests, 'get', return_value='')\n    def test_instrument_panel(self, mock_get):\n        mock_get.return_value = self.return_prometheus_alerts_response()\n        resp = self.get(self.instrument_panel_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    def tearDown(self):\n        super(InstrumentPanelTest, self).tearDown()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_monitor_agent_restart.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_monitor_agent_restart\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-09 11:42\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport random\nfrom unittest import mock\n\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import HostsResourceMixin\nfrom promemonitor.tasks import monitor_agent_restart\n\n\nclass MonitorAgentRestartTest(AutoLoginTest, HostsResourceMixin):\n    \"\"\" 监控Agent重启测试类 \"\"\"\n\n    def setUp(self):\n        super(MonitorAgentRestartTest, self).setUp()\n        self.restartMonitorAgent_url = reverse(\"restartMonitorAgent-list\")\n\n    @mock.patch.object(monitor_agent_restart, \"delay\", return_value=None)\n    def test_success(self, monitor_agent_restart_obj):\n        \"\"\" 请求成功测试 \"\"\"\n        host_obj_ls = self.get_hosts(2)\n\n        host_obj_id_ls = list(map(lambda x: x.id, host_obj_ls))\n        resp = self.post(\n            self.restartMonitorAgent_url,\n            data={\"host_ids\": host_obj_id_ls}\n        ).json()\n        self.assertDictEqual(resp, {\n            \"code\": 0,\n            \"message\": \"success\",\n            \"data\": {\n                \"host_ids\": host_obj_id_ls\n            }\n        })\n\n        self.destroy_hosts()\n\n    @mock.patch.object(monitor_agent_restart, \"delay\", return_value=None)\n    def test_failed(self, monitor_agent_restart_obj):\n        \"\"\" 请求失败测试 \"\"\"\n        self.get_hosts(2)\n\n        resp = self.post(\n            self.restartMonitorAgent_url,\n            data={\"host_ids\": [random.randint(10000, 20000)]}\n        ).json()\n        self.assertEqual(resp.get(\"code\"), 1)\n\n        self.destroy_hosts()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_promemonitor_url.py",
    "content": "from rest_framework.reverse import reverse\nfrom utils.parse_config import MONITOR_PORT\nfrom tests.base import AutoLoginTest\nfrom db_models.models import MonitorUrl\n\n\nclass PromemonitorTest(AutoLoginTest):\n\n    def setUp(self):\n        super(PromemonitorTest, self).setUp()\n        self.create_monitorurl_url = reverse(\"monitorurl-list\")\n        self.multiple_update = self.create_monitorurl_url + \"multiple_update/\"\n        MonitorList = []\n        local_ip = \"127.0.0.1:\"\n        MonitorList.append(MonitorUrl(id=\"1\", name=\"prometheus\",\n                                      monitor_url=local_ip + str(MONITOR_PORT.get(\"prometheus\", \"19011\"))))\n        MonitorList.append(MonitorUrl(id=\"2\", name=\"alertmanager\",\n                                      monitor_url=local_ip + str(MONITOR_PORT.get(\"alertmanager\", \"19013\"))))\n        MonitorList.append(MonitorUrl(\n            id=\"3\", name=\"grafana\", monitor_url=local_ip + str(MONITOR_PORT.get(\"grafana\", \"19014\"))))\n        MonitorUrl.objects.bulk_create(MonitorList)\n\n    def test_list_promeurl(self):\n        \"\"\" 测试监控配置列表 \"\"\"\n\n        # 查询配置列表 -> 查询成功\n        resp = self.get(self.create_monitorurl_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n\n    def test_create_promeurl(self):\n        # name名字重复 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\n            \"name\": \"prometheus\",\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"name已经存在\",\n            \"data\": None\n        })\n        # name名字重复,批量创建 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\"data\": [{\n            \"name\": \"prometheus\",\n            \"monitor_url\": \"127.0.0.1:8080\"\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"name字段已经存在,detail:prometheus\",\n            \"data\": None\n        })\n\n        # name名字空 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"This field is required.\",\n            \"data\": None\n        })\n\n        # name名字空,批量创建 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\"data\": [{\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"name字段不为空\",\n            \"data\": None\n        })\n\n        # name字段超限 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\n            \"name\": \"prometheusprometheusprometheusprometheusprometheusprometheusprometheusprometheus\",\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Ensure this field has no more than 32 characters.\",\n            \"data\": None\n        })\n\n        # name字段超限,批量创建 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\"data\": [{\n            \"name\": \"prometheusprometheusprometheusprometheusprometheusprometheusprometheusprometheus\",\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"name字段长度超过32,detail:prometheusprometheusprometheusprometheusprometheusprometheusprometheusprometheus\",\n            \"data\": None\n        })\n\n        # monitor_url字段空 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\n            \"name\": \"test1\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"This field is required.\",\n            \"data\": None\n        })\n\n        # monitor_url字段空,批量 -> 无法创建\n        resp = self.post(self.create_monitorurl_url, {\"data\": [{\n            \"name\": \"test1\",\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"monitor_url是必须字段\",\n            \"data\": None\n        })\n\n        # 创建成功\n        resp = self.post(self.create_monitorurl_url, {\n            \"name\": \"test1\",\n            \"monitor_url\": \"127.0.0.1:8080\",\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        # 成功批量\n        resp = self.post(self.create_monitorurl_url, {\"data\": [{\n            \"name\": \"test3\",\n            \"monitor_url\": \"127.0.0.1:8080\"\n        },\n            {\n                \"name\": \"test2\",\n                \"monitor_url\": \"127.0.0.1:8080\"\n        }\n        ]}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n    def test_partial_update_promeurl(self):\n        # monitor_url字非法,批量 -> 无法修改\n        resp = self.patch(self.multiple_update, {\"data\": [{\n            \"id\": \"3\",\n            \"monitor_url\": \"😊\"\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"监控地址url地址存在非法字符\",\n            \"data\": None\n        })\n\n        # 修改url, -> 创建成功\n        resp = self.patch(self.multiple_update, {\"data\": [{\n            \"id\": \"3\",\n            \"monitor_url\": \"127.0.0.1:19999\"\n        }]}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        # 修改url批量, -> 创建成功\n        resp = self.patch(self.multiple_update, {\"data\": [\n            {\n                \"id\": \"2\",\n                \"monitor_url\": \"127.0.0.1:29999\"\n            }, {\n                \"id\": \"3\",\n                \"monitor_url\": \"127.0.0.1:19999\"\n            }]}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_prometheus.py",
    "content": "import json\n\nimport requests\nfrom django.test import TestCase\n\nfrom promemonitor.prometheus import Prometheus\nfrom db_models.models import MonitorUrl\nfrom unittest import mock\nfrom db_models.models import HostThreshold\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status_code = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass TestPrometheus(TestCase):\n\n    def setUp(self):\n        MonitorUrl.objects.create(\n            name='prometheus', monitor_url='127.0.0.1:19011')\n        hts = list()\n        for metric in (\"cpu_used\", \"memory_used\", \"disk_root_used\", \"disk_data_used\"):\n            for level in (\"warning\", \"critical\"):\n                hts.append(HostThreshold(\n                    index_type=metric,\n                    condition=\">=\",\n                    condition_value=\"80\" if level == \"warning\" else \"90\",\n                    alert_level=level,\n                    # create_date=\"\",\n                    env_id=1\n                ))\n        HostThreshold.objects.bulk_create(hts)\n\n    @staticmethod\n    def return_host_list():\n        host_list = [\n            {\n                'ip': '10.0.3.71',\n                'data_folder': '/boot',\n            }\n        ]\n        return host_list\n\n    @staticmethod\n    def return_host_info_data():\n        correct_host_info_data = [\n            {'ip': '10.0.3.71', 'data_folder': '/boot', 'cpu_usage': 12, 'mem_usage': 12, 'root_disk_usage': 12,\n             'data_disk_usage': 12, 'cpu_status': \"normal\", 'mem_status': \"normal\", 'root_disk_status': \"normal\",\n             'data_disk_status': \"normal\"}\n        ]\n        return correct_host_info_data\n\n    request_get_response = {\"status\": \"success\", \"data\": {\"resultType\": \"vector\", \"result\": [\n        {\"metric\": {\"instance\": \"10.0.3.71\"}, \"value\": [\n            1633782875.771, \"11.360416666623973\"]},\n        {\"metric\": {\"instance\": \"10.0.3.72\"}, \"value\": [\n            1633782875.771, \"11.04166666666666\"]}\n    ]}}\n\n    error_request_get_response = {\"status\": \"error\", \"data\": {\"resultType\": \"vector\", \"result\": [\n        {\"metric\": {\"instance\": \"10.0.3.71\"}, \"value\": [\n            1633782875.771, \"11.360416666623973\"]},\n        {\"metric\": {\"instance\": \"10.0.3.72\"}, \"value\": [\n            1633782875.771, \"11.04166666666666\"]}\n    ]}}\n\n    @mock.patch.object(requests, 'get', return_value='')\n    def test_get_prometheus_info(self, mock_post):\n        mock_post.return_value = MockResponse(self.request_get_response)\n        prometheus = Prometheus()\n        result = prometheus.get_host_info(self.return_host_list())\n        # print(result)\n        self.assertListEqual(result,\n                             self.return_host_info_data())\n\n    def test_get_host_metric_status(self):\n        p = Prometheus()\n        result_none = p.get_host_metric_status('cpu', None)\n        self.assertIsNone(result_none)\n\n        result_critical = p.get_host_metric_status('cpu', 91)\n        self.assertEqual(result_critical, 'critical')\n\n        result_warning = p.get_host_metric_status('cpu', 81)\n        self.assertEqual(result_warning, 'warning')\n\n    @mock.patch.object(requests, 'get', return_value='')\n    def test_error_get_host_arg_usage(self, mock_get):\n        mock_get.return_value = MockResponse(self.error_request_get_response)\n        p = Prometheus()\n        result_get_host_cpu_usage = p.get_host_cpu_usage(\n            self.return_host_list())\n        self.assertIsNone(result_get_host_cpu_usage[0].get(\"cpu_usage\"))\n\n        result_get_host_mem_usage = p.get_host_mem_usage(\n            self.return_host_list())\n        self.assertIsNone(result_get_host_mem_usage[0].get(\"mem_usage\"))\n\n        result_get_host_root_disk_usage = p.get_host_root_disk_usage(\n            self.return_host_list())\n        self.assertIsNone(\n            result_get_host_root_disk_usage[0].get(\"root_disk_usage\"))\n\n        result_get_host_data_disk_usage = p.get_host_data_disk_usage(\n            self.return_host_list())\n        self.assertIsNone(\n            result_get_host_data_disk_usage[0].get(\"data_disk_usage\"))\n\n        mock_get.return_value.status_code = -1\n        result_get_host_cpu_usage = p.get_host_cpu_usage(\n            self.return_host_list())\n        self.assertIsNone(result_get_host_cpu_usage[0].get(\"cpu_usage\"))\n\n        result_get_host_mem_usage = p.get_host_mem_usage(\n            self.return_host_list())\n        self.assertIsNone(result_get_host_mem_usage[0].get(\"mem_usage\"))\n\n        result_get_host_root_disk_usage = p.get_host_root_disk_usage(\n            self.return_host_list())\n        self.assertIsNone(\n            result_get_host_root_disk_usage[0].get(\"root_disk_usage\"))\n\n        result_get_host_data_disk_usage = p.get_host_data_disk_usage(\n            self.return_host_list())\n        self.assertIsNone(\n            result_get_host_data_disk_usage[0].get(\"data_disk_usage\"))\n\n        mock_get.return_value = MockResponse(self.request_get_response)\n        mock_get.return_value.status_code = 200\n        result_get_host_cpu_usage = p.get_host_cpu_usage(1)\n        self.assertEqual(result_get_host_cpu_usage, 1)\n\n        result_get_host_mem_usage = p.get_host_mem_usage(1)\n        self.assertEqual(result_get_host_mem_usage, 1)\n\n        result_get_host_root_disk_usage = p.get_host_root_disk_usage(1)\n        self.assertEqual(result_get_host_root_disk_usage, 1)\n\n        result_get_host_data_disk_usage = p.get_host_data_disk_usage(\n            [{\"1\": 1}, {\"2\": 2}])\n        self.assertEqual(result_get_host_data_disk_usage, [{'1': 1, 'data_disk_usage': None, 'data_disk_status': None},\n                                                           {'2': 2, 'data_disk_usage': None, 'data_disk_status': None}])\n\n    def tearDown(self):\n        MonitorUrl.objects.filter(name='prometheus').delete()\n        HostThreshold.objects.filter(env_id=1).delete()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_prometheus_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_prometheus_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-11 22:58\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nprometheus 工具集的单元测试代码\n\"\"\"\nimport os\nimport uuid\nimport shutil\nimport json\nfrom unittest import mock\n\nimport requests\n\nfrom tests.base import BaseTest\nfrom omp_server.settings import PROJECT_DIR\nfrom promemonitor.prometheus_utils import PrometheusUtils\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status_code = 0\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass PrometheusUtilsTest(BaseTest):\n    \"\"\" 主机Agent的测试类 \"\"\"\n\n    @staticmethod\n    def delete_conf_dir():\n        \"\"\"\n        删除prometheus文件\n        :return:\n        \"\"\"\n        conf_dir = os.path.join(PROJECT_DIR, \"component/prometheus/conf\")\n        if os.path.exists(conf_dir):\n            shutil.rmtree(conf_dir)\n\n    def setUp(self):\n        super(PrometheusUtilsTest, self).setUp()\n        self.delete_conf_dir()\n        self.prometheus_obj = PrometheusUtils()\n        if not os.path.exists(self.prometheus_obj.prometheus_rules_path):\n            os.makedirs(self.prometheus_obj.prometheus_rules_path)\n        if not os.path.exists(self.prometheus_obj.prometheus_targets_path):\n            os.makedirs(self.prometheus_obj.prometheus_targets_path)\n        if not os.path.exists(self.prometheus_obj.prometheus_conf_path):\n            with open(self.prometheus_obj.prometheus_conf_path, 'w') as pf:\n                pf.write('')\n        self.nodes_data = [\n            {\n                \"data_path\": \"/data\",\n                \"env\": \"default\",\n                \"ip\": \"127.0.0.1\"\n            }\n        ]\n\n    def test_init_success(self):\n        \"\"\"\n        测试PrometheusUtils可正常实例化情况\n        :return:\n        \"\"\"\n        self.assertEqual(\n            isinstance(self.prometheus_obj, PrometheusUtils), True)\n\n    def test_add_node_failed_with_null_data(self):\n        \"\"\"\n        测试add_node传入空数据情况\n        :return:\n        \"\"\"\n        flag, message = self.prometheus_obj.add_node([])\n        self.assertEqual(flag, False)\n        self.assertEqual(message, \"nodes_data can not be null\")\n\n    def test_add_node_success(self):\n        \"\"\"\n        测试add_node成功的情况\n        :return:\n        \"\"\"\n        flag, message = self.prometheus_obj.add_node(self.nodes_data)\n        self.assertEqual(flag, True)\n\n    def test_add_node_success_2(self):\n        \"\"\"\n        测试add_node成功的情况\n        :return:\n        \"\"\"\n        nodes_data = [\n            {\n                \"data_path\": \"/data1\",\n                \"env\": \"default\",\n                \"ip\": \"127.0.0.1\"\n            }\n        ]\n        self.prometheus_obj.add_node(self.nodes_data)\n        flag, message = self.prometheus_obj.add_node(nodes_data)\n        self.assertEqual(flag, True)\n\n    def test_add_node_success_3(self):\n        \"\"\"\n        测试add_node成功的情况\n        :return:\n        \"\"\"\n        nodes_data = [\n            {\n                \"data_path\": \"/data1\",\n                \"env\": \"default\",\n                \"ip\": \"127.0.0.1\"\n            }\n        ]\n        self.prometheus_obj.add_node(self.nodes_data)\n        self.prometheus_obj.add_node(self.nodes_data)\n        flag, message = self.prometheus_obj.add_node(nodes_data)\n        self.assertEqual(flag, True)\n\n    def test_delete_node_failed_with_null_data(self):\n        \"\"\"\n        测试 delete_node 接收空数据\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.delete_node([])[0], False)\n\n    def test_delete_node_success(self):\n        \"\"\"\n        测试 delete_node 成功\n        :return:\n        \"\"\"\n        self.test_add_node_success()\n        flag, msg = self.prometheus_obj.delete_node(self.nodes_data)\n        self.assertEqual(flag, True)\n\n    def test_delete_node_failed_with_file_not_exist(self):\n        \"\"\"\n        测试 delete_node 失败，target文件不存在\n        :return:\n        \"\"\"\n        flag, msg = self.prometheus_obj.delete_node(self.nodes_data)\n        self.assertEqual(flag, False)\n\n    def test_delete_node_success_with_empty_file(self):\n        \"\"\"\n        测试 delete_node 失败，target文件不存在\n        :return:\n        \"\"\"\n        with open(self.prometheus_obj.node_exporter_targets_file, \"w\") as fp:\n            fp.write(\"\")\n        flag, msg = self.prometheus_obj.delete_node(self.nodes_data)\n        self.assertEqual(flag, True)\n\n    def test_add_rules_failed(self):\n        \"\"\"\n        测试 add_rules 失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.add_rules(\"aaa\")[0], False)\n\n    def test_add_rules_for_node_success(self):\n        \"\"\"\n        测试 add_rules 主机成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.add_rules(\"node\")[0], True)\n\n    def test_add_rules_for_service_success(self):\n        \"\"\"\n        测试 add_rules service成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.add_rules(\"service\")[0], True)\n\n    def test_add_rules_for_exporter_success(self):\n        \"\"\"\n        测试 add_rules exporter成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.add_rules(\"exporter\")[0], True)\n\n    def test_delete_rules_failed(self):\n        \"\"\"\n        测试 delete_rules 失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.delete_rules(\"aaa\")[0], False)\n\n    def test_delete_rules_for_node_success(self):\n        \"\"\"\n        测试 delete_rules node成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.delete_rules(\"node\")[0], True)\n\n    def test_delete_rules_for_service_success(self):\n        \"\"\"\n        测试 delete_rules status成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.prometheus_obj.delete_rules(\"service\")[0], True)\n\n    def test_delete_rules_for_exporter_success(self):\n        \"\"\"\n        测试 delete_rules exporter成功\n        :return:\n        \"\"\"\n        self.prometheus_obj.add_rules(\"exporter\")\n        self.assertEqual(self.prometheus_obj.delete_rules(\"exporter\")[0], True)\n\n    def test_replace_placeholder_failed(self):\n        \"\"\"\n        测试文件不存在时无法替换placeholder\n        :return:\n        \"\"\"\n        self.assertEqual(\n            self.prometheus_obj.replace_placeholder(\n                f\"/tmp/{str(uuid.uuid4())}\",\n                []\n            )[0], False)\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_add_service(self, mock_post):\n        mock_post.return_value = MockResponse(\n            {\"return_code\": 0, \"message\": \"success\"})\n        from db_models.models import Host\n        h1 = Host.objects.create(\n            instance_name=\"mysql_instance_1\",\n            ip=\"127.0.0.1\",\n            port=36000,\n            username=\"root\",\n            password=\"XoIc56a3HiStUZb3Pu9jXEHj8YvMTRpMYnNFD2YS7MA\",\n            data_folder=\"/data\",\n            operate_system=\"CentOS\"\n        )\n        test_service_data = {\n            \"service_name\": \"mysql\",\n            \"instance_name\": \"mysql_dosm\",\n            \"data_path\": \"/data/appData/mysql\",\n            \"log_path\": \"/data/logs/mysql\",\n            \"env\": \"default\",\n            \"ip\": \"127.0.0.1\",\n            \"listen_port\": \"3306\"\n        }\n        flag, msg = self.prometheus_obj.add_service(test_service_data)\n        self.assertEqual(flag, True)\n        h1.delete()\n\n    @mock.patch.object(requests, 'post', return_value='')\n    def test_delete_service(self, mock_post):\n        mock_post.return_value = MockResponse(\n            {\"return_code\": 0, \"message\": \"success\"})\n        mysql_json_file = os.path.join(\n            self.prometheus_obj.prometheus_targets_path, 'mysqlExporter_all.json')\n        with open(mysql_json_file, 'w') as mp:\n            mp.write('')\n        from db_models.models import Host\n        h2 = Host.objects.create(\n            instance_name=\"mysql_instance_1\",\n            ip=\"127.0.0.1\",\n            port=36000,\n            username=\"root\",\n            password=\"XoIc56a3HiStUZb3Pu9jXEHj8YvMTRpMYnNFD2YS7MA\",\n            data_folder=\"/data\",\n            operate_system=\"CentOS\"\n        )\n        test_service_data = {\n            \"service_name\": \"mysql\",\n            \"instance_name\": \"mysql_dosm\",\n            \"data_path\": \"/data/appData/mysql\",\n            \"log_path\": \"/data/logs/mysql\",\n            \"env\": \"default\",\n            \"ip\": \"127.0.0.1\",\n            \"listen_port\": \"3306\"\n        }\n        flag, msg = self.prometheus_obj.delete_service(test_service_data)\n        self.assertEqual(flag, True)\n        h2.delete()\n\n    def tearDown(self) -> None:\n        \"\"\"\n        测试结束操作\n        :return:\n        \"\"\"\n        self.delete_conf_dir()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_receive_alert.py",
    "content": "import json\n\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom db_models.models import Host, Alert\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status_code = 0\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass ReceiveAlertTest(AutoLoginTest):\n    \"\"\" 接收并解析alertmanager告警测试类 \"\"\"\n\n    def setUp(self):\n        super(ReceiveAlertTest, self).setUp()\n        self.receive_alert_url = reverse(\"receiveAlert-list\")\n        # 正确请求数据\n        self.origin_alert_str = {\n            \"receiver\": \"cloudwise\",\n            \"status\": \"firing\",\n            \"alerts\": [\n                {\n                    \"status\": \"firing\",\n                    \"labels\": {\n                        \"alertname\": \"host cpu_used critical alert\",\n                        \"instance\": \"10.0.7.146\",\n                        \"job\": \"nodeExporter\",\n                        \"severity\": \"critical\"\n                    },\n                    \"annotations\": {\n                        \"consignee\": \"123456789@qq.com\",\n                        \"description\": \"主机 10.0.7.146 CPU 使用率为 10.06%, 大于阈值 10\",\n                        \"summary\": \"cpu_used (instance 10.0.7.146)\"\n                    },\n                    \"startsAt\": \"2021-06-26T08:13:32.950510932Z\",\n                    \"endsAt\": \"2021-06-26T08:15:02.950510932Z\",\n                    \"generatorURL\": \"http://centos7:19011/graph?g0.expr=sum+by%28instance%29+%28avg+without%28cpu%29+\"\n                                    \"%28irate%28node_cpu_seconds_total%7Benv%3D%22caleb%22%2Cmode%21%3D%22idle%22%7D%5B\"\n                                    \"5m%5D%29%29%29+%2A+100+%3E%3D+10&g0.tab=1\",\n                    \"fingerprint\": \"3e16190fffa56fe0\"\n                },\n                {\n                    \"status\": \"firing\",\n                    \"labels\": {\n                        \"alertname\": \"host cpu_used critical alert\",\n                        \"instance\": \"10.0.9.62\",\n                        \"job\": \"nodeExporter\",\n                        \"severity\": \"critical\"\n                    },\n                    \"annotations\": {\n                        \"consignee\": \"123456789@qq.com\",\n                        \"description\": \"主机 10.0.9.62 CPU 使用率为 10.11%, 大于阈值 10\",\n                        \"summary\": \"cpu_used (instance 10.0.7.146)\"\n                    },\n                    \"startsAt\": \"2021-06-26T08:13:32.950510932Z\",\n                    \"endsAt\": \"2021-06-26T08:15:02.950510932Z\",\n                    \"generatorURL\": \"http://centos7:19011/graph?g0.expr=sum+by%28instance%29+%28avg+without%28cpu%29+\"\n                                    \"%28irate%28node_cpu_seconds_total%7Benv%3D%22caleb%22%2Cmode%21%3D%22idle%22%7D%5B\"\n                                    \"5m%5D%29%29%29+%2A+100+%3E%3D+10&g0.tab=1\",\n                    \"fingerprint\": \"3e16190fffa56fe0\"\n                },\n                {'status': 'firing',\n                 'labels': {'alertname': 'app state',\n                            'app': 'alertChannel', 'env': '118',\n                            'instance': '10.0.7.146',\n                            'job': 'alertChannelExporter',\n                            'severity': 'critical'},\n                 'annotations': {'consignee': 'cw-email-address',\n                                 'description': '主机 10.0.7.146 中的 服务 alertChannel 已经down掉超过一分钟.',\n                                 'summary': 'app state(instance 10.0.7.166)'},\n                 'startsAt': '2021-06-26T06:45:31.343952065Z',\n                 'endsAt': '0001-01-01T00:00:00Z',\n                 'generatorURL': 'http://centos7:19011/graph?g0.expr=probe_success+%3D%3D+0&g0.tab=1',\n                 'fingerprint': '941445cf659314a2'\n                 },\n                {'status': 'firing',\n                 'labels': {'alertname': 'app state',\n                            'app': 'cmdbServer', 'env': '118',\n                            'instance': '10.0.9.61',\n                            'job': 'cmdbServerExporter',\n                            'severity': 'critical'},\n                 'annotations': {'consignee': 'cw-email-address',\n                                 'description': '主机 10.0.9.61 中的 服务 cmdbServer 已经down掉超过一分钟.',\n                                 'summary': 'app state(instance 10.0.7.166)'},\n                 'startsAt': '2021-06-26T06:45:31.343952065Z',\n                 'endsAt': '0001-01-01T00:00:00Z',\n                 'generatorURL': 'http://centos7:19011/graph?g0.expr=probe_success+%3D%3D+0&g0.tab=1',\n                 'fingerprint': '941445cf659314a2'\n                 }\n            ]\n        }\n        Host.objects.create(\n            instance_name=\"mysql_instance_1\",\n            ip=\"10.0.7.146\",\n            port=36000,\n            username=\"root\",\n            password=\"XoIc56a3HiStUZb3Pu9jXEHj8YvMTRpMYnNFD2YS7MA\",\n            data_folder=\"/data\",\n            operate_system=\"CentOS\"\n        )\n\n    request_post_response = {\n        \"code\": 0,\n        \"message\": \"success\",\n        \"data\": {'key': ['...']}\n    }\n\n    def test_receive_alerts(self):\n        \"\"\"\n        接收并解析alertmanager告警\n        \"\"\"\n        # mock_post.return_value = MockResponse(self.request_post_response)\n\n        resp = self.post(self.receive_alert_url, self.origin_alert_str).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get('data'))\n\n    def test_alert_util_exception(self):\n        \"\"\"测试alert_util中的异常处理\"\"\"\n        from promemonitor.alert_util import get_monitor_url, get_log_url, utc_to_local\n        gmu_result = get_monitor_url(1)\n        self.assertIsNone(gmu_result)\n\n        glu_result = get_log_url(1)\n        self.assertIsNone(glu_result)\n\n        utl_result = utc_to_local(\"123\")\n        self.assertIsNotNone(utl_result)\n\n    def tearDown(self):\n        Alert.objects.filter(alert_host_ip='10.0.7.146').delete()\n        Host.objects.filter(ip='10.0.7.146').delete()\n"
  },
  {
    "path": "omp_server/tests/test_promemonitor/test_threshold_rw.py",
    "content": "import json\nimport os\nimport shutil\n\nimport requests\nfrom rest_framework.reverse import reverse\nfrom unittest import mock\n\nfrom db_models.models import HostThreshold, ServiceThreshold, ServiceCustomThreshold\nfrom tests.base import AutoLoginTest\nfrom omp_server.settings import PROJECT_DIR\n\n\nclass MockResponse:\n    \"\"\"\n    自定义mock response类\n    \"\"\"\n    status = 200\n\n    def __init__(self, data):\n        self.text = json.dumps(data)\n        self.status_code = self.status\n\n    def json(self):\n        return json.loads(self.text)\n\n\nclass ThresholdRW(AutoLoginTest):\n\n    @staticmethod\n    def delete_conf_dir():\n        \"\"\"\n        删除prometheus文件\n        :return:\n        \"\"\"\n        prometheus_conf_dir = os.path.join(\n            PROJECT_DIR, \"component/prometheus/conf\")\n        if os.path.exists(prometheus_conf_dir):\n            shutil.rmtree(prometheus_conf_dir)\n\n    def setUp(self):\n        super(ThresholdRW, self).setUp()\n        self.delete_conf_dir()\n        prometheus_conf_dir = os.path.join(\n            PROJECT_DIR, \"component/prometheus/conf\")\n        prometheus_rules_dir = os.path.join(prometheus_conf_dir, \"rules\")\n        if not os.path.exists(prometheus_rules_dir):\n            os.makedirs(prometheus_rules_dir)\n\n        default_node_yml_file = os.path.join(\n            prometheus_rules_dir, \"default_node_rule.yml\")\n        with open(default_node_yml_file, 'w') as fp:\n            fp.write(\"default_node_yml_dict\")\n\n        self.host_threshold_rw_url = reverse(\"hostThreshold-list\")\n        self.service_threshold_rw_url = reverse(\"serviceThreshold-list\")\n        self.custom_threshold_rw_url = reverse(\"customThreshold-list\")\n\n        self.host_threshold = HostThreshold.objects.create(\n            index_type=\"cpu_used\",\n            condition=\">=\",\n            condition_value=\"90\",\n            alert_level=\"critical\",\n            env_id=1\n        )\n        self.service_threshold = ServiceThreshold.objects.create(\n            index_type=\"cpu_used\",\n            condition=\">=\",\n            condition_value=\"90\",\n            alert_level=\"critical\",\n            env_id=1\n        )\n        self.custom_threshold = ServiceCustomThreshold.objects.create(\n            service_name=\"kafka\",\n            index_type=\"cpu_used\",\n            condition=\">=\",\n            condition_value=\"90\",\n            alert_level=\"critical\",\n            env_id=1\n        )\n\n    def tearDown(self):\n        super(ThresholdRW, self).tearDown()\n        self.delete_conf_dir()\n        self.host_threshold.delete()\n        self.service_threshold.delete()\n        self.custom_threshold.delete()\n\n    def test_get_host_threshold_config(self):\n        resp = self.get(f\"{self.host_threshold_rw_url}{'?env_id=1'}\").json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch.object(requests, 'post', return_value=None)\n    def test_update_host_threshold_config(self, mock_post=None):\n        post_data = {\n            \"update_data\": {\n                \"cpu_used\": [\n                    {\n                        \"index_type\": \"cpu_used\",\n                        \"condition\": \">=\",\n                        \"value\": 91,\n                        \"level\": \"critical\"\n                    },\n                    {\n                        \"index_type\": \"cpu_used\",\n                        \"condition\": \">=\",\n                        \"value\": \"80\",\n                        \"level\": \"warning\"\n                    }]},\n            \"env_id\": 1}\n        resp = self.post(self.host_threshold_rw_url, data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    def test_get_service_threshold_config(self):\n        resp = self.get(f\"{self.service_threshold_rw_url}{'?env_id=1'}\").json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch.object(requests, 'post', return_value=None)\n    def test_update_service_threshold_config(self, mock_post=None):\n        post_data = {\n            \"update_data\": {\n                \"cpu_used\": [\n                    {\n                        \"index_type\": \"cpu_used\",\n                        \"condition\": \">=\",\n                        \"value\": 91,\n                        \"level\": \"critical\"\n                    },\n                    {\n                        \"index_type\": \"cpu_used\",\n                        \"condition\": \">=\",\n                        \"value\": \"80\",\n                        \"level\": \"warning\"\n                    }]},\n            \"env_id\": 1}\n        resp = self.post(self.service_threshold_rw_url, data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    def test_get_custom_threshold_config(self):\n        resp = self.get(f\"{self.custom_threshold_rw_url}{'?env_id=1'}\").json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n\n    @mock.patch.object(requests, 'post', return_value=None)\n    def test_update_custom_threshold_config(self, mock_post=None):\n        post_data = {\n            \"env_id\": 1,\n            \"service_name\": \"kafka\",\n            \"index_type\": \"kafka_consumergroup_lag\",\n            \"index_type_info\": [\n                {\n                    \"condition\": \">=\",\n                    \"index_type\": \"kafka_consumergroup_lag\",\n                    \"level\": \"critical\",\n                    \"value\": 5000\n                },\n                {\n                    \"condition\": \">=\",\n                    \"index_type\": \"kafka_consumergroup_lag\",\n                    \"level\": \"warning\",\n                    \"value\": 3000\n                }\n            ]\n        }\n        resp = self.post(self.custom_threshold_rw_url, data=post_data).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_services/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/test_services/test_service_actions.py",
    "content": "import json\n\nfrom rest_framework.reverse import reverse\n\nfrom db_models.models import (\n    ServiceHistory, Service, Env,\n    ApplicationHub, DetailInstallHistory\n)\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import (\n    ServicesResourceMixin\n)\nfrom services.tasks import exec_action\nfrom unittest import mock\nimport time\n\ninstall_detail = {\n    \"ip\": \"10.0.7.184\", \"name\": \"mysql\", \"version\": \"5.7.31\",\n    \"app_port\": [{\"key\": \"service_port\", \"name\": \"服务端口\", \"default\": \"3306\"}],\n    \"deploy_mode\": {\"key\": \"single\", \"name\": \"单实例\"},\n    \"install_args\": [\n        {\"key\": \"base_dir\", \"name\": \"安装目录\",\n         \"default\": \"/webber/mysql\", \"dir_key\": \"{data_path}\",\n         \"check_msg\": \"success\", \"check_flag\": \"true\"},\n        {\"key\": \"data_dir\", \"name\": \"数据目录\",\n         \"default\": \"/webber/mysql/data\", \"dir_key\": \"{data_path}\",\n         \"check_msg\": \"success\", \"check_flag\": \"true\"},\n        {\"key\": \"log_dir\", \"name\": \"日志目录\",\n         \"default\": \"/webber/mysql/log\", \"dir_key\": \"{data_path}\",\n         \"check_msg\": \"success\", \"check_flag\": \"true\"},\n        {\"key\": \"username\", \"name\": \"用户名\", \"default\": \"root\"},\n        {\"key\": \"password\", \"name\": \"密码\", \"default\": \"123456\"}],\n    \"service_instance_name\": \"mysql-7-184\"\n}\n\n\nclass ListActionTest(AutoLoginTest, ServicesResourceMixin):\n    \"\"\" 服务动作测试类 \"\"\"\n\n    def setUp(self):\n        super(ListActionTest, self).setUp()\n        env_obj = Env.objects.create(name=\"default\")\n        app_obj = ApplicationHub.objects.create(\n            app_name=\"test_app\", app_version=\"1.0.0\",\n            app_dependence=json.dumps(\n                [{\"name\": \"jda\", \"version\": \"1.0.0\"}]\n            )\n        )\n        Service.objects.create(\n            ip=\"192.168.0.110\",\n            service_instance_name=\"test1\",\n            service_status=5,\n            alert_count=6,\n            self_healing_count=6,\n            service_controllers={\"start\": \"1.txt\", \"stop\": \"2.txt\"},\n            env=env_obj,\n            service=app_obj,\n            service_port=json.dumps([{'default': '18080', 'key': 'http_port'}])\n        )\n        self.create_action_url = reverse(\"action-list\")\n        Service.objects.create(\n            ip=\"192.168.0.111\",\n            service_instance_name=\"test2-jdk\",\n            service_status=5,\n            alert_count=6,\n            self_healing_count=6,\n            service_controllers={\"start\": \"1.txt\", \"stop\": \"2.txt\"},\n            service=app_obj,\n            service_port=json.dumps([{'default': '18080', 'key': 'http_port'}])\n        )\n\n    @mock.patch(\n        \"utils.plugin.salt_client.SaltClient.cmd\",\n        return_value=(True, \"success\"))\n    def test_service_action_true(self, status):\n        service_obj = Service.objects.get(ip=\"192.168.0.110\")\n        exec_action(\"1\", service_obj.id, \"admin\")\n        history_count = ServiceHistory.objects.filter(\n            service=service_obj).count()\n        service_obj.refresh_from_db()\n        res = {service_obj.service_status: history_count}\n        self.assertDictEqual(res, {\n            0: 1\n        })\n\n    @mock.patch(\n        \"utils.plugin.salt_client.SaltClient.cmd\",\n        return_value=(False, \"false\"))\n    def test_service_action_false(self, status):\n        service_obj = Service.objects.get(ip=\"192.168.0.110\")\n        exec_action(\"1\", service_obj.id, \"admin\")\n        history_count = ServiceHistory.objects.filter(\n            service=service_obj).count()\n        service_obj.refresh_from_db()\n        res = {service_obj.service_status: history_count}\n        self.assertDictEqual(res, {\n            4: 1\n        })\n\n    @mock.patch(\n        \"utils.plugin.salt_client.SaltClient.cmd\",\n        return_value=\"\")\n    @mock.patch(\n        \"promemonitor.prometheus_utils.PrometheusUtils.delete_service\",\n        return_value=\"\"\n    )\n    @mock.patch(\n        \"utils.plugin.salt_client.SaltClient\",\n        return_value=\"\")\n    def test_service_action_delete(self, salt_client, delete_service, status):\n        status.side_effect = [\n            (True, \"success\"), (True, \"success\"), (True, \"success\")\n        ]\n        service_obj = Service.objects.get(ip=\"192.168.0.110\")\n        time_array = time.localtime(int(time.time()))\n        time_style = time.strftime(\"%Y-%m-%d %H:%M:%S\", time_array)\n        service_history = ServiceHistory(\n            username='admin',\n            description='测试',\n            result=0,\n            created=time_style,\n            service=service_obj\n        )\n        service_history.save()\n        DetailInstallHistory.objects.create(\n            service=service_obj,\n            send_msg=\"send_log\",\n            unzip_msg=\"unzip_log\",\n            install_msg=\"install_log\",\n            init_msg=\"init_log\",\n            start_msg=\"start_log\",\n            install_detail_args=install_detail\n        )\n        exec_action(\"4\", service_obj.id, \"admin\")\n        history_count = ServiceHistory.objects.filter(\n            service=service_obj).count()\n        new_service = Service.objects.filter(ip=\"192.168.0.110\").count()\n        self.assertEqual(history_count, 0)\n        self.assertEqual(new_service, 0)\n\n    @mock.patch(\"services.tasks.exec_action.delay\",\n                return_value=True)\n    def test_service_action_post(self, tasks):\n        # 参数正常 -> 成功\n        resp = self.post(self.create_action_url, {\"data\": [{\n            \"action\": \"1\",\n            \"id\": \"1\",\n            \"operation_user\": \"admin\",\n        }]}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        # 参数缺失 -> 失败\n        resp = self.post(self.create_action_url, {\"data\": [{\n            \"action\": \"1\",\n        }]}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"请输入action或id\",\n            \"data\": None\n        })\n\n    def test_service_delete_post(self):\n        # 参数正常 -> 成功\n        create_delete_url = reverse(\"delete-list\")\n        service_obj = Service.objects.get(ip=\"192.168.0.110\")\n        resp = self.post(create_delete_url, {\"data\": [{\n            \"action\": \"1\",\n            \"id\": str(service_obj.id),\n            \"operation_user\": \"admin\",\n        }]}).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n"
  },
  {
    "path": "omp_server/tests/test_services/test_services.py",
    "content": "import random\nfrom rest_framework.reverse import reverse\n\nfrom db_models.models import Service\nfrom tests.base import AutoLoginTest\nfrom tests.mixin import (\n    LabelsResourceMixin, ServicesResourceMixin\n)\n\n\nclass ListServiceTest(AutoLoginTest, ServicesResourceMixin):\n    \"\"\" 服务列表测试类 \"\"\"\n\n    def setUp(self):\n        super(ListServiceTest, self).setUp()\n        self.list_service_url = reverse(\"services-list\")\n        self.service_ls = self.get_services()\n\n    def tearDown(self):\n        super(ListServiceTest, self).tearDown()\n        self.destroy_services()\n\n    def test_services_list_filter(self):\n        \"\"\" 测试服务列表过滤 \"\"\"\n\n        # 查询服务列表 -> 展示所有非基础环境服务\n        resp = self.get(self.list_service_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            resp.get(\"data\").get(\"count\"),\n            self.service_ls.filter(\n                service__is_base_env=False).count()\n        )\n        # 所有服务冗余字段 '亲和力' 为 tengine 的字段，视为前端服务，状态设置为 '正常'\n        res_ls = resp.get(\"data\").get(\"results\")\n        for service in res_ls:\n            if service.get(\"is_web\"):\n                self.assertNotEqual(service.get(\"service_status\"), \"未监控\")\n\n        # IP 过滤 -> 模糊匹配\n        ip_field = str(random.randint(1, 20))\n        resp = self.get(self.list_service_url, {\n            \"ip\": ip_field\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            resp.get(\"data\").get(\"count\"),\n            Service.objects.filter(\n                service__is_base_env=False,\n                ip__contains=ip_field).count()\n        )\n\n        # 服务实例名称过滤 -> 模糊匹配\n        name_field = str(random.randint(1, 20))\n        resp = self.get(self.list_service_url, {\n            \"service_instance_name\": name_field\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            resp.get(\"data\").get(\"count\"),\n            self.service_ls.filter(\n                service__is_base_env=False,\n                service_instance_name__contains=name_field).count()\n        )\n\n        # 功能模块过滤 -> 精确匹配\n        label_field = f\"{LabelsResourceMixin.LABEL_NAME_START}_{random.randint(1, 10)}\"\n        resp = self.get(self.list_service_url, {\n            \"label_name\": label_field\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            resp.get(\"data\").get(\"count\"),\n            Service.objects.filter(\n                service__is_base_env=False,\n                service__app_labels__label_name=label_field).count()\n        )\n\n        # 服务类型过滤 -> 精确匹配\n        app_type = random.choice((0, 1))\n        resp = self.get(self.list_service_url, {\n            \"app_type\": app_type\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertEqual(\n            resp.get(\"data\").get(\"count\"),\n            Service.objects.filter(\n                service__is_base_env=False,\n                service__app_type=app_type).count()\n        )\n\n    def test_services_list_order(self):\n        \"\"\" 测试服务列表排序 \"\"\"\n\n        # 不传递排序字段\n        resp = self.get(self.list_service_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIsNotNone(resp.get(\"data\"))\n\n        # 传递排序字段，按照指定字段排序\n        reverse_flag = random.choice((\"\", \"-\"))\n        order_field = \"service_instance_name\"\n        ordering = f\"{reverse_flag}{order_field}\"\n        resp = self.get(self.list_service_url, {\n            \"ordering\": ordering\n        }).json()\n        instance_name_ls = list(map(\n            lambda x: x.get(\"service_instance_name\"),\n            resp.get(\"data\").get(\"results\"))\n        )\n        target_instance_name_ls = list(\n            self.service_ls.filter(\n                service__is_base_env=False).order_by(\n                ordering).values_list(\n                \"service_instance_name\", flat=True))[:10]\n        self.assertEqual(instance_name_ls, target_instance_name_ls)\n\n\nclass ServiceDetailTest(AutoLoginTest, ServicesResourceMixin):\n    \"\"\" 服务详情测试类 \"\"\"\n\n    def setUp(self):\n        super(ServiceDetailTest, self).setUp()\n        self.service_ls = self.get_services()\n\n    def tearDown(self):\n        super(ServiceDetailTest, self).tearDown()\n        self.destroy_services()\n\n    def test_service_detail(self):\n        \"\"\" 测试服务详情 \"\"\"\n\n        # 使用不存在 id -> 未找到\n        resp = self.get(reverse(\"services-detail\", [9999])).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '未找到',\n            'data': None\n        })\n\n        # 使用存在 id -> 查询成功\n        resp = self.get(reverse(\"services-detail\", [\n            random.choice(self.service_ls).id\n        ])).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_users/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tests/test_users/test_login.py",
    "content": "import datetime\n\nfrom rest_framework.reverse import reverse\n\nfrom tests.base import BaseTest\n\n\nclass LoginTest(BaseTest):\n    \"\"\" 登录功能测试类 \"\"\"\n\n    def setUp(self):\n        super(LoginTest, self).setUp()\n        self.login_url = reverse(\"login\")\n        self.users_list_url = reverse(\"users-list\")\n\n    @staticmethod\n    def get_interval_time(gmt_time):\n        \"\"\" 获取间隔时间 \"\"\"\n        expiration_time = datetime.datetime.strptime(\n            gmt_time, \"%a, %d %b %Y %H:%M:%S GMT\"\n        ) + datetime.timedelta(hours=8)\n        expiration_time = expiration_time - datetime.datetime.now()\n        # 加一分钟，避免时间损耗影响计算精度\n        return expiration_time + datetime.timedelta(minutes=1)\n\n    def test_login(self):\n        \"\"\" 测试用户登录 \"\"\"\n\n        # 不提供用户名、密码 -> 登录失败\n        resp = self.post(self.login_url, {}).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Unable to log in with provided credentials.\",\n            \"data\": None,\n        })\n\n        # 用户名、密码为空 -> 登录失败\n        resp = self.post(self.login_url, {\n            \"username\": \"      \",\n            \"password\": self.default_user.password,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Unable to log in with provided credentials.\",\n            \"data\": None,\n        })\n\n        # 用户名错误 -> 登录失败\n        resp = self.post(self.login_url, {\n            \"username\": \"wrong_user\",\n            \"password\": self.default_user.password,\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Unable to log in with provided credentials.\",\n            \"data\": None,\n        })\n\n        # 密码错误 -> 登录失败\n        resp = self.post(self.login_url, {\n            \"username\": self.default_user.username,\n            \"password\": \"wrong_password\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Unable to log in with provided credentials.\",\n            \"data\": None,\n        })\n\n        # 用户名、密码错误 -> 登录失败\n        resp = self.post(self.login_url, {\n            \"username\": \"wrong_user\",\n            \"password\": \"wrong_password\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"Unable to log in with provided credentials.\",\n            \"data\": None,\n        })\n\n        # 用户名、密码正确 -> 登录成功，生成 token 令牌\n        resp = self.post(self.login_url, {\n            \"username\": self.default_user.username,\n            \"password\": self.default_user.password,\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertIn(\"token\", resp.get(\"data\"))\n\n    def test_access_api(self):\n        \"\"\" 测试访问 API \"\"\"\n\n        # 未登录用户 -> 无法访问，提示 \"未认证\"\n        resp = self.get(self.users_list_url).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"未认证\",\n            \"data\": None,\n        })\n\n        # 已登录用户 -> 允许访问\n        self.login()\n        resp = self.get(self.users_list_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n\n        # 退出登录 -> 无法访问，提示 \"未认证\"\n        self.logout()\n        resp = self.get(self.users_list_url).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"未认证\",\n            \"data\": None,\n        })\n\n    def test_jwt_expiration(self):\n        \"\"\" 测试 jwt 过期时间 \"\"\"\n\n        # 登录 (默认) -> jwt 过期时间 1 天\n        self.login()\n        gmt_time = self.client.cookies.get(\"jwtToken\").get(\"expires\")\n        self.assertEqual(self.get_interval_time(gmt_time).days, 1)\n        self.logout()\n\n        # 登录 (记住密码) -> jwt 过期时间 7 天\n        self.login(remember=True)\n        gmt_time = self.client.cookies.get(\"jwtToken\").get(\"expires\")\n        self.assertEqual(self.get_interval_time(gmt_time).days, 7)\n        self.logout()\n"
  },
  {
    "path": "omp_server/tests/test_users/test_users.py",
    "content": "from rest_framework.reverse import reverse\n\nfrom tests.base import AutoLoginTest\nfrom db_models.models import UserProfile\n\n\nclass UsersTest(AutoLoginTest):\n    \"\"\" 用户功能测试类 \"\"\"\n\n    @staticmethod\n    def get_user():\n        \"\"\" 获取用户 \"\"\"\n        user = UserProfile.objects.create_user(\n            username=\"test_user\",\n            password=\"test_user\",\n            email=\"test_user@cloudwise.com\",\n        )\n        return user\n\n    @staticmethod\n    def destroy_user():\n        \"\"\" 销毁用户 \"\"\"\n        UserProfile.objects.filter(\n            username=\"test_user\").delete()\n\n    def setUp(self):\n        super(UsersTest, self).setUp()\n        self.create_user_url = reverse(\"users-list\")\n        self.list_user_url = reverse(\"users-list\")\n\n    def test_create_user(self):\n        \"\"\" 测试创建用户 \"\"\"\n\n        # 已存在用户名 -> 无法创建\n        resp = self.post(self.create_user_url, {\n            \"username\": self.default_user.username,\n            \"password\": self.default_user.password,\n            \"re_password\": self.default_user.password,\n            \"email\": self.default_user.email,\n        }).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '用户名已存在',\n            'data': None\n        })\n\n        # 两次密码不一致 -> 无法创建\n        resp = self.post(self.create_user_url, {\n            \"username\": \"new_user\",\n            \"password\": \"new_password\",\n            \"re_password\": \"diff_password\",\n            \"email\": \"user@cloudwise.com\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"两次密码不一致\",\n            \"data\": None\n        })\n\n        # 邮箱格式不正确 -> 无法创建\n        resp = self.post(self.create_user_url, {\n            \"username\": \"new_user\",\n            \"password\": \"new_password\",\n            \"re_password\": \"diff_password\",\n            \"email\": \"this is a email\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"邮箱格式不正确\",\n            \"data\": None\n        })\n\n        # 全新用户名，两次密码一致 -> 创建成功\n        resp = self.post(self.create_user_url, {\n            \"username\": \"new_user\",\n            \"password\": \"new_password\",\n            \"re_password\": \"new_password\",\n            \"email\": \"user@cloudwise.com\",\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n\n    def test_list_user(self):\n        \"\"\" 测试查询用户列表 \"\"\"\n\n        # 查询用户列表 -> 查询成功\n        resp = self.get(self.create_user_url).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n\n    def test_retrieve_user(self):\n        \"\"\" 测试查询一个用户 \"\"\"\n\n        user = self.get_user()\n\n        # 查询不存在用户 -> 查询失败\n        resp = self.get(reverse(\"users-detail\", [9999])).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '未找到',\n            'data': None\n        })\n\n        # 查询存在用户 -> 查询成功\n        resp = self.get(reverse(\"users-detail\", [user.id])).json()\n        user_info = resp.get(\"data\", None)\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(user_info is not None)\n        self.assertEqual(user_info.get(\"username\"), user.username)\n        self.assertNotEqual(user_info.get(\"password\"), user.password)\n        self.assertEqual(user_info.get(\"email\"), user.email)\n\n        self.destroy_user()\n\n    def test_update_user(self):\n        \"\"\" 测试更新一个已有用户 \"\"\"\n\n        user = self.get_user()\n\n        # 更新不存在用户 -> 更新失败\n        resp = self.put(reverse(\"users-detail\", [9999]), {\n            \"username\": user.username,\n            \"password\": \"update_user_pass\",\n            \"re_password\": \"update_user_pass\",\n            \"email\": \"update_user@cloudwise.com\",\n        }).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '未找到',\n            'data': None\n        })\n\n        # 更新已有用户，密码不一致 -> 更新失败\n        resp = self.put(reverse(\"users-detail\", [user.id]), {\n            \"username\": user.username,\n            \"password\": \"update_user_pass\",\n            \"re_password\": \"update_user_pass_diff\",\n            \"email\": \"update_user@cloudwise.com\",\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"两次密码不一致\",\n            \"data\": None\n        })\n\n        # 更新已有用户，两次密码一致 -> 更新成功\n        new_email = \"update_user_email@cloudwise.com\"\n        resp = self.put(reverse(\"users-detail\", [user.id]), {\n            \"username\": user.username,\n            \"password\": \"update_user_pass\",\n            \"re_password\": \"update_user_pass\",\n            \"email\": new_email,\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n\n        self.destroy_user()\n\n    def test_partial_update_user(self):\n        \"\"\" 测试更新一个现有用户的一个或多个字段 \"\"\"\n\n        user = self.get_user()\n\n        # 更新不存在用户 -> 更新失败\n        resp = self.patch(reverse(\"users-detail\", [9999]), {\n            \"password\": \"partial_update_user_pass\",\n            \"re_password\": \"partial_update_user_pass\",\n            \"email\": \"partial_update_user_email@cloudwise.com\",\n        }).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '未找到',\n            'data': None\n        })\n\n        # 更新存在用户 -> 更新成功\n        new_email = \"partial_update_user_email@cloudwise.com\"\n        resp = self.patch(reverse(\"users-detail\", [user.id]), {\n            \"password\": \"new_password_one\",\n            \"re_password\": \"new_password_one\",\n            \"email\": new_email,\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\", None) is not None)\n\n        self.destroy_user()\n\n    def test_delete_user(self):\n        \"\"\" 测试删除一个现有用户 \"\"\"\n\n        self.get_user()\n\n        # 删除不存在用户 -> 删除失败\n        resp = self.delete(reverse(\"users-detail\", [9999])).json()\n        self.assertDictEqual(resp, {\n            'code': 1,\n            'message': '未找到',\n            'data': None\n        })\n\n        self.destroy_user()\n\n\nclass UserUpdatePasswordTest(AutoLoginTest):\n    \"\"\" 用户更新密码测试类 \"\"\"\n\n    def setUp(self):\n        super(UserUpdatePasswordTest, self).setUp()\n        self.update_password_url = reverse(\"updatePassword-list\")\n\n    def test_update_password(self):\n        \"\"\" 测试更新密码 \"\"\"\n\n        # 原密码错误 -> 更新失败\n        resp = self.post(self.update_password_url, {\n            \"username\": self.default_user.username,\n            \"old_password\": \"error_password\",\n            \"new_password\": \"new_password\"\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"当前密码不正确\",\n            \"data\": None\n        })\n\n        # 新密码包含中文特殊符号 -> 更新失败\n        resp = self.post(self.update_password_url, {\n            \"username\": self.default_user.username,\n            \"old_password\": \"error_password\",\n            \"new_password\": \"zh。～password\"\n        }).json()\n        self.assertDictEqual(resp, {\n            \"code\": 1,\n            \"message\": \"新密码格式不合法\",\n            \"data\": None\n        })\n\n        # 原密码正确 -> 更新成功\n        resp = self.post(self.update_password_url, {\n            \"username\": self.default_user.username,\n            \"old_password\": self.default_user.password,\n            \"new_password\": \"new_password\"\n        }).json()\n        self.assertEqual(resp.get(\"code\"), 0)\n        self.assertEqual(resp.get(\"message\"), \"success\")\n        self.assertTrue(resp.get(\"data\") is not None)\n"
  },
  {
    "path": "omp_server/tests/test_utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 10:24\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_agent_util.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_agent_util\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 10:25\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n主机Agent使用的测试代码\n\"\"\"\n\nimport os\nimport shutil\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom utils.plugin.ssh import SSH\nfrom utils.plugin.agent_util import Agent\nfrom omp_server.settings import PROJECT_DIR\n\n\nclass AgentUtilTest(BaseTest):\n    \"\"\" 主机Agent的测试类 \"\"\"\n\n    def setUp(self):\n        super(AgentUtilTest, self).setUp()\n        self.agent = Agent(\n            host=\"127.0.0.1\",\n            port=22,\n            username=\"root\",\n            password=\"root\",\n            install_dir=\"/data\"\n        )\n        _test_conf_path = os.path.join(PROJECT_DIR, \"package_hub/127.0.0.1\")\n        if os.path.exists(_test_conf_path):\n            shutil.rmtree(_test_conf_path)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    def test_deploy_agent_success(self, check, cmd, file_push):\n        \"\"\"\n        测试成功部署\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_deploy()[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(False, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    def test_deploy_agent_false_ssh(self, check, cmd, file_push):\n        \"\"\"\n        测试ssh连接失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    def test_deploy_agent_false_cmd(self, check, cmd, file_push):\n        \"\"\"\n        测试命令执行失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=(False, \"failed\"))\n    def test_deploy_agent_false_agent_file(self, check, cmd, file_push):\n        \"\"\"\n        测试agent文件推送失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=\"\")\n    def test_deploy_agent_false_file_config(self, file_push, cmd, check):\n        \"\"\"\n        测试配置文件推送失败\n        :return:\n        \"\"\"\n        file_push.side_effect = [\n            (True, \"success\"), (False, \"error\"), (True, \"success\")]\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=\"\")\n    def test_deploy_agent_false_file_script(self, file_push, cmd, check):\n        \"\"\"\n        测试Agent脚本文件推送失败\n        :return:\n        \"\"\"\n        file_push.side_effect = [\n            (True, \"success\"), (True, \"success\"), (False, \"error\")]\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=\"\")\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    def test_deploy_agent_false_start(self, file_push, cmd, check):\n        \"\"\"\n        测试Agent启动失败\n        :return:\n        \"\"\"\n        cmd.side_effect = [(True, \"success\"),\n                           (True, \"success\"), (False, \"error\")]\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=\"\")\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    def test_deploy_agent_success_start(self, file_push, cmd, check):\n        \"\"\"\n        测试Agent启动成功\n        :return:\n        \"\"\"\n        cmd.side_effect = [\n            (True, \"success\"),\n            (True, \"success\"),\n            (False, \"INIT_OMP_SALT_AGENT_SUCCESS\")\n        ]\n        self.assertEqual(self.agent.agent_deploy()[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"file_push\", return_value=(True, \"success\"))\n    @mock.patch.object(Agent, \"generate_conf\", return_value=(False, \"success\"))\n    def test_deploy_agent_false_generate_conf(self, check, cmd, file_push, generate_conf):\n        \"\"\"\n        测试生成配置文件报错\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_deploy()[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_agent_start_success(self, cmd, check):\n        \"\"\"\n        测试启动成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"start\", \"/data/omp_salt_agent\")[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    def test_agent_start_failed(self, cmd, check):\n        \"\"\"\n        测试启动失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"start\", \"/data/omp_salt_agent\")[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_agent_stop_success(self, cmd, check):\n        \"\"\"\n        测试停止成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"stop\", \"/data/omp_salt_agent\")[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    def test_agent_stop_failed(self, cmd, check):\n        \"\"\"\n        测试停止失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"stop\", \"/data/omp_salt_agent\")[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_agent_status_success(self, cmd, check):\n        \"\"\"\n        测试查看状态成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"status\", \"/data/omp_salt_agent\")[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    def test_agent_status_failed(self, cmd, check):\n        \"\"\"\n        测试查看状态失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"status\", \"/data/omp_salt_agent\")[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(True, \"success\"))\n    def test_agent_restart_success(self, cmd, check):\n        \"\"\"\n        测试重启成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"restart\", \"/data/omp_salt_agent\")[0], True)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    def test_agent_restart_failed(self, cmd, check):\n        \"\"\"\n        测试重启失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"restart\", \"/data/omp_salt_agent\")[0], False)\n\n    @mock.patch.object(SSH, \"check\", return_value=(True, \"success\"))\n    @mock.patch.object(SSH, \"cmd\", return_value=(False, \"success\"))\n    def test_agent_method_failed(self, cmd, check):\n        \"\"\"\n        测试管理方法错误\n        :return:\n        \"\"\"\n        self.assertEqual(self.agent.agent_manage(\n            \"test\", \"/data/omp_salt_agent\")[0], False)\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_crontab_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_crontab_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-15 21:18\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n定时任务单元测试代码\n\"\"\"\n\nimport uuid\n\nfrom django_celery_beat.models import PeriodicTask\n\nfrom tests.base import BaseTest\nfrom utils.plugin.crontab_utils import CrontabUtils\n\n\nclass CrontabUtilTest(BaseTest):\n    def setUp(self):\n        super(CrontabUtilTest, self).setUp()\n        self.task_dic = {\n            \"task_name\": str(uuid.uuid4()),\n            \"task_func\": \"test.test.func\",\n            \"task_args\": (1, ),\n            \"task_kwargs\": {\"test\": \"a\"},\n            \"task_timeout\": None\n        }\n\n    def test_create_crontab_job(self):\n        cron_obj = CrontabUtils(**self.task_dic)\n        flag, msg = cron_obj.create_crontab_job()\n        self.assertEqual(flag, True)\n        is_exist = PeriodicTask.objects.filter(\n            name=self.task_dic.get(\"task_name\")).exists()\n        self.assertEqual(is_exist, True)\n        flag, msg = cron_obj.create_crontab_job()\n        self.assertEqual(flag, False)\n        flag, msg = cron_obj.delete_job()\n        self.assertEqual(flag, True)\n        flag, msg = cron_obj.delete_job()\n        self.assertEqual(flag, False)\n\n    def test_create_internal_job(self):\n        cron_obj = CrontabUtils(**self.task_dic)\n        flag, msg = cron_obj.create_internal_job(10)\n        self.assertEqual(flag, True)\n        is_exist = PeriodicTask.objects.filter(\n            name=self.task_dic.get(\"task_name\")).exists()\n        self.assertEqual(is_exist, True)\n        flag, msg = cron_obj.create_internal_job(10)\n        self.assertEqual(flag, False)\n\n    def test_create_internal_failed(self):\n        cron_obj = CrontabUtils(**self.task_dic)\n        flag, msg = cron_obj.create_internal_job(\"test\")\n        self.assertEqual(flag, False)\n        flag, msg = cron_obj.create_internal_job(10, \"test\")\n        self.assertEqual(flag, False)\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_crypto.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_crypto\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 20:00\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nfrom tests.base import BaseTest\nfrom utils.plugin.crypto import AESCryptor\n\n\nclass CryptoUtilTest(BaseTest):\n    \"\"\" 加密解密测试类 \"\"\"\n\n    def setUp(self):\n        super(CryptoUtilTest, self).setUp()\n        self.aes_obj = AESCryptor()\n        self.test_str = \"testStrings\"\n        self.encode_str = \"uea_xeU_d_6YHCCY7Q-e2xZolSw2z2C3KGhLY6iMdnI\"\n\n    def test_encode_success(self):\n        \"\"\"\n        测试加密\n        :return:\n        \"\"\"\n        self.assertEqual(self.encode_str, self.aes_obj.encode(self.test_str))\n\n    def test_decode_success(self):\n        \"\"\"\n        测试解密\n        :return:\n        \"\"\"\n        self.assertEqual(self.test_str, self.aes_obj.decode(self.encode_str))\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_monitor_agent.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_monitor_agent\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-07 15:47\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n测试monitor agent脚本\n\"\"\"\n\nimport os\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom db_models.models import Env\nfrom db_models.models import Host\nfrom omp_server.settings import PROJECT_DIR\nfrom utils.plugin.salt_client import SaltClient\nfrom promemonitor.prometheus_utils import PrometheusUtils\nfrom utils.plugin.monitor_agent import MonitorAgentManager\n\n\nclass MonitorAgentTest(BaseTest):\n    \"\"\" 主机Agent的测试类 \"\"\"\n\n    def setUp(self):\n        super(MonitorAgentTest, self).setUp()\n        env_obj = Env()\n        env_obj.name = \"default\"\n        env_obj.save()\n        self.correct_host_data = {\n            \"instance_name\": \"mysql_instance_1\",\n            \"ip\": \"127.0.0.10\",\n            \"port\": 36000,\n            \"username\": \"root\",\n            \"password\": \"root_password\",\n            \"data_folder\": \"/data\",\n            \"operate_system\": \"CentOS\",\n            \"disk\": {\"/\": 90, \"/data\": 99},\n            \"env\": env_obj\n        }\n        Host(**self.correct_host_data).save()\n        self.host_obj_lst = list(Host.objects.all())\n        self.agent_path = os.path.join(\n            PROJECT_DIR, \"package_hub/omp_monitor_agent.tar.gz\")\n        if not os.path.exists(self.agent_path):\n            with open(self.agent_path, \"w\") as fp:\n                fp.write(\"test\")\n        self.monitor_agent_name = \"omp_monitor_agent.tar.gz\"\n\n    def _install(self):\n        manager = MonitorAgentManager(host_objs=self.host_obj_lst)\n        manager.monitor_agent_package_name = self.monitor_agent_name\n        return manager.install()\n\n    @mock.patch.object(SaltClient, \"cp_file\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_package_do_not_exist(self, *args, **kwargs):\n        \"\"\"\n        测试monitor agent安装包不存在场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        manager = MonitorAgentManager(host_objs=self.host_obj_lst)\n        manager.monitor_agent_package_name = \"\"\n        flag, msg = manager.install()\n        self.assertEqual(flag, False)\n\n    @mock.patch.object(SaltClient, \"cp_file\", return_value=(False, \"success\"))\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_install_failed_send_package(self, *args, **kwargs):\n        \"\"\"\n        测试成功安装monitor agent场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        flag, msg = self._install()\n        self.assertEqual(flag, False)\n\n    @mock.patch.object(SaltClient, \"cp_file\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(False, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_install_failed_cmd(self, *args, **kwargs):\n        \"\"\"\n        测试成功安装monitor agent场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        self.assertEqual(self._install()[0], False)\n\n    @mock.patch.object(SaltClient, \"cp_file\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    @mock.patch.object(\n        PrometheusUtils, \"add_node\", return_value=(True, \"success\"))\n    def test_install_success(self, *args, **kwargs):\n        \"\"\"\n        测试成功安装monitor agent场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        flag, msg = self._install()\n        self.assertEqual(flag, True)\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(False, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_uninstall_failed(self, *args, **kwargs):\n        \"\"\"\n        测试卸载失败场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        manager = MonitorAgentManager(host_objs=self.host_obj_lst)\n        manager.monitor_agent_package_name = self.monitor_agent_name\n        self.assertEqual(manager.uninstall()[0], False)\n\n    @mock.patch.object(SaltClient, \"cmd\", return_value=(True, \"success\"))\n    @mock.patch.object(SaltClient, \"__init__\", return_value=None)\n    def test_uninstall_success(self, *args, **kwargs):\n        \"\"\"\n        测试卸载失败场景\n        :param args:\n        :param kwargs:\n        :return:\n        \"\"\"\n        manager = MonitorAgentManager(host_objs=self.host_obj_lst)\n        manager.monitor_agent_package_name = self.monitor_agent_name\n        self.assertEqual(manager.uninstall()[0], True)\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_public_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_public_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-09 21:24\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n测试公共工具类\n\"\"\"\n\nimport os\nimport uuid\nimport socket\nfrom unittest import mock\n\nfrom tests.base import BaseTest\nfrom utils.plugin.public_utils import get_file_md5\nfrom utils.plugin.public_utils import check_ip_port\n\n\nclass GetFileMd5Test(BaseTest):\n    \"\"\" 获取文件md5值测试 \"\"\"\n\n    def setUp(self):\n        super(GetFileMd5Test, self).setUp()\n        self.file_path = os.path.realpath(__file__)\n\n    def test_get_md5_failed_file_not_exist(self):\n        \"\"\"\n        测试文件不存在时现象\n        :return:\n        \"\"\"\n        file_path = self.file_path + str(uuid.uuid4())\n        flag, message = get_file_md5(file_path)\n        self.assertEqual(flag, False)\n\n    def test_get_md5_success(self):\n        \"\"\"\n        测试正常解析时现象\n        :return:\n        \"\"\"\n        flag, message = get_file_md5(self.file_path)\n        self.assertEqual(flag, True)\n\n\nclass CheckIpPortTest(BaseTest):\n    \"\"\" ip 端口检查测试 \"\"\"\n\n    def setUp(self):\n        super(CheckIpPortTest, self).setUp()\n\n    @mock.patch.object(socket.socket, \"connect_ex\", return_value=0)\n    def test_success(self, *args, **kwargs):\n        flag, msg = check_ip_port(ip=\"127.0.0.1\", port=123)\n        self.assertEqual(flag, True)\n\n    @mock.patch.object(socket.socket, \"connect_ex\", return_value=1)\n    def test_failed(self, *args, **kwargs):\n        flag, msg = check_ip_port(ip=\"127.0.0.1\", port=123)\n        self.assertEqual(flag, False)\n\n    def test_failed_with_ip_wrong(self):\n        flag, msg = check_ip_port(ip=\"127\", port=123)\n        self.assertEqual(flag, False)\n        self.assertEqual(msg, \"ip address not correct\")\n\n    def test_failed_with_port_wrong(self):\n        flag, msg = check_ip_port(ip=\"127.0.0.1\", port=123456)\n        self.assertEqual(flag, False)\n        self.assertEqual(msg, \"port must be 0 ~ 65535\")\n\n    def test_failed_with_port_str(self):\n        flag, msg = check_ip_port(ip=\"127.0.0.1\", port=\"port\")\n        self.assertEqual(flag, False)\n        self.assertEqual(msg, \"port must be 0 ~ 65535, int or string\")\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_salt_client.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_salt_client\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-24 15:10\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nsalt client相关测试\n\"\"\"\nfrom unittest import mock\n\nimport salt.client\n\nfrom tests.base import BaseTest\nfrom utils.plugin.salt_client import SaltClient\n\n\nclass SaltClientUtilTest(BaseTest):\n    \"\"\" salt客户端测试类 \"\"\"\n\n    @mock.patch.object(salt.client.LocalClient, \"__init__\", return_value=None)\n    def setUp(self, local_client):\n        super(SaltClientUtilTest, self).setUp()\n        self.cmd_run = \"cmd.run\"\n        self.obj = SaltClient()\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_salt_module_update_failed(self, local_client):\n        \"\"\"\n        测试同步salt模块成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.salt_module_update()[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\n        '192.168.175': {'ret': [], 'retcode': 0, 'jid': '20210113213356939481'},\n        '192.168.176': False,\n        '192.168.177': [],\n    })\n    def test_salt_module_update_success(self, local_client):\n        \"\"\"\n        测试同步salt模块成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.salt_module_update()[0], True)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_fun_for_multi_failed(self, local_client):\n        \"\"\"\n        测试批量执行时错误的情况\n        :return:\n        \"\"\"\n        local_client.side_effect = Exception(\"aa\")\n        self.assertEqual(self.obj.fun_for_multi(\"*\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={})\n    def test_fun_for_multi_success(self, local_client):\n        \"\"\"\n        测试批量执行成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun_for_multi(\"*\", self.cmd_run), {})\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_fun_failed(self, local_client):\n        \"\"\"\n        测试执行fun出现异常情况\n        :return:\n        \"\"\"\n        local_client.side_effect = Exception(\"aa\")\n        self.assertEqual(self.obj.fun(\"*\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\n        \"key1\": {'ret': \"success\", 'retcode': 0, 'jid': '20210113213356939481'}\n    })\n    def test_fun_success(self, local_client):\n        \"\"\"\n        测试批量执行成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], True)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_fun_failed_1(self, local_client):\n        \"\"\"\n        测试salt.cmd返回不是字典的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={})\n    def test_fun_failed_2(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中不带salt-key的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": False})\n    def test_fun_failed_3(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中salt-key 为False情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": {}})\n    def test_fun_failed_4(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中retcode不存在情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": {'retcode': 1}})\n    def test_fun_failed_5(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中retcode不为0情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.fun(\"key1\", self.cmd_run)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_cmd_failed(self, local_client):\n        \"\"\"\n        测试执行cmd出现异常情况\n        :return:\n        \"\"\"\n        local_client.side_effect = Exception(\"aa\")\n        self.assertEqual(self.obj.cmd(\"*\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\n        \"key1\": {'ret': \"success\", 'retcode': 0, 'jid': '20210113213356939481'}\n    })\n    def test_cmd_success(self, local_client):\n        \"\"\"\n        测试cmd执行成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], True)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_cmd_failed_1(self, local_client):\n        \"\"\"\n        测试salt.cmd返回不是字典的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={})\n    def test_cmd_failed_2(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中不带salt-key的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": False})\n    def test_cmd_failed_3(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中salt-key 为False情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": {}})\n    def test_cmd_failed_4(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中retcode不存在情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": {'retcode': 1}})\n    def test_cmd_failed_5(self, local_client):\n        \"\"\"\n        测试salt.cmd返回中retcode不为0情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cmd(\"key1\", self.cmd_run, 1)[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_cp_file_failed(self, local_client):\n        \"\"\"\n        测试执行cp_file出现异常情况\n        :return:\n        \"\"\"\n        local_client.side_effect = Exception(\"aa\")\n        self.assertEqual(self.obj.cp_file(\"*\", \"\", \"\")[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\n        \"key1\": \"a\"\n    })\n    def test_cp_file_success(self, local_client):\n        \"\"\"\n        测试推送文件成功的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cp_file(\"key1\", \"a\", \"a\")[0], True)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value=\"\")\n    def test_cp_file_failed_1(self, local_client):\n        \"\"\"\n        测试cp_file返回不是字典的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cp_file(\"*\", \"\", \"\")[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={})\n    def test_cp_file_failed_2(self, local_client):\n        \"\"\"\n        测试cp_file返回中不带salt-key的情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cp_file(\"*\", \"\", \"\")[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": \"PermissionError Permission denied\"})\n    def test_cp_file_failed_3(self, local_client):\n        \"\"\"\n        测试cp_file返回中权限错误情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cp_file(\"*\", \"\", \"\")[0], False)\n\n    @mock.patch.object(salt.client.LocalClient, \"cmd\", return_value={\"key1\": {\"ret\": \"\"}})\n    def test_cp_file_failed_4(self, local_client):\n        \"\"\"\n        测试cp_file返回值不准确情况\n        :return:\n        \"\"\"\n        self.assertEqual(self.obj.cp_file(\"*\", \"aa\", \"aa\")[0], False)\n"
  },
  {
    "path": "omp_server/tests/test_utils/test_ssh.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: test_ssh\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-23 15:48\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\nssh 单元测试代码\n# TODO 待完善，与环境隔离\n\"\"\"\n\nfrom unittest import mock\n\nfrom scp import SCPClient\nfrom paramiko import SSHClient\n\nfrom tests.base import BaseTest\nfrom utils.plugin.ssh import SSH\n\n\ndef get_ssh_obj(username=\"root\"):\n    \"\"\"\n    获取ssh对象\n    :return:\n    \"\"\"\n    return SSH(\n        hostname=\"127.0.0.1\",\n        port=22,\n        username=username,\n        password=\"root\",\n        timeout=1\n    )\n\n\nclass ChannelMock(object):\n    \"\"\" 模拟channel \"\"\"\n\n    def recv_exit_status(self):\n        \"\"\"\n        模拟方法\n        :return:\n        \"\"\"\n\n\nclass StdoutMock(object):\n    \"\"\" 模拟输出 \"\"\"\n\n    def __init__(self, content):\n        self.content = content\n        self.channel = ChannelMock()\n\n    def readline(self):\n        \"\"\"\n        读取数据\n        :return:\n        \"\"\"\n        return self.content\n\n    def readlines(self):\n        \"\"\"\n        读取数据\n        :return:\n        \"\"\"\n        if self.content:\n            return [self.content]\n        return []\n\n\nclass SshUtilTest(BaseTest):\n    \"\"\" ssh工具测试类 \"\"\"\n\n    def setUp(self):\n        super(SshUtilTest, self).setUp()\n        self.ssh = get_ssh_obj()\n\n    def test_get_connection_failed(self):\n        \"\"\"\n        测试ssh连接失败信息\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh._get_connection(), None)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    def test_get_connection_success(self, *args, **kwargs):\n        \"\"\"\n        测试ssh连接正常\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh._get_connection(), None)\n        self.assertEqual(self.ssh.is_error, None)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_is_sudo_success(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试sudo\n        :return:\n        \"\"\"\n        stdout = StdoutMock(\"success\")\n        exec_command.side_effect = [(\"\", stdout, 0), ]\n        self.assertEqual(self.ssh.is_sudo()[0], True)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_is_sudo_failed(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试sudo\n        :return:\n        \"\"\"\n        stdout = StdoutMock(\"failed\")\n        exec_command.side_effect = [(\"\", stdout, 0), ]\n        self.assertEqual(self.ssh.is_sudo()[0], False)\n\n    def test_is_sudo_failed_connected(self):\n        \"\"\"\n        测试ssh检查报错，连接信息报错\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.is_sudo()[0], False)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_check_failed(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试ssh检查报错\n        :return:\n        \"\"\"\n        stdout = mock.MagicMock()\n        exec_command.side_effect = [(\"\", stdout, 0), ]\n        self.assertEqual(self.ssh.check()[0], False)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_check_success(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试ssh检查成功\n        :return:\n        \"\"\"\n        stdout = StdoutMock(\"root\")\n        exec_command.side_effect = [(\"\", stdout, 0), ]\n        self.assertEqual(self.ssh.check()[0], True)\n\n    def test_ssh_check_failed_connected(self):\n        \"\"\"\n        测试ssh检查报错，连接信息报错\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.check()[0], False)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_cmd_failed(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试执行命令失败\n        :return:\n        \"\"\"\n        stderr = StdoutMock(\"failed\")\n        stdout = StdoutMock(\"test\")\n        exec_command.side_effect = [(\"\", stdout, stderr), ]\n        self.assertEqual(self.ssh.cmd(\"aaa\", get_pty=False)[0], False)\n\n    def test_ssh_cmd_failed_connected(self):\n        \"\"\"\n        测试ssh检查报错，连接信息报错\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.cmd(\"ip a\")[0], False)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_cmd_success(self, exec_command, *args, **kwargs):\n        \"\"\"\n        测试执行命令成功\n        :return:\n        \"\"\"\n        stderr = StdoutMock(\"\")\n        stdout = StdoutMock(\"root\")\n        exec_command.side_effect = [(\"\", stdout, stderr), ]\n        self.assertEqual(self.ssh.cmd(\"whoami\")[0], True)\n\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SSHClient, \"close\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SCPClient, \"close\", return_value=None)\n    def test_ssh_close_success(self, *args, **kwargs):\n        \"\"\"\n        关闭连接成功\n        :return:\n        \"\"\"\n        self.ssh._get_connection()\n        self.assertEqual(self.ssh.close(), None)\n\n    @mock.patch.object(SSH, \"make_remote_path_exist\", return_value=None)\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SCPClient, \"put\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_file_push_success(self, *args, **kwargs):\n        \"\"\"\n        发送文件成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.file_push(__file__, \"/tmp\")[0], True)\n\n    @mock.patch.object(SSH, \"close\", return_value=None)\n    @mock.patch.object(SSH, \"make_remote_path_exist\", return_value=None)\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    @mock.patch.object(SSHClient, \"exec_command\", return_value=(\"\", \"success\", 0))\n    def test_ssh_file_push_failed_exception(self, *args, **kwargs):\n        \"\"\"\n        发送文件成功\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.file_push(__file__, \"/tmp\")[0], False)\n\n    def test_ssh_file_push_failed_connected(self):\n        \"\"\"\n        发送文件失败\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.file_push(__file__, \"/tmp\")[0], False)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=None)\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    def test_make_remote_path_exist_root(self, *args, **kwargs):\n        \"\"\"\n        测试root用户情况下的远程目录存在\n        :return:\n        \"\"\"\n        self.assertEqual(self.ssh.make_remote_path_exist(\"/tmp\"), None)\n\n    @mock.patch.object(SSH, \"cmd\", return_value=None)\n    @mock.patch.object(SSHClient, \"set_missing_host_key_policy\", return_value=None)\n    @mock.patch.object(SSHClient, \"connect\", return_value=None)\n    @mock.patch.object(SSHClient, \"get_transport\", return_value=None)\n    @mock.patch.object(SCPClient, \"__init__\", return_value=None)\n    def test_make_remote_path_exist_not_root(self, *args, **kwargs):\n        \"\"\"\n        测试root用户情况下的远程目录存在\n        :return:\n        \"\"\"\n        # ssh_obj = get_ssh_obj(\"aaa\")\n        # self.assertEqual(ssh_obj.make_remote_path_exist(\"/tmp\"), None)\n        pass\n"
  },
  {
    "path": "omp_server/tool/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/tool/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/tool/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ToolConfig(AppConfig):\n    name = 'tool'\n"
  },
  {
    "path": "omp_server/tool/find_tools.py",
    "content": "import os\nimport uuid\nimport logging\nfrom concurrent.futures import ThreadPoolExecutor, as_completed, wait, \\\n    ALL_COMPLETED\n\nfrom django.conf import settings\nfrom ruamel import yaml\n\nfrom app_store.new_install_utils import RedisDB\nfrom db_models.models import ToolInfo\nfrom utils.parse_config import THREAD_POOL_MAX_WORKERS\nfrom utils.plugin.public_utils import file_md5, local_cmd\n\n\nlogger = logging.getLogger(\"server\")\n\n\nbase_tar_path = os.path.join(settings.PROJECT_DIR, \"package_hub/tool\")\nverify_tar_path = os.path.join(base_tar_path, \"verify_tar\")\nverified_folder_path = os.path.join(base_tar_path, \"folder\")\nverified_tar_path = os.path.join(base_tar_path, \"tar\")\n\n\nclass ValidForm:\n\n    def __init__(self, form_list):\n        self.form_list = form_list\n\n    def base_valid(self, form):\n        for k in [\"key\", \"name\"]:\n            if not form.get(k):\n                raise Exception(f\"args中部分缺少{k}！\")\n            if not isinstance(form.get(k), str):\n                raise Exception(f\"args中{k}参数格式不正确！\")\n        if \"key\" == \"output\":\n            raise Exception(f\"args中key不可为output！\")\n        if not form.get(\"required\"):\n            form[\"required\"] = False\n        else:\n            form[\"required\"] = True\n\n    def valid_file(self, form):\n        if form.get(\"default\"):\n            form.pop(\"default\")\n\n    def valid_select(self, form):\n        options = form.get(\"options\")\n        if not options:\n            raise Exception(f\"args中单选缺少options参数！\")\n        if not isinstance(options, list):\n            raise Exception(f\"args中单选options参数类型不正确！\")\n        for option in options:\n            if not isinstance(option, str):\n                raise Exception(f\"args中单选options选项类型只支持字符串！\")\n        if form.get(\"default\") and form.get(\"default\") not in options:\n            raise Exception(f\"args中单选default选项必须在options中！\")\n\n    def valid_input(self, form):\n        if form.get(\"default\") and not isinstance(form.get(\"default\"), str):\n            raise Exception(f\"args中文本内容default只支持字符串\")\n\n    def __call__(self, *args, **kwargs):\n        for form in self.form_list:\n            form_type = form.get(\"type\")\n            if not hasattr(self, f\"valid_{form_type}\"):\n                raise Exception(f\"暂不支持{form_type}类型\")\n            self.base_valid(form)\n            getattr(self, f\"valid_{form_type}\")(form)\n        return self.form_list\n\n\nclass ValidToolTar:\n    key_default = {\n        \"labels\": \"management\",\n        \"spec\": {\"target\": \"host\"},\n        \"args\": [],\n        \"output\": {\"type\": \"terminal\", \"required\": False},\n        \"send_package\": []\n    }\n    key_required = {\"name\", \"desc\", \"script_name\", \"script_type\"}\n\n    def __init__(self, tmp_package, tar_file):\n        self.tmp_package = tmp_package\n        self.tar_file = tar_file\n        self.tool_info = {}\n\n    def verify_args(self, script_args):\n        ValidForm(script_args)\n        self.tool_info[\"script_args\"] = script_args\n        return True\n    verify_args._type = list\n\n    def verify_name(self, name):\n        if not bool(name):\n            raise Exception(\"name不可为空！\")\n        self.tool_info[\"name\"] = name\n        return True\n    verify_name._type = str\n\n    def verify_desc(self, desc):\n        if not bool(desc):\n            raise Exception(\"desc不可为空！\")\n        self.tool_info[\"description\"] = desc\n        return True\n    verify_desc._type = str\n\n    def verify_labels(self, kind):\n        if not hasattr(ToolInfo, f\"KIND_{kind.upper()}\"):\n            raise Exception(\"yaml中labels类型不支持！\")\n        self.tool_info[\"kind\"] = getattr(ToolInfo, f\"KIND_{kind.upper()}\")\n        return True\n    verify_labels._type = str\n\n    def verify_spec(self, spec):\n        target_name = spec.get(\"target\", \"host\")\n        if not isinstance(target_name, str):\n            raise Exception(\"yaml中target参数类型错误！\")\n        self.tool_info[\"target_name\"] = target_name\n        templates = spec.get(\"templates\") or []\n        if not isinstance(templates, list):\n            raise Exception(\"templates参数类型不正确！\")\n        for template in templates:\n            if not os.path.isfile(os.path.join(self.folder_path, template)):\n                raise Exception(f\"模版文件{template}文件不存在！\")\n        self.tool_info[\"template_filepath\"] = templates\n        connection_args = spec.get(\"connection_args\") or []\n        if not isinstance(connection_args, list):\n            raise Exception(\"connection_args参数类型不正确！\")\n        for connection_arg in connection_args:\n            if not isinstance(connection_arg, str):\n                raise Exception(\"connection_args参数类型不正确！\")\n        self.tool_info[\"obj_connection_args\"] = connection_args\n        return True\n    verify_spec._type = dict\n\n    def verify_output(self, output):\n        if not hasattr(ToolInfo, f\"OUTPUT_{output.get('type').upper()}\"):\n            raise Exception(\"output参数只能是terminal或file！\")\n        self.tool_info[\"output\"] = output\n        return True\n    verify_output._type = dict\n\n    def verify_send_package(self, send_package):\n        for package in send_package:\n            if not os.path.isfile(os.path.join(self.folder_path, package)):\n                raise Exception(f\"需要发送的文件{package}文件不存在！\")\n        self.tool_info[\"send_package\"] = send_package\n        return True\n    verify_send_package._type = list\n\n    def verify_script_name(self, script_name):\n        if not os.path.isfile(os.path.join(self.folder_path, script_name)):\n            raise Exception(f\"需要发送的文件{script_name}文件不存在！\")\n        self.tool_info[\"script_path\"] = script_name\n        return True\n    verify_script_name._type = str\n\n    def verify_script_type(self, script_type):\n        if not hasattr(ToolInfo, f\"SCRIPT_TYPE_{script_type.upper()}\"):\n            raise Exception(\"script_type参数只能是python3或shell！\")\n        self.tool_info[\"script_type\"] = getattr(\n            ToolInfo, f\"SCRIPT_TYPE_{script_type.upper()}\"\n        )\n        return True\n    verify_script_type._type = str\n\n    def verify_yaml_info(self, package_name):\n        yaml_path = os.path.join(self.folder_path, f\"{package_name}.yaml\")\n        with open(yaml_path, \"r\", encoding=\"utf8\") as fp:\n            content = yaml.load(fp.read(), yaml.Loader)\n        for k in self.key_required:\n            if k not in content:\n                raise Exception(f\"yaml中{k}参数为必填！\")\n        for k, default in self.key_default.items():\n            if not content.get(k):\n                content[k] = default\n        for k, v in content.items():\n            verify_func = getattr(self, f\"verify_{k}\")\n            if not isinstance(v, getattr(verify_func, \"_type\")):\n                raise Exception(f\"{k}参数类型不正确！\")\n            verify_func(v)\n        return True\n\n    def read_read_me(self):\n        read_me_path = os.path.join(self.folder_path, \"README.md\")\n        with open(read_me_path, \"r\", encoding=\"utf-8\") as f:\n            data = f.read()\n        self.tool_info[\"readme_info\"] = data\n\n    def create_tool_info(self, package_name, md5):\n        tar_save_name = self.tar_file.replace(\".tar.gz\", f\"-{md5}.tar.gz\")\n        new_tar_path = os.path.join(verified_tar_path, tar_save_name)\n        old_tar_path = os.path.join(self.tmp_package, self.tar_file)\n\n        old_package_folder = os.path.join(\n            self.tmp_package, f\"{package_name}_{md5}/{package_name}\")\n        new_package_folder = os.path.join(\n            verified_folder_path, f\"{package_name}-{md5}\")\n        _out, _err, _code = local_cmd(\n            f\"mv {old_tar_path} {new_tar_path} && \"\n            f\"mv {old_package_folder} {new_package_folder}\"\n        )\n        if _code:\n            return _out\n        self.tool_info[\"source_package_md5\"] = md5\n        tool_folder = f\"tool/folder/{package_name}-{md5}\"\n        self.tool_info[\"tool_folder_path\"] = tool_folder\n        self.tool_info[\"source_package_path\"] = f\"tool/tar/{tar_save_name}\"\n        output_dict = self.tool_info.pop(\"output\")\n        output = getattr(ToolInfo, f\"OUTPUT_{output_dict.get('type').upper()}\")\n        if output == ToolInfo.OUTPUT_FILE:\n            self.tool_info[\"script_args\"].append(\n                {\n                    'key': 'output',\n                    'name': 'output',\n                    'type': 'input',\n                    'required': output_dict.get(\"required\", False)\n                }\n            )\n        if os.path.exists(os.path.join(new_package_folder, 'logo.svg')):\n            self.tool_info[\"logo\"] = f\"{tool_folder}/logo.svg\"\n        ToolInfo(output=output, **self.tool_info).save()\n        self.rm_tool_package()\n\n    def rm_tool_package(self):\n        local_cmd(f\"/bin/rm -rf {self.tmp_package}\")\n\n    def __call__(self, *args, **kwargs):\n        package_name = self.tar_file.split(\"-\")[0]\n        if not package_name:\n            return f\"{self.tar_file}名称不符合要求！\"\n        file_path = os.path.join(self.tmp_package, self.tar_file)\n        md5 = file_md5(file_path)\n        if not md5:\n            local_cmd(f'/bin/rm -rf {file_path}')\n            return f\"获取{self.tar_file}的md5值失败！\"\n        if ToolInfo.objects.filter(source_package_md5=md5).exists():\n            return f\"{self.tar_file}已存在！\"\n        tar_folder = os.path.join(self.tmp_package, f\"{package_name}_{md5}\")\n        _out, _err, _code = local_cmd(\n            f\"mkdir -p {tar_folder} && tar -mxf {file_path} -C {tar_folder}\"\n        )\n        if _code:\n            self.rm_tool_package()\n            return _out\n        self.folder_path = os.path.join(tar_folder, package_name)\n        if not os.path.isfile(\n                os.path.join(self.folder_path, f\"{package_name}.yaml\")\n        ):\n            self.rm_tool_package()\n            return f\"{self.tar_file}中必须包含文件{package_name}.yaml！\"\n        try:\n            self.verify_yaml_info(package_name)\n        except Exception as e:\n            self.rm_tool_package()\n            return str(e)\n        if os.path.isfile(os.path.join(self.folder_path, \"README.md\")):\n            self.read_read_me()\n        self.create_tool_info(package_name, md5)\n        return \"\"\n\n\ndef verify_tar_files(tmp_package, tar_files):\n    with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n        all_task = []\n        for tar_file in tar_files:\n            valid_obj = ValidToolTar(tmp_package, tar_file)\n            future_obj = executor.submit(valid_obj)\n            all_task.append(future_obj)\n        wait(all_task, return_when=ALL_COMPLETED)\n        success = True\n        for future in as_completed(all_task):\n            if future.result():\n                logger.info(future.result())\n                success = False\n    local_cmd(f\"/bin/rm -rf {tmp_package}\")\n    return success\n\n\ndef load_verify_tar(tar_name=None):\n    tmp_package = os.path.join(\n        settings.PROJECT_DIR, \"tmp\", uuid.uuid4().hex)\n    local_cmd(f'mkdir -p {tmp_package}')\n    tar_files = []\n    with RedisDB().conn.lock(settings.SCAN_TOOL_LOCK_KEY):\n        if tar_name:\n            file_path = os.path.join(verify_tar_path, tar_name)\n            if os.path.exists(file_path):\n                local_cmd(f'mv {file_path} {tmp_package}')\n                tar_files.append(tar_name)\n            return tmp_package, tar_files\n        tar_packages = os.listdir(verify_tar_path)\n        for tar_package in tar_packages:\n            file_path = os.path.join(verify_tar_path, tar_package)\n            if not tar_package.endswith(\".tar.gz\"):\n                local_cmd(f'/bin/rm -rf {file_path}')\n                continue\n            local_cmd(f'mv {file_path} {tmp_package}')\n            tar_files.append(tar_package)\n    return tmp_package, tar_files\n\n\ndef find_tools_package(tar_name=None):\n    tmp_package, tar_files = load_verify_tar(tar_name)\n    return verify_tar_files(tmp_package, tar_files)\n"
  },
  {
    "path": "omp_server/tool/serializers.py",
    "content": "import json\nimport os\nimport random\nimport string\n\nfrom django.db import transaction\nfrom rest_framework import serializers\nfrom db_models.models import ToolExecuteMainHistory, ToolInfo, Host, Service, \\\n    ToolExecuteDetailHistory, UploadFileHistory\nfrom tool.tasks import exec_tools_main\nfrom utils.common.exceptions import GeneralError\n\n\nclass ToolInfoSerializer(serializers.ModelSerializer):\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ToolInfo\n        fields = (\"target_name\",)\n\n\nclass ToolDetailSerializer(serializers.ModelSerializer):\n    tool = ToolInfoSerializer()\n    tool_detail = serializers.SerializerMethodField()\n    count = serializers.SerializerMethodField()\n    tool_args = serializers.SerializerMethodField()\n    duration = serializers.SerializerMethodField()\n    run_user = serializers.SerializerMethodField()\n    time_out = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ToolExecuteMainHistory\n        fields = (\n            \"tool\", \"tool_detail\", \"count\", \"tool_args\",\n            \"duration\", \"run_user\", \"time_out\", \"task_name\",\n            \"operator\", \"status\", \"start_time\", \"end_time\"\n        )\n\n    def tools_boj_ls(self, obj):\n        if hasattr(self, \"tools_obj\"):\n            return self.tools_obj\n        tools_obj = ToolExecuteDetailHistory.objects.filter(main_history=obj)\n        setattr(self, \"tools_obj\", tools_obj)\n        return tools_obj\n\n    def get_duration(self, obj):\n        return obj.duration\n\n    def get_run_user(self, obj):\n        user = self.tools_boj_ls(obj).first().run_user\n        user = user if user else \"salt执行用户\"\n        return user\n\n    def get_time_out(self, obj):\n        return self.tools_boj_ls(obj).first().time_out\n\n    def get_count(self, obj):\n        return self.tools_boj_ls(obj).count()\n\n    def get_tool_detail(self, obj):\n        \"\"\"\n        获取detail详情\n        \"\"\"\n        tool_list = []\n        for obj in self.tools_boj_ls(obj):\n            url = \"\"\n            if obj.output:\n                url = f\"tool/download_data/{obj.output.get('file')[0]}\"\n            tool_list.append(\n                {\n                    \"ip\": obj.target_ip,\n                    \"status\": obj.status,\n                    \"log\": obj.execute_log,\n                    \"url\": url\n                }\n            )\n        return tool_list\n\n    def get_tool_args(self, obj):\n        tool_args = []\n        detail_args = obj.toolexecutedetailhistory_set.first().execute_args\n        for args in obj.tool.script_args:\n            value = detail_args.get(args.get('key'), \"\")\n            if value:\n                tool_args.append({\n                    \"name\": args.get('name'),\n                    \"value\": value\n                })\n        return tool_args\n\n\nclass ToolFormDetailSerializer(serializers.ModelSerializer):\n    default_form = serializers.DictField(source=\"load_default_form\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = ToolInfo\n        fields = (\"id\", \"name\", \"default_form\", \"script_args\")\n\n\nclass ToolListSerializer(serializers.ModelSerializer):\n    \"\"\"工具列表序列化\"\"\"\n    used_number = serializers.SerializerMethodField()\n\n    def get_used_number(self, obj):\n        return ToolExecuteMainHistory.objects.filter(tool=obj).count()\n\n    class Meta:\n        model = ToolInfo\n        fields = (\"id\", \"logo\", \"name\", \"kind\", \"used_number\", \"description\")\n\n\nclass ToolInfoDetailSerializer(serializers.ModelSerializer):\n    \"\"\"工具详情序列化\"\"\"\n\n    class Meta:\n        model = ToolInfo\n        fields = (\"name\", \"description\", \"logo\", \"tar_url\", \"kind\",\n                  \"target_name\", \"script_path\", \"script_args\", \"templates\",\n                  \"readme_info\")\n\n\nclass ToolTargetObjectHostSerializer(serializers.ModelSerializer):\n    host_agent_state = serializers.SerializerMethodField()\n\n    def get_host_agent_state(self, obj):\n        if obj.host_agent == str(obj.AGENT_RUNNING):\n            return \"正常\"\n        return \"异常\"\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Host\n        fields = (\"id\", \"instance_name\", \"ip\", \"host_agent_state\")\n\n\nclass ToolTargetObjectServiceSerializer(serializers.ModelSerializer):\n    instance_name = serializers.CharField(source=\"service_instance_name\")\n    host_agent_state = serializers.SerializerMethodField()\n    modifiable_kwargs = serializers.SerializerMethodField()\n\n    def get_host_agent_state(self, obj):\n        host = Host.objects.filter(ip=obj.ip).first()\n        if host and host.host_agent == str(host.AGENT_RUNNING):\n            return \"正常\"\n        return \"异常\"\n\n    def get_modifiable_kwargs(self, obj):\n        modifiable_kwargs = {}\n        tool = self.context.get(\"view\").kwargs[\"tool\"]\n        connection_args = tool.obj_connection_args\n        connect_obj = obj.service_connect_info\n        port_infos = json.loads(obj.service_port)\n        port_dict = {}\n        for port_info in port_infos:\n            port_dict.update({port_info.get(\"key\"): port_info.get(\"default\")})\n        for arg_key in connection_args:\n            if arg_key in port_dict:\n                modifiable_kwargs[arg_key] = port_dict[arg_key]\n            elif connect_obj and hasattr(connect_obj, arg_key):\n                modifiable_kwargs[arg_key] = getattr(connect_obj, arg_key)\n            else:\n                modifiable_kwargs[arg_key] = \"\"\n        return modifiable_kwargs\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = Service\n        fields = (\"id\", \"instance_name\", \"ip\", \"host_agent_state\",\n                  \"modifiable_kwargs\")\n\n\nclass ValidFormAnswer:\n\n    def __init__(self, questions, answers):\n        self.questions = questions\n        self.answers = answers\n\n    def valid_file(self, question):\n        if not question.get(\"default\", {}):\n            return True\n        file_union_id = question.get(\"default\", {}).get(\"union_id\")\n        file = UploadFileHistory.objects.filter(union_id=file_union_id).last()\n        if not file:\n            raise GeneralError(f\"表单{question.get('name')}提交的文件不存在！\")\n        question[\"default\"].update(\n            file_name=file.file_name,\n            file_url=file.file_url\n        )\n        return True\n\n    def valid_select(self, question):\n        options = question.get(\"options\")\n        answer = question.get(\"default\")\n        if answer and answer not in options:\n            raise GeneralError(f\"表单{question.get('name')}提交的选项不正确！\")\n        return True\n\n    def valid_input(self, question):\n        return True\n\n    def is_valid(self):\n        for question in self.questions:\n            form_type = question.get(\"type\")\n            if not hasattr(self, f\"valid_{form_type}\"):\n                raise GeneralError(f\"暂不支持{form_type}类型\")\n            answer = self.answers.get(question.get(\"key\"))\n            if question.get(\"required\") and not answer:\n                raise GeneralError(f\"{question.get('name')}为必填！\")\n            question.update(default=answer)\n            getattr(self, f\"valid_{form_type}\")(question)\n        return self.questions\n\n\nclass ToolFormAnswerSerializer(serializers.Serializer):\n    id = serializers.IntegerField(read_only=True, default=0)\n\n    default_form = serializers.DictField(\n        help_text=\"默认表单\",\n        required=True,\n        error_messages={\"required\": \"默认表单为必填\"}\n    )\n    script_args = serializers.ListField(help_text=\"自定义参数\", required=False)\n\n    def verify_task_name(self, value):\n        if not value:\n            raise GeneralError(\"任务名称为必填字段\")\n        return str(value)\n\n    def verify_host_info(self, values):\n        target_ips = Host.objects.filter(\n            id__in=[value.get(\"id\") for value in values],\n            host_agent=str(Host.AGENT_RUNNING)\n        ).values(\"ip\", \"data_folder\")\n        if len(target_ips) != len(values):\n            raise GeneralError(\"主机数据异常，请重新选择执行对象！\")\n        data_folders = {}\n        for target_ip in target_ips:\n            data_folders[target_ip[\"ip\"]] = target_ip[\"data_folder\"]\n        self.context.get(\"view\").kwargs[\"data_folders\"] = data_folders\n\n    def verify_service_info(self, values, tool):\n        ids = []\n        for value in values:\n            ids.append(value.get(\"id\"))\n            modifiable_kwargs = value.get(\"modifiable_kwargs\", {})\n            for _arg in tool.obj_connection_args:\n                if _arg not in modifiable_kwargs:\n                    raise GeneralError(f\"参数{_arg}必填！\")\n        target_ips = list(\n            Service.objects.filter(\n                service__app_name=tool.target_name,\n                id__in=ids\n            ).values_list(\"ip\", flat=True)\n        )\n        if len(target_ips) != len(values):\n            raise GeneralError(\"服务数据异常，请重新选择执行对象！\")\n        ips = set(target_ips)\n        hosts = Host.objects.filter(ip__in=ips).values(\"ip\", \"data_folder\")\n        data_folders = {}\n        for host in hosts:\n            data_folders[host[\"ip\"]] = host[\"data_folder\"]\n        self.context.get(\"view\").kwargs[\"data_folders\"] = data_folders\n\n    def verify_target_objs(self, values):\n        tool = self.context.get(\"view\").kwargs[\"tool\"]\n        if tool.target_name == \"host\":\n            self.verify_host_info(values)\n        else:\n            self.verify_service_info(values, tool)\n        return True\n\n    def verify_runuser(self, value):\n        return True\n\n    def verify_timeout(self, value):\n        if not value:\n            raise GeneralError(\"超时时间不可以等于0！\")\n        return True\n\n    def validate_default_form(self, value):\n        tool = self.context.get(\"view\").kwargs[\"tool\"]\n        for k in tool.load_default_form().keys():\n            getattr(self, f\"verify_{k}\")(value.get(k))\n        return value\n\n    def validate_script_args(self, value):\n        tool = self.context.get(\"view\").kwargs[\"tool\"]\n        if not tool.script_args:\n            return []\n        answers = {}\n        for script_arg in value:\n            answers[script_arg.get(\"key\")] = script_arg.get(\"default\")\n        form_answers = ValidFormAnswer(tool.script_args, answers).is_valid()\n        return form_answers\n\n    @transaction.atomic\n    def create(self, validated_data):\n        view_kwargs = self.context.get(\"view\").kwargs\n        tool = view_kwargs[\"tool\"]\n        request = self.context.get(\"request\")\n        default_form = validated_data.get(\"default_form\")\n        script_args = validated_data.get(\"script_args\", [])\n\n        history = ToolExecuteMainHistory.objects.create(\n            tool=tool,\n            task_name=default_form.get(\"task_name\"),\n            operator=request.user.username,\n            form_answer=validated_data\n        )\n        common_args = {}\n        file_args = {}\n        for script_arg in script_args:\n            if script_arg.get(\"type\") == \"file\":\n                file_name = script_arg.get(\"default\", {}).get(\"file_name\")\n                if not file_name:\n                    continue\n                file_args[script_arg.get(\"key\")] = script_arg.get(\n                    \"default\", {}).get(\"file_url\")\n            else:\n                common_args[script_arg.get(\"key\")] = script_arg.get(\"default\")\n        execute_details = []\n        for target_obj in default_form.get(\"target_objs\"):\n            target_detail = {\n                \"target_ip\": target_obj.get(\"ip\"),\n                \"main_history\": history,\n                \"time_out\": default_form.get(\"timeout\"),\n                \"run_user\": default_form.get(\"runuser\")\n            }\n            execute_args = {\n                \"ip\": target_obj.get(\"ip\"),\n                **target_obj.get(\"modifiable_kwargs\", {}),\n                **common_args\n            }\n            remote_folder = os.path.join(\n                view_kwargs[\"data_folders\"].get(target_obj.get(\"ip\"), \"/tmp\"),\n                \"omp_packages\"\n            )\n            # output file\n            if \"output\" in execute_args:\n                file_name = execute_args.get('output')\n                if file_name:\n                    random_str = ''.join(\n                        random.sample(string.digits+string.ascii_lowercase, 6))\n                    file_name = f\"{random_str}-{file_name}\"\n                    execute_args[\"output\"] = os.path.join(\n                        remote_folder,\n                        tool.tool_folder_path,\n                        f\"{file_name}\"\n                    )\n                    target_detail[\"output\"] = {\"file\": [file_name]}\n                if not file_name:\n                    execute_args.pop('output')\n            # input file\n            for k, file_url in file_args.items():\n                execute_args[k] = os.path.join(remote_folder, file_url)\n            execute_details.append(\n                ToolExecuteDetailHistory(\n                    **target_detail,\n                    execute_args=execute_args,\n                )\n            )\n        ToolExecuteDetailHistory.objects.bulk_create(execute_details)\n        exec_tools_main.delay(history.id)\n        validated_data.update(id=history.id)\n        return validated_data\n\n\nclass ToolExecuteHistoryListSerializer(serializers.ModelSerializer):\n    kind = serializers.CharField(source=\"tool.kind\")\n\n    class Meta:\n        model = ToolExecuteMainHistory\n        fields = (\"id\", \"tool_id\", \"task_name\", \"kind\", \"start_time\",\n                  \"status\", \"duration\")\n"
  },
  {
    "path": "omp_server/tool/tasks.py",
    "content": "\"\"\"\n服务相关异步任务\n\"\"\"\n\nimport logging\nimport os\nimport time\n\nfrom concurrent.futures import ThreadPoolExecutor, as_completed, wait, \\\n    ALL_COMPLETED\nfrom django.utils import timezone\n\nfrom celery import shared_task\nfrom celery.utils.log import get_task_logger\nfrom db_models.models import ToolExecuteMainHistory, ToolExecuteDetailHistory\n\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.plugin import public_utils\n\n\nTHREAD_POOL_MAX_WORKERS = 20\n# 屏蔽celery任务日志中的paramiko日志\nlogging.getLogger(\"paramiko\").setLevel(logging.WARNING)\nlogger = get_task_logger(\"celery_log\")\n\n\nclass ThreadUtils:\n    def __init__(self):\n        self.timeout = 10\n        self.salt = SaltClient()\n        self.salt_data = self.salt.client.opts.get(\"root_dir\")\n        self.count = 0\n\n    @staticmethod\n    def send_message(tool_detail_obj, index=None, message=None):\n        \"\"\"\n        标准打印日志\n        \"\"\"\n        message_info = [\"占位\", \"开始执行工具包\", \"开始获取输出文件\", \"工具执行成功\", \"开始发送工具包\"]\n        if index:\n            message = message_info[index]\n        tool_detail_obj.execute_log += \"{1} {0}\\n\".format(\n            message, timezone.now())\n        tool_detail_obj.save()\n\n    def receive_file(self, tool_detail_obj, receive_files, ip):\n        \"\"\"\n        接收文件\n        \"\"\"\n        self.send_message(tool_detail_obj, 2)\n        pull_dc = receive_files.get(\"output_files\", [])\n        receive_to = receive_files.get(\"receive_to\", \"/tmp\")\n        upload_real_paths = []\n        for file in pull_dc:\n            status, message = self.salt.cp_push(\n                target=ip,\n                source_path=file,\n                upload_path=file.rsplit(\"/\", 1)[1])\n            upload_real_paths.append(\n                os.path.join(self.salt_data,\n                             f\"var/cache/salt/master/minions/{ip}/files/{file.rsplit('/', 1)[1]}\"))\n            if not status:\n                tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_FAILED\n                self.send_message(tool_detail_obj, message=message)\n                return False\n        if upload_real_paths:\n            _out, _err, _code = public_utils.local_cmd(\n                f'mv {\" \".join(upload_real_paths)} {receive_to}')\n            if _code != 0:\n                tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_FAILED\n                self.send_message(tool_detail_obj, message=_out)\n                return False\n        return True\n\n    def __call__(self, tool_detail_obj, *args, **kwargs):\n        \"\"\"\n        执行单个工具任务函数\n        \"\"\"\n        tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_RUNNING\n        # 发送文件\n        ip = tool_detail_obj.target_ip\n        self.send_message(tool_detail_obj, 4)\n        send_dc = tool_detail_obj.get_send_files()\n        for file in send_dc:\n            status, message = self.salt.cp_file(\n                target=ip,\n                source_path=file.get(\"local_file\"),\n                target_path=file.get(\"remote_file\")\n            )\n            if not status:\n                tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_FAILED\n                self.send_message(tool_detail_obj, message=message)\n                return status, message\n        # 执行脚本\n        self.send_message(tool_detail_obj, 1)\n        cmd_str = tool_detail_obj.get_cmd_str()\n        if tool_detail_obj.run_user:\n            cmd_str = 'su -s /bin/bash {1} -c \"{0}\"'.format(\n                cmd_str, tool_detail_obj.run_user\n            )\n        self.send_message(tool_detail_obj, message=f\"执行脚本的命令: {cmd_str}\")\n        status, message = self.salt.cmd(\n            target=ip,\n            command=cmd_str,\n            timeout=self.timeout,\n            real_timeout=tool_detail_obj.time_out\n        )\n        if not status:\n            if 'Timed out' in message:\n                tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_TIMEOUT\n            else:\n                tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_FAILED\n            self.send_message(tool_detail_obj, message=message)\n            return status, message\n        self.send_message(tool_detail_obj, message=f\"脚本输出如下: {message}\")\n        # 获取目标输出文件\n        receive_files = tool_detail_obj.get_receive_files()\n        if receive_files:\n            status = self.receive_file(tool_detail_obj, receive_files, ip)\n        if not status:\n            return False, \"执行失败\"\n        self.send_message(tool_detail_obj, 3)\n        tool_detail_obj.status = ToolExecuteDetailHistory.STATUS_SUCCESS\n        tool_detail_obj.save()\n        return True, \"执行成功\"\n\n\n@shared_task\ndef exec_tools_main(tool_main_id):\n    \"\"\"\n    工具执行类\n    \"\"\"\n    # 当磁盘写入较慢时需稍等\n    time.sleep(2)\n    tool_main_obj = ToolExecuteMainHistory.objects.select_related().filter(id=tool_main_id)\n    exec_ing_dc = {\n        \"status\": ToolExecuteMainHistory.STATUS_RUNNING,\n        \"start_time\": timezone.now()\n    }\n    if tool_main_obj.exists():\n        tool_main_obj.update(**exec_ing_dc)\n    else:\n        logger.error(f\"主工具执行id不存在{tool_main_id}\")\n        raise ValueError(f\"主工具执行id不存在{tool_main_id}\")\n    # 开始下发各个目标节点任务\n    tool_detail_objs = tool_main_obj.first().toolexecutedetailhistory_set.all()\n    # tool_detail_objs = ToolExecuteMainHistory.objects.filter(\n    #    tool=tool_main_obj)\n    with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n        future_list = []\n        for obj in tool_detail_objs:\n            future_obj = executor.submit(ThreadUtils(), obj)\n            future_list.append(future_obj)\n        wait(future_list, return_when=ALL_COMPLETED)\n        success = True\n        for future in as_completed(future_list):\n            if not future.result():\n                success = False\n                break\n    # 查看各个任务执行状态，修改主状态页。\n    exec_ed_status = ToolExecuteMainHistory.STATUS_FAILED if not success \\\n        else ToolExecuteMainHistory.STATUS_SUCCESS\n    exec_ed_dc = {\n        \"status\": exec_ed_status,\n        \"end_time\": timezone.now()\n    }\n    tool_main_obj.update(**exec_ed_dc)\n"
  },
  {
    "path": "omp_server/tool/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "omp_server/tool/tool_filters.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: tool_filters\n# Create time: 2022/2/10 3:23 下午\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\nfrom rest_framework.filters import BaseFilterBackend\n\nfrom db_models.models import ToolInfo\n\n\nclass ToolFilter(FilterSet):\n    name = django_filters.CharFilter(\n        help_text=\"实用工具名称\", field_name=\"name\", lookup_expr=\"icontains\")\n    kind = django_filters.NumberFilter(\n        help_text=\"实用工具分类：0-管理工具；2-安全工具\",\n        field_name=\"kind\",\n        lookup_expr=\"exact\"\n    )\n\n    class Meta:\n        model = ToolInfo\n        fields = (\"name\", \"kind\")\n\n\nclass ToolInfoKindFilter(BaseFilterBackend):\n\n    def filter_queryset(self, request, queryset, view):\n        param = request.query_params.get(\"kind\", \"\")\n        param = param.replace('\\x00', '').replace('null', '')\n        if not param:\n            return queryset\n        queryset = queryset.filter(tool__kind=int(param))\n        return queryset\n"
  },
  {
    "path": "omp_server/tool/urls.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: urls\n# Create time: 2022/2/10 6:23 下午\nfrom django.urls import path\nfrom rest_framework.routers import DefaultRouter\nfrom tool.views import ToolListView, ToolDetailView, GetToolDetailView, \\\n    ToolFormDetailAPIView, ToolTargetObjectAPIView, ToolFormAnswerAPIView, \\\n    ToolExecuteHistoryListApiView\n\nrouter = DefaultRouter()\nrouter.register(\"toolList\", ToolListView, basename=\"toolList\")\nrouter.register(\"toolList\", ToolDetailView, basename=\"toolList\")\nrouter.register(r'result', GetToolDetailView, basename=\"result\")\nrouter.register(r'form', ToolFormDetailAPIView, basename=\"form\")\n\nurlpatterns = [\n    path(\n        'form/<int:pk>/target-object',\n        ToolTargetObjectAPIView.as_view(),\n        name=\"target-object\"\n    ),\n    path(\n        'form/<int:pk>/answer',\n        ToolFormAnswerAPIView.as_view(),\n        name=\"answer\"\n    ),\n    path(\n        'execute-history',\n        ToolExecuteHistoryListApiView.as_view(),\n        name=\"execute-history\"\n    ),\n]\n\nurlpatterns += router.urls\n\n\n"
  },
  {
    "path": "omp_server/tool/views.py",
    "content": "# Create your views here.\nfrom django.shortcuts import get_object_or_404\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\nfrom rest_framework.filters import SearchFilter, OrderingFilter\nfrom rest_framework.generics import ListAPIView, CreateAPIView\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import RetrieveModelMixin, ListModelMixin\nfrom db_models.models import (ToolExecuteMainHistory, ToolInfo, Host, Service)\nfrom tool.tool_filters import ToolFilter, ToolInfoKindFilter\nfrom tool.serializers import ToolListSerializer, ToolInfoDetailSerializer, \\\n    ToolTargetObjectServiceSerializer, ToolFormAnswerSerializer, \\\n    ToolExecuteHistoryListSerializer\nfrom utils.common.paginations import PageNumberPager\nfrom tool.serializers import ToolDetailSerializer, ToolFormDetailSerializer, \\\n    ToolTargetObjectHostSerializer\n\n\nclass ToolRetrieveAPIMixin:\n\n    def load_tool_obj(self):\n        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field\n        tool = get_object_or_404(\n            ToolInfo.objects.all(),\n            **{self.lookup_field: self.kwargs[lookup_url_kwarg]}\n        )\n        self.kwargs.update(tool=tool)\n        return tool\n\n\nclass GetToolDetailView(GenericViewSet, RetrieveModelMixin):\n    \"\"\"\n    任务详情页\n    \"\"\"\n    queryset = ToolExecuteMainHistory.objects.all()\n    get_description = \"任务详情页\"\n    serializer_class = ToolDetailSerializer\n\n\nclass ToolFormDetailAPIView(GenericViewSet, RetrieveModelMixin):\n    queryset = ToolInfo.objects.all()\n    get_description = \"小工具执行表单页\"\n    serializer_class = ToolFormDetailSerializer\n\n\nclass ToolListView(GenericViewSet, ListModelMixin):\n    \"\"\"查询所有实用工具列表\"\"\"\n    queryset = ToolInfo.objects.all().order_by(\"-created\")\n    serializer_class = ToolListSerializer\n    pagination_class = PageNumberPager\n    # 过滤排序字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = ToolFilter\n    # 操作信息描述\n    get_description = \"查询所有实用工具列表\"\n\n\nclass ToolDetailView(GenericViewSet, RetrieveModelMixin):\n    \"\"\"获取实用工具详情\"\"\"\n    queryset = ToolInfo.objects.all().order_by(\"-created\")\n    serializer_class = ToolInfoDetailSerializer\n    # 操作描述信息\n    get_description = \"获取实用工具详情\"\n\n\nclass ToolTargetObjectAPIView(ListAPIView, ToolRetrieveAPIMixin):\n    get_description = \"小工具执行对象展示页\"\n    pagination_class = PageNumberPager\n\n    def get(self, request, *args, **kwargs):\n        self.load_tool_obj()\n        return self.list(request, *args, **kwargs)\n\n    def get_queryset(self):\n        if self.kwargs[\"tool\"].target_name == \"host\":\n            return Host.objects.all()\n        return Service.objects.filter(\n            service__app_name=self.kwargs[\"tool\"].target_name\n        )\n\n    def get_serializer_class(self):\n        if self.kwargs[\"tool\"].target_name == \"host\":\n            return ToolTargetObjectHostSerializer\n        return ToolTargetObjectServiceSerializer\n\n\nclass ToolFormAnswerAPIView(CreateAPIView, ToolRetrieveAPIMixin):\n    get_description = \"小工具执行表单页\"\n    serializer_class = ToolFormAnswerSerializer\n\n    def post(self, request, *args, **kwargs):\n        self.load_tool_obj()\n        return self.create(request, *args, **kwargs)\n\n\nclass ToolExecuteHistoryListApiView(ListAPIView):\n    get_description = \"小工具执行列表页\"\n    pagination_class = PageNumberPager\n    serializer_class = ToolExecuteHistoryListSerializer\n    queryset = ToolExecuteMainHistory.objects.all().select_related(\"tool\")\n    filter_backends = (SearchFilter, OrderingFilter, ToolInfoKindFilter)\n    search_fields = (\"task_name\", )\n    ordering_fields = (\"start_time\",)\n    ordering = ('-start_time',)\n"
  },
  {
    "path": "omp_server/users/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/users/admin.py",
    "content": "# from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "omp_server/users/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass UsersConfig(AppConfig):\n    name = 'users'\n"
  },
  {
    "path": "omp_server/users/urls.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: urls\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-10 17:21\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n用户相关的路由\n\"\"\"\n\nfrom rest_framework.routers import DefaultRouter\n\nfrom users.views import (\n    UsersView, OperateLogView, UserUpdatePasswordView,\n    UserLoginOperateView, CaptchaView\n)\n\nrouter = DefaultRouter()\nrouter.register(\"users\", UsersView, basename=\"users\")\nrouter.register(\"operateLog\", OperateLogView, basename=\"operateLog\")\nrouter.register(\"UserLoginLog\", UserLoginOperateView, basename=\"operateLog\")\nrouter.register(\"updatePassword\", UserUpdatePasswordView,\n                basename=\"updatePassword\")\nrouter.register(\"captcha\", CaptchaView, basename=\"captcha\")"
  },
  {
    "path": "omp_server/users/users_filters.py",
    "content": "\"\"\"\n用户相关过滤器\n\"\"\"\nimport django_filters\nfrom django_filters.rest_framework import FilterSet\n\nfrom db_models.models import (\n    UserProfile, OperateLog,\n    UserLoginLog\n)\n\n\nclass UserFilter(FilterSet):\n    \"\"\" 主机过滤类 \"\"\"\n    username = django_filters.CharFilter(\n        help_text=\"用户名，模糊匹配\", field_name=\"username\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = UserProfile\n        fields = (\"username\",)\n\n\nclass UserOperateFilter(FilterSet):\n    \"\"\" 用户操作过滤类 \"\"\"\n\n    username = django_filters.CharFilter(\n        help_text=\"操作用户\", field_name=\"username\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = OperateLog\n        fields = (\"username\",)\n\n\nclass UserLoginOperateFilter(FilterSet):\n    \"\"\" 用户登陆日志过滤类 \"\"\"\n\n    username = django_filters.CharFilter(\n        help_text=\"操作用户\", field_name=\"username\", lookup_expr=\"icontains\")\n\n    class Meta:\n        model = UserLoginLog\n        fields = (\"username\",)\n"
  },
  {
    "path": "omp_server/users/users_serializers.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: users_serializers\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-10 17:16\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n用户序列化使用方法\n\"\"\"\nfrom django.contrib.auth import authenticate\nfrom django.contrib.auth.hashers import make_password\n\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.serializers import (\n    ModelSerializer, Serializer\n)\nfrom rest_framework_jwt.serializers import JSONWebTokenSerializer\n\nfrom db_models.models import (\n    UserProfile, OperateLog,\n    UserLoginLog\n)\nfrom utils.common.validators import UserPasswordValidator\nfrom utils.plugin.crypto import decrypt_rsa\n\n\nclass UserSerializer(ModelSerializer):\n    \"\"\" 用户序列化类 \"\"\"\n    re_password = serializers.CharField(\n        max_length=32, required=True,\n        write_only=True,\n        error_messages={\"required\": \"必须包含re_password字段\"},\n        help_text=\"二次确认密码\")\n    email = serializers.EmailField(\n        required=True,\n        error_messages={\"required\": \"必须包含email字段\", \"invalid\": \"邮箱格式不正确\"},\n        help_text=\"电子邮件\")\n    password = serializers.CharField(\n        max_length=32, required=True,\n        write_only=True,\n        error_messages={\"required\": \"必须包含password字段\"},\n        help_text=\"密码\")\n    username = serializers.CharField(\n        max_length=32, required=True,\n        error_messages={\"required\": \"必须包含名字\"},\n        help_text=\"用户名\")\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = UserProfile\n        fields = (\"id\", \"username\", \"password\", \"email\", \"re_password\",\n                  \"date_joined\", \"is_active\", \"is_superuser\", \"role\")\n        read_only_fields = (\"date_joined\", \"is_active\", \"is_superuser\")\n\n    def validate_username(self, username):\n        \"\"\"\n        校验用户名是否唯一\n        :param username: 用户名\n        :return:\n        \"\"\"\n        request = self.context[\"request\"]\n        if request.method != \"PUT\" and \\\n                UserProfile.objects.filter(username=username).count() != 0:\n            raise ValidationError(\"用户名已存在\")\n        return username\n\n    def validate(self, attrs):\n        \"\"\"\n        校验\n        :param attrs:\n        :return:\n        \"\"\"\n        password = attrs.get('password')\n        re_password = attrs.pop('re_password')\n        if password != re_password:\n            raise ValidationError({'re_password': '两次密码不一致'})\n        attrs[\"password\"] = make_password(password)\n        return attrs\n\n\nclass OperateLogSerializer(ModelSerializer):\n    \"\"\" 用户操作记录序列化 \"\"\"\n\n    create_time = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = OperateLog\n        fields = (\n            \"id\", \"username\", \"request_ip\", \"request_method\",\n            \"description\", \"create_time\"\n        )\n\n    def get_create_time(self, obj):\n        if obj.create_time:\n            return str(obj.create_time).split(\".\")[0]\n        return obj.create_time\n\n\nclass UserLoginOperateSerializer(ModelSerializer):\n    \"\"\"登录记录序列化\"\"\"\n\n    login_time = serializers.SerializerMethodField()\n\n    class Meta:\n        \"\"\" 元数据 \"\"\"\n        model = UserLoginLog\n        fields = \"__all__\"\n\n    def get_login_time(self, obj):\n        if obj.login_time:\n            return str(obj.login_time).split(\".\")[0]\n        return obj.login_time\n\n\nclass JwtSerializer(JSONWebTokenSerializer):\n    \"\"\" Jwt序列化类 \"\"\"\n\n    remember = serializers.BooleanField(\n        required=False, default=False,\n        help_text=\"Boolean类型，缺省值为False\")\n\n    def validate(self, attrs):\n        validate_dict = super(JwtSerializer, self).validate(attrs)\n        validate_dict[\"remember\"] = attrs.get(\"remember\")\n        return validate_dict\n\n\nclass UserUpdatePasswordSerializer(Serializer):\n    \"\"\" 用户更新密码序列化器 \"\"\"\n\n    username = serializers.CharField(\n        help_text=\"用户名\",\n        min_length=344, max_length=344, required=True,\n        error_messages={\"required\": \"必须包含名字\"})\n    old_password = serializers.CharField(\n        help_text=\"原密码\", required=True,\n        min_length=344, max_length=344,\n        error_messages={\"required\": \"必须包含password字段\"}\n    )\n    new_password = serializers.CharField(\n        help_text=\"新密码\", required=True,\n        min_length=344, max_length=344,\n        error_messages={\"required\": \"必须包含new_password字段\"}\n    )\n\n    def validate(self, attrs):\n        \"\"\" 校验，用户的原密码是否正确 \"\"\"\n        credentials = {\n            \"username\": decrypt_rsa(attrs.get(\"username\")),\n            \"password\": decrypt_rsa(attrs.get(\"old_password\"))\n        }\n        user = authenticate(**credentials)\n        if not user:\n            raise ValidationError({\"old_password\": \"当前密码不正确\"})\n        new_password = decrypt_rsa(attrs.get(\"new_password\"))\n        UserPasswordValidator()(new_password, self.fields.get(\"new_password\"))\n        attrs[\"user_obj\"] = user\n        attrs[\"new_password\"] = new_password\n        return attrs\n\n    def create(self, validated_data):\n        \"\"\" 用户密码加密入库 \"\"\"\n        user = validated_data[\"user_obj\"]\n        user.set_password(validated_data.get(\"new_password\"))\n        user.save()\n        return validated_data\n"
  },
  {
    "path": "omp_server/users/views.py",
    "content": "\"\"\"\n用户视图相关函数\n\"\"\"\nimport re\nimport datetime\nfrom io import BytesIO\n\nfrom django.http import HttpResponse\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.response import Response\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import (\n    ListModelMixin, CreateModelMixin, RetrieveModelMixin,\n    DestroyModelMixin, UpdateModelMixin\n)\nfrom rest_framework_jwt.views import JSONWebTokenAPIView\nfrom rest_framework_jwt.settings import api_settings\n\nfrom django_filters.rest_framework.backends import DjangoFilterBackend\n\nfrom db_models.models import (\n    UserProfile, OperateLog,\n    UserLoginLog\n)\nfrom rest_framework.filters import OrderingFilter\nfrom users.users_filters import (\n    UserFilter, UserOperateFilter,\n    UserLoginOperateFilter\n)\nfrom utils.common.paginations import PageNumberPager\nfrom users.users_serializers import (\n    UserSerializer, JwtSerializer,\n    OperateLogSerializer, UserUpdatePasswordSerializer,\n    UserLoginOperateSerializer\n)\n\nimport ipware\nimport ipaddress\nimport logging\nfrom django.utils import timezone\n\nfrom utils.plugin.crypto import decrypt_rsa\nfrom utils.plugin.captcha import check_code\n\nlogger = logging.getLogger(\"server\")\n\n\nclass UsersView(ListModelMixin, RetrieveModelMixin, CreateModelMixin,\n                DestroyModelMixin, UpdateModelMixin, GenericViewSet):\n    \"\"\"\n        list:\n        查询用户列表\n\n        retrieve:\n        查询一个用户\n\n        create:\n        创建一个新用户\n\n        delete:\n        删除一个现有用户\n\n        update:\n        更新一个现有用户\n\n        partial_update:\n        更新一个现有用户的一个或多个字段\n    \"\"\"\n    queryset = UserProfile.objects.all().order_by(\"id\")\n    serializer_class = UserSerializer\n    pagination_class = PageNumberPager\n    # 过滤字段\n    filter_backends = (DjangoFilterBackend,)\n    filter_class = UserFilter\n    # 操作描述信息\n    get_description = \"获取用户\"\n    post_description = \"新建用户\"\n    put_description = \"更新用户\"\n    delete_description = \"删除用户\"\n\n\nclass OperateLogView(ListModelMixin, RetrieveModelMixin, GenericViewSet):\n    \"\"\"\n        list:\n        查询操作记录列表\n\n        retrieve:\n        查询一条操作记录\n    \"\"\"\n    queryset = OperateLog.objects.all().order_by(\"-create_time\")\n    serializer_class = OperateLogSerializer\n    pagination_class = PageNumberPager\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = UserOperateFilter\n    ordering_fields = (\"create_time\", \"username\", \"request_ip\")\n    # 操作描述信息\n    get_description = \"获取用户操作记录\"\n\n\nclass UserLoginOperateView(ListModelMixin, RetrieveModelMixin, GenericViewSet):\n    \"\"\"\n        list:\n        查询操作记录列表\n\n        retrieve:\n        查询一条操作记录\n    \"\"\"\n    queryset = UserLoginLog.objects.all().exclude(\n        username=\"匿名用户\").order_by(\"-login_time\")\n    serializer_class = UserLoginOperateSerializer\n    pagination_class = PageNumberPager\n    filter_backends = (DjangoFilterBackend, OrderingFilter)\n    filter_class = UserLoginOperateFilter\n    ordering_fields = (\"login_time\", \"username\", \"ip\", \"role\")\n    # 操作描述信息\n    get_description = \"获取用户操作记录\"\n\n\nclass JwtAPIView(JSONWebTokenAPIView):\n    \"\"\"\n        post:\n        登录，签发 JwtToken 令牌\n    \"\"\"\n    serializer_class = JwtSerializer\n\n    @staticmethod\n    def validate_ip(str_ip):\n        \"\"\"\n        校验ip格式\n        暂时不用\n        \"\"\"\n        try:\n            ipaddress.ip_address(str_ip)\n            return True\n        except ValueError:\n            pass\n        return False\n\n    @staticmethod\n    def _get_login_log(request):\n        \"\"\"\n        创建登陆记录\n        暂时不用\n        \"\"\"\n        login_ip, routeable = ipware.get_client_ip(request)\n        data = {\n            'username': request.data.get(\"username\", \"\"),\n            'login_time': timezone.now(),\n            'role': \"超级管理员用户\",\n            'ip': login_ip\n        }\n        try:\n            if not (login_ip and JwtAPIView.validate_ip(login_ip)):\n                data.update({'ip': login_ip[:15]})\n            UserLoginLog.objects.create(**data)\n        except Exception as e:\n            logger.error(\"Create login log error: {}\".format(e))\n\n    def post(self, request, *args, **kwargs):\n        code = request.data.get(\"code\", \"\")\n        if code.lower() != request.session.get(\"valid_code\", \"\").lower():\n            raise ValidationError(\"code error\")\n\n        # django authenticate 缺陷，验证 username 大小写不敏感\n        username = decrypt_rsa(request.data.get(\"username\", \"\"))\n        password = decrypt_rsa(request.data.get(\"password\", \"\"))\n        if not UserProfile.objects.filter(username=username).exists():\n            raise ValidationError(f\"{username} dose not exists.\")\n        if re.search(r\"\\s\", password):\n            raise ValidationError(\n                \"Unable to log in with provided credentials.\")\n\n        serializer = self.get_serializer(\n            data={\n                \"username\": username,\n                \"password\": password,\n                \"remember\": request.data.get(\"remember\")\n            }\n        )\n\n        if not serializer.is_valid():\n            raise ValidationError(\n                \"Unable to log in with provided credentials.\")\n        user = serializer.object.get(\"user\") or request.user\n        token = serializer.object.get(\"token\")\n        response_data = api_settings.JWT_RESPONSE_PAYLOAD_HANDLER(\n            token, user, request)\n        response = Response(response_data)\n        # self._get_login_log(request)\n        if api_settings.JWT_AUTH_COOKIE:\n            # remember 取值 True，则 cookie 过期时间为 7 天\n            expiration_time = api_settings.JWT_EXPIRATION_DELTA\n            if serializer.validated_data.get(\"remember\"):\n                expiration_time = datetime.timedelta(days=7)\n            expiration = (datetime.datetime.utcnow() + expiration_time)\n            response.set_cookie(\n                api_settings.JWT_AUTH_COOKIE,\n                token,\n                expires=expiration,\n            )\n        return response\n\n\nclass UserUpdatePasswordView(GenericViewSet, CreateModelMixin):\n    \"\"\"\n        create:\n        修改用户密码\n    \"\"\"\n\n    queryset = UserProfile.objects.all()\n    serializer_class = UserUpdatePasswordSerializer\n    # 操作描述\n    post_description = \"更新用户密码\"\n\n\nclass CaptchaView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        获取图形验证码\n    \"\"\"\n    authentication_classes = ()\n    permission_classes = ()\n\n    def list(self, request, *args, **kwargs):\n        # 调用函数生成图片验证码\n        image_obj, code = check_code()\n        # 将验证码字符存入session\n        request.session[\"valid_code\"] = code\n        request.session.set_expiry(60)\n\n        stream = BytesIO()\n        image_obj.save(stream, \"png\")\n        # 将从内存空间获取的图片验证码传给前端\n        return HttpResponse(stream.getvalue())\n"
  },
  {
    "path": "omp_server/utils/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-10 16:21\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n"
  },
  {
    "path": "omp_server/utils/common/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/utils/common/exceptions.py",
    "content": "\"\"\"\n    公共异常\n    注意 common_exception_handler 的优先级按照以下顺序进行：\n        1. GeneralError 异常实例\n        2. FORMAT_ERRORS 字典，自定义格式化函数处理的错误\n        3. ERROR_MESSAGE 字典，含描述信息的错误\n        4. CODE_MESSAGE 字典，含描述信息的状态码\n    将返回的结果注入 response 的 message 字段中，缺省值为 \"后端程序错误\"\n\"\"\"\nfrom django.db import DatabaseError\nfrom rest_framework.exceptions import ValidationError\n\n\nclass GeneralError(Exception):\n    \"\"\" 通用错误 \"\"\"\n\n    def __init__(self, err=\"通用异常\"):\n        super(GeneralError, self).__init__(err)\n\n\nclass OperateError(GeneralError):\n    \"\"\" 操作错误 \"\"\"\n\n    def __init__(self, err=\"操作发生错误\"):\n        super(OperateError, self).__init__(err)\n\n\ndef _validation_error_message(exc, response):\n    \"\"\" ValidationError 错误数据格式化 \"\"\"\n    err_message = \"数据校验错误\"\n    assert response.data is not None\n    data = response.data\n\n    if isinstance(data, list):\n        err_message = \"; \".join(data)\n    if isinstance(data, dict):\n        err_message_ls = []\n        for k, v in data.items():\n            if isinstance(v, list):\n                ip_err = \"Enter a valid IPv4 or IPv6 address.\"\n                if ip_err in v:\n                    v[v.index(ip_err)] = \"IP格式不合法\"\n                err_message_ls.append(\"; \".join(v))\n            else:\n                err_message_ls.append(v)\n        err_message = \"; \".join(err_message_ls)\n    return err_message\n\n\n# 自定义格式化函数处理的错误\nFORMAT_ERRORS = {\n    ValidationError: _validation_error_message,\n}\n\n# 含描述信息的错误\nERROR_MESSAGE = {\n    NameError: \"变量未被定义或引用\",\n    DatabaseError: \"数据库错误\",\n}\n\n# 含描述信息的状态码\nCODE_MESSAGE = {\n    401: \"未认证\",\n    403: \"无访问权限\",\n    404: \"未找到\",\n    405: \"暂不支持此请求\",\n    500: \"服务器错误\",\n}\n"
  },
  {
    "path": "omp_server/utils/common/paginations.py",
    "content": "\"\"\"\n公共分页器\n\"\"\"\n\nfrom rest_framework.pagination import (\n    PageNumberPagination,\n    LimitOffsetPagination,\n    CursorPagination\n)\n\n\nclass PageNumberPager(PageNumberPagination):\n    \"\"\" 容量分页 \"\"\"\n    page_size = 10\n    max_page_size = 100\n    page_query_param = 'page'\n    page_size_query_param = 'size'\n\n\nclass LimitOffsetPager(LimitOffsetPagination):\n    \"\"\" 偏移分页 \"\"\"\n    default_limit = 10\n    max_limit = 100\n    limit_query_param = 'limit'\n    offset_query_param = 'offset'\n\n\nclass CursorPager(CursorPagination):\n    \"\"\" 游标分页 \"\"\"\n    page_size = 10\n    cursor_query_param = 'cursor'\n"
  },
  {
    "path": "omp_server/utils/common/serializers.py",
    "content": "\"\"\"\n公共序列化器\n\"\"\"\n\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\n\nfrom db_models import models\nfrom db_models.models import Host, UploadFileHistory\n\n\nclass HostIdsSerializer(serializers.Serializer):\n    \"\"\" 主机 id 列表序列化类 \"\"\"\n\n    host_ids = serializers.ListField(\n        help_text=\"主机 ID 列表\",\n        required=True,\n        error_messages={\"required\": \"必须包含[host_ids]字段\"},\n        allow_empty=False)\n\n    def validate_host_ids(self, host_ids):\n        \"\"\" 校验主机 ID 列表中主机是否都存在 \"\"\"\n        exists_ids = Host.objects.filter(\n            id__in=host_ids).values_list(\"id\", flat=True)\n        diff = set(host_ids) - set(exists_ids)\n        if diff:\n            raise ValidationError(\n                f\"主机列表中有不存在的ID [\"\n                f\"{','.join(map(lambda x: str(x), diff))}\"\n                f\"]\")\n        return host_ids\n\n\nclass UploadFileSerializer(serializers.Serializer):\n    file = serializers.FileField(\n        help_text=\"上传的文件\",\n        required=True,\n        error_messages={\"required\": \"必须包含[file]字段\"}\n    )\n    storage_klass = serializers.CharField(\n        help_text=\"存储方式\", required=False, default=\"location\")\n    module = serializers.CharField(\n        help_text=\"需要上传文件的model\", required=False, default=\"\")\n    module_id = serializers.IntegerField(\n        help_text=\"需要上传文件的model id\", required=False, default=0)\n    file_name = serializers.CharField(\n        help_text=\"保存后文件名\", required=False, default=\"\")\n    file_url = serializers.CharField(\n        help_text=\"保存后文件访问路径\", required=False, default=\"\")\n    union_id = serializers.CharField(\n        help_text=\"文件union_id\", required=False, default=\"\")\n\n    def validate(self, attrs):\n        module = attrs.get(\"module\")\n        module_id = attrs.get(\"module_id\")\n        if module:\n            _obj = getattr(models, module).objects.filter(id=module_id).first()\n            if not _obj:\n                raise ValidationError({\n                    \"module\": \"请确定指定上传文件的module\"\n                })\n            setattr(self, \"module_obj\", _obj)\n        if attrs.get(\"storage_klass\", \"location\") \\\n                not in UploadFileHistory.STORAGE_KLASS:\n            raise ValidationError({\n                \"storage_klass\":\n                    f\"文件存储方式目前只支持{UploadFileHistory.STORAGE_KLASS}\"\n            })\n        return attrs\n\n    def create(self, validated_data):\n        user = self.context.get(\"request\").user\n        file = validated_data.get(\"file\")\n        if hasattr(self, \"module_obj\"):\n            module_obj = self.module_obj\n        else:\n            module_obj = None\n        obj = getattr(\n            UploadFileHistory,\n            validated_data.get(\"storage_klass\", \"location\")\n        )(file, module_obj, user)\n        validated_data.update(\n            {\n                \"file_name\": obj.file_name,\n                \"union_id\": obj.union_id,\n                \"file_url\": obj.file_url\n            }\n        )\n        return validated_data\n\n\nclass DynamicFieldsModelSerializer(serializers.ModelSerializer):\n    \"\"\"\n    A ModelSerializer that takes an additional `fields` argument that\n    controls which fields should be displayed.\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        # Don't pass the 'fields' arg up to the superclass\n        fields = kwargs.pop('fields', None)\n\n        # 正常地实例化父类\n        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)\n\n        if fields is not None:\n            # 删除fields参数中未指定的任何字段\n            allowed = set(fields)\n            existing = set(self.fields)\n            for field_name in existing - allowed:\n                self.fields.pop(field_name)\n"
  },
  {
    "path": "omp_server/utils/common/urls.py",
    "content": "from rest_framework.routers import DefaultRouter\n\nfrom utils.common.views import UploadFileAPIView\n\nrouter = DefaultRouter()\nrouter.register(r'upload_file', UploadFileAPIView, basename=\"upload_file\")\nurlpatterns = router.urls\n"
  },
  {
    "path": "omp_server/utils/common/validators.py",
    "content": "\"\"\"\n公共验证器\n\"\"\"\nimport re\nimport emoji\n\nfrom rest_framework.exceptions import ValidationError\n\n\nclass ReValidator:\n    \"\"\" 正则表达式验证器 \"\"\"\n    requires_context = True\n\n    def __init__(self, regex, message=\"格式不合法\"):\n        self.regex = regex\n        self.message = message\n\n    def __call__(self, value, serializer_field):\n        if re.match(self.regex, value) is None:\n            field = serializer_field.field_name\n            if serializer_field.help_text is not None:\n                field = serializer_field.help_text\n            raise ValidationError(f\"{field}{self.message}\")\n\n\nclass NoEmojiValidator:\n    \"\"\" 表情验证器 \"\"\"\n    requires_context = True\n\n    def __init__(self, message=\"不可含有表情\"):\n        self.message = message\n\n    def __call__(self, value, serializer_field):\n        if emoji.emoji_count(value) > 0:\n            field = serializer_field.field_name\n            if serializer_field.help_text is not None:\n                field = serializer_field.help_text\n            raise ValidationError(f\"{field}{self.message}\")\n\n\nclass NoChineseValidator:\n    \"\"\" 中文验证器 \"\"\"\n    requires_context = True\n\n    def __init__(self, message=\"不可含有中文\"):\n        self.message = message\n\n    def __call__(self, value, serializer_field):\n        if any(re.findall(r\"[\\u4e00-\\u9fa5]?\", value)):\n            field = serializer_field.field_name\n            if serializer_field.help_text is not None:\n                field = serializer_field.help_text\n            raise ValidationError(f\"{field}{self.message}\")\n\n\nclass UserPasswordValidator:\n    \"\"\" 用户密码验证器 \"\"\"\n    requires_context = True\n\n    def __init__(self, message=\"格式不合法\"):\n        self.char_list = (\"`\", \"~\", \"!\", \"?\", \"@\", \"#\", \"$\", \"%\", \"^\",\n                          \"&\", \",\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\", \"_\",\n                          \"+\", \"-\", \"*\", \"/\", \".\", \";\", \":\")\n        self.message = message\n\n    def __call__(self, value, serializer_field):\n        for e in value:\n            if re.match(r\"[a-zA-Z0-9]\", str(e)) is not None:\n                continue\n            if str(e) not in self.char_list:\n                field = serializer_field.field_name\n                if serializer_field.help_text is not None:\n                    field = serializer_field.help_text\n                raise ValidationError(f\"{field}{self.message}\")\n"
  },
  {
    "path": "omp_server/utils/common/views.py",
    "content": "import os\nimport logging\n\nfrom django.conf import settings\nfrom django.http import FileResponse\n\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework.mixins import ListModelMixin, CreateModelMixin\n\nfrom db_models.models import UploadFileHistory\nfrom utils.common.exceptions import OperateError\nfrom utils.common.serializers import UploadFileSerializer\n\nlogger = logging.getLogger(\"server\")\n\n\nclass BaseDownLoadTemplateView(GenericViewSet, ListModelMixin):\n    \"\"\"\n        list:\n        获取模板文件\n    \"\"\"\n    # 操作描述信息\n    get_description = \"获取模板文件\"\n\n    def list(self, request, *args, **kwargs):\n        template_file_name = kwargs.get(\"template_file_name\")\n        assert template_file_name is not None\n        parent_path = kwargs.get(\"parent_path\", \"template\")\n        template_path = os.path.join(\n            settings.BASE_DIR.parent,\n            \"package_hub\", parent_path, template_file_name)\n        try:\n            file = open(template_path, 'rb')\n            response = FileResponse(file)\n            response[\"Content-Type\"] = \"application/octet-stream\"\n            response[\"Content-Disposition\"] = \\\n                f\"attachment;filename={template_file_name}\"\n        except FileNotFoundError:\n            logger.error(f\"{template_path} not found\")\n            raise OperateError(\"组件模板文件缺失\")\n        return response\n\n\nclass UploadFileAPIView(GenericViewSet, CreateModelMixin):\n\n    post_description = \"上传文件\"\n    queryset = UploadFileHistory.objects.all()\n    serializer_class = UploadFileSerializer\n"
  },
  {
    "path": "omp_server/utils/exception_handler.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: exception_handler\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-10 16:32\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n整个项目内的异常处理方法\n\"\"\"\n\nimport logging\nimport traceback\n\nfrom rest_framework.views import exception_handler\nfrom rest_framework.response import Response\n\nfrom utils.common.exceptions import (\n    GeneralError,\n    FORMAT_ERRORS, ERROR_MESSAGE, CODE_MESSAGE\n)\n\nlogger = logging.getLogger(\"server\")\n\n\nclass ExceptionResponse:\n    def __init__(self, exc, context):\n        self.exc = exc\n        self.context = context\n\n    def err_response(self):\n        logger.error(f\"ExceptionResponse: {str(self.exc)}; {self.context}\")\n        logger.error(f\"ExceptionResponse: {traceback.format_exc()};\")\n        response = exception_handler(self.exc, self.context)\n        response_status_code = 200\n        if response:\n            response_status_code = response.status_code\n        message = \"后端程序错误\"\n        error_response = Response(\n            status=200,\n            data={\n                \"code\": 1,\n                \"data\": None\n            })\n        # GeneralError 异常实例\n        if isinstance(self.exc, GeneralError):\n            message = str(self.exc)\n        # 自定义格式化函数处理的错误\n        elif type(self.exc) in FORMAT_ERRORS:\n            error_format_fun = FORMAT_ERRORS.get(type(self.exc))\n            message = error_format_fun(self.exc, response)\n        # 含描述信息的错误\n        elif type(self.exc) in ERROR_MESSAGE:\n            message = ERROR_MESSAGE.get(type(self.exc))\n        # 含描述信息的状态码\n        elif response_status_code in CODE_MESSAGE:\n            message = CODE_MESSAGE.get(response_status_code)\n        error_response.data[\"message\"] = message\n        return error_response\n\n\ndef common_exception_handler(exc, context):\n    \"\"\"\n    异常处理函数\n    :param exc:\n    :param context:\n    :return:\n    \"\"\"\n    return ExceptionResponse(exc, context).err_response()\n"
  },
  {
    "path": "omp_server/utils/middleware_handler.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: middleware_handler\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-11 16:48\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n公共中间件\n\"\"\"\n\nimport json\nimport ipware\nimport logging\n\nfrom django.urls import resolve\nfrom django.utils.deprecation import MiddlewareMixin\nfrom django.http import JsonResponse\n\nfrom rest_framework.reverse import reverse\nfrom rest_framework_jwt.utils import jwt_decode_handler\n\nfrom jwt import DecodeError\n\nfrom db_models.models import (\n    OperateLog, UserLoginLog, UserProfile\n)\nfrom django.utils import timezone\nfrom omp_server.settings import INTERFACE_KINDS\n\nlogger = logging.getLogger(\"server\")\n\n\ndef get_username_of_token(token):\n    \"\"\"通过jwt token 解析username\"\"\"\n    _token_user = jwt_decode_handler(token)\n    _username = _token_user.get(\"username\")\n    return _username\n\n\nUSER_TO_ROLE_EN_DICT = {\n    \"superuser\": \"普通管理员\",\n    \"readonlyuser\": \"只读用户\"\n}\n\n\nclass OperationLogMiddleware(MiddlewareMixin):\n    \"\"\"用于处理操作日志的中间件\"\"\"\n\n    def process_response(self, request, response):\n        \"\"\"\n        拦截请求的中间件\n        :param request: 请求对象\n        :type request HttpRequest\n        :param response: 响应对象\n        :return:\n        \"\"\"\n        _method = request.method.lower()\n        if _method == \"get\":\n            return response\n        _url = request.path\n        if _url.startswith(\"/proxy/v1/grafana\"):\n            return response\n        view_class = resolve(_url).func.cls\n        if hasattr(view_class, f\"{_method}_description\"):\n            _desc = getattr(view_class, f\"{_method}_description\")\n        else:\n            _desc = \"无法确定用户行为\"\n        _ip, _ = ipware.get_client_ip(request)\n        try:\n            token = request.COOKIES.get(\"jwtToken\", \"toke\")\n            _username = get_username_of_token(token)\n        except DecodeError:\n            _username = \"匿名用户\"\n        if _url == reverse(\"login\"):\n            _desc = \"用户登录\"\n            request_result = \"\"\n            _token = None\n            if \"token\" in response.data:\n                request_result = \"登录成功\"\n                _token = response.data.get(\"token\", \"\")\n                _username = get_username_of_token(_token)\n            else:\n                return response\n            _token_user = UserProfile.objects.filter(username=_username).first()\n            data = {\n                'username': _username,\n                'login_time': timezone.now(),\n                'role': USER_TO_ROLE_EN_DICT.get(_token_user.role.lower(), \"\"),\n                'ip': _ip,\n                'request_result': request_result\n            }\n            UserLoginLog.objects.create(**data)\n        else:\n            # 读取已封装响应数据\n            try:\n                res_data = json.loads(response.rendered_content)\n            except Exception as e:\n                return response\n            method_dc = {\n                'put': '修改',\n                'get': '查看',\n                'delete': '删除',\n                'trace': '查看',\n                'patch': '修改'\n            }\n            method_st = method_dc.get(_method)\n            if not method_st:\n                method_st = INTERFACE_KINDS.get(_url, \"修改\")\n            if _username != \"匿名用户\":\n                OperateLog(\n                    username=_username, request_ip=_ip, request_method=method_st,\n                    request_url=_url, description=_desc,\n                    response_code=res_data.get(\"code\", 0),\n                    request_result=res_data.get(\"message\", \"\")\n                ).save()\n        return response\n\n\nclass RoleAuthenticationMiddleware(MiddlewareMixin):\n    \"\"\"用户角色访问限制\"\"\"\n\n    def process_view(self, request, func, *args, **kwargs):\n        _method = request.method.lower()\n        if _method == \"get\":\n            return None\n        _url = request.path\n        if _url.startswith(\"/proxy/v1/grafana\") or _url.startswith(\"/api/login/\"):\n            return None\n        try:\n            token = request.COOKIES.get(\"jwtToken\", \"toke\")\n            _token_user = jwt_decode_handler(token)\n            _username = _token_user.get(\"username\")\n        except DecodeError:\n            _username = \"匿名用户\"\n        _token_user = UserProfile.objects.filter(username=_username).first()\n        if not _token_user:\n            # 非页面访问omp接口，放行\n            return None\n        if _token_user.role.lower() == \"superuser\":\n            return None\n        logger.error(f\"{_token_user} prohibited this action\")\n        return JsonResponse({\"code\": 1, \"data\": None, \"message\": f\"该{_token_user.username}用户无权限\"})\n"
  },
  {
    "path": "omp_server/utils/parse_config.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: parse_config\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-15 09:26\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n解析配置文件\n\"\"\"\n\nimport os\n\nfrom ruamel import yaml\n\nproject_path = os.path.dirname(\n    os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n)\nconfig_file_path = os.path.join(project_path, \"config/omp.yaml\")\n\nprivate_key_path = os.path.join(project_path, \"config/private_key.pem\")\n\n# openssl genrsa -out private_key.pem 2048\n# openssl rsa -in private_key.pem -out public_key.pem -pubout\nwith open(private_key_path, \"r\") as key_f:\n    PRIVATE_KEY = key_f.read()\n\nwith open(config_file_path, \"r\") as fp:\n    CONFIG_DIC = yaml.load(fp, Loader=yaml.SafeLoader)\n\nGLOBAL_RUNUSER = CONFIG_DIC.get(\"global_runuser\")\nLOCAL_IP = CONFIG_DIC.get(\"local_ip\")\nTENGINE_PORT = CONFIG_DIC.get(\"tengine\")\nSSH_CMD_TIMEOUT = CONFIG_DIC.get(\"ssh_cmd_timeout\", 60)\nSSH_CHECK_TIMEOUT = CONFIG_DIC.get(\"ssh_check_timeout\", 10)\nTHREAD_POOL_MAX_WORKERS = CONFIG_DIC.get(\"thread_pool_max_workers\", 20)\nSALT_RET_PORT = CONFIG_DIC.get(\"salt_master\", {}).get(\"ret_port\", 19005)\nTOKEN_EXPIRATION = CONFIG_DIC.get(\"token_expiration\", 1)\nMONITOR_PORT = CONFIG_DIC.get(\"monitor_port\")\nGRAFANA_API_KEY = CONFIG_DIC.get(\"grafana_api_key\")\nPROMETHEUS_AUTH = CONFIG_DIC.get(\"prometheus_auth\", {})\nGRAFANA_AUTH = CONFIG_DIC.get(\"grafana_auth\", {})\nLOKI_CONFIG = CONFIG_DIC.get(\"loki_config\", {})\nHEALTH_REDIS_TIMEOUT = int(CONFIG_DIC.get(\"health_redis_timeout\", 60)) * 60\nHEALTH_REQUEST_COUNT = int(CONFIG_DIC.get(\"health_request_count\", 6))\nHEALTH_REQUEST_SLEEP = int(CONFIG_DIC.get(\"health_request_sleep\", 5))\nCLEAR_DB = CONFIG_DIC.get(\"clear_table\", {})\nSERVICE_DISCOVERY = CONFIG_DIC.get(\"service_discovery\", [])\nOMP_REDIS_HOST = os.getenv(\n    \"OMP_REDIS_HOST\",\n    CONFIG_DIC.get(\"redis\", {}).get(\"host\")\n)\nOMP_REDIS_PORT = os.getenv(\n    \"OMP_REDIS_PORT\",\n    CONFIG_DIC.get(\"redis\", {}).get(\"port\")\n)\nOMP_REDIS_PASSWORD = os.getenv(\n    \"OMP_REDIS_PASSWORD\",\n    CONFIG_DIC.get(\"redis\", {}).get(\"password\")\n)\nOMP_MYSQL_HOST = os.getenv(\n    \"OMP_MYSQL_HOST\",\n    CONFIG_DIC.get(\"mysql\", {}).get(\"host\")\n)\nOMP_MYSQL_PORT = os.getenv(\n    \"OMP_MYSQL_PORT\",\n    CONFIG_DIC.get(\"mysql\", {}).get(\"port\")\n)\nOMP_MYSQL_USERNAME = os.getenv(\n    \"OMP_MYSQL_USERNAME\",\n    CONFIG_DIC.get(\"mysql\", {}).get(\"username\")\n)\nOMP_MYSQL_PASSWORD = os.getenv(\n    \"OMP_MYSQL_PASSWORD\",\n    CONFIG_DIC.get(\"mysql\", {}).get(\"password\")\n)\nBASIC_ORDER = CONFIG_DIC.get(\"basic_order\", {})\nAFFINITY_FIELD = CONFIG_DIC.get(\"affinity\", {})\nHADOOP_ROLE = CONFIG_DIC.get(\"hadoop_role\", {})\nHOSTNAME_PREFIX = CONFIG_DIC.get(\"hostname_prefix\", {})\nBACKUP_SERVICE = CONFIG_DIC.get(\"backup_service\", [])"
  },
  {
    "path": "omp_server/utils/plugin/__init__.py",
    "content": ""
  },
  {
    "path": "omp_server/utils/plugin/agent_util.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: agent_util\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-05-10 20:13\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装主机Agent的方法\n\"\"\"\n\nimport os\n\nimport yaml\nfrom celery.utils.log import get_task_logger\n\nfrom utils.plugin.ssh import SSH\nfrom omp_server.settings import PROJECT_DIR\nfrom utils.parse_config import LOCAL_IP\nfrom utils.parse_config import SALT_RET_PORT\n\nlogger = get_task_logger(\"celery_log\")\n\n\nclass Agent(object):\n    \"\"\"agent安装管理\"\"\"\n\n    def __init__(self, host, port, username, password, install_dir):\n        \"\"\"\n        agent管理需要的初始化文件\n        :param host: 主机ip\n        :param port: 主机端口\n        :param username: 主机用户名\n        :param password: 主机密码\n        :param install_dir: 安装路径\n        \"\"\"\n        logger.info(\n            f\"Agent Manager: {host}; {port}; {username}; {install_dir}\")\n        self.host = host\n        self.port = port\n        self.username = username\n        self.password = password\n        self.run_user = username\n        self.master_ip = LOCAL_IP\n        self.master_port = SALT_RET_PORT\n        self.install_dir = install_dir\n        self.agent_name = \"omp_salt_agent\"\n        self.package_hub = os.path.join(PROJECT_DIR, \"package_hub\")\n        self.agent_file_path = os.path.join(\n            self.package_hub, \"omp_salt_agent.tar.gz\")\n        self.ssh = SSH(\n            hostname=self.host,\n            port=int(self.port),\n            username=self.username,\n            password=self.password\n        )\n\n    def generate_conf(self):\n        \"\"\"\n        生成agent的配置文件\n        :return:\n        \"\"\"\n        try:\n            logger.info(f\"Generate Conf For {self.host}!\")\n            agent_conf_dic = {\n                \"master\": self.master_ip,\n                \"master_port\": self.master_port,\n                \"user\": self.run_user,\n                \"id\": self.host,\n                \"root_dir\": os.path.join(self.install_dir, f\"{self.agent_name}/data/\"),\n                \"conf_file\": os.path.join(self.install_dir, f\"{self.agent_name}/conf/minion\"),\n                \"rejected_retry\": True\n            }\n            with open(os.path.join(self.package_hub, self.host, \"minion\"), \"w\") as fp:\n                yaml.dump(agent_conf_dic, fp, Dumper=yaml.SafeDumper)\n            return True, \"generate success.\"\n        except Exception as error:\n            return False, str(error)\n\n    def agent_deploy(self):\n        \"\"\"\n        安装agent\n        :return:\n        \"\"\"\n        logger.info(f\"deploy host for agent {self.host}!\")\n        # step1: 判断是否可连接\n        ssh_state, _ = self.ssh.check()\n        if not ssh_state:\n            return False, \"ssh connect failed\"\n\n        # 删除原有omp_salt_agent\n        logger.info(f\"delete origin omp_salt_agent for {self.host}\")\n        omp_salt = os.path.join(self.install_dir, \"omp_salt_agent\")\n        _delete_cron_cmd = f\"sed -i '/omp_salt_agent/d' /var/spool/cron/{self.username}; \"\n        # _stop_agent = f\"bash {omp_salt}/bin/omp_salt_agent stop; rm -rf {omp_salt}\"\n        _stop_agent = f\"bash {omp_salt}/bin/omp_salt_agent stop; /bin/rm -rf {omp_salt}/data/*\"\n        final_cmd = f\"{_delete_cron_cmd} {_stop_agent}\"\n        self.ssh.cmd(final_cmd, timeout=60)\n\n        # step2: push agent.tar.gz to remote host and install\n        logger.info(f\"push omp_salt_agent.tar.gz to {self.host}!\")\n        tar_push_state, tar_push_msg = self.ssh.file_push(\n            self.agent_file_path, self.install_dir)\n        if not tar_push_state:\n            logger.error(\n                f\"file push to {self.host} with error: {tar_push_msg}\")\n            return False, tar_push_msg\n\n        # step3: install agent\n        logger.info(f\"execute install command for {self.host}!\")\n        command = \"cd {0} && tar xmf {1}.tar.gz && chown -R {2}:{2} {1} && rm -f {1}.tar.gz\".format(\n            self.install_dir, self.agent_name, self.run_user)\n        cmd_exec_state, cmd_exec_msg = self.ssh.cmd(command)\n        if not cmd_exec_state:\n            logger.error(\n                f\"Error while install agent for {self.host}: {cmd_exec_msg}\")\n            return False, cmd_exec_msg\n\n        # step4: make package_hub/host_ip/\n        logger.info(f\"push config to {self.host}!\")\n        config_tmp_dir = os.path.join(self.package_hub, self.host)\n        if not os.path.isdir(config_tmp_dir):\n            os.makedirs(config_tmp_dir)\n        config_tmp = os.path.join(config_tmp_dir, 'minion')\n        config_gen_state, config_gen_msg = self.generate_conf()\n        if not config_gen_state:\n            logger.error(\n                f\"Error while generate conf file for agent {self.host}: {config_gen_msg}\")\n            return False, config_gen_msg\n\n        # step5: push config file to remote host\n        config_push_state, config_push_msg = self.ssh.file_push(\n            config_tmp,\n            os.path.join(self.install_dir, f'{self.agent_name}/conf/')\n        )\n        if not config_push_state:\n            logger.error(\n                f\"Error while send agent config to {self.host}: {config_push_msg}\")\n            return False, config_push_msg\n\n        # step6: make and push scripts\n        logger.info(f\"push script to {self.host}!\")\n        with open(os.path.join(PROJECT_DIR, \"scripts/source/omp_salt_agent\"), \"r\") as fp:\n            _script_content = fp.read()\n        with open(os.path.join(config_tmp_dir, \"omp_salt_agent\"), \"w\") as fp:\n            _content = _script_content.replace(\n                \"UNIQUE_INSTALL_DIR_FLAG\", self.install_dir)\n            _content = _content.replace(\"RUNUSER\", self.run_user)\n            fp.write(_content)\n        script_push_state, script_push_msg = self.ssh.file_push(\n            os.path.join(config_tmp_dir, \"omp_salt_agent\"),\n            os.path.join(self.install_dir, '{}/bin/'.format(self.agent_name))\n        )\n        if not script_push_state:\n            logger.error(\n                f\"Error while send agent script for {self.host}: {script_push_msg}\")\n            return False, script_push_msg\n        # shutil.rmtree(config_tmp_dir)\n\n        # step7: start and init agent\n        logger.info(f\"init omp_salt_agent for {self.host}!\")\n        command = f\"cd {self.install_dir} && \" \\\n                  f\"chown -R {self.run_user}.{self.run_user} {self.agent_name} && \" \\\n                  f\"bash {self.agent_name}/bin/{self.agent_name} init\"\n        cmd_exec_state, cmd_exec_msg = self.ssh.cmd(command, timeout=120)\n        if not cmd_exec_state:\n            if \"INIT_OMP_SALT_AGENT_SUCCESS\" in cmd_exec_msg:\n                return True, \"agent deploy success\"\n            logger.error(\n                f\"Error while start agent for {self.host}: {cmd_exec_msg}\")\n            return False, cmd_exec_msg\n        logger.info(f\"success deploy agent for {self.host}!\")\n        return True, \"agent deploy success.\"\n\n    def agent_manage(self, action, install_app_dir):\n        \"\"\"\n        manage salt agent, start stop status\n\n        :param str action: [start|stop|status|restart]\n        :param str install_app_dir:  e.g. /data/app\n\n        :return:\n            the (state, status) , as a 2-tuple\n        \"\"\"\n        if action == \"start\":\n            command = \"cd {}/{} && bash ./bin/omp_salt_agent start\".format(\n                install_app_dir, self.agent_name)\n        elif action == \"stop\":\n            command = \"cd {}/{} && bash ./bin/omp_salt_agent stop\".format(\n                install_app_dir, self.agent_name)\n        elif action == \"status\":\n            command = \"cd {}/{} && bash ./bin/omp_salt_agent status\".format(\n                install_app_dir, self.agent_name)\n        elif action == \"restart\":\n            command = \"cd {}/{} && bash ./bin/omp_salt_agent restart\".format(\n                install_app_dir, self.agent_name)\n        else:\n            return False, \"start|stop|status|restart\"\n        cmd_exec_state, cmd_exec_msg = self.ssh.cmd(command)\n        if cmd_exec_state:\n            return True, cmd_exec_msg[0].strip()\n        else:\n            return False, cmd_exec_msg\n\n\nif __name__ == '__main__':\n    # test_agent = Agent(\n    #     host=\"10.0.7.146\",\n    #     port=36000,\n    #     username=\"root\",\n    #     password=\"yunzhihui123\",\n    #     run_user=\"root\",\n    #     install_dir=\"/data/app\"\n    # )\n    # flag, message = test_agent.agent_deploy()\n    # print(flag, message)\n    pass\n"
  },
  {
    "path": "omp_server/utils/plugin/captcha/__init__.py",
    "content": "from .captcha import check_code\n\n__all__ = [\n    check_code\n]\n"
  },
  {
    "path": "omp_server/utils/plugin/captcha/captcha.py",
    "content": "import os\nimport random\nfrom PIL import Image, ImageDraw, ImageFilter, ImageFont\n\nfont_path = os.path.join(\n    os.path.dirname(__file__), \"monaco.ttf\"\n)\n\n\ndef check_code(width=120, height=38, char_length=4, font_file=font_path, font_size=32):\n    code = []\n    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))\n    draw = ImageDraw.Draw(img, mode='RGB')\n\n    def rndChar():\n        \"\"\"\n        生成随机字母\n        :return:\n        \"\"\"\n        return chr(random.randint(65, 90))\n\n    def rndColor():\n        \"\"\"\n        生成随机颜色\n        :return:\n        \"\"\"\n        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))\n\n    # 写文字\n    font = ImageFont.truetype(font_file, font_size)\n    for i in range(char_length):\n        char = rndChar()\n        code.append(char)\n        h = random.randint(0, 4)\n        draw.text([i * width / char_length, h],\n                  char, font=font, fill=rndColor())\n\n    # 写干扰点\n    for i in range(40):\n        draw.point([random.randint(0, width),\n                    random.randint(0, height)], fill=rndColor())\n\n    # 写干扰圆圈\n    for i in range(40):\n        draw.point([random.randint(0, width),\n                    random.randint(0, height)], fill=rndColor())\n        x = random.randint(0, width)\n        y = random.randint(0, height)\n        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())\n\n    # 画干扰线\n    for i in range(5):\n        x1 = random.randint(0, width)\n        y1 = random.randint(0, height)\n        x2 = random.randint(0, width)\n        y2 = random.randint(0, height)\n\n        draw.line((x1, y1, x2, y2), fill=rndColor())\n\n    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)\n    return img, ''.join(code)\n\n\nif __name__ == '__main__':\n    image_obj, code = check_code()\n    image_obj.show()  # 直接打开\n"
  },
  {
    "path": "omp_server/utils/plugin/crontab_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: crontab_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-14 11:35\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n定时周期性任务设置方法\n\"\"\"\n\nimport json\nimport pytz\nfrom copy import deepcopy\nimport logging\n\nfrom django_celery_beat.models import PeriodicTask\nfrom django_celery_beat.models import CrontabSchedule\nfrom django_celery_beat.models import IntervalSchedule\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom functools import wraps\nfrom omp_server.settings import TIME_ZONE\nfrom db_models.models import Maintain\n\nlogger = logging.getLogger(\"server\")\n\n\nclass CrontabUtils(object):\n    \"\"\" 定时、周期性任务创建工具 \"\"\"\n\n    def __init__(\n            self, task_name=None, task_func=\"\",\n            task_args=None, task_kwargs=None, task_timeout=None):\n        \"\"\"\n        定时任务初始化方法\n        :param task_name: 任务名称，全局唯一\n        :param task_func: 任务执行方法\n        :param task_args: 任务执行位置参数\n        :param task_kwargs: 任务执行关键字参数\n        :param task_timeout: 任务超时时间(暂时未定)\n        \"\"\"\n        self.task_name = task_name\n        self.task_func = task_func\n        self.task_args = task_args if task_args else ()\n        self.task_kwargs = task_kwargs if task_kwargs else {}\n        self.task_time = task_timeout\n        self.create_task_obj_dic = {\n            \"name\": self.task_name,\n            \"task\": self.task_func,\n            \"args\": json.dumps(self.task_args),\n            \"kwargs\": json.dumps(self.task_kwargs)\n        }\n\n    def create_crontab_job(\n            self, minute=\"*\", hour=\"*\", day_of_month=\"*\",\n            month_of_year=\"*\", day_of_week=\"*\", job_timezone=TIME_ZONE):\n        \"\"\"\n        创建定时任务\n        如果定时任务正在执行，但是在指定频率内未完成，那么新任务仍能下发\n        :param minute: 分钟\n        :param hour: 小时\n        :param day_of_month: 每月的第几天\n        :param month_of_year: 每年的第几个月\n        :param day_of_week: 每周的第几天\n        :param job_timezone: 时区\n        :return: (flag, msg)\n        \"\"\"\n        if self.check_task_exist():\n            return False, f\"{self.task_name} already exist!\"\n        _schedule, _created = CrontabSchedule.objects.get_or_create(\n            minute=minute,\n            hour=hour,\n            day_of_week=day_of_week,\n            day_of_month=day_of_month,\n            month_of_year=month_of_year,\n            timezone=pytz.timezone(job_timezone)\n        )\n        _dic = deepcopy(self.create_task_obj_dic)\n        _dic.update({\"crontab\": _schedule})\n        job_obj = PeriodicTask.objects.create(**_dic)\n        return True, job_obj.name\n\n    def create_internal_job(self, num, unit_type=\"minutes\"):\n        \"\"\"\n        创建周期性任务\n        周期类型有\n            DAYS = DAYS\n            HOURS = HOURS\n            MINUTES = MINUTES\n            SECONDS = SECONDS\n        :param num: 周期时长\n        :param unit_type: 周期类型\n        :return:\n        \"\"\"\n        if self.check_task_exist():\n            return False, f\"{self.task_name} already exist!\"\n        if not num or not isinstance(num, int):\n            return False, \"num must be integer\"\n        period = getattr(IntervalSchedule, unit_type.upper(), None)\n        if not period:\n            return False, \"unit_type must be one of DAYS/HOURS/MINUTES/SECONDS\"\n        _schedule, _created = IntervalSchedule.objects.get_or_create(\n            every=10,\n            period=IntervalSchedule.SECONDS\n        )\n        _dic = deepcopy(self.create_task_obj_dic)\n        _dic.update({\"interval\": _schedule})\n        job_obj = PeriodicTask.objects.create(**_dic)\n        return True, job_obj.name\n\n    def check_task_exist(self):\n        \"\"\"\n        检查task是否存在，从task name来\n        :return:\n        \"\"\"\n        return PeriodicTask.objects.filter(name=self.task_name).exists()\n\n    def delete_job(self):\n        \"\"\"\n        删除定时任务\n        :return:\n        \"\"\"\n        try:\n            PeriodicTask.objects.get(name=self.task_name).delete()\n            return True, \"success\"\n        except ObjectDoesNotExist:\n            return False, f\"{self.task_name} not exists!\"\n\n\ndef change_task(task_id, data=dict()):\n    task_func = data.get(\"task_func\", \"backups.tasks.backup_service\")\n    task_name = data.get(\"task_name\", f\"backups_cron_task_{task_id}\")\n    try:\n        cron_args = data.get(\"crontab_detail\", {})\n        hour = cron_args.get(\"hour\", \"\")\n        hour_ls = str(hour).split(\"/\")\n        if len(hour_ls) == 2:\n            cron_args[\"hour\"] = ','.join(\n                list(\n                    map(str, range(0, 24, int(hour_ls[1])))\n                ))\n\n        create = data.get(\"is_on\", False)\n\n        cron_obj = CrontabUtils(task_name=task_name, task_func=task_func,\n                                task_kwargs={\"task_id\": task_id})\n        if cron_obj.check_task_exist():\n            res, msg = cron_obj.delete_job()\n            logger.info(f\"删除周期任务{task_name},结果{res},详情{msg}\")\n        if create:\n            res, msg = cron_obj.create_crontab_job(**cron_args)\n            logger.info(f\"创建周期任务{task_name},结果{res},详情{msg}\")\n        return 0, \"\"\n    except Exception as e:\n        logger.error(f\"执行定时任务失败：{str(e)}\")\n        return 1, \"执行定时任务失败，请重试！\"\n\n\ndef get_per_cron(cron_args):\n    \"\"\"\n    */int 进行拆除\n    \"\"\"\n    max_time = {\n        \"hour\": 24,\n        \"minute\": 60,\n        \"day_of_month\": 30,\n        \"day_of_week\": 7,\n        \"month_of_year\": 12\n    }\n    for k, v in cron_args.items():\n        v_ls = str(v).split(\"/\")\n        if len(v_ls) == 2:\n            cron_args[k] = ','.join(\n                list(\n                    map(str, range(0, max_time.get(k), int(v_ls[1])))\n                ))\n    return cron_args\n\n\ndef change_task(task_id, data=dict()):\n    task_func = data.get(\"task_func\", \"backups.tasks.backup_service\")\n    task_name = data.get(\"task_name\", f\"backups_cron_task_{task_id}\")\n    try:\n        cron_args = data.get(\"crontab_detail\", {}).copy()\n        cron_args = get_per_cron(cron_args)\n\n        create = data.get(\"is_on\", False)\n\n        cron_obj = CrontabUtils(task_name=task_name, task_func=task_func,\n                                task_kwargs={\"task_id\": task_id})\n        if cron_obj.check_task_exist():\n            res, msg = cron_obj.delete_job()\n            logger.info(f\"删除周期任务{task_name},结果{res},详情{msg}\")\n        if create:\n            res, msg = cron_obj.create_crontab_job(**cron_args)\n            logger.info(f\"创建周期任务{task_name},结果{res},详情{msg}\")\n        return 0, \"\"\n    except Exception as e:\n        logger.error(f\"执行定时任务失败：{str(e)}\")\n        return 1, \"执行定时任务失败，请重试！\"\n\n\ndef maintain(f):\n    @wraps(f)\n    def decorated(*args, **kwargs):\n        if Maintain.objects.filter(matcher_value=\"default\").first():\n            logger.info(\"任务处于维护模式\")\n            return \"task will not run\"\n        return f(*args, **kwargs)\n\n    return decorated\n"
  },
  {
    "path": "omp_server/utils/plugin/crypto.py",
    "content": "import base64\nimport hashlib\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Cipher import PKCS1_v1_5, AES\nfrom base64 import urlsafe_b64decode, urlsafe_b64encode\n\nfrom django.conf import settings\n\n\nclass AESCryptor:\n    \"\"\" AES 加密器 \"\"\"\n\n    def __init__(self):\n        self.__encryptKey = settings.SECRET_KEY\n        self.__key = hashlib.md5(self.__encryptKey.encode(\"utf8\")).digest()\n\n    @staticmethod\n    def pad(text, block_size=32):\n        pad = block_size - (len(text) % block_size)\n        return (text + pad * chr(pad)).encode(\"utf8\")\n\n    @staticmethod\n    def un_pad(text):\n        index = text[-1]\n        return text[:-index].decode(\"utf8\")\n\n    def encode(self, plaintext):\n        \"\"\" AES 加密 \"\"\"\n        ciphertext = AES.new(\n            self.__key, AES.MODE_ECB\n        ).encrypt(self.pad(plaintext))\n        return urlsafe_b64encode(ciphertext).decode(\"utf8\").rstrip(\"=\")\n\n    def decode(self, ciphertext):\n        \"\"\" AES 解密 \"\"\"\n        ciphertext = urlsafe_b64decode(\n            ciphertext + \"=\" * (4 - len(ciphertext) % 4))\n        cipher = AES.new(self.__key, AES.MODE_ECB)\n        return self.un_pad(cipher.decrypt(ciphertext))\n\n\ndef decrypt_rsa(encrypt_str, private_key=settings.PRIVATE_KEY):\n    rsakey = RSA.importKey(private_key.encode())\n    cipher = PKCS1_v1_5.new(rsakey)\n    st = base64.b64decode(encrypt_str.encode())\n    return cipher.decrypt(st, sentinel=\"\").decode()\n"
  },
  {
    "path": "omp_server/utils/plugin/install_ntpdate.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: instanll_ntpdate\n# Create time: 2022/2/23 1:32 下午\nimport os\nimport yaml\nimport logging\nfrom utils.plugin.salt_client import SaltClient\nfrom concurrent.futures import ThreadPoolExecutor\nfrom omp_server.settings import PROJECT_DIR\n\nlogger = logging.getLogger(\"server\")\n\n\nclass InstallNtpdate(object):\n    def __init__(self, host_obj_list=None):\n        if not host_obj_list or not isinstance(host_obj_list, list):\n            logger.error(\"host_obj_list must be type of list\")\n            raise TypeError(\"host_obj_list must be type of list!\")\n        self.host_obj_list = host_obj_list\n        self.salt_client = SaltClient()\n        self.name = \"ntpdate\"\n        self.ntpdate_package_name = \"\"\n        self.config_path = os.path.join(PROJECT_DIR, \"config/omp.yaml\")\n        self.package_hub_dir = os.path.join(PROJECT_DIR, \"package_hub\")\n        self.check_ntpdate_package()\n\n    def check_ntpdate_package(self):\n        \"\"\"检查ntpdate源码包是否存在\"\"\"\n        for file in os.listdir(self.package_hub_dir):\n            if file.startswith(self.name) and file.endswith(\"tar.gz\"):\n                self.ntpdate_package_name = file\n                break\n\n    def get_config_dic(self):\n        \"\"\"\n        获取配置文件详细信息\n        :return:\n        \"\"\"\n        with open(self.config_path, \"r\", encoding=\"utf8\") as fp:\n            return yaml.load(fp, Loader=yaml.FullLoader)\n\n    def get_run_user(self):\n        \"\"\"获取run_user\"\"\"\n        return self.get_config_dic().get(\"global_user\")\n\n    @staticmethod\n    def execute(host_obj_lst, thread_name_prefix, func):\n        \"\"\"\n        执行函数\n        :param host_obj_lst: 主机对象组成的列表\n        :param thread_name_prefix: 线程前缀\n        :param func: 执行函数对象, 仅接收host主机对象参数\n        :return:\n        \"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=20, thread_name_prefix=thread_name_prefix)\n        # futures_list:[(ip, future)]\n        futures_list = list()\n        for item in host_obj_lst:\n            future = thread_p.submit(func, item)\n            futures_list.append((item.ip, future))\n        # result_list:[(ip, res_bool, res_msg), ...]\n        result_list = list()\n        for f in futures_list:\n            result_list.append((f[0], f[1].result()[0], f[1].result()[1]))\n        thread_p.shutdown(wait=True)\n        error_msg = \"\"\n        for el in result_list:\n            if not el[1]:\n                error_msg += \\\n                    f\"{el[0]}: (execute_flag: {el[1]}; execute_msg: {el[2]});\"\n        if error_msg:\n            return False, error_msg\n        return True, \"success!\"\n\n    def _install_ntpdate(self, host_obj):\n        \"\"\"安装ntpdate\"\"\"\n        if not self.ntpdate_package_name:\n            logger.error(\"ntpdate_package not existed!\")\n            return False, \"ntpdate_package not existed!\"\n        host_obj.ntpdate_install_status = 2\n        host_obj.save()\n        agent_dir = host_obj.agent_dir\n        app_dir = os.path.join(agent_dir, \"app\")\n        target_package_path = os.path.join(app_dir, self.ntpdate_package_name)\n        data_dir = os.path.join(agent_dir, \"appData\")\n        log_dir = os.path.join(agent_dir, \"logs\")\n        ntpdate_cron_path = os.path.join(app_dir, f\"{self.name}/scripts/{self.name}_cron.sh\")\n        scripts_path = os.path.join(app_dir, f\"{self.name}/scripts/{self.name}\")\n        # 1.发包\n        send_flag, send_msg = self.salt_client.cp_file(\n            target=host_obj.ip,\n            source_path=self.ntpdate_package_name,\n            target_path=target_package_path,\n            makedirs=True\n        )\n        logger.info(\n            f\"send ntpdate packages,send_flag: {send_flag}; send_msg: {send_msg}\"\n        )\n        if not send_flag:\n            return send_flag, send_msg\n        # 2.解压缩与安装、启动\n        cmd_flag, cmd_msg = self.salt_client.cmd(\n            target=host_obj.ip,\n            command=f\"(test -d {app_dir}|| mkdir -p {app_dir})&&\"\n                    f\"(test -d {data_dir}|| mkdir -p {data_dir})&&\"\n                    f\"(test -d {log_dir}|| mkdir -p {log_dir})&&\"\n                    f\"cd {app_dir} &&\"\n                    f\" tar -xmf {self.ntpdate_package_name} &&\"\n                    f\" /bin/rm -rf {self.ntpdate_package_name} &&\"\n                    f\"sed -i -e \\\"s#\\${{CW_INSTALL_APP_DIR}}#{app_dir}#g\\\" -e 's#\\${{CW_NTP_ADDRESS}}#{host_obj.ntpd_server}#g' {ntpdate_cron_path} &&\"\n                    f\"sed -i -e 's#\\${{CW_INSTALL_APP_DIR}}#{app_dir}#g' -e 's#\\${{CW_INSTALL_LOGS_DIR}}#{log_dir}#g' -e 's#\\${{CW_INSTALL_DATA_DIR}}#{data_dir}#g' -e's#\\${{CW_RUN_USER}}#{self.get_run_user()}#g' {scripts_path};\"\n                    f\"bash {scripts_path} start\",\n            timeout=120\n        )\n        logger.info(f\"install ntpdate cmd, cmd_flag: {cmd_flag};cmd_msg: {cmd_msg}\")\n        if not cmd_flag:\n            host_obj.ntpdate_install_status = 3\n            host_obj.save()\n            return cmd_flag, cmd_msg\n        host_obj.ntpdate_install_status = 0\n        host_obj.save()\n        return True, \"success\"\n\n    def install(self):\n        return self.execute(host_obj_lst=self.host_obj_list, thread_name_prefix=\"install_\", func=self._install_ntpdate)\n"
  },
  {
    "path": "omp_server/utils/plugin/monitor_agent.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: monitor_agent\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-07 13:45\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装、卸载、更新monitor agent\n\"\"\"\nimport json\nimport os\nimport time\nimport random\nimport logging\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom db_models.models import Host\nfrom omp_server.settings import PROJECT_DIR\nfrom promemonitor.prometheus_utils import PrometheusUtils\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger(\"server\")\n\n\nclass MonitorAgentManager(object):\n    \"\"\" 监控Agent的管理类 \"\"\"\n\n    def __init__(self, host_objs=None):\n        \"\"\"\n        初始化方法\n        :param host_objs: 主机对象组成的列表\n        \"\"\"\n        if not host_objs or not isinstance(host_objs, list):\n            raise TypeError(\"host_objs must be a list of host objs!\")\n        self.name = \"omp_monitor_agent\"\n        self.host_objs = host_objs\n        self.package_hub_dir = os.path.join(PROJECT_DIR, \"package_hub\")\n        self.monitor_agent_package_name = \"\"\n        self.check_monitor_agent_package()\n\n    def check_monitor_agent_package(self):\n        \"\"\"\n        检查monitor agent的源码包是否存在\n        :return:\n        \"\"\"\n        # 判断monitor agent源码包文件是否存在\n        for item in os.listdir(self.package_hub_dir):\n            if item.startswith(self.name) and item.endswith(\"tar.gz\"):\n                self.monitor_agent_package_name = item\n                break\n\n    @staticmethod\n    def execute(host_obj_lst, thread_name_prefix, func):\n        \"\"\"\n        执行函数\n        :param host_obj_lst: 主机对象组成的列表\n        :param thread_name_prefix: 线程前缀\n        :param func: 执行函数对象, 仅接收host主机对象参数\n        :return:\n        \"\"\"\n        thread_p = ThreadPoolExecutor(\n            max_workers=20, thread_name_prefix=thread_name_prefix)\n        # futures_list:[(ip, future)]\n        futures_list = list()\n        for item in host_obj_lst:\n            future = thread_p.submit(func, item)\n            futures_list.append((item.ip, future))\n        # result_list:[(ip, res_bool, res_msg), ...]\n        result_list = list()\n        for f in futures_list:\n            result_list.append((f[0], f[1].result()[0], f[1].result()[1]))\n        thread_p.shutdown(wait=True)\n        error_msg = \"\"\n        for el in result_list:\n            if not el[1]:\n                error_msg += \\\n                    f\"{el[0]}: (execute_flag: {el[1]}; execute_msg: {el[2]});\"\n        if error_msg:\n            return False, error_msg\n        return True, \"success!\"\n\n    def _install(self, obj):\n        \"\"\"\n        安装函数\n        :param obj: 主机对象\n        :type obj: Host\n        :return:\n        \"\"\"\n        # step1: 判断源码包是否存在\n        if not self.monitor_agent_package_name:\n            return False, \"omp_monitor_agent package does not exist!\"\n        # step2: 发送源码包文件\n        salt_obj = SaltClient()\n        send_flag, send_msg = salt_obj.cp_file(\n            target=obj.ip,\n            source_path=self.monitor_agent_package_name,\n            target_path=obj.agent_dir,\n            makedirs=False)\n        logger.info(\n            f\"Send omp_monitor_agent, \"\n            f\"send_flag: {send_flag}; send_msg: {send_msg}\")\n        if not send_flag:\n            return send_flag, send_msg\n        # step3: 解压源码包并启动服务\n        metrics_auth = json.dumps(\n            {\"username\": \"mokey\", \"password\": \"w7SiYs$oE\"})  # TODO  后续从配置文件读取\n        services_info = json.dumps({\"node\": {\"quotes\": [], \"exporter_port\": 19017, \"exporter_metric\": \"metrics\",\n                                             \"auth_user\": \"mokey\",\n                                             \"auth_password_encryption\": \"$2y$12$GThv30aK.STxvx6A32CXVubbveBkEq3vatYTPpuIVTT6uRE24L91O\"}})\n        cmd_flag, cmd_res = salt_obj.cmd(\n            target=obj.ip,\n            command=f\"cd {obj.agent_dir} && \"\n                    f\"tar -xmf {self.monitor_agent_package_name} && \"\n                    f\"/bin/rm -rf {self.monitor_agent_package_name} && \"\n                    f\"cd {self.name} && \"\n                    f\"./install --agent_ip={obj.ip} --metrics_auth='{metrics_auth}' --services_info='{services_info}' && \"\n                    f\"bash monitor_agent.sh init &&\"\n                    f\"bash monitor_agent.sh start\",\n            timeout=120)\n        logger.info(\n            f\"Install omp_monitor_agent cmd, \"\n            f\"cmd_flag: {cmd_flag}; cmd_res: {cmd_res}\")\n        # 安装成功后更新数据库的状态\n        if not cmd_flag:\n            Host.objects.filter(ip=obj.ip).update(\n                monitor_agent=4,\n                monitor_agent_error=str(cmd_res) if len(\n                    str(cmd_res)) < 200 else str(cmd_res)[:200]\n            )\n            return cmd_flag, cmd_res\n        Host.objects.filter(ip=obj.ip).update(\n            monitor_agent=0,\n            monitor_agent_error=None\n        )\n        return True, \"success\"\n\n    def install(self):\n        \"\"\"\n        安装monitor agent\n        :return:\n        \"\"\"\n        flag, msg = self.execute(\n            host_obj_lst=self.host_objs,\n            thread_name_prefix=\"install_\",\n            func=self._install)\n        if not flag:\n            return flag, msg\n        # 更新监控Server端配置，暂时靠sleep解决文件并发操作\n        time.sleep(random.randint(1, 5))\n        _pro_obj = PrometheusUtils()\n        _pro_obj.add_node(self.parse_hosts_data())\n        return True, \"success!\"\n\n    def parse_hosts_data(self):\n        \"\"\"\n        解析主机信息\n        :return:\n        \"\"\"\n        hosts_data = list()\n        for item in self.host_objs:\n            # TODO 支持手动添加服务器，暂不支持无SSH\n            _ip = item.ip\n            _env = item.env.name\n            _data_folder = item.data_folder\n            # {\"/\": 90, \"/data\": 100}\n            _disk_info = item.disk\n            if not _disk_info:\n                _disk_info = Host.objects.get(id=item.id).disk\n            data_path = \"\"\n            if _disk_info and isinstance(_disk_info, dict):\n                for key, _ in _disk_info.items():\n                    if key == \"/\":\n                        continue\n                    if _data_folder.startswith(key):\n                        data_path = key\n                        break\n            # prometheus 使用的target数据\n            hosts_data.append({\n                \"ip\": _ip,\n                \"env\": _env,\n                \"data_path\": data_path or _data_folder,\n                \"instance_name\": item.instance_name\n            })\n        return hosts_data\n\n    def _uninstall(self, obj):\n        \"\"\"\n        卸载函数\n        :param obj: 主机对象\n        :type obj: Host\n        :return:\n        \"\"\"\n        salt_obj = SaltClient()\n        monitor_agent_home = os.path.join(obj.agent_dir, self.name)\n        cmd_flag, cmd_res = salt_obj.cmd(\n            target=obj.ip,\n            command=f\"cd {monitor_agent_home} && \"\n                    f\"./manage stop_all && bash monitor_agent.sh stop && \"\n                    f\"cd {obj.agent_dir} && /bin/rm -rf {self.name}\",\n            timeout=120)\n        logger.info(\n            f\"Uninstall monitor_agent, \"\n            f\"cmd_flag: {cmd_flag}; cmd_res: {cmd_res}\")\n        if not cmd_flag:\n            return cmd_flag, cmd_res\n        return True, \"success\"\n\n    def uninstall(self):\n        \"\"\"\n        卸载monitor agent\n        :return:\n        \"\"\"\n        # TODO 需要确定exporter停止脚本使用参数情况\n        return self.execute(\n            host_obj_lst=self.host_objs,\n            thread_name_prefix=\"uninstall_\",\n            func=self._uninstall)\n"
  },
  {
    "path": "omp_server/utils/plugin/public_utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: public_utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-09 19:55\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n文件md5值处理模块\n\"\"\"\n\nimport os\nimport socket\nimport hashlib\nimport ipaddress\nimport subprocess\n\n\ndef local_cmd(command):\n    \"\"\"\n    执行本地shell命令\n    :param command: 执行命令\n    :return: (stdout, stderr, ret_code)\n    \"\"\"\n    p = subprocess.Popen(\n        command,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        shell=True\n    )\n    stdout, stderr = p.communicate()\n    _out, _err, _code = \\\n        stdout.decode(\"utf8\"), stderr.decode(\"utf8\"), p.returncode\n    return _out, _err, _code\n\n\ndef get_file_md5(file_path):\n    \"\"\"\n    获取文件的md5值\n    :param file_path: 文件路径\n    :return:\n    \"\"\"\n    if not os.path.exists(file_path):\n        return False, \"File not exists!\"\n    m = hashlib.md5()\n    with open(file_path, 'rb') as f_obj:\n        while True:\n            data = f_obj.read(4096)\n            if not data:\n                break\n            m.update(data)\n    return True, m.hexdigest()\n\n\ndef check_is_ip_address(value):\n    \"\"\"\n    检查是否为ip地址\n    :param value: ip地址字符串\n    :return:\n    \"\"\"\n    try:\n        ipaddress.ip_address(value)\n        return True, value\n    except ValueError:\n        return False, \"not valid ip address!\"\n\n\ndef check_ip_port(ip, port):\n    \"\"\"\n    检查ip、port的联通性\n    :param ip: 地址\n    :param port: 端口\n    :return:\n    \"\"\"\n    if not check_is_ip_address(value=ip)[0]:\n        return False, \"ip address not correct\"\n    try:\n        int_port = int(port)\n        if int_port < 0 or int_port > 65535:\n            return False, \"port must be 0 ~ 65535\"\n    except ValueError:\n        return False, \"port must be 0 ~ 65535, int or string\"\n    sock_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    result = sock_obj.connect_ex((ip, port))\n    if result == 0:\n        return_tuple = (True, \"success\")\n    else:\n        return_tuple = (False, \"failed\")\n    sock_obj.close()\n    return return_tuple\n\n\nclass DurationTime:\n\n    def __init__(self, seconds):\n        self.second = seconds\n        self.minute = self.hour = self.day = 0\n\n    def analysis_day(self):\n        day, hour = divmod(self.hour, 24)\n        setattr(self, \"hour\", hour)\n        setattr(self, \"day\", day)\n\n    def analysis_hour(self):\n        hour, minute = divmod(self.minute, 60)\n        setattr(self, \"minute\", minute)\n        setattr(self, \"hour\", hour)\n\n    def analysis_minute(self):\n        minute, second = divmod(self.second, 60)\n        setattr(self, \"second\", second)\n        setattr(self, \"minute\", minute)\n\n    def __call__(self, *args, **kwargs):\n        for key in [\"minute\", \"hour\", \"day\"]:\n            getattr(self, f\"analysis_{key}\")()\n            if not getattr(self, key):\n                return self\n        return self\n\n\ndef timedelta_strftime(timedelta):\n    \"\"\"\n    四舍五入格式化timedelta\n    :param timedelta: <class datetime.timedelta>\n    :return: \"XX天XX时XX分XX秒\"\n    \"\"\"\n    seconds = round(timedelta.total_seconds())\n    duration = DurationTime(seconds)()\n    en_zh = [(\"day\", \"天\"), (\"hour\", \"时\"), (\"minute\", \"分\"), (\"second\", \"秒\")]\n    strftime = \"\"\n    for en, zh in en_zh:\n        if strftime:\n            strftime += f\"{getattr(duration, en)}{zh}\"\n        elif not strftime and getattr(duration, en):\n            strftime += f\"{getattr(duration, en)}{zh}\"\n    return strftime\n\n\ndef file_md5(file_path):\n    # md5校验生成\n    md5_out = local_cmd(f'md5sum {file_path}')\n\n    if md5_out[2] != 0:\n        return None\n    return md5_out[0].split()[0]\n\n\ndef format_location_size(size):\n    # 格式化文件大小\n    if int(size/1024) < 100:\n        return \"%.3f\" % (size / 1024) + \"K\"\n    size = size/1024\n    if int(size/1024) < 100:\n        return \"%.3f\" % (size / 1024) + \"M\"\n    return \"%.3f\" % (size/1024/1024) + \"G\"\n"
  },
  {
    "path": "omp_server/utils/plugin/salt_client.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: salt_client\n# Author: jon.liu@yunzhihui.com\n# Create time: 2020-12-31 14:46\n# IDE: PyCharm\n# Version: 1.0\n# Introduction: 定义与salt交互过程中使用的某些类及方法。\n\n\"\"\"\nsalt 封装的相关模块或方法\n\"\"\"\n\nimport os\nimport logging\nimport traceback\nimport salt.client\n\nfrom omp_server.settings import PROJECT_DIR\n\nsalt_master_config = os.path.join(PROJECT_DIR, \"config/salt/master\")\n\nlogger = logging.getLogger('server')\n\nSALT_ERROR_MSG = \"Salt 执行错误，请检查相关配置是否正确！\"\nAGENT_OFFLINE_MSG = \"当前目标主机不在线或该目标主机未纳管！\"\n\n\nclass SaltClient(object):\n    \"\"\"本地salt管理接口\"\"\"\n\n    def __init__(self, config_path=salt_master_config):\n        \"\"\"\n        salt管理客户端初始化操作\n        :param config_path: salt-master的配置文件绝对路径\n        \"\"\"\n        self.config_path = config_path\n        # logger.info(\n        #     f\"Init salt client with config path: {self.config_path}!\")\n        self.client = salt.client.LocalClient(\n            c_path=self.config_path, auto_reconnect=True)\n\n    def salt_module_update(self):\n        \"\"\"\n        用于更新salt的自定义模块\n        :return:\n        \"\"\"\n        try:\n            cmd_res = self.client.cmd(\n                tgt=\"*\",\n                fun=\"saltutil.sync_modules\",\n            )\n            \"\"\"\n            # {'192.168.175.149': {'ret': [], 'retcode': 0, 'jid': '20210113213356939481'}, 'ruban-dev': False}\n            # {'192.168.175.149': [], 'ruban-dev': False}\n            \"\"\"\n            ret_dic = dict()\n            for key, value in cmd_res.items():\n                if isinstance(value, dict) and value.get(\"retcode\", 1000) == 0:\n                    ret_dic.update({key: True})\n                elif isinstance(value, list):\n                    ret_dic.update({key: True})\n                else:\n                    ret_dic.update({key: False})\n            return True, ret_dic\n        except Exception as e:\n            return False, f\"在同步salt模块的过程中出错: {str(e)}\"\n\n    def fun_for_multi(self, target, fun, arg=(), kwarg=None, timeout=None, tgt_type=\"glob\"):\n        \"\"\"\n        可自行执行模块的命令，适用于批量执行操作，需要自行判断函数执行结果\n        :param target: 目标主机\n        :param fun: 执行模块，如cmd.run\n        :param arg: 位置参数信息\n        :param kwarg: 关键字参数信息\n        :param timeout: 超时时间\n        :param tgt_type: 匹配target的格式，glob正则匹配 or list匹配\n        :return: 返回的执行结果\n        \"\"\"\n        try:\n            logger.info(\n                f\"Execute by salt fun_for_multi: {target}|{fun}|{arg}|{kwarg}|{timeout}\")\n            if kwarg is None:\n                kwarg = {}\n            cmd_res = self.client.cmd(\n                tgt=target,\n                fun=fun,\n                arg=arg,\n                kwarg=kwarg,\n                tgt_type=tgt_type,\n                timeout=timeout,\n                full_return=True\n            )\n            logger.info(f\"Execute by salt fun_for_multi res: {cmd_res}!\")\n            return cmd_res\n        except Exception as e:\n            logger.error(\n                f\"Execute by salt fun_for_multi with Exception: {traceback.format_exc()}\")\n            return False, f\"执行{str(fun)}过程中出现错误: {str(e)}\"\n\n    def fun(self, target, fun, arg=(), kwarg=None, timeout=None):\n        \"\"\"\n        可自行执行模块的命令，适用于单个主机的部分模块\n        :param target: 目标主机\n        :param fun: 执行模块，如cmd.run\n        :param arg: 位置参数信息\n        :param kwarg: 关键字参数信息\n        :param timeout: 超时时间\n        :return: 返回的执行结果\n        \"\"\"\n        try:\n            logger.info(\n                f\"Execute by salt fun: {target}|{fun}|{arg}|{kwarg}|{timeout}\")\n            if kwarg is None:\n                kwarg = {}\n            cmd_res = self.client.cmd(\n                tgt=target,\n                fun=fun,\n                arg=arg,\n                kwarg=kwarg,\n                timeout=timeout,\n                full_return=True\n            )\n            logger.info(f\"Execute by salt fun res: {cmd_res}!\")\n            if not isinstance(cmd_res, dict):\n                return False, SALT_ERROR_MSG\n            if target not in cmd_res:\n                return False, AGENT_OFFLINE_MSG\n            if target in cmd_res and cmd_res[target] is False:\n                return False, \"当前主机agent状态异常!\"\n            if 'retcode' not in cmd_res[target]:\n                return False, f\"当前执行未出现预期结果，详情如下: {cmd_res[target]}\"\n            if cmd_res[target][\"retcode\"] != 0:\n                return False, cmd_res[target][\"ret\"]\n            return True, cmd_res[target][\"ret\"]\n        except Exception as e:\n            logger.error(\n                f\"Execute by salt fun_for_multi with Exception: {traceback.format_exc()}\")\n            return False, f\"执行{str(fun)}过程中出现错误: {str(e)}\"\n\n    def cmd(self, target, command, timeout, real_timeout=None):\n        \"\"\"\n        执行shell命令接口\n        :param target: 目标agent的id，一般为ip\n        :param command: 将要执行的shell命令\n        :param timeout: salt连接超时时间\n        :param real_timeout: cmd命令执行超时时间\n        :return: 命令执行结果\n        \"\"\"\n        try:\n            logger.info(\n                f\"Execute by salt cmd: {target}|{command}|{timeout}\")\n            cmd_res = self.client.cmd(\n                tgt=target,\n                fun=\"cmd.run\",\n                arg=(command,),\n                timeout=timeout,\n                full_return=True,\n                kwarg={\"timeout\": real_timeout}\n            )\n            logger.info(f\"Execute by salt cmd res: {cmd_res}\")\n            if not isinstance(cmd_res, dict):\n                return False, SALT_ERROR_MSG\n            if target not in cmd_res:\n                return False, AGENT_OFFLINE_MSG\n            if cmd_res[target] is False:\n                return False, AGENT_OFFLINE_MSG\n            if 'retcode' not in cmd_res[target]:\n                return False, f\"当前执行未出现预期结果，详情如下: {cmd_res[target]}\"\n            if cmd_res[target][\"retcode\"] != 0:\n                return False, cmd_res[target][\"ret\"]\n            if \"Timed out after\" in cmd_res[target][\"ret\"]:\n                return False, cmd_res[target][\"ret\"]\n            return True, cmd_res[target][\"ret\"]\n        except Exception as e:\n            logger.error(f\"Execute by salt cmd with Exception: {str(e)}\")\n            return False, f\"执行命令的过程中出现错误: {str(e)}\"\n\n    def cp_file(self, target, source_path, target_path, makedirs=True):\n        \"\"\"\n        salt-master发送文件到目标服务器\n        :param target: 目标主机\n        :param source_path: 源文件路径，salt://\n        :param target_path: 目标主机上存放路径\n        :param makedirs: 当文件夹不存在时，是否在目标主机上创建文件夹\n        :return:\n        \"\"\"\n        try:\n            logger.info(\n                f\"Execute by salt cp_file: {target}|{source_path}|{target_path}|{makedirs}\")\n            source_path = \"salt://\" + source_path\n            cmd_res = self.client.cmd(\n                tgt=target,\n                fun=\"cp.get_file\",\n                arg=(source_path, target_path),\n                kwarg={\"makedirs\": makedirs},\n                timeout=60 * 10\n            )\n            logger.info(f\"Execute by salt cp_file res: {cmd_res}\")\n            if not isinstance(cmd_res, dict):\n                return False, SALT_ERROR_MSG\n            if target not in cmd_res:\n                return False, AGENT_OFFLINE_MSG\n            if \"PermissionError\" in cmd_res[target] and \"Permission denied\" in cmd_res[target]:\n                return False, \"当前目标主机上此用户无法在目标路径下创建目录或文件！\"\n            if str(cmd_res[target]).startswith(target_path) or \\\n                    not cmd_res[target]:\n                return True, cmd_res[target]\n            return False, f\"当前出现未知错误: {cmd_res[target]}\"\n        except Exception as e:\n            logger.error(\n                f\"Execute by salt cp_file with Exception: {traceback.format_exc()}\")\n            return False, f\"发送文件过程中出现错误: {str(e)}\"\n\n    def cp_push(self, target, source_path, upload_path):\n        \"\"\"\n        salt-master从目标服务器拉取文件\n        拉取过来的文件存放路径为：/data/omp/data/salt/var/cache/salt/master/minions/10.0.3.24/files\n        :param target: 目标主机\n        :param source_path: 目标主机上源文件路径，/data/backup\n        :param upload_path: 目标主机上文件名\n        :return:\n        \"\"\"\n        try:\n            cmd_res = self.client.cmd(\n                tgt=target,\n                fun=\"cp.push\",\n                arg=(source_path,),\n                kwarg={\"upload_path\": upload_path, \"remove_source\": True},\n                timeout=60 * 10\n            )\n            logger.info(f\"执行拉取文件的接口，获取到的返回结果是: {cmd_res}\")\n            if not isinstance(cmd_res, dict):\n                return False, \"Salt 执行错误，请检查相关配置是否正确！\"\n            if target not in cmd_res:\n                return False, \"当前目标主机不在线或该目标主机未纳管！\"\n            if cmd_res[target] is True:\n                return True, \"success\"\n            return False, f\"当前出现未知错误: {cmd_res[target]}\"\n        except Exception as e:\n            print(traceback.format_exc())\n            logger.error(f\"拉取文件过程中程序出现错误: {traceback.format_exc()}\")\n            return False, f\"拉取文件过程中出现错误: {str(e)}\"\n"
  },
  {
    "path": "omp_server/utils/plugin/send_email.py",
    "content": "import logging\nimport threading\nimport traceback\n\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.core.mail.backends.smtp import EmailBackend\n\nfrom db_models.models import EmailSMTPSetting\n\nlogger = logging.getLogger(\"server\")\n\n\nclass ResultThread(threading.Thread):\n    \"\"\"\n    需要运行结果的多线程\n    \"\"\"\n\n    def __init__(self, target, args, name=''):\n        threading.Thread.__init__(self)\n        self.target = target\n        self.name = name\n        self.args = args\n\n    def run(self):\n        self.result = self.target(*self.args)\n\n    def get_result(self):\n        return self.result\n\n\nclass ModelSettingEmailBackend:\n    # 邮件 SMTP 服务器链接backend(修改默认的settings)\n\n    @property\n    def load_settings(self):\n        if hasattr(self, \"setting_kwargs\"):\n            return self.setting_kwargs\n        setting_obj = EmailSMTPSetting.objects.first()\n        setting_kwargs = {}\n        if setting_obj:\n            setting_kwargs = setting_obj.get_dict()\n        setattr(self, \"setting_kwargs\", setting_obj.get_dict())\n        return setting_kwargs\n\n    def load_connection(self):\n        setting_kwargs = self.load_settings\n        if not setting_kwargs:\n            return None\n        return EmailBackend(**setting_kwargs)\n\n\nclass SendEmailContent:\n    # 邮件内容基类\n    subject = \"\"  # 邮件主题\n    from_user = \"运维管理平台<{}>\"  # 发件人\n    is_html = False  # 是否是html邮件\n    file = False  # 存在的文件使用\n    file_content = False  # 临时文件使用\n\n    def __init__(self, _obj):\n        self._obj = _obj\n\n    @property\n    def fetch_content(self):\n        # 获取邮件内容\n        if hasattr(self, \"email_content\"):\n            return self.email_content\n        email_content = self._obj.send_email_content()\n        setattr(self, \"email_content\", email_content)\n        return email_content\n\n    def fetch_html_content(self):\n        # 获取邮件html内容\n        pass\n\n    def fetch_file_kwargs(self):\n        # 文件\n        if hasattr(self, \"file_kwargs\"):\n            return self.file_kwargs\n        file_kwargs = self._obj.fetch_file_kwargs()\n        setattr(self, \"file_kwargs\", file_kwargs)\n        return file_kwargs\n\n    def fetch_file_content(self):\n        if hasattr(self, \"file_content\"):\n            return self.file_content\n        file_content = self._obj.fetch_file_content()\n        setattr(self, \"file_content\", file_content)\n        return file_content\n\n\nclass SendAlertEmailContent(SendEmailContent):\n    subject = \"运维管理平台警消息通知\"\n\n\nclass SendBackupHistoryEmailContent(SendEmailContent):\n    subject = \"运维管理平台备份通知邮件\"\n    file = True\n\n\nclass SendDeepInspectionEmailContent(SendBackupHistoryEmailContent):\n    subject = \"运维管理平台深度巡检报告通知邮件\"\n    file = True\n\n\nclass SendNormalInspectionEmailContent(SendBackupHistoryEmailContent):\n    subject = \"运维管理平台主机、组件巡检报告通知邮件\"\n    file = True\n\n\nclass EmailSendTool(object):\n    # 邮件发送\n\n    def __init__(self, con_backend, content, users):\n        from_user = content.from_user.format(\n            con_backend.load_settings.get(\"username\"))\n        self.email_msg = EmailMultiAlternatives(\n            content.subject,\n            content.fetch_content,\n            from_user,\n            users,\n            connection=con_backend.load_connection()\n        )\n        self.content = content\n        self.need_send_count = len(users)\n\n    def send(self):\n        \"\"\"\n        发送邮件\n        :return:\n        \"\"\"\n        try:\n            if self.content.is_html:\n                self.email_msg.attach_alternative(\n                    self.content.fetch_html_content, \"text/html\")\n            if self.content.file:\n                self.email_msg.attach_file(\n                    **self.content.fetch_file_kwargs()\n                )\n            elif self.content.file_content:\n                self.email_msg.attach(\n                    **self.content.fetch_file_content()\n                )\n        except Exception as e:\n            logger.error(f\"发送邮件失败，失败原因： {str(e)}，详情为：{traceback.format_exc()}\")\n        try:\n            count = self.email_msg.send()\n        except Exception as e:\n            logger.error(f\"发送邮件失败，失败原因：{str(e)}\")\n            return False\n        if self.need_send_count - count:\n            return False\n        return True\n\n\ndef many_send(connection, content, users):\n    \"\"\"\n    多个并发同时发送，用以区分失败人\n    :param connection: 邮件服务器配置对象：ModelSettingEmailBackend\n    :param content:  邮件内容对象：SendEmailContent\n    :param users: 用户邮箱list、set\n    :return:\n    \"\"\"\n\n    threading_list = []\n    for user in users:\n        send_tool = EmailSendTool(connection, content, (user,))\n        _thread = ResultThread(\n            target=send_tool.send,\n            args=())\n        threading_list.append(_thread)\n    threading_list_result = []\n    [thread_obj.start() for thread_obj in threading_list]\n\n    for thread_obj in threading_list:\n        thread_obj.join()\n        threading_list_result.append(thread_obj.get_result())\n    fail_user = []\n    for user, result in zip(users, threading_list_result):\n        if not result:\n            fail_user.append(user)\n    return fail_user\n"
  },
  {
    "path": "omp_server/utils/plugin/ssh.py",
    "content": "\"\"\"\nssh相关操作\n\"\"\"\nimport os\nimport logging\n\nimport paramiko\nfrom scp import SCPClient\n\nfrom utils.parse_config import (\n    SSH_CMD_TIMEOUT, SSH_CHECK_TIMEOUT\n)\n\nlogger = logging.getLogger(\"server\")\n\n\nclass SSH(object):\n    \"\"\" SSH 工具类 \"\"\"\n\n    def __init__(self, hostname, port, username, password, timeout=SSH_CHECK_TIMEOUT):\n        \"\"\"\n        初始化ssh\n        :param hostname: 主机名或ip地址\n        :param port: 端口\n        :param username: 用户名\n        :param password: 密码\n        :param timeout: 超时时间\n        \"\"\"\n        logger.info(\n            f\"SSH init with params: {hostname}; {port}; {username}; {timeout}\")\n        self.hostname = hostname\n        self.port = port\n        self.username = username\n        self.password = password\n        self.timeout = timeout\n        # 连接对象\n        self.connect = None\n        self.ssh_client = None\n        self.scp_client = None\n        # 错误信息\n        self.is_error = None\n        self.error_message = None\n\n    def _get_connection(self):\n        \"\"\" 获取连接对象 \"\"\"\n        if not self.connect:\n            try:\n                ssh_client = paramiko.SSHClient()\n                ssh_client.set_missing_host_key_policy(\n                    paramiko.AutoAddPolicy())\n                ssh_client.connect(\n                    hostname=self.hostname,\n                    port=self.port,\n                    username=self.username,\n                    password=self.password,\n                    timeout=self.timeout\n                )\n                scp_client = SCPClient(ssh_client.get_transport())\n            except Exception as error:\n                self.is_error = True\n                self.error_message = error\n                self.close()\n                return\n            self.ssh_client = ssh_client\n            self.scp_client = scp_client\n\n    def check(self):\n        \"\"\"\n        检查SSH连接信息\n        :return: is_connect, message\n        \"\"\"\n        self._get_connection()\n        if self.is_error:\n            return False, str(self.error_message)\n        _, stdout, _ = self.ssh_client.exec_command(\"whoami\")\n        who = stdout.readline().strip()\n        if who == self.username:\n            return True, \"check passed\"\n        return False, f\"stdout: {who}\"\n\n    def is_sudo(self):\n        \"\"\"\n        检查用户是否具有sudo权限\n        :return: is_sudo, message\n        \"\"\"\n        self._get_connection()\n        if self.is_error:\n            return False, str(self.error_message)\n        _, stdout, _ = self.ssh_client.exec_command(\n            \"sudo -n echo 'success'\", get_pty=True)\n        res = stdout.readline().strip()\n        if res == \"success\":\n            return True, \"is sudo\"\n        return False, \"not sudo\"\n\n    def cmd(self, command, timeout=SSH_CMD_TIMEOUT, get_pty=True):\n        \"\"\"\n        执行shell命令\n        :param command:\n        :param timeout:\n        :param get_pty:\n        :return:\n        \"\"\"\n        self._get_connection()\n        if self.is_error:\n            return False, str(self.error_message)\n        _, stdout, stderr = self.ssh_client.exec_command(\n            command, get_pty=get_pty, timeout=timeout)\n        stdout.channel.recv_exit_status()\n        res_stdout = stdout.readlines()\n        res_stderr = stderr.readlines()\n        if len(res_stderr) != 0:\n            return False, res_stderr[0].strip() + \" \" + str(stdout)\n        return True, \"\\n\".join(res_stdout)\n\n    def make_remote_path_exist(self, remote_path):\n        \"\"\"\n        mkdir -p remote_path\n        :param remote_path: 远程文件夹路径\n        :type remote_path str\n        :return:\n        \"\"\"\n        self._get_connection()\n        if self.username == \"root\":\n            command = \"test -d {0} || mkdir -p {0}\".format(remote_path)\n        else:\n            is_sudo_flag, _ = self.is_sudo()\n            if is_sudo_flag:\n                command = f\"sudo mkdir -p {remote_path} && \" \\\n                          f\"sudo chown -R {self.username}.{self.username} {remote_path}\"\n            else:\n                # 普通用户仅尝试创建文件夹\n                command = \"test -d {0} || mkdir -p {0}\".format(remote_path)\n        self.cmd(command)\n\n    def file_push(self, file, remote_path=\"/tmp\"):\n        \"\"\"\n        push file to remote directory use scp\n\n        :param str file: file path\n        :param str remote_path: remote directory path\n        \"\"\"\n        file_name = os.path.basename(file)\n        remote_file_full_path = os.path.join(remote_path, file_name)\n        self._get_connection()\n        if self.is_error:\n            return False, str(self.error_message)\n        try:\n            self.make_remote_path_exist(remote_path)\n            self.scp_client.put(file, recursive=True,\n                                remote_path=remote_file_full_path)\n        except Exception as error:\n            import traceback\n            logger.error(traceback.format_exc())\n            self.close()\n            return False, str(error)\n        return True, \"push success: {}\".format(remote_file_full_path)\n\n    def close(self):\n        \"\"\" 关闭连接对象 \"\"\"\n        if self.ssh_client:\n            self.ssh_client.close()\n        if self.scp_client:\n            self.scp_client.close()\n"
  },
  {
    "path": "omp_server/utils/plugin/synch_grafana.py",
    "content": "import json\nimport time\nimport traceback\n\nimport requests\n\nfrom db_models.models import MonitorUrl, GrafanaMainPage\nfrom utils.parse_config import GRAFANA_AUTH\n\n\ndef make_request(url, headers, payload):\n    \"\"\"\n    请求\n    :param url:\n    :param headers:\n    :param payload:\n    :return:\n    \"\"\"\n    flag = 0\n    auth = (GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"username\", \"admin\"),\n            GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"plaintext_password\", \"Yunweiguanli@OMP_123\"))\n    while flag < 5:\n        response = requests.get(url=url, headers=headers,\n                                data=payload, auth=auth)\n        r = json.loads(response.text)\n        for url in r:\n            if not isinstance(url, dict):\n                break\n        else:\n            return True, r\n        flag += 1\n        time.sleep(30)\n    return False, None\n\n\ndef synch_grafana_info():\n    \"\"\"如果存在则不再添加,修改会追加一条数据\"\"\"\n\n    monitor_ip = MonitorUrl.objects.filter(name=\"grafana\")\n    monitor_url = monitor_ip[0].monitor_url if len(\n        monitor_ip) else \"127.0.0.1:19014\"\n\n    url = \"http://{0}/proxy/v1/grafana/api/search?\" \\\n          \"query=&starred=false&skipRecent=false&skipStarred=false&\" \\\n          \"folderIds=0&layout=folders\".format(monitor_url)\n    payload = {}\n    headers = {'Content-Type': 'application/json'}\n    try_times = 0\n    while try_times <= 3:\n        try:\n            try_times += 1\n            # print(f\"start request to: {url}\")\n            flag, r = make_request(url=url, headers=headers, payload=payload)\n            if not flag:\n                return\n            break\n        except requests.exceptions.MissingSchema as e:\n            print(f\"grafana error: {str(e)}, try again after 10s!\")\n        except Exception as e:\n            print(e)\n            print(traceback.format_exc())\n            return\n    else:\n        return\n    url_type = {\"service\": \"fu\", \"node\": \"zhu\", \"log\": \"applogs\"}\n    url_dict = {}\n    for url in r:\n        url_name = url.get(\"uri\").rsplit(\"/\", 1)[1].split(\"-\", 1)[0]\n        url_dict[url_name.lower()] = url.get(\"url\")\n\n    for key, value in url_type.items():\n        url_dict.update({key: url_dict.pop(value)})\n\n    if GrafanaMainPage.objects.all().count() != len(url_dict):\n        dbname = [i.instance_name for i in GrafanaMainPage.objects.all()]\n        difference = list(set(url_dict.keys()).difference(set(dbname)))\n        grafana_obj = [GrafanaMainPage(\n            instance_name=i, instance_url=url_dict.get(i)) for i in difference]\n        GrafanaMainPage.objects.bulk_create(grafana_obj)\n"
  },
  {
    "path": "omp_server/utils/prometheus/__init__.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/14 4:49 下午\n# Description:\n"
  },
  {
    "path": "omp_server/utils/prometheus/create_html_tar.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/26 9:45 上午\n# Description:\nimport os\nimport json\nimport subprocess\nfrom django.conf import settings\n\n\ndef cmd(command):\n    \"\"\"执行本地shell命令\"\"\"\n    p = subprocess.Popen(\n        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n    stdout, stderr = p.communicate()\n    _out, _err, _code = \\\n        stdout.decode(\"utf8\"), stderr.decode(\"utf8\"), p.returncode\n    return _out, _err, _code\n\n\ndef create_html_tar(new_html_dir_name, report_data):\n    \"\"\"\n    生成巡检报告tar.gz包\n    :param new_html_dir_name: 包名\n    :param report_data: 报告数据\n    :return:\n    \"\"\"\n    # 拷贝目录\n    inspection_file = os.path.join(\n        settings.PROJECT_DIR, f\"data/inspection_file/{new_html_dir_name}\")\n    old_file_dir = os.path.join(\n        settings.PROJECT_DIR, \"package_hub/template/inspection_html\")\n    _out, _err, _code = cmd(f\"cp -r {old_file_dir} {inspection_file}\")\n    if _code:\n        return False, \"拷贝文件失败！\"\n    # 修改源数据\n    js_path = os.path.join(inspection_file, \"static/js/main.e4ade54a.chunk.js\")\n    with open(js_path, \"r\") as f:\n        content = f.read()\n    content = content.replace(\"'!@#$'\", json.dumps(report_data))\n    with open(js_path, \"w\") as f:\n        f.write(content)\n    # 打包\n    inspection_dir = os.path.join(settings.PROJECT_DIR, f\"data/inspection_file\")\n    tar_file = f\"{new_html_dir_name}.tar.gz\"\n    _out, _err, _code = cmd(\n        f\"cd {inspection_dir} && tar -cvf {tar_file} {new_html_dir_name}\")\n    # 删除源文件\n    cmd(f\"rm -rf {inspection_file}\")\n    if _code:\n        return False, \"打包巡检报告失败\"\n    return True, tar_file\n"
  },
  {
    "path": "omp_server/utils/prometheus/prometheus.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/14 4:01 下午\n# Description:\nimport json\nimport logging\nimport requests\nfrom datetime import datetime\nfrom db_models.models import MonitorUrl\nfrom db_models.models import InspectionHistory, InspectionReport\nfrom utils.parse_config import PROMETHEUS_AUTH\n\n\nlogger = logging.getLogger(\"server\")\n\n\nclass Prometheus(object):\n    \"\"\"\n    prometheus\n    执行prosql查询数据、查询alerts\n    \"\"\"\n\n    def __init__(self):\n        # prometheus 的 ip:port\n        self.address = MonitorUrl.objects.get(name='prometheus').monitor_url\n        self.basic_auth = (PROMETHEUS_AUTH.get(\n            \"username\", \"omp\"), PROMETHEUS_AUTH.get(\"plaintext_password\", \"\"))\n\n    def query(self, expr):\n        \"\"\"\n        请求prometheus开放接口，执行prosql，查询数据\n        :para expr: 需要执行的sql\n        :return: 查询到的实时数据\n        \"\"\"\n        url = f\"http://{self.address}/api/v1/query?query={expr}\"\n        try:\n            rsp = json.loads(requests.get(url=url, timeout=0.5, auth=self.basic_auth\n                                          ).content.decode('utf8', 'ignore'))\n            if rsp.get('status') == 'success':\n                return True, rsp.get('data')\n            else:\n                return False, {}\n        except Exception as e:\n            logger.error(f\"Query query from prometheus error: {str(e)}\")\n            return False, {}\n\n    @staticmethod\n    def clean_alert(alerts):\n        \"\"\"\n        清洗告警，去掉同类不同级别告警\n        :param alerts:\n        :return:\n        \"\"\"\n        unique_alert_dic = dict()\n        clean_alerts = list()\n        for item in alerts:\n            _key = item.get(\"labels\", {}).get(\"alertname\", \"\") + \\\n                item.get(\"labels\", {}).get(\"instance\", \"\")\n            _level = item.get(\"labels\", {}).get(\"severity\", \"\")\n            if _key not in unique_alert_dic:\n                unique_alert_dic[_key] = {\n                    \"warning\": \"\",\n                    \"critical\": \"\"\n                }\n            unique_alert_dic[_key][_level] = item\n        for key, value in unique_alert_dic.items():\n            if value.get(\"critical\"):\n                clean_alerts.append(value.get(\"critical\"))\n            else:\n                clean_alerts.append(value.get(\"warning\"))\n        return clean_alerts\n\n    @staticmethod\n    def unified_job(is_success, ret):\n        \"\"\"\n        实例方法 返回值统一处理\n        :ret: 返回值\n        :is_success: 请求是否成功\n        \"\"\"\n        if is_success:\n            if ret.get('result'):\n                return ret['result'][0].get('value')[1]\n            else:\n                return 0\n        else:\n            return 0\n\n    def query_alerts(self):\n        url = f'http://{self.address}/api/v1/alerts'\n        try:\n            rsp = json.loads(requests.get(url=url, timeout=0.5, auth=self.basic_auth\n                                          ).content.decode('utf8', 'ignore'))\n            if rsp.get('status') == 'success':\n                # 处理重复级别告警问题 jon.liu\n                alerts = rsp.get('data').get('alerts')\n                return self.clean_alert(alerts)\n            else:\n                return {}\n        except Exception as e:\n            logger.error(f\"Query Alerts from prometheus error: {str(e)}\")\n            return {}\n\n\ndef back_fill(history_id, report_id, host_data=None, serv_data=None,\n              serv_plan=None, risk_data=None, scan_info=None, scan_result=None,\n              file_name=None):\n    \"\"\"\n    异步反填报告数据\n    :history_id : 巡检历史记录id\n    :report_id : 巡检报告id\n    :host_data : 主机巡检数据\n    :serv_data : 组件巡检数据\n    :serv_plan : 服务\n    :risk_data : 报警数据\n    :scan_info : 扫描统计\n    :scan_result : 分析结果\n    :file_name : 导出文件名\n    \"\"\"\n    # 反填巡检历史记录InspectionHistory表，结束时间end_time、巡检用时duration字段、巡检状态inspection_status\n    now = datetime.now()\n    his_obj = InspectionHistory.objects.filter(id=history_id)\n    duration = (now - his_obj[0].start_time).seconds\n    duration = duration if duration > 0 else 1\n    his_obj.update(end_time=now, duration=duration, inspection_status=2)\n\n    # 反填巡检报告InspectionReport表\n    rep_obj = InspectionReport.objects.filter(id=report_id)\n    if host_data:\n        # 反填巡检报告InspectionReport表，主机列表host_data字段\n        rep_obj.update(host_data=host_data)\n    if serv_data:\n        # 反填巡检报告InspectionReport表，服务列表serv_data字段\n        rep_obj.update(serv_data=serv_data)\n    if serv_plan:\n        # 反填巡检报告InspectionReport表，服务列表serv_plan字段\n        rep_obj.update(serv_plan=serv_plan)\n    if risk_data:\n        # 反填巡检报告InspectionReport表，服务列表risk_data字段\n        rep_obj.update(risk_data=risk_data)\n\n    # 反填巡检报告InspectionReport表scan_info、scan_result\n    rep_obj.update(\n        scan_info=scan_info, scan_result=scan_result, file_name=file_name)\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_host.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/14 6:26 下午\n# Description: 主机指标\nimport json\nimport logging\nimport random\nfrom datetime import datetime\nfrom db_models.models import Host\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\nfrom utils.prometheus.utils import get_host_data_folder\n\nlogger = logging.getLogger(\"server\")\n\n\ndef target_host_thread(env, instance):\n    \"\"\"\n    主机巡检，多线程执行\n    :env: 环境 queryset 对象\n    :instance: 主机ip地址\n    \"\"\"\n    temp = dict()\n    # 主机 prometheus 数据请求\n    h_w_obj = HostCrawl(env=env.name, instance=instance)\n    h_w_obj.run()\n    _p = h_w_obj.ret\n    temp['id'] = random.randint(1, 99999999)\n    temp['mem_usage'] = _p.get('mem_usage')\n    temp['cpu_usage'] = _p.get('cpu_usage')\n    temp['disk_usage_root'] = _p.get('disk_usage_root')\n    temp['disk_usage_data'] = _p.get('disk_usage_data')\n    temp['sys_load'] = _p.get('sys_load')\n    temp['run_time'] = _p.get('run_time')\n    temp['host_ip'] = instance\n    temp['memory_top'] = _p.get('memory_top', [])\n    temp['cpu_top'] = _p.get('cpu_top', [])\n    temp['kernel_parameters'] = _p.get('kernel_parameters', [])\n    # 操作系统\n    _h = Host.objects.filter(ip=instance).first()\n    temp['release_version'] = _h.operate_system if _h else ''\n    # 配置信息\n    host_massage = \\\n        f\"{_h.cpu if _h else '-'}C|{_h.memory if _h else '-'}G|\" \\\n        f\"{sum(_h.disk.values()) if _h and _h.disk else '-'}G\"\n    temp['host_massage'] = host_massage\n    temp['basic'] = [\n        {\"name\": \"IP\", \"name_cn\": \"主机IP\", \"value\": instance},\n        {\"name\": \"hostname\", \"name_cn\": \"主机名\",\n         \"value\": _h.host_name if _h else '-'},\n        {\"name\": \"kernel_version\", \"name_cn\": \"内核版本\",\n         \"value\": _p.get('kernel_version')},\n        {\"name\": \"selinux\", \"name_cn\": \"SElinux 状态\",\n         \"value\": _p.get('selinux')},\n        {\"name\": \"max_openfile\", \"name_cn\": \"最大打开文件数\",\n         \"value\": _p.get('max_openfile')},\n        {\"name\": \"iowait\", \"name_cn\": \"IOWait\", \"value\": _p.get('iowait')},\n        {\"name\": \"inode_usage\", \"name_cn\": \"inode 使用率\",\n         \"value\": {\"/\": _p.get('inode_usage')}},\n        {\"name\": \"now_time\", \"name_cn\": \"当前时间\",\n         \"value\": datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")},\n        {\"name\": \"run_process\", \"name_cn\": \"进程数\",\n         \"value\": _p.get('run_process')},\n        {\"name\": \"umask\", \"name_cn\": \"umask\", \"value\": _p.get('umask')},\n        {\"name\": \"bandwidth\", \"name_cn\": \"带宽\", \"value\": _p.get('bandwidth')},\n        {\"name\": \"throughput\", \"name_cn\": \"IO\", \"value\": _p.get('throughput')},\n        {\"name\": \"zombies_process\", \"name_cn\": \"僵尸进程\",\n         \"value\": _p.get('zombies_process')}\n    ]\n    return temp\n\n\nclass HostCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus 主机指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        Prometheus.__init__(self)\n\n    @staticmethod\n    def unified_job(is_success, ret):\n        \"\"\"\n        实例方法 返回值统一处理\n        :ret: 返回值\n        :is_success: 请求是否成功\n        \"\"\"\n        if is_success:\n            if ret.get('result'):\n                return ret['result'][0].get('value')[1]\n            else:\n                return 0\n        else:\n            return 0\n\n    def run_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"round(up{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"job='nodeExporter'}})\"\n        self.ret['run_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"运行时间\"\"\"\n        expr = f\"avg(time() - node_boot_time_seconds{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}})\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"总cpu使用率\"\"\"\n        expr = f\"100 - (avg(rate(node_cpu_seconds_total\" \\\n               f\"{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"mode='idle'}}[5m])) * 100)\"\n        self.ret['cpu_usage'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}%\"\n\n    def iowait(self):\n        \"\"\"IO wait使用率\"\"\"\n        expr = f\"avg(rate(node_cpu_seconds_total{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',mode='iowait'}}[5m])) * 100\"\n        self.ret['iowait'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}%\"\n\n    def mem_usage(self):\n        \"\"\"内存使用率\"\"\"\n        expr = f\"(1 - (node_memory_MemAvailable_bytes\" \\\n               f\"{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}} / \" \\\n               f\"(node_memory_MemTotal_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}})))* 100\"\n        self.ret['mem_usage'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}%\"\n\n    def disk_usage_root(self):\n        \"\"\"根分区使用率\"\"\"\n        expr = f\"(node_filesystem_size_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"fstype=~'ext.*|xfs',mountpoint='/'}} - \" \\\n               f\"node_filesystem_avail_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"fstype=~'ext.*|xfs',mountpoint='/'}}) / \" \\\n               f\"node_filesystem_size_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"fstype=~'ext.*|xfs',mountpoint='/'}} * 100\"\n        self.ret['disk_usage_root'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}%\"\n\n    def disk_usage_data(self):\n        \"\"\"数据分区使用率\"\"\"\n        # 数据分区应该由主机表中的data_folder目录决定\n        # 并协同disk信息判断出数据分区挂载点是哪个\n        _data_path = get_host_data_folder(self.instance)\n        if not _data_path:\n            return \"_\"\n        expr = f\"(1-(node_filesystem_free_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',mountpoint='{_data_path}', \" \\\n               f\"fstype=~'ext.*|xfs'}}\" \\\n               f\" / node_filesystem_size_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',mountpoint='{_data_path}', \" \\\n               f\"fstype=~'ext.*|xfs'}}))\" \\\n               f\" * 100\"\n        _ = self.unified_job(*self.query(expr))\n        self.ret['disk_usage_data'] = f\"{round(float(_), 2)}%\" if _ else '_'\n\n    def rate_exchange_disk(self):\n        \"\"\"交换分区使用率\"\"\"\n        expr = f\"(1 - (node_memory_SwapFree_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}} / \" \\\n               f\"node_memory_SwapTotal_bytes{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}})) * 100\"\n        self.ret['rate_exchange_disk'] = self.unified_job(*self.query(expr))\n\n    def rate_cpu_io_wait(self):\n        \"\"\"cpu io wait\"\"\"\n        expr = f\"avg(rate(node_cpu_seconds_total\" \\\n               f\"{{instance=~'{self.instance}',mode='iowait'}}[5m])) * 100\"\n        self.ret['rate_cpu_io_wait'] = self.unified_job(*self.query(expr))\n\n    def inode_usage(self):\n        \"\"\"inode使用率\"\"\"\n        expr = f\"(1-node_filesystem_files_free{{fstype=~'xfs|ext4',\" \\\n               f\"mountpoint='/', env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}} / \" \\\n               f\"node_filesystem_files{{fstype=~'xfs|ext4',mountpoint='/', \" \\\n               f\"env='{self.env}',instance=~'{self.instance}'}})*100\"\n        self.ret['inode_usage'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}%\"\n\n    def max_openfile(self):\n        \"\"\"总文件描述符\"\"\"\n        expr = f\"avg(node_filefd_maximum{{instance=~'{self.instance}'}})\"\n        self.ret['max_openfile'] = self.unified_job(*self.query(expr))\n\n    def sys_load(self):\n        \"\"\"系统平均负载\"\"\"\n        # 1分钟负载\n        expr = f\"node_load1{{env='{self.env}',instance=~'{self.instance}'}}\"\n        load1 = self.unified_job(*self.query(expr))\n        # 5分钟负载\n        expr = f\"node_load5{{env='{self.env}',instance=~'{self.instance}'}}\"\n        load5 = self.unified_job(*self.query(expr))\n        # 15分钟负载\n        expr = f\"node_load15{{env='{self.env}',instance=~'{self.instance}'}}\"\n        load15 = self.unified_job(*self.query(expr))\n        self.ret['sys_load'] = f\"{load1},{load5},{load15}\"\n\n    def bandwidth(self):\n        \"\"\"网络带宽使用\"\"\"\n        # eth0 下载\n        expr = f\"sum(rate(node_network_receive_bytes_total{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',device=~'eth0'}}[2m])*8/1024)\"\n        self.ret['bandwidth'] = {\n            'receive': f\"{round(float(self.unified_job(*self.query(expr))), 2)}\"\n                       f\"kb/s\"}\n        # eth0 上传\n        expr = f\"sum(rate(node_network_transmit_bytes_total\" \\\n               f\"{{env='{self.env}',instance=~'{self.instance}',\" \\\n               f\"device=~'eth0'}}[2m])*8/1024)\"\n        self.ret['bandwidth']['transmit'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}kb/s\"\n\n    def throughput(self):\n        \"\"\"磁盘io\"\"\"\n        # 读\n        expr = f\"sum(rate(node_disk_read_bytes_total{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}[2m])) / 1024\"\n        self.ret['throughput'] = \\\n            {'read': f\"{round(float(self.unified_job(*self.query(expr))), 2)}\"\n                     f\"kb/s\"}\n        # 写\n        expr = f\"sum(rate(node_disk_written_bytes_total\" \\\n               f\"{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}[2m])) / 1024\"\n        self.ret['throughput']['write'] = \\\n            f\"{round(float(self.unified_job(*self.query(expr))), 2)}kb/s\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"host_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception as e:\n            logger.error(f\"Salt host_check.main failed with error: {str(e)}\")\n            ret = {}\n\n        self.ret['memory_top'] = ret.get('memory_top', [])\n        self.ret['cpu_top'] = ret.get('cpu_top', [])\n        self.ret['kernel_parameters'] = ret.get('kernel_parameters', [])\n        self.ret['kernel_version'] = ret.get('kernel_version')\n        self.ret['selinux'] = ret.get('selinux')\n        self.ret['run_process'] = ret.get('run_process')\n        self.ret['umask'] = ret.get('umask')\n        self.ret['zombies_process'] = ret.get('zombies_process')\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        # target为实例方法，目的是统一执行实例方法并统一返回值\n        target = ['mem_usage', 'cpu_usage', 'disk_usage_root',\n                  'disk_usage_data', 'sys_load', 'run_time', 'max_openfile',\n                  'inode_usage', 'iowait', 'bandwidth', 'throughput',\n                  'salt_json', 'run_status']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n\n\nif __name__ == '__main__':\n    h = HostCrawl(env='default', instance='10.0.14.224')\n    h.run()\n    print(h.ret)\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/22 10:04 下午\n# Description:\nimport json\nimport random\nimport logging\nimport traceback\n\nfrom db_models.models import Service\nfrom utils.prometheus.thread import MyThread\nfrom utils.prometheus.target_service_jvm_base import ServiceBase\nfrom utils.prometheus.target_service_arangodb import ServiceArangodbCrawl\nfrom utils.prometheus.target_service_beanstalk import ServiceBeanstalkCrawl\nfrom utils.prometheus.target_service_clickhouse import ServiceClickhouseCrawl\nfrom utils.prometheus.target_service_elasticsearch import ServiceElasticsearchCrawl\nfrom utils.prometheus.target_service_flink import ServiceFlinkCrawl\nfrom utils.prometheus.target_service_gotty import ServiceGottyCrawl\nfrom utils.prometheus.target_service_grafana import ServiceGrafanaCrawl\nfrom utils.prometheus.target_service_hadoop import ServiceHadoopCrawl\nfrom utils.prometheus.target_service_httpd import ServiceHttpdCrawl\nfrom utils.prometheus.target_service_ignite import ServiceIgniteCrawl\nfrom utils.prometheus.target_service_ntpd import ServiceNtpdCrawl\nfrom utils.prometheus.target_service_postgresql import ServicePostgresqlCrawl\nfrom utils.prometheus.target_service_prometheus import ServicePrometheusCrawl\nfrom utils.prometheus.target_service_tengine import ServiceTengineCrawl\nfrom utils.prometheus.target_service_mysql import ServiceMysqlCrawl\nfrom utils.prometheus.target_service_redis import ServiceRedisCrawl\nfrom utils.prometheus.target_service_kafka import ServiceKafkaCrawl\nfrom utils.prometheus.target_service_nacos import ServiceNacosCrawl\nfrom utils.prometheus.target_service_rocketmq import ServiceRocketmqCrawl\nfrom utils.prometheus.target_service_zookeeper import ServiceZookeeperCrawl\n\nlogger = logging.getLogger(\"server\")\n\n\nopen_source_class_dict = {\n    \"arangodb\": ServiceArangodbCrawl,\n    \"beanstalk\": ServiceBeanstalkCrawl,\n    \"clickhouse\": ServiceClickhouseCrawl,\n    \"elasticsearch\": ServiceElasticsearchCrawl,\n    \"flink\": ServiceFlinkCrawl,\n    \"gotty\": ServiceGottyCrawl,\n    \"grafana\": ServiceGrafanaCrawl,\n    \"hadoop\": ServiceHadoopCrawl,\n    \"httpd\": ServiceHttpdCrawl,\n    \"ignite\": ServiceIgniteCrawl,\n    \"kafka\": ServiceKafkaCrawl,\n    \"mysql\": ServiceMysqlCrawl,\n    \"nacos\": ServiceNacosCrawl,\n    \"ntpd\": ServiceNtpdCrawl,\n    \"postgreSql\": ServicePostgresqlCrawl,\n    \"prometheus\": ServicePrometheusCrawl,\n    \"redis\": ServiceRedisCrawl,\n    \"rocketmq\": ServiceRocketmqCrawl,\n    \"tengine\": ServiceTengineCrawl,\n    \"zookeeper\": ServiceZookeeperCrawl\n}\n\n\ndef get_port_and_status(i):\n    \"\"\"\n    组建巡检：统一获取每个服务的端口信息及服务状态\n    从target_service_run方法拆出来一段\n    \"\"\"\n    # 组装端口\n    service_port, service_ports = '', []\n    ports = json.loads(i.get('service_port')) if i.get('service_port') else []\n    for p in ports:\n        service_ports.append(p.get('default'))\n        if p.get('key') == 'service_port':\n            service_port = p.get('default')\n\n    # 组装服务状态\n    serv_status = {0: \"正常\", 1: \"启动中\", 2: \"停止中\", 3: \"重启中\", 4: \"停止\"}\n    service_status = serv_status.get(i.get('service_status'))\n    return [service_port, list(set(service_ports)), service_status]\n\n\ndef _joint(i, ret, basics, service_port, service_ports, service_status):\n    \"\"\"\n     组件巡检 统一数据组装\n    \"desc: i\" server表基础信息\n    \"desc: ret\" 各服务基础数据,字典类型\n    \"desc: basic\" 各服务拓展数据,列表类型\n    \"desc: service_port\" 服务端口\n    \"desc: service_status\" 服务状态\n    \"desc: service_ports\" 服务全部的端口\n    \"\"\"\n    basic = [\n        {\"name\": \"port_status\", \"name_cn\": \"监听端口\", \"value\": service_ports},\n        {\"name\": \"IP\", \"name_cn\": \"IP地址\", \"value\": i.get('ip')},\n    ] + basics\n    return {\n        'id': random.randint(1, 99999999),\n        'host_ip': i.get('ip'),\n        'cluster_name': \"-\",\n        \"cpu_usage\": ret.get('cpu_usage', '-'),\n        \"log_level\": ret.get('log_level', '-'),\n        \"mem_usage\": ret.get('mem_usage', '-'),\n        \"run_time\": ret.get('run_time', '-'),\n        \"service_name\": i.get('service_instance_name'),\n        \"service_port\": service_port,\n        \"service_status\": service_status,\n        \"service_type\": \"2\",\n        \"basic\": basic\n    }\n\n\ndef target_service_thread(env, i):\n    \"\"\"\n    组件数据采集,把顺序执行的代码拷贝出来-多线程执行\n    :param env: 环境表对象\n    :param i: 服务表基础数据(端口/服务名/app_name...)\n    \"\"\"\n    # 获取每个服务的端口信息及服务状态\n    _port_status = get_port_and_status(i)\n    if i.get('service__app_monitor', {}).get('type') == 'JavaSpringBoot':\n        _ = ServiceBase(env.name, i.get('ip'),\n                        f'{i.get(\"service__app_name\")}Exporter')\n        _.run()\n        tag_total_num = _.metric_num  # 总指标数累加\n        tmp = _joint(i, _.ret, _.basic, *_port_status)\n    # elif i.get('service__app_monitor', {}).get('type') == 'open_source':  # TODO\n    else:\n        try:\n            crawl_class = open_source_class_dict.get(\n                i.get(\"service__app_name\"))\n            if not crawl_class:\n                return [0, _joint(i, {}, [], *_port_status)]\n            _ = crawl_class(env=env.name, instance=i.get('ip'))\n            _.run()\n            tag_total_num = _.metric_num  # 总指标数累加\n            tmp = _joint(i, _.ret, _.basic, *_port_status)\n        except Exception as e:\n            logger.error(f'服务巡检失败，详情为{traceback.format_exc(e)}')\n            tag_total_num = 0  # 总指标数 计算\n            ret, basics = {}, []\n            tmp = _joint(i, ret, basics, *_port_status)\n    # else:\n    #     tag_total_num = 0   # 总指标数 计算\n    #     ret, basics = {}, []\n    #     tmp = _joint(i, ret, basics, *_port_status)\n\n    return [tag_total_num, tmp]\n\n\ndef target_service_run(env, services):\n    \"\"\"\n    组建巡检，多线程执行\n    :env: 环境 queryset 对象\n    :services: 服务id 列表\n    \"\"\"\n    tmp_list = list()\n    threads = list()\n    total_no = error_no = 0  # 总指标数、异常指标数\n    # 查询该环境下服务\n    services = Service.objects.filter(env=env, id__in=services)\n    services = services.values(\n        'service_instance_name', 'ip', 'service_port', 'service__app_name',\n        'service__app_install_args', 'service_status', 'service__app_monitor')\n    for i in services:\n        threads.append(MyThread(func=target_service_thread, args=(env, i)))\n\n    for t in threads:\n        t.start()\n\n    for t in threads:\n        t.join()  # 用join等待线程执行结束\n        if not t.res:\n            continue\n        total_no += t.res[0]\n        tmp_list.append(t.res[1])  # 等线程结束，回收返回值\n\n    # 扫描统计\n    scan_info = {\"host\": 0, \"service\": len(services), \"component\": 0}\n    # 分析结果\n    scan_result = {\n        \"all_target_num\": total_no, \"abnormal_target\": error_no, \"healthy\": \"-\"\n    }\n    return scan_info, scan_result, tmp_list\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_arangodb.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceArangodbCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus arangodb 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 18\n        self.service_name = \"arangodb\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"arangodb 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"arangodb cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"arangodb 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def rocksdb_base_level(self):\n        expr = f\"rocksdb_base_level{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_base_level\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_base_level\",\n            \"name_cn\": \"rocksdb基本等级\",\n            \"value\": val\n        })\n\n    def client_connections(self):\n        expr = f\"arangodb_client_connection_statistics_client_connections{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"client_connections\"] = val\n        self.basic.append({\n            \"name\": \"client_connections\",\n            \"name_cn\": \"客户端连接数\",\n            \"value\": val\n        })\n\n    def rocksdb_background_errors(self):\n        expr = f\"rocksdb_background_errors{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_background_errors\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_background_errors\",\n            \"name_cn\": \"rocksdb_background_errors\",\n            \"value\": val\n        })\n\n    def arangodb_transactions_started(self):\n        expr = f\"arangodb_transactions_started{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"arangodb_transactions_started\"] = val\n        self.basic.append({\n            \"name\": \"arangodb_transactions_started\",\n            \"name_cn\": \"事务开启数\",\n            \"value\": val\n        })\n\n    def thread_numbers(self):\n        expr = f\"arangodb_process_statistics_number_of_threads{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"thread_numbers\"] = val\n        self.basic.append({\n            \"name\": \"thread_numbers\",\n            \"name_cn\": \"线程数\",\n            \"value\": val\n        })\n\n    def rocksdb_cache_limit(self):\n        expr = f\"rocksdb_cache_limit{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_cache_limit\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_cache_limit\",\n            \"name_cn\": \"缓存限制\",\n            \"value\": val\n        })\n\n    def rocksdb_size_all_mem_tables(self):\n        expr = f\"rocksdb_size_all_mem_tables{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_size_all_mem_tables\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_size_all_mem_tables\",\n            \"name_cn\": \"表占用内存总字节数\",\n            \"value\": val\n        })\n\n    def rocksdb_cache_allocated(self):\n        expr = f\"rocksdb_cache_allocated{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_cache_allocated\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_cache_allocated\",\n            \"name_cn\": \"rocksdb_cache_allocated\",\n            \"value\": val\n        })\n\n    def rocksdb_num_snapshots(self):\n        expr = f\"rocksdb_num_snapshots{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_num_snapshots\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_num_snapshots\",\n            \"name_cn\": \"快照数\",\n            \"value\": val\n        })\n\n    def arangodb_transactions_committed(self):\n        expr = f\"arangodb_transactions_committed{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"arangodb_transactions_committed\"] = val\n        self.basic.append({\n            \"name\": \"arangodb_transactions_committed\",\n            \"name_cn\": \"已提交事务数\",\n            \"value\": val\n        })\n\n    def rocksdb_estimate_num_keys(self):\n        expr = f\"rocksdb_estimate_num_keys{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_estimate_num_keys\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_estimate_num_keys\",\n            \"name_cn\": \"预测key数\",\n            \"value\": val\n        })\n\n    def rocksdb_actual_delayed_write_rate(self):\n        expr = f\"rocksdb_actual_delayed_write_rate{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_actual_delayed_write_rate\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_actual_delayed_write_rate\",\n            \"name_cn\": \"延迟写入率\",\n            \"value\": val\n        })\n\n    def rocksdb_cache_hit_rate_recent(self):\n        expr = f\"rocksdb_cache_hit_rate_recent{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rocksdb_cache_hit_rate_recent\"] = val\n        self.basic.append({\n            \"name\": \"rocksdb_cache_hit_rate_recent\",\n            \"name_cn\": \"当前缓存命中率\",\n            \"value\": val\n        })\n\n    def arangodb_transactions_aborted(self):\n        expr = f\"arangodb_transactions_aborted{{env='{self.env}',instance='{self.instance}',job='arangodbExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"arangodb_transactions_aborted\"] = val\n        self.basic.append({\n            \"name\": \"arangodb_transactions_aborted\",\n            \"name_cn\": \"已中断事务数\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'rocksdb_base_level', 'client_connections',\n                  'rocksdb_background_errors', 'arangodb_transactions_started',\n                  'thread_numbers',\n                  'rocksdb_cache_limit', 'rocksdb_size_all_mem_tables', 'rocksdb_cache_allocated',\n                  'rocksdb_num_snapshots',\n                  'arangodb_transactions_committed', 'rocksdb_estimate_num_keys', 'rocksdb_actual_delayed_write_rate',\n                  'rocksdb_cache_hit_rate_recent', 'arangodb_transactions_aborted']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_beanstalk.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceBeanstalkCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus beanstalk 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 18\n        self.service_name = \"beanstalk\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"beanstalk 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"beanstalk cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"beanstalk 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def total_connections(self):\n        expr = f\"total_connections{{job='beanstalkExporter',env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"total_connections\"] = val\n\n        self.basic.append({\n            \"name\": \"total_connections\",\n            \"name_cn\": \"总连接数\",\n            \"value\": val\n        })\n\n    def total_jobs(self):\n        expr = f\"total_jobs{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"total_jobs\"] = val\n\n        self.basic.append({\n            \"name\": \"total_jobs\",\n            \"name_cn\": \"总任务数\",\n            \"value\": val\n        })\n\n    def buried_jobs(self):\n        expr = f\"current_jobs_buried{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"buried_jobs\"] = val\n\n        self.basic.append({\n            \"name\": \"buried_jobs\",\n            \"name_cn\": \"buried job数\",\n            \"value\": val\n        })\n\n    def delayed_jobs(self):\n        expr = f\"current_jobs_delayed{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"delayed_jobs\"] = val\n\n        self.basic.append({\n            \"name\": \"delayed_jobs\",\n            \"name_cn\": \"延迟的job数\",\n            \"value\": val\n        })\n\n    def timeout_job_num(self):\n        expr = f\"job_timeouts{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"timeout_job_num\"] = val\n\n        self.basic.append({\n            \"name\": \"timeout_job_num\",\n            \"name_cn\": \"超时的job数\",\n            \"value\": val\n        })\n\n    def stats_cmd_num(self):\n        expr = f\"cmd_stats{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"stats_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"stats_cmd_num\",\n            \"name_cn\": \"stats命令数\",\n            \"value\": val\n        })\n\n    def reverse_cmd_num(self):\n        expr = f\"cmd_reserve{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"reverse_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"reverse_cmd_num\",\n            \"name_cn\": \"reverse命令数\",\n            \"value\": val\n        })\n\n    def release_cmd_num(self):\n        expr = f\"cmd_release{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"release_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"release_cmd_num\",\n            \"name_cn\": \"release命令数\",\n            \"value\": val\n        })\n\n    def put_cmd_num(self):\n        expr = f\"cmd_put{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"put_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"put_cmd_num\",\n            \"name_cn\": \"put命令数\",\n            \"value\": val\n        })\n\n    def peek_cmd_num(self):\n        expr = f\"cmd_peek{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"peek_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"peek_cmd_num\",\n            \"name_cn\": \"peak命令数\",\n            \"value\": val\n        })\n\n    def kick_cmd_num(self):\n        expr = f\"cmd_kick{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"kick_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"kick_cmd_num\",\n            \"name_cn\": \"kick命令数\",\n            \"value\": val\n        })\n\n    def ignore_cmd_num(self):\n        expr = f\"cmd_ignore{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ignore_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"ignore_cmd_num\",\n            \"name_cn\": \"ignore命令数\",\n            \"value\": val\n        })\n\n    def delete_cmd_num(self):\n        expr = f\"cmd_delete{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"delete_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"delete_cmd_num\",\n            \"name_cn\": \"delete命令数\",\n            \"value\": val\n        })\n\n    def bury_cmd_num(self):\n        expr = f\"cmd_bury{{env='{self.env}',instance='{self.instance}',job='beanstalkExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"bury_cmd_num\"] = val\n\n        self.basic.append({\n            \"name\": \"bury_cmd_num\",\n            \"name_cn\": \"bury命令数\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage',\n                  'total_connections', 'total_jobs', 'buried_jobs', 'delayed_jobs', 'timeout_job_num', 'stats_cmd_num',\n                  'reverse_cmd_num', 'release_cmd_num', 'put_cmd_num', 'peek_cmd_num', 'kick_cmd_num', 'ignore_cmd_num',\n                  'delete_cmd_num', 'bury_cmd_num']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_clickhouse.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceClickhouseCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus clickhouse 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 12\n        self.service_name = \"clickhouse\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"clickhouse 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"clickhouse cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"clickhouse 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def query_nums(self):\n        expr = f\"clickhouse_query{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"query\"] = val\n        self.basic.append({\n            \"name\": \"query_nums\",\n            \"name_cn\": \"查询总次数\",\n            \"value\": val\n        })\n\n    def merge_nums(self):\n        expr = f\"clickhouse_merge{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"merge\"] = val\n        self.basic.append({\n            \"name\": \"merge_nums\",\n            \"name_cn\": \"merge总次数\",\n            \"value\": val\n        })\n\n    def read_only_replica(self):\n        expr = f\"sum(clickhouse_readonly_replica{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"read_only_replica\"] = val\n        self.basic.append({\n            \"name\": \"read_only_replica\",\n            \"name_cn\": \"仅读副本数\",\n            \"value\": val\n        })\n\n    def replication(self):\n        expr = f\"clickhouse_replicated_checks{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"replication\"] = val\n        self.basic.append({\n            \"name\": \"replication\",\n            \"name_cn\": \"事务数\",\n            \"value\": val\n        })\n\n    def clickhouse_read(self):\n        expr = f\"clickhouse_read{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"clickhouse_read\"] = val\n        self.basic.append({\n            \"name\": \"clickhouse_read\",\n            \"name_cn\": \"已读字节数\",\n            \"value\": val\n        })\n\n    def clickhouse_write(self):\n        expr = f\"clickhouse_write{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"clickhouse_write\"] = val\n        self.basic.append({\n            \"name\": \"clickhouse_write\",\n            \"name_cn\": \"已写字节数\",\n            \"value\": val\n        })\n\n    def pool_tasks(self):\n        expr = f\"clickhouse_background_pool_task{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"pool_tasks\"] = val\n        self.basic.append({\n            \"name\": \"pool_tasks\",\n            \"name_cn\": \"pool task数\",\n            \"value\": val\n        })\n\n    def connections(self):\n        expr = f\"clickhouse_tcp_connection{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"connections\"] = val\n        self.basic.append({\n            \"name\": \"connections\",\n            \"name_cn\": \"连接数\",\n            \"value\": val\n        })\n\n    def clickhouse_memory_tracking(self):\n        expr = f\"clickhouse_memory_tracking{{env='{self.env}',instance='{self.instance}',job='clickhouseExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"tracking_memory\"] = val\n        self.basic.append({\n            \"name\": \"tracking_memory\",\n            \"name_cn\": \"tracking 内存\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'query_nums', 'merge_nums',\n                  'read_only_replica', 'replication',\n                  'clickhouse_read', 'clickhouse_write', 'pool_tasks', 'connections', 'clickhouse_memory_tracking']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_elasticsearch.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceElasticsearchCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus elasticsearch 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 25\n        self.service_name = \"elasticsearch\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"elasticsearch 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"elasticsearch cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"elasticsearch 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def running_nodes(self):\n        expr = f\"sum(elasticsearch_cluster_health_number_of_nodes{{env='{self.env}',cluster='cw-es'}})/count(elasticsearch_cluster_health_number_of_nodes{{env='{self.env}',cluster='cw-es'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"running_nodes\"] = val\n        self.basic.append({\n            \"name\": \"running_nodes\",\n            \"name_cn\": \"运行节点数\",\n            \"value\": val\n        })\n\n    def active_data_nodes(self):\n        expr = f\"elasticsearch_cluster_health_number_of_data_nodes{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"active_data_nodes\"] = val\n        self.basic.append({\n            \"name\": \"active_data_nodes\",\n            \"name_cn\": \"活跃数据节点数\",\n            \"value\": val\n        })\n\n    def pending_tasks(self):\n        expr = f\"elasticsearch_cluster_health_number_of_pending_tasks{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"pending_tasks\"] = val\n        self.basic.append({\n            \"name\": \"pending_tasks\",\n            \"name_cn\": \"pending任务数\",\n            \"value\": val\n        })\n\n    def active_shards(self):\n        expr = f\"elasticsearch_cluster_health_active_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"active_shards\"] = val\n        self.basic.append({\n            \"name\": \"active_shards\",\n            \"name_cn\": \"活跃shard数\",\n            \"value\": val\n        })\n\n    def active_primary_shards(self):\n        expr = f\"elasticsearch_cluster_health_active_primary_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"active_primary_shards\"] = val\n        self.basic.append({\n            \"name\": \"active_primary_shards\",\n            \"name_cn\": \"活跃 primary shard数\",\n            \"value\": val\n        })\n\n    def initializing_shards(self):\n        expr = f\"elasticsearch_cluster_health_initializing_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"initializing_shards\"] = val\n        self.basic.append({\n            \"name\": \"initializing_shards\",\n            \"name_cn\": \"初始化中的 shard数\",\n            \"value\": val\n        })\n\n    def relocating_shards(self):\n        expr = f\"elasticsearch_cluster_health_relocating_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"relocating_shards\"] = val\n        self.basic.append({\n            \"name\": \"relocating_shards\",\n            \"name_cn\": \"迁移中的shard数\",\n            \"value\": val\n        })\n\n    def unassigned_shards(self):\n        expr = f\"elasticsearch_cluster_health_unassigned_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"unassigned_shards\"] = val\n        self.basic.append({\n            \"name\": \"unassigned_shards\",\n            \"name_cn\": \"未分配shard数\",\n            \"value\": val\n        })\n\n    def delayed_unassigned_shards(self):\n        expr = f\"elasticsearch_cluster_health_delayed_unassigned_shards{{env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"delayed_unassigned_shards\"] = val\n        self.basic.append({\n            \"name\": \"delayed_unassigned_shards\",\n            \"name_cn\": \"延迟shard数\",\n            \"value\": val\n        })\n\n    def documents_indexed(self):\n        expr = f\"sum(elasticsearch_indices_docs{{env='{self.env}',cluster='cw-es'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"documents_indexed\"] = val\n        self.basic.append({\n            \"name\": \"documents_indexed\",\n            \"name_cn\": \"已索引文档数\",\n            \"value\": val\n        })\n\n    def index_size(self):\n        expr = f\"sum(elasticsearch_indices_store_size_bytes{{env='{self.env}',cluster='cw-es'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"index_size\"] = val\n        self.basic.append({\n            \"name\": \"index_size\",\n            \"name_cn\": \"索引大小\",\n            \"value\": val\n        })\n\n    def documents_indexed_rate(self):\n        expr = f\"rate(elasticsearch_indices_indexing_index_total{{env='{self.env}',cluster='cw-es'}}[1h])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"documents_indexed_rate\"] = val\n        self.basic.append({\n            \"name\": \"documents_indexed_rate\",\n            \"name_cn\": \"文档索引率\",\n            \"value\": val\n        })\n\n    def query_rate(self):\n        expr = f\"rate(elasticsearch_indices_search_fetch_total{{env='{self.env}',cluster='cw-es'}}[1h])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"query_rate\"] = val\n        self.basic.append({\n            \"name\": \"query_rate\",\n            \"name_cn\": \"查询率\",\n            \"value\": val\n        })\n\n    def queue_count(self):\n        expr = f\"sum(elasticsearch_thread_pool_queue_count{{env='{self.env}',cluster='cw-es', type!='management'}}) by (type)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"queue_count\"] = val\n        self.basic.append({\n            \"name\": \"queue_count\",\n            \"name_cn\": \"队列数\",\n            \"value\": val\n        })\n\n    def gc_seconds(self):\n        expr = f\"irate(elasticsearch_jvm_gc_collection_seconds_sum{{env='{self.env}',cluster='cw-es'}}[1m])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"gc_seconds\"] = val\n        self.basic.append({\n            \"name\": \"gc_seconds\",\n            \"name_cn\": \"gc总时间\",\n            \"value\": val\n        })\n\n    def thread_pool_rejections(self):\n        expr = f\"rate(elasticsearch_thread_pool_rejected_count{{env='{self.env}',cluster='cw-es', type!='management'}}[5m])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"thread_pool_rejections\"] = val\n        self.basic.append({\n            \"name\": \"thread_pool_rejections\",\n            \"name_cn\": \"线程池拒绝数\",\n            \"value\": val\n        })\n\n    def thread_pool_active_count(self):\n        expr = f\"sum(elasticsearch_thread_pool_active_count{{env='{self.env}',cluster='cw-es', type!='management'}}) by (type)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"thread_pools\"] = val\n        self.basic.append({\n            \"name\": \"thread_pool_active_count\",\n            \"name_cn\": \"线程池活跃数\",\n            \"value\": val\n        })\n\n    def avg_heap_in_15min(self):\n        expr = f\"avg_over_time(elasticsearch_jvm_memory_used_bytes{{area='heap',env='{self.env}',cluster='cw-es'}}[15m]) / elasticsearch_jvm_memory_max_bytes{{area='heap',env='{self.env}',cluster='cw-es'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"avg_heap_in_15min\"] = val\n        self.basic.append({\n            \"name\": \"avg_heap_in_15min\",\n            \"name_cn\": \"15分钟堆内存平均使用大小\",\n            \"value\": val\n        })\n\n    def rx_rate_5m(self):\n        expr = f\"sum(rate(elasticsearch_transport_rx_packets_total{{env='{self.env}',cluster='cw-es'}}[5m]))\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rx_tx_rate_5m\"] = val\n        self.basic.append({\n            \"name\": \"rx_rate_5m\",\n            \"name_cn\": \"rx_rate_5m\",\n            \"value\": val\n        })\n\n    def tx_rate_5m(self):\n        expr = f\"sum(rate(elasticsearch_transport_tx_packets_total{{env='{self.env}',cluster='cw-es'}}[5m]))\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"rx_tx_rate_5m\"] = val\n        self.basic.append({\n            \"name\": \"tx_rate_5m\",\n            \"name_cn\": \"tx_rate_5m\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'running_nodes',\n                  'active_data_nodes',\n                  'pending_tasks', 'active_shards', 'active_primary_shards', 'initializing_shards', 'relocating_shards',\n                  'unassigned_shards', 'delayed_unassigned_shards', 'documents_indexed', 'index_size',\n                  'documents_indexed_rate',\n                  'query_rate', 'queue_count', 'gc_seconds', 'thread_pool_rejections', 'thread_pool_active_count',\n                  'avg_heap_in_15min',\n                  'rx_rate_5m', 'tx_rate_5m']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_flink.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceFlinkCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus flink 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"flink\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"flink 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"flink cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"flink 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"flink_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.ret['service_status'] = ret.get('service_status', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_func.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/31 7:28 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\n\n\ndef salt_json(instance, func):\n    \"\"\"\n    salt执行脚本，获取返回值\n    \"\"\"\n    ret = {}\n    try:\n        _obj = SaltClient()\n        _obj.salt_module_update()\n        ret = _obj.fun(instance, func)\n        if ret and ret[0]:\n            ret = json.loads(ret[1])\n        else:\n            ret = {}\n    except:\n        pass\n\n    return ret\n\n\nif __name__ == '__main__':\n    salt_json('10.0.7.194', 'tomcat_check.main')\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_gotty.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceGottyCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus gotty 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"gotty\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"gotty 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"gotty cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"gotty 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"gotty_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.ret['service_status'] = ret.get('service_status', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_grafana.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceGrafanaCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus grafana 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"grafana\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"grafana 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"grafana cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"grafana 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"grafana_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.ret['service_status'] = ret.get('service_status', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_hadoop.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceHadoopCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus hadoop 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"hadoop\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"hadoop 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"hadoop cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"hadoop 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"hadoop_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.ret['service_status'] = ret.get('service_status', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_httpd.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceHttpdCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus httpd 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 14\n        self.service_name = \"httpd\"\n        Prometheus.__init__(self)\n\n    def run_time(self):\n        \"\"\"httpd 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"httpd cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"httpd 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def process_max_fds(self):\n        expr = f\"process_max_fds{{env='{self.env}',instance='$host',job='httpdExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"process_max_fds\"] = val\n        self.basic.append({\n            \"name\": \"process_max_fds\",\n            \"name_cn\": \"进程打开文件最大数\",\n            \"value\": val\n        })\n\n    def process_cpu_seconds_total(self):\n        expr = f\"process_cpu_seconds_total{{env='{self.env}',instance='$host',job='httpdExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"process_cpu_seconds_total\"] = val\n        self.basic.append({\n            \"name\": \"process_cpu_seconds_total\",\n            \"name_cn\": \"进程占用cpu总时间\",\n            \"value\": val\n        })\n\n    def apache_accesses_total(self):\n        expr = f\"apache_accesses_total{{env='{self.env}',instance='$host'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"apache_accesses_total\"] = val\n        self.basic.append({\n            \"name\": \"apache_accesses_total\",\n            \"name_cn\": \"access总数\",\n            \"value\": val\n        })\n\n    def apache_cpuload(self):\n        expr = f\"apache_cpuload{{env='{self.env}',instance='$host'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"apache_cpuload\"] = val\n        self.basic.append({\n            \"name\": \"apache_cpuload\",\n            \"name_cn\": \"cpu负载\",\n            \"value\": val\n        })\n\n    def apache_workers(self):\n        expr = f\"apache_workers{{env='{self.env}',instance='$host'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"apache_workers\"] = val\n        self.basic.append({\n            \"name\": \"apache_workers\",\n            \"name_cn\": \"worker数\",\n            \"value\": val\n        })\n\n    def http_request_size_bytes(self):\n        expr = f\"http_request_size_bytes{{env='{self.env}',instance='$host'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"http_request_size_bytes\"] = val\n        self.basic.append({\n            \"name\": \"http_request_size_bytes\",\n            \"name_cn\": \"http请求字节数\",\n            \"value\": val\n        })\n\n    def apache_scoreboard(self):\n        expr = f\"apache_scoreboard{{env='{self.env}',instance='$host'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"apache_scoreboard\"] = val\n        self.basic.append({\n            \"name\": \"apache_scoreboard\",\n            \"name_cn\": \"scoreboard值\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'process_max_fds',\n                  'process_cpu_seconds_total', 'apache_accesses_total', 'apache_cpuload', 'apache_workers',\n                  'http_request_size_bytes', 'http_request_size_bytes',\n                  'apache_scoreboard']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_ignite.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceIgniteCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus ignite 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 19\n        self.service_name = \"ignite\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"ignite 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"ignite cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"ignite 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def ignite_started_thread_count(self):\n        expr = f\"ignite_started_thread_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ignite_started_thread_count\"] = val\n        self.basic.append({\n            \"name\": \"ignite_started_thread_count\",\n            \"name_cn\": \"开启线程数\",\n            \"value\": val\n        })\n\n    def sent_messages_count(self):\n        expr = f\"sent_messages_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"sent_messages_count\"] = val\n        self.basic.append({\n            \"name\": \"sent_messages_count\",\n            \"name_cn\": \"发送message数\",\n            \"value\": val\n        })\n\n    def ignite_received_messages_count(self):\n        expr = f\"ignite_received_messages_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ignite_received_messages_count\"] = val\n        self.basic.append({\n            \"name\": \"ignite_received_messages_count\",\n            \"name_cn\": \"收到message数\",\n            \"value\": val\n        })\n\n    def average_job_wait_time(self):\n        expr = f\"average_job_wait_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"average_job_wait_time\"] = val\n        self.basic.append({\n            \"name\": \"average_job_wait_time\",\n            \"name_cn\": \"任务平均等待时间\",\n            \"value\": val\n        })\n\n    def current_job_wait_time(self):\n        expr = f\"current_job_wait_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_job_wait_time\"] = val\n        self.basic.append({\n            \"name\": \"current_job_wait_time\",\n            \"name_cn\": \"当前任务等待时间\",\n            \"value\": val\n        })\n\n    def maximum_job_wait_time(self):\n        expr = f\"maximum_job_wait_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"maximum_job_wait_time\"] = val\n        self.basic.append({\n            \"name\": \"maximum_job_wait_time\",\n            \"name_cn\": \"最大任务等待时间\",\n            \"value\": val\n        })\n\n    def average_job_execute_time(self):\n        expr = f\"average_job_execute_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"average_job_execute_time\"] = val\n        self.basic.append({\n            \"name\": \"average_job_execute_time\",\n            \"name_cn\": \"任务平均执行时间\",\n            \"value\": val\n        })\n\n    def current_job_execute_time(self):\n        expr = f\"current_job_execute_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_job_execute_time\"] = val\n        self.basic.append({\n            \"name\": \"current_job_execute_time\",\n            \"name_cn\": \"当前任务执行时间\",\n            \"value\": val\n        })\n\n    def maximum_job_execute_time(self):\n        expr = f\"maximum_job_execute_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"maximum_job_execute_time\"] = val\n        self.basic.append({\n            \"name\": \"current_job_execute_time\",\n            \"name_cn\": \"任务最大执行时间\",\n            \"value\": val\n        })\n\n    def busy_time_percentage(self):\n        expr = f\"busy_time_percentage{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}*100\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"busy_time_percentage\"] = val\n        self.basic.append({\n            \"name\": \"busy_time_percentage\",\n            \"name_cn\": \"忙碌时间占比\",\n            \"value\": val\n        })\n\n    def ignite_busy_time_total(self):\n        expr = f\"rate(total_busy_time{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}[5m])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ignite_busy_time_total\"] = val\n        self.basic.append({\n            \"name\": \"ignite_busy_time_total\",\n            \"name_cn\": \"忙碌态总时间\",\n            \"value\": val\n        })\n\n    def ignite_idle_time_total(self):\n        expr = f\"rate(ignite_idle_time_total{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}[5m])\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ignite_idle_time_total\"] = val\n        self.basic.append({\n            \"name\": \"ignite_idle_time_total\",\n            \"name_cn\": \"空闲态总时间\",\n            \"value\": val\n        })\n\n    def current_daemon_thread_count(self):\n        expr = f\"current_daemon_thread_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_daemon_thread_count\"] = val\n        self.basic.append({\n            \"name\": \"current_daemon_thread_count\",\n            \"name_cn\": \"当前后台线程数\",\n            \"value\": val\n        })\n\n    def maximum_thread_count(self):\n        expr = f\"maximum_thread_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"maximum_thread_count\"] = val\n        self.basic.append({\n            \"name\": \"maximum_thread_count\",\n            \"name_cn\": \"最大线程数\",\n            \"value\": val\n        })\n\n    def current_thread_count(self):\n        expr = f\"current_thread_count{{env='{self.env}',instance='{self.instance}',job='igniteExporter'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_thread_count\"] = val\n        self.basic.append({\n            \"name\": \"current_thread_count\",\n            \"name_cn\": \"当前线程数\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'ignite_started_thread_count',\n                  'sent_messages_count', 'ignite_received_messages_count', 'average_job_wait_time',\n                  'current_job_wait_time',\n                  'maximum_job_wait_time', 'average_job_execute_time', 'current_job_execute_time',\n                  'maximum_job_execute_time',\n                  'busy_time_percentage', 'ignite_busy_time_total', 'ignite_idle_time_total',\n                  'current_daemon_thread_count',\n                  'maximum_thread_count', 'current_thread_count']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_jvm_base.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceBase(Prometheus):\n    \"\"\"\n    查询 prometheus java 指标,基类\n    \"\"\"\n\n    def __init__(self, env, instance, job):\n        self.ret = {}\n        self.basic = []\n        self.job = job  # Exporter类型\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self.metric_num = 10\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}',\" \\\n               f\"app!='node'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"cpu使用率\"\"\"\n        expr = f\"process_cpu_usage{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}', \" \\\n               f\"job='{self.job}'}} * 100\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 2) if val else '0.00'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"内存使用率\"\"\"\n        expr = f\"sum(jvm_memory_used_bytes{{area='nonheap', env='{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"job='{self.job}'}}) / \" \\\n               f\"sum(jvm_memory_max_bytes{{area='nonheap', env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',\" \\\n               f\"job='{self.job}'}}) * 100\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 2) if val else '0.00'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def thread_num(self):\n        \"\"\"进程数量\"\"\"\n        expr = f\"jvm_threads_daemon_threads{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"thread_num\", \"name_cn\": \"进程数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def load_average_1m(self):\n        \"\"\"系统一分钟负载占用情况\"\"\"\n        expr = f\"system_load_average_1m{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"load_average_1m\", \"name_cn\": \"系统一分钟负载占用情况\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def tomcat_sessions(self):\n        \"\"\"Tomcat当前活跃session数量\"\"\"\n        expr = f\"tomcat_sessions_active_current_sessions{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"tomcat_sessions\", \"name_cn\": \"Tomcat当前活跃session数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def files_max_files(self):\n        \"\"\"可打开的最大文件描述符数量\"\"\"\n        expr = f\"process_files_max_files{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"files_max_files\", \"name_cn\": \"可打开的最大文件描述符数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def files_open_files(self):\n        \"\"\"当前打开的最大文件描述符数量\"\"\"\n        expr = f\"process_files_open_files{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"files_open_files\", \"name_cn\": \"当前打开的最大文件描述符数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def cpu_count(self):\n        \"\"\"java虚拟机可用的cpu数量\"\"\"\n        expr = f\"system_cpu_count{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}',job=~'{self.job}'}}\"\n        self.basic.append({\n            \"name\": \"cpu_count\", \"name_cn\": \"java虚拟机可用的cpu数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage',\n                  'thread_num', 'load_average_1m', 'tomcat_sessions',\n                  'files_max_files', 'files_open_files', 'cpu_count']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_kafka.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceKafkaCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus kafka 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"kafka\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"kafka 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"kafka cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"kafka 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def kafka_brokers(self):\n        \"\"\"kafka brokers\"\"\"\n        expr = f\"kafka_brokers{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"kafka_brokers\"] = val\n        self.basic.append({\n            \"name\": \"kafka_brokers\",\n            \"name_cn\": \"broker数\",\n            \"value\": val\n        })\n\n    def process_open_fds(self):\n        \"\"\"kafka brokers\"\"\"\n        expr = f\"process_open_fds{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"process_open_fds\"] = val\n        self.basic.append({\n            \"name\": \"process_open_fds\",\n            \"name_cn\": \"打开文件描述符数\",\n            \"value\": val\n        })\n\n    def process_resident_memory_bytes(self):\n        \"\"\"kafka brokers\"\"\"\n        expr = f\"process_resident_memory_bytes{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"process_resident_memory_bytes\"] = val\n        self.basic.append({\n            \"name\": \"process_resident_memory_bytes\",\n            \"name_cn\": \"resident memory\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'kafka_brokers', 'process_open_fds',\n                  'process_resident_memory_bytes']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_mysql.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/10/21 5:11 下午\n# Description:\nfrom utils.prometheus.prometheus import Prometheus\nfrom utils.plugin.salt_client import SaltClient\n\n\nclass ServiceMysqlCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus mysql 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 10\n        self.service_name = \"mysql\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"mysql 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"mysql cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"mysql 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def slow_query(self):\n        \"\"\"慢查询\"\"\"\n        expr = \"rate(mysql_global_status_slow_queries[5m])\"\n        self.basic.append({\"name\": \"slow_query\", \"name_cn\": \"慢查询\",\n                           \"value\": self.unified_job(*self.query(expr))})\n\n    def conn_num(self):\n        \"\"\"当前连接数量\"\"\"\n        expr = \"rate(mysql_global_status_threads_connected[5m])\"\n        self.basic.append({\"name\": \"conn_num\", \"name_cn\": \"连接数量\",\n                           \"value\": self.unified_job(*self.query(expr))})\n\n    def max_connections(self):\n        \"\"\"最大连接数\"\"\"\n        expr = \"mysql_global_variables_max_connections\"\n        self.basic.append({\"name\": \"max_connections\", \"name_cn\": \"最大连接数\",\n                           \"value\": self.unified_job(*self.query(expr))})\n\n    def threads_running(self):\n        \"\"\"活跃连接数量\"\"\"\n        expr = \"mysql_global_status_threads_running\"\n        self.basic.append({\"name\": \"threads_running\", \"name_cn\": \"活跃连接数\",\n                           \"value\": self.unified_job(*self.query(expr))})\n\n    def qps(self):\n        \"\"\"qps\"\"\"\n        expr = \"rate(mysql_global_status_questions[5m])\"\n        _ = self.unified_job(*self.query(expr))\n        _ = round(float(_), 2) if _ else 0\n        self.basic.append({\"name\": \"qps\", \"name_cn\": \"qps\", \"value\": _})\n\n    def backup_status(self):\n        \"\"\"备份状态\"\"\"\n        expr = \"mysql_global_status_slave_open_temp_tables\"\n        self.basic.append({\"name\": \"backup_status\", \"name_cn\": \"数据同步状态\",\n                           \"value\": self.unified_job(*self.query(expr))})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'slow_query', 'conn_num',\n                  'max_connections', 'threads_running', 'qps', 'backup_status']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n\n\nif __name__ == '__main__':\n    h = ServiceMysqlCrawl(env='demo', instance='10.0.9.60')\n    h.run()\n    print(h.ret)\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_nacos.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceNacosCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus Nacos 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self.metric_num = 17\n        self.service_name = \"nacos\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"count(nacos_monitor{{name='configCount',env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}})\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"nacos 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"nacos cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"nacos 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def service_count(self):\n        expr = f\"max(nacos_monitor{{name='serviceCount',env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"service_count\"] = val\n        self.basic.append({\n            \"name\": \"service_count\",\n            \"name_cn\": \"注册服务数\",\n            \"value\": val\n        })\n\n    def ip_count(self):\n        expr = f\"max(nacos_monitor{{name='ipCount',env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"ip_count\"] = val\n        self.basic.append({\n            \"name\": \"ip_count\",\n            \"name_cn\": \"注册ip数\",\n            \"value\": val\n        })\n\n    def config_count(self):\n        expr = f\"max(nacos_monitor{{name='configCount',env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"config_count\"] = val\n        self.basic.append({\n            \"name\": \"config_count\",\n            \"name_cn\": \"注册config数\",\n            \"value\": val\n        })\n\n    def config_push_total(self):\n        expr = f\"sum(nacos_monitor{{name='getConfig',env='{self.env}',instance='{self.instance}'}}) by (name)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"config_push_total\"] = val\n        self.basic.append({\n            \"name\": \"config_push_total\",\n            \"name_cn\": \"获取config数\",\n            \"value\": val\n        })\n\n    def threads(self):\n        expr = f\"max(jvm_threads_daemon_threads{{env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"threads\"] = val\n        self.basic.append({\n            \"name\": \"threads\",\n            \"name_cn\": \"后台线程数\",\n            \"value\": val\n        })\n\n    def notify_rt(self):\n        expr = f\"sum(rate(nacos_timer_seconds_sum{{env='{self.env}',instance='{self.instance}'}}[1m]))/sum(rate(nacos_timer_seconds_count{{env='{self.env}',instance='{self.instance}'}}[1m])) * 1000\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"notify_rt\"] = val\n        self.basic.append({\n            \"name\": \"notify_rt\",\n            \"name_cn\": \"notify_rt\",\n            \"value\": val\n        })\n\n    def long_polling(self):\n        expr = f\"sum(nacos_monitor{{name='longPolling', env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"long_polling\"] = val\n        self.basic.append({\n            \"name\": \"long_polling\",\n            \"name_cn\": \"long_polling\",\n            \"value\": val\n        })\n\n    def qps(self):\n        expr = f\"sum(rate(http_server_requests_seconds_count{{uri='/v1/cs/configs|/nacos/v1/ns/instance|/nacos/v1/ns/health', env='{self.env}',instance='{self.instance}'}}[1m])) by (method,uri)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"qps\"] = val\n        self.basic.append({\n            \"name\": \"qps\",\n            \"name_cn\": \"qps\",\n            \"value\": val\n        })\n\n    def leader_status(self):\n        expr = f\"sum(nacos_monitor{{name='leaderStatus', env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"leader_status\"] = val\n        self.basic.append({\n            \"name\": \"leader_status\",\n            \"name_cn\": \"leader状态\",\n            \"value\": val\n        })\n\n    def avg_push_cost(self):\n        expr = f\"sum(nacos_monitor{{name='avgPushCost', env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"avg_push_cost\"] = val\n        self.basic.append({\n            \"name\": \"avg_push_cost\",\n            \"name_cn\": \"avg_push_cost\",\n            \"value\": val\n        })\n\n    def max_push_cost(self):\n        expr = f\"max(nacos_monitor{{name='maxPushCost', env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"max_push_cost\"] = val\n        self.basic.append({\n            \"name\": \"max_push_cost\",\n            \"name_cn\": \"max_push_cost\",\n            \"value\": val\n        })\n\n    def config_statistics(self):\n        expr = f\"sum(nacos_monitor{{name='publish', env='{self.env}',instance='{self.instance}'}}) by (name)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"config_statistics\"] = val\n        self.basic.append({\n            \"name\": \"config_statistics\",\n            \"name_cn\": \"config_statistics\",\n            \"value\": val\n        })\n\n    def health_check(self):\n        expr = f\"sum(rate(nacos_monitor{{name='.*HealthCheck', env='{self.env}',instance='{self.instance}'}}[1m])) by (name) * 60\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"health_check\"] = val\n        self.basic.append({\n            \"name\": \"health_check\",\n            \"name_cn\": \"health_check\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'service_count', 'ip_count',\n                  'config_count', 'config_push_total', 'threads', 'notify_rt',\n                  'long_polling', 'qps', 'leader_status',\n                  'avg_push_cost', 'max_push_cost', 'config_statistics', 'health_check']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_ntpd.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceNtpdCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus ntpd 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"ntpd\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"ntpd 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"ntpd cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"ntpd 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"ntpd_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.ret['service_status'] = ret.get('service_status', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_postgresql.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServicePostgresqlCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus postgresql 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 16\n        self.service_name = \"postgresql\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"postgresql 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"postgresql cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"postgresql 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def current_fetch_data(self):\n        expr = f\"SUM(pg_stat_database_tup_fetched{{env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_fetch_data\"] = val\n        self.basic.append({\n            \"name\": \"current_fetch_data\",\n            \"name_cn\": \"当前fetch数据\",\n            \"value\": val\n        })\n\n    def current_insert_data(self):\n        expr = f\"SUM(pg_stat_database_tup_inserted{{release='$release', env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_insert_data\"] = val\n        self.basic.append({\n            \"name\": \"current_insert_data\",\n            \"name_cn\": \"当前insert数据\",\n            \"value\": val\n        })\n\n    def current_update_data(self):\n        expr = f\"SUM(pg_stat_database_tup_updated{{env='{self.env}',instance='{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"current_update_data\"] = val\n        self.basic.append({\n            \"name\": \"current_update_data\",\n            \"name_cn\": \"当前update数据\",\n            \"value\": val\n        })\n\n    def max_connections(self):\n        expr = f\"pg_settings_max_connections{{release='$release', env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"max_connections\"] = val\n        self.basic.append({\n            \"name\": \"max_connections\",\n            \"name_cn\": \"最大连接数\",\n            \"value\": val\n        })\n\n    def open_file_descriptors(self):\n        expr = f\"process_open_fds{{release='$release', env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"open_file_descriptors\"] = val\n        self.basic.append({\n            \"name\": \"open_file_descriptors\",\n            \"name_cn\": \"打开文件描述符数\",\n            \"value\": val\n        })\n\n    def shared_buffers(self):\n        expr = f\"pg_settings_shared_buffers_bytes{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"shared_buffers\"] = val\n        self.basic.append({\n            \"name\": \"shared_buffers\",\n            \"name_cn\": \"shared_buffers\",\n            \"value\": val\n        })\n\n    def effective_cache(self):\n        expr = f\"pg_settings_effective_cache_size_bytes{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"effective_cache\"] = val\n        self.basic.append({\n            \"name\": \"effective_cache\",\n            \"name_cn\": \"有效缓存\",\n            \"value\": val\n        })\n\n    def max_wal_size(self):\n        expr = f\"pg_settings_max_wal_size_bytes{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"max_wal_size\"] = val\n        self.basic.append({\n            \"name\": \"max_wal_size\",\n            \"name_cn\": \"max_wal_size\",\n            \"value\": val\n        })\n\n    def random_page_cost(self):\n        expr = f\"pg_settings_random_page_cost{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"random_page_cost\"] = val\n        self.basic.append({\n            \"name\": \"random_page_cost\",\n            \"name_cn\": \"random_page_cost\",\n            \"value\": val\n        })\n\n    def seq_page_cost(self):\n        expr = f\"pg_settings_seq_page_cost{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"seq_page_cost\"] = val\n        self.basic.append({\n            \"name\": \"seq_page_cost\",\n            \"name_cn\": \"seq_page_cost\",\n            \"value\": val\n        })\n\n    def max_worker_processes(self):\n        expr = f\"pg_settings_max_worker_processes{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"max_worker_processes\"] = val\n        self.basic.append({\n            \"name\": \"max_worker_processes\",\n            \"name_cn\": \"最大进程worker数\",\n            \"value\": val\n        })\n\n    def max_parallel_workers(self):\n        expr = f\"pg_settings_max_parallel_workers{{env='{self.env}',instance='{self.instance}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"max_parallel_workers\"] = val\n        self.basic.append({\n            \"name\": \"max_parallel_workers\",\n            \"name_cn\": \"max_parallel_workers\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'current_fetch_data',\n                  'current_insert_data', 'current_update_data', 'max_connections',\n                  'open_file_descriptors', 'shared_buffers', 'effective_cache',\n                  'max_wal_size',\n                  'random_page_cost', 'seq_page_cost', 'max_worker_processes', 'max_parallel_workers']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_prometheus.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/15 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServicePrometheusCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus prometheus 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"prometheus\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"prometheus 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"prometheus cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"prometheus 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time',\n                  'cpu_usage', 'mem_usage', 'test_test']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_redis.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceRedisCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus redis 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self.metric_num = 8\n        self.service_name = \"redis\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"redis 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"redis cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"redis 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def conn_num(self):\n        \"\"\"连接数量\"\"\"\n        expr = f\"redis_connected_clients{{env='{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}\"\n        self.basic.append({\n            \"name\": \"conn_num\", \"name_cn\": \"连接数量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def hit_rate(self):\n        \"\"\"命中率\"\"\"\n        expr = f\"(redis_keyspace_hits_total{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='redisExporter'}}  / \" \\\n               f\"(redis_keyspace_hits_total{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='redisExporter'}} + \" \\\n               f\"redis_keyspace_misses_total{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='redisExporter'}})) * 100\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else 0\n        self.basic.append({\n            \"name\": \"hit_rate\", \"name_cn\": \"缓存命中率\",\n            \"value\": f\"{val}%\"}\n        )\n\n    def max_memory(self):\n        \"\"\"最大内存\"\"\"\n        expr = f\"redis_memory_max_bytes{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}})\"\n        val = self.unified_job(*self.query(expr))\n        val = round(int(val) / 1048576, 2) if val else '-'\n        self.basic.append({\n            \"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n            \"value\": f\"{val}m\"}\n        )\n\n    def network_io(self):\n        \"\"\"网络io\"\"\"\n        expr = f\"redis_net_input_bytes_total{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}} / 1000000\"\n        val_in = self.unified_job(*self.query(expr))\n        val_in = round(float(val_in), 2) if val_in else 0\n\n        expr = f\"redis_net_output_bytes_total{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}} / 1000000\"\n        val_out = self.unified_job(*self.query(expr))\n        val_out = round(float(val_out), 2) if val_out else 0\n\n        self.basic.append({\n            \"name\": \"network_io\", \"name_cn\": \"网络io\",\n            \"value\": f\"{val_in}B/{val_out}B\"}\n        )\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage',\n                  'conn_num', 'hit_rate', 'max_memory', 'network_io']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_rocketmq.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nimport json\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceRocketmqCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus rocketmq 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 4\n        self.service_name = \"rocketmq\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"rocketmq 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"rocketmq cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"rocketmq 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def broker_tps(self):\n        \"\"\"生产消息数量/s\"\"\"\n        expr = f\"rocketmq_broker_tps{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}\"\n        self.basic.append({\n            \"name\": \"broker_tps\", \"name_cn\": \"生产消息数量/s\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def broker_qps(self):\n        \"\"\"消费消息数量/s\"\"\"\n        expr = f\"rocketmq_broker_qps{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}\"\n        self.basic.append({\n            \"name\": \"broker_qps\", \"name_cn\": \"消费消息数量/s\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def message_accumulation(self):\n        \"\"\"消息堆积量\"\"\"\n        expr = f\"rocketmq_message_accumulation{{env=~'{self.env}',\" \\\n               f\"instance=~'{self.instance}'}}\"\n        self.basic.append({\n            \"name\": \"message_accumulation\", \"name_cn\": \"消息堆积量\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def salt_json(self):\n        try:\n            self._obj.salt_module_update()\n            ret = self._obj.fun(self.instance, \"rocketmq_check.main\")\n            if ret and ret[0]:\n                ret = json.loads(ret[1])\n            else:\n                ret = {}\n        except Exception:\n            ret = {}\n\n        self.ret['cpu_usage'] = ret.get('cpu_usage', '-')\n        self.ret['mem_usage'] = ret.get('mem_usage', '-')\n        self.ret['run_time'] = ret.get('run_time', '-')\n        self.ret['log_level'] = ret.get('log_level', '-')\n        self.basic.append({\"name\": \"max_memory\", \"name_cn\": \"最大内存\",\n                           \"value\": ret.get('max_memory', '-')})\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        # target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage',\n        #           'broker_tps', 'broker_qps', 'message_accumulation',\n        #           'salt_json']\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_tengine.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: lingyang guo\n# CreateDate: 2021/12/8 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceTengineCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus tengine 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 11\n        self.service_name = \"tengine\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"tengine 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"tengine cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"tengine 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def server_connections(self):\n        expr = f\"nginx_server_connections{{env='{self.env}',instance='{self.instance}',status='active|writing|reading|waiting'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"server_connections\"] = val\n        self.basic.append({\n            \"name\": \"server_connections\",\n            \"name_cn\": \"连接数\",\n            \"value\": val\n        })\n\n    def server_cache(self):\n        expr = f\"sum(irate(nginx_server_cache{{env='{self.env}',instance='{self.instance}'}}[5m])) by (status)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"server_cache\"] = val\n        self.basic.append({\n            \"name\": \"server_cache\",\n            \"name_cn\": \"缓存\",\n            \"value\": val\n        })\n\n    def server_requests(self):\n        expr = f\"sum(irate(nginx_server_requests{{env='{self.env}',instance='{self.instance}', code!='total'}}[5m])) by (code)\"\n        val = self.unified_job(*self.query(expr))\n        val = val if val else 0\n        self.ret[\"server_requests\"] = val\n        self.basic.append({\n            \"name\": \"server_requests\",\n            \"name_cn\": \"request数\",\n            \"value\": val\n        })\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage', 'server_connections', 'server_cache',\n                  'server_requests']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/target_service_zookeeper.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/8 8:00 下午\n# Description:\nfrom utils.plugin.salt_client import SaltClient\nfrom utils.prometheus.prometheus import Prometheus\n\n\nclass ServiceZookeeperCrawl(Prometheus):\n    \"\"\"\n    查询 prometheus zookeeper 指标\n    \"\"\"\n\n    def __init__(self, env, instance):\n        self.ret = {}\n        self.basic = []\n        self.env = env  # 环境\n        self.instance = instance  # 主机ip\n        self._obj = SaltClient()\n        self.metric_num = 10\n        self.service_name = \"zookeeper\"\n        Prometheus.__init__(self)\n\n    def service_status(self):\n        \"\"\"运行状态\"\"\"\n        expr = f\"probe_success{{env='{self.env}', instance='{self.instance}', \" \\\n               f\"app='{self.service_name}'}}\"\n        self.ret['service_status'] = self.unified_job(*self.query(expr))\n\n    def run_time(self):\n        \"\"\"zookeeper 运行时间\"\"\"\n        expr = f\"process_uptime_seconds{{env='{self.env}', instance='{self.instance}', app='{self.service_name}'}}\"\n        _ = self.unified_job(*self.query(expr))\n        _ = float(_) if _ else 0\n        minutes, seconds = divmod(_, 60)\n        hours, minutes = divmod(minutes, 60)\n        days, hours = divmod(hours, 24)\n        if int(days) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(days)}天{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        elif int(hours) > 0:\n            self.ret['run_time'] = \\\n                f\"{int(hours)}小时{int(minutes)}分钟{int(seconds)}秒\"\n        else:\n            self.ret['run_time'] = f\"{int(minutes)}分钟{int(seconds)}秒\"\n\n    def cpu_usage(self):\n        \"\"\"zookeeper cpu使用率\"\"\"\n        expr = f\"service_process_cpu_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['cpu_usage'] = f\"{val}%\"\n\n    def mem_usage(self):\n        \"\"\"zookeeper 内存使用率\"\"\"\n        expr = f\"service_process_memory_percent{{instance='{self.instance}',app='{self.service_name}'}}\"\n        val = self.unified_job(*self.query(expr))\n        val = round(float(val), 4) if val else '-'\n        self.ret['mem_usage'] = f\"{val}%\"\n\n    def packets_received(self):\n        \"\"\"收包数\"\"\"\n        expr = f\"zk_packets_received{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"packets_received\", \"name_cn\": \"收包数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def packets_sent(self):\n        \"\"\"发包数\"\"\"\n        expr = f\"zk_packets_sent{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"packets_sent\", \"name_cn\": \"发包数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def num_alive_connections(self):\n        \"\"\"活跃连接数\"\"\"\n        expr = f\"zk_num_alive_connections{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"num_alive_connections\", \"name_cn\": \"活跃连接数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def outstanding_requests(self):\n        \"\"\"堆积请求数\"\"\"\n        expr = f\"zk_outstanding_requests{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"outstanding_requests\", \"name_cn\": \"堆积请求数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def znode_count(self):\n        \"\"\"znode数\"\"\"\n        expr = f\"zk_znode_count{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"znode_count\", \"name_cn\": \"节点数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def watch_count(self):\n        \"\"\"watch数\"\"\"\n        expr = f\"zk_watch_count{{env='{self.env}', \" \\\n               f\"instance=~'{self.instance}', job='zookeeperExporter'}}\"\n        self.basic.append({\n            \"name\": \"watch_count\", \"name_cn\": \"监测点数\",\n            \"value\": self.unified_job(*self.query(expr))}\n        )\n\n    def run(self):\n        \"\"\"统一执行实例方法\"\"\"\n        target = ['service_status', 'run_time', 'cpu_usage', 'mem_usage',\n                  'packets_received', 'packets_sent', 'num_alive_connections',\n                  'outstanding_requests', 'znode_count', 'watch_count']\n        for t in target:\n            if getattr(self, t):\n                getattr(self, t)()\n"
  },
  {
    "path": "omp_server/utils/prometheus/thread.py",
    "content": "# !/usr/bin/python3\n# -*-coding:utf-8-*-\n# Author: len chen\n# CreateDate: 2021/11/10 6:11 下午\n# Description:\nimport threading\n\n\nclass MyThread(threading.Thread):\n    \"\"\"\n    封装下多线程\n    重写下run\n    拿到每个线程的返回值\n    \"\"\"\n\n    def __init__(self, func, args):\n        threading.Thread.__init__(self)\n        self.func = func\n        self.args = args\n        self.res = None\n\n    def run(self):\n        self.res = self.func(*self.args)\n\n    def result(self):\n        return self.res\n"
  },
  {
    "path": "omp_server/utils/prometheus/update_threshold.py",
    "content": "# -*- coding: utf-8 -*-\nimport argparse\nimport os\n\nimport requests\nimport yaml\nimport logging\n\nfrom omp_server.settings import PROJECT_DIR\n\nlogger = logging.getLogger(\"server\")\n\ndescription = {\n    \"cpu_used\": \"主机 {{ $labels.instance }} CPU 使用率为 {{ $value | humanize }}%, 大于阈值 $condition_value$%\",\n    \"memory_used\": \"主机 {{ $labels.instance }} 内存使用率为 {{ $value | humanize }}%，大于阈值 $condition_value$%\",\n    \"disk_root_used\": \"主机 {{ $labels.instance }} 根分区使用率为 {{ $value | humanize }}%, 大于 阈值 $condition_value$%\",\n    \"disk_data_used\": \"主机 {{ $labels.instance }} 数据分区使用率为 {{ $value | humanize }}%, 大于 阈值 $condition_value$%\",\n    \"kafka_consumergroup_lag\": \"Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多  {{ humanize $value }}\"\n}\nexpr = {\n    \"cpu_used\": \"(100 - sum(avg without (cpu)(irate(node_cpu_seconds_total{mode='idle', \"\n                \"env=\\\"$env_name$\\\"}[2m]))) by (instance) * 100) $condition$ $condition_value$\",\n    \"memory_used\": \"(1 - (node_memory_MemAvailable_bytes{env=\\\"$env_name$\\\"} / (node_\"\n                   \"memory_MemTotal_bytes{env=\\\"$env_name$\\\"}))) * 100 $condition$ $condition_value$\",\n    \"disk_root_used\": \"max((node_filesystem_size_bytes{env=\\\"$env_name$\\\",\"\n                      \"mountpoint=\\\"/\\\"}-node_filesystem_free_bytes\"\n                      \"{env=\\\"$env_name$\\\",mountpoint=\\\"/\\\"}) *100/(node_filesystem_avail_\"\n                      \"bytes{env=\\\"$env_name$\\\",mountpoint=\\\"/\\\"}+(node_filesystem_size_bytes{\"\n                      \"env=\\\"$env_name$\\\",mountpoint=\\\"/\\\"}-node\"\n                      \"_filesystem_free_bytes{env=\\\"$env_name$\\\",mountpoint=\\\"/\\\"})))by(instance)\"\n                      \" $condition$ $condition_value$\",\n    \"disk_data_used\": \"max((node_filesystem_size_bytes{env=\\\"$env_name$\\\",\"\n                      \"mountpoint=\\\"$disk_data_path$\\\"}-node_filesystem_free_bytes\"\n                      \"{env=\\\"$env_name$\\\",mountpoint=\\\"$disk_data_path$\\\"}) *100/(node_filesystem_avail_\"\n                      \"bytes{env=\\\"$env_name$\\\",mountpoint=\\\"$disk_data_path$\\\"}+(node_filesystem_size_bytes{\"\n                      \"env=\\\"$env_name$\\\",mountpoint=\\\"$disk_data_path$\\\"}-node\"\n                      \"_filesystem_free_bytes{env=\\\"$env_name$\\\",mountpoint=\\\"$disk_data_path$\\\"})))by(instance)\"\n                      \" $condition$ $condition_value$\",\n    \"kafka_consumergroup_lag\": \"sum(kafka_consumergroup_lag{env=\\\"$env_name$\\\"}) by (consumergroup,instance,job,env) $condition$ $condition_value$\"\n\n}\n\n\ndef gen_summary(index_type):\n    return replace_value(\"$index_type$ (instance {{ $labels.instance }})\", index_type=index_type)\n\n\ndef replace_value(line, env_name=None, condition=None, condition_value=None, alert_level=None, index_type=None,\n                  disk_data_path=None):\n    if env_name:\n        line = line.replace(\"$env_name$\", str(env_name))\n    if condition:\n        line = line.replace(\"$condition$\", str(condition))\n    if condition_value:\n        line = line.replace(\"$condition_value$\", str(condition_value))\n    if alert_level:\n        line = line.replace(\"$alert_level$\", str(alert_level))\n    if index_type:\n        line = line.replace(\"$index_type$\", str(index_type))\n    if disk_data_path:\n        line = line.replace(\"$disk_data_path$\", str(disk_data_path))\n    return line\n\n\ndef update_node_rule_yaml(quotes_info):\n    \"\"\"\n    更新主机指标文件\n    \"\"\"\n    metric_en_cn_dict = {\n        \"cpu_used\": \"CPU\",\n        \"memory_used\": \"内存\",\n        \"disk_root_used\": \"根分区磁盘\",\n        \"disk_data_used\": \"数据分区磁盘\"\n    }\n    env_name = quotes_info.get(\"env_name\")\n    node_rule_yml_path = os.path.join(PROJECT_DIR, 'component',\n                                      'prometheus/conf/rules',\n                                      '{}_node_rule.yml'.format(env_name))\n    instance_alert = {\n        \"alert\": \"实例宕机\",\n        \"annotations\": {\n            \"consignee\": \"{}\".format(\"\"),  # TODO\n            \"description\": \"实例 {{ $labels.instance }} monitor_agent进程丢失或主机发生宕机已超过1分钟\",\n            \"summary\": \"实例宕机({{ $labels.instance }})\"\n        },\n        \"expr\": \"sum(up{job=\\\"nodeExporter\\\", env=\\'%s\\'}) by (instance) < 1\" % env_name,\n        \"for\": \"1m\",\n        \"labels\": {\n            \"job\": \"nodeExporter\",\n            \"severity\": \"critical\"\n        }\n    }\n    node_rules = {\"name\": \"node alert\", \"rules\": [instance_alert]}\n    dict_total_rules = {\"groups\": [node_rules]}\n    node_quotes = quotes_info.get(\"hosts\")\n    env_name = quotes_info.get(\"env_name\")\n    disk_data_path = quotes_info.get(\"disk_data_path\", None)\n    try:\n        for host_quote_info in node_quotes:\n            index_type = host_quote_info.get(\"index_type\")\n            if index_type == \"disk_data_used\":\n                if not disk_data_path:\n                    continue\n            alert_level = host_quote_info.get(\"alert_level\")\n            condition_value = host_quote_info.get(\"condition_value\")\n            condition = host_quote_info.get(\"condition\")\n            quote_info = {\n                \"alert\": \"主机 {} 使用率过高\".format(metric_en_cn_dict.get(index_type)),\n                \"annotations\": {\n                    \"consignee\": \"{}\".format(\"\"),  # TODO\n                    \"description\": replace_value(description.get(index_type), condition_value=condition_value),\n                    \"summary\": gen_summary(index_type=index_type),\n                },\n                \"expr\": replace_value(expr.get(index_type), env_name=env_name, condition=condition,\n                                      condition_value=condition_value, disk_data_path=disk_data_path),\n                \"for\": \"1m\",\n                \"labels\": {\n                    \"job\": \"nodeExporter\",\n                    \"severity\": alert_level,\n                }\n\n            }\n            logger.info(quote_info)\n            node_rules[\"rules\"].append(quote_info)\n        logger.info('开始更新告警规则文件{}'.format(node_rule_yml_path))\n\n        with open(node_rule_yml_path, \"w\") as fw:\n            yaml.dump(dict_total_rules, fw, allow_unicode=True)\n        logger.info(\"更新主机告警规则文件成功\")\n        return True\n    except Exception as e:\n        logger.error(e)\n        return False\n\n\ndef update_service_rule_yaml(quotes_info):\n    \"\"\"\n    更新服务指标文件\n    \"\"\"\n    env_name = quotes_info.get(\"env_name\")\n    service_rule_yml_path = os.path.join(PROJECT_DIR, 'component',\n                                         'prometheus/conf/rules',\n                                         '{}_service_status_rule.yml'.format(env_name))\n    instance_alert = {\n        \"alert\": \"app state\",\n        \"annotations\": {\n            \"consignee\": \"{}\".format(\"\"),  # TODO\n            \"description\": \"主机 {{ $labels.instance }} 中的 服务 {{ $labels.app }} 已经down掉超过一分钟.\",\n            \"summary\": \"app state(instance {{ $labels.instance }})\"\n        },\n        \"expr\": \"probe_success{env=\\'%s\\'} == 0 \" % env_name,\n        \"for\": \"1m\",\n        \"labels\": {\n            \"severity\": \"critical\"\n        }\n    }\n    service_rules = {\"name\": \"App state\", \"rules\": [instance_alert]}\n    dict_total_rules = {\"groups\": [service_rules]}\n    service_quotes = quotes_info.get(\"services\")\n    try:\n        for service_name, service_quote_info in service_quotes.items():\n            for service_quote in service_quote_info:\n                index_type = service_quote.get(\"index_type\")\n                alert_level = service_quote.get(\"alert_level\")\n                condition_value = service_quote.get(\"condition_value\")\n                condition = service_quote.get(\"condition\")\n                quote_info = {\n                    \"alert\": \"{} {} alert\".format(service_name, index_type),\n                    \"annotations\": {\n                        \"consignee\": \"{}\".format(\"\"),  # TODO\n                        \"description\": replace_value(description.get(index_type), condition_value=condition_value),\n                        \"summary\": gen_summary(index_type=index_type),\n                    },\n                    \"expr\": replace_value(expr.get(index_type), env_name=env_name, condition=condition,\n                                          condition_value=condition_value),\n                    \"for\": \"1m\",\n                    \"labels\": {\n                        \"severity\": alert_level,\n                    }\n\n                }\n                logger.info(quote_info)\n                service_rules[\"rules\"].append(quote_info)\n        logger.info('开始更新服务告警规则文件{}'.format(service_rule_yml_path))\n        with open(service_rule_yml_path, \"w\") as fw:\n            yaml.dump(dict_total_rules, fw, allow_unicode=True)\n        logger.info(\"更新服务告警规则文件成功\")\n        return True\n    except Exception as e:\n        logger.error(e)\n        return False\n\n\ndef config_update(quotes_info):\n    \"\"\"\n    :param quotes_info: 相关指标阈值信息\n    :return:\n    {\"env_name\": \"env_name\",\n    \"hosts\": [\n        {\"index_type\": \"cpu_used\",\"condition\": \">=\",\"condition_value\": \"80\",\"alert_level\": \"warning\"},\n        {\"index_type\": \"cpu_used\",\"condition\": \">=\",\"condition_value\": \"90\",\"alert_level\": \"critical\"},\n        {\"index_type\": \"memory_used\",\"condition\": \">=\",\"condition_value\": \"80\",\"alert_level\": \"warning\"},\n        {\"index_type\": \"memory_used\",\"condition\": \">=\",\"condition_value\": \"90\",\"alert_level\": \"critical\"},\n        {\"index_type\": \"disk_root_used\",\"condition\": \">=\",\"condition_value\": \"80\",\"alert_level\": \"warning\"},\n        {\"index_type\": \"disk_root_used\",\"condition\": \">=\",\"condition_value\": \"90\",\"alert_level\": \"critical\"},\n        {\"index_type\": \"disk_data_used\",\"condition\": \">\",\"condition_value\": \"80\",\"alert_level\": \"warning\"},\n        {\"index_type\": \"disk_data_used\",\"condition\": \">\",\"condition_value\": \"90\",\"alert_level\": \"critical\"}\n            ],\n    \"services\": {\n        \"kafka\": [\n            {\"index_type\": \"kafka_consumergroup_lag\",\"condition\": \">\",\"condition_value\": 400,\"alert_level\": \"warning\"},\n            {\"index_type\": \"kafka_consumergroup_lag\",\"condition\": \">\",\"condition_value\": 600,\"alert_level\": \"critical\"}\n                ]\n                }\n    }\n    \"\"\"\n    logger.info('收到阈值更新json：{}'.format(quotes_info))\n    # quotes_info = json.loads(quotes_info)\n    # 更新主机规则文件\n    update_node_mark = update_node_rule_yaml(quotes_info)\n    if not update_node_mark:\n        logger.error(\"更新主机告警规则失败\")\n    # 更新服务规则文件\n    update_service_mark = update_service_rule_yaml(quotes_info)\n    if not update_service_mark:\n        logger.error(\"更新服务告警规则失败\")\n    try:\n        from promemonitor.prometheus import Prometheus\n        url = \"http://{}/-/reload\".format(Prometheus.get_prometheus_config())  # NOQA\n        response = requests.request(\"POST\", url)\n        if response.status_code == 200:\n            logger.info('重载prometheus配置成功！')\n        else:\n            logger.error('重载prometheus配置失败！')\n    except ConnectionRefusedError:\n        logger.error('重载prometheus配置失败！')\n    return True, None\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(\n        usage=\"it's usage tip.\", description=\"help info.\")\n    parser.add_argument(\"--threshold-json\",\n                        dest=\"threshold_json\", help=\"the json of threshold\")\n    args = parser.parse_args()\n\n    threshold_json = args.threshold_json\n\n    config_update(threshold_json)\n"
  },
  {
    "path": "omp_server/utils/prometheus/utils.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: utils\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-31 14:07\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n公共数据问题\n\"\"\"\n\nfrom db_models.models import Host\n\n\ndef get_host_data_folder(instance):\n    \"\"\"\n    解析主机数据，获取主机磁盘分区数据\n    :param instance:\n    :return:\n    \"\"\"\n    item = Host.objects.filter(ip=instance).last()\n    if not item:\n        return \"\"\n    _data_folder = item.data_folder\n    # {\"/\": 90, \"/data\": 100}\n    _disk_info = item.disk\n    if not _disk_info:\n        _disk_info = Host.objects.get(id=item.id).disk\n    data_path = \"\"\n    if _disk_info and isinstance(_disk_info, dict):\n        for key, _ in _disk_info.items():\n            if key == \"/\":\n                continue\n            _check_data_folder = _data_folder.rstrip(\"/\") + \"/\"\n            _check_key = key.rstrip(\"/\") + \"/\"\n            if _check_data_folder.startswith(_check_key):\n                data_path = key\n                break\n    return data_path\n"
  },
  {
    "path": "omp_server/utils/response_handler.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: response_handler\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-10 21:36\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n重新封装的响应数据类\n\"\"\"\n\nfrom rest_framework.renderers import JSONRenderer\n\n\nclass APIRenderer(JSONRenderer):\n    \"\"\"自定义响应数据类\"\"\"\n\n    def render(self, data, accepted_media_type=None, renderer_context=None):\n        \"\"\"\n        自定义render返回数据\n        :param data: 返回数据\n        :param accepted_media_type:\n        :param renderer_context:\n        :return:\n        \"\"\"\n        dic = {\"code\": 0, \"message\": \"success\", \"data\": None}\n        if isinstance(data, dict):\n            if data.get(\"code\") == 1:\n                dic = {\"code\": 1, \"message\": data.get(\"message\"), \"data\": None}\n            elif \"non_field_errors\" in data:\n                if isinstance(data.get(\"non_field_errors\"), list):\n                    _message = \"\"\n                    for item in data.get(\"non_field_errors\"):\n                        _message += f\"{item} \"\n                    dic = {\"code\": 1, \"message\": _message, \"data\": None}\n            else:\n                dic = {\"code\": 0, \"message\": \"success\", \"data\": data}\n        else:\n            dic = {\"code\": 0, \"message\": \"success\", \"data\": data}\n        return super().render(\n            data=dic,\n            accepted_media_type=accepted_media_type,\n            renderer_context=renderer_context)\n"
  },
  {
    "path": "omp_web/README.md",
    "content": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scripts\n\nIn the project directory, you can run:\n\n### `yarn start`\n\nRuns the app in the development mode.\\\nOpen [http://localhost:3000](http://localhost:3000) to view it in the browser.\n\nThe page will reload if you make edits.\\\nYou will also see any lint errors in the console.\n\n### `yarn test`\n\nLaunches the test runner in the interactive watch mode.\\\nSee the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.\n\n### `yarn build`\n\nBuilds the app for production to the `build` folder.\\\nIt correctly bundles React in production mode and optimizes the build for the best performance.\n\nThe build is minified and the filenames include the hashes.\\\nYour app is ready to be deployed!\n\nSee the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.\n\n### `yarn eject`\n\n**Note: this is a one-way operation. Once you `eject`, you can’t go back!**\n\nIf you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.\n\nInstead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.\n\nYou don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.\n\n## Learn More\n\nYou can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).\n\nTo learn React, check out the [React documentation](https://reactjs.org/).\n\n### Code Splitting\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)\n\n### Analyzing the Bundle Size\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)\n\n### Making a Progressive Web App\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)\n\n### Advanced Configuration\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)\n\n### Deployment\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)\n\n### `yarn build` fails to minify\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)\n"
  },
  {
    "path": "omp_web/config-overrides.js",
    "content": "const {\n  addWebpackAlias,\n  override,\n  overrideDevServer,\n  addLessLoader,\n  addPostcssPlugins,\n  fixBabelImports,\n  addWebpackPlugin,\n} = require(\"customize-cra\");\nconst ProgressBarPlugin = require(\"progress-bar-webpack-plugin\");\nconst UglifyJsPlugin = require(\"uglifyjs-webpack-plugin\");\n\nconst path = require(\"path\");\n// 跨域配置\nconst devServerConfig = () => (config) => {\n  return {\n    ...config,\n    proxy: {\n      \"/api\": {\n        target: \"http://10.0.14.200:19001/\",\n        changeOrigin: true,\n      },\n    },\n  };\n};\n\nmodule.exports = {\n  webpack: override(\n    fixBabelImports(\"import\", {\n      libraryName: \"antd\",\n      libraryDirectory: \"es\",\n      style: true, //自动打包相关的样式 默认为 style:'css'\n    }),\n    // 使用less-loader对源码重的less的变量进行重新制定，设置antd自定义主题\n    addLessLoader({\n      javascriptEnabled: true,\n      modifyVars: {\n        \"@primary-color\": \"#4986f7\",\n        \"@text-color\": \"rgba(0,0,0,0.65)\",\n        // \"@border-radius-base\": \"4px\"\n      },\n    }),\n    // addPostcssPlugins([require(\"postcss-px2rem-exclude\")({\n    //     remUnit: 16,\n    //     propList: ['*'],\n    //     exclude: ''\n    // })]),\n    addWebpackPlugin(new ProgressBarPlugin()),\n    process.env.NODE_ENV === \"production\" &&\n      addWebpackPlugin(\n        new UglifyJsPlugin({\n          // 开启打包缓存\n          cache: true,\n          // 开启多线程打包\n          parallel: true,\n          uglifyOptions: {\n            // 删除警告\n            warnings: false,\n            // 压缩\n            compress: {\n              // 移除console\n              drop_console: true,\n              // 移除debugger\n              drop_debugger: true,\n            },\n          },\n        })\n      ),\n    addWebpackAlias({\n      \"@\": path.resolve(__dirname, \"./src\"),\n      assets: path.resolve(__dirname, \"./src/assets\"),\n      components: path.resolve(__dirname, \"./src/components\"),\n      pages: path.resolve(__dirname, \"./src/pages\"),\n      common: path.resolve(__dirname, \"./src/common\"),\n    }),\n    (config) => {\n      if (process.env.NODE_ENV === \"production\") config.devtool = false;\n      if (process.env.NODE_ENV === \"production\") {\n        const paths = require(\"react-scripts/config/paths\");\n        paths.appBuild = path.join(path.dirname(paths.appBuild), \"dist\");\n        config.output.path = path.join(\n          path.dirname(config.output.path),\n          \"dist\"\n        );\n      }\n      return config;\n    }\n  ),\n  devServer: overrideDevServer(devServerConfig()),\n};\n"
  },
  {
    "path": "omp_web/package.json",
    "content": "{\n  \"name\": \"omp-fontend\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@testing-library/jest-dom\": \"^5.11.4\",\n    \"@testing-library/react\": \"^11.1.0\",\n    \"@testing-library/user-event\": \"^12.1.10\",\n    \"antd\": \"4.17.1-alpha.1\",\n    \"axios\": \"^0.21.1\",\n    \"babel-plugin-import\": \"^1.13.3\",\n    \"browser-md5-file\": \"^1.1.1\",\n    \"customize-cra\": \"^1.0.0\",\n    \"echarts\": \"^5.1.2\",\n    \"echarts-for-react\": \"^3.0.1\",\n    \"highlight.js\": \"^11.4.0\",\n    \"http-proxy-middleware\": \"2.0\",\n    \"jsencrypt\": \"3.0.0-rc.1\",\n    \"less-loader\": \"5.0.0\",\n    \"markdown-it\": \"^12.3.2\",\n    \"markdown-it-colors\": \"^2.0.4\",\n    \"moment\": \"2.29.1\",\n    \"postcss-px2rem-exclude\": \"^0.0.6\",\n    \"postcss-pxtorem\": \"^6.0.0\",\n    \"ramda\": \"0.27.1\",\n    \"react\": \"^17.0.2\",\n    \"react-app-rewired\": \"^2.1.8\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-redux\": \"^7.2.4\",\n    \"react-router-dom\": \"^5.2.0\",\n    \"react-scripts\": \"4.0.3\",\n    \"redux\": \"^4.1.0\",\n    \"typescript\": \"^4.3.4\",\n    \"uglifyjs-webpack-plugin\": \"^2.2.0\",\n    \"web-vitals\": \"^1.0.1\",\n    \"xlsx\": \"0.16.0\"\n  },\n  \"scripts\": {\n    \"start\": \"react-app-rewired start\",\n    \"build\": \"CI=false && react-app-rewired build\",\n    \"test\": \"react-app-rewired test\",\n    \"eject\": \"react-app-rewired eject\",\n    \"publish\": \"rsync -avzP --delete -e 'ssh -p36000' ./dist/* root@10.0.9.67:/root/domh/ompServer/frontend/\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ],\n    \"rules\": {\n      \"no-undef\": \"off\",\n      \"no-restricted-globals\": \"off\",\n      \"no-unused-vars\": \"off\"\n    }\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"compression-webpack-plugin\": \"^8.0.0\",\n    \"less\": \"^4.1.1\",\n    \"progress-bar-webpack-plugin\": \"^2.1.0\",\n    \"react-app-rewire-less\": \"^2.1.3\",\n    \"react-app-rewire-less-modules\": \"^1.3.0\"\n  }\n}\n"
  },
  {
    "path": "omp_web/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- 文档兼容模式的定义 -->\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <!-- 优先使用webkit内核渲染页面 -->\n    <meta name=\"renderer\" content=\"webkit\" />\n    <!-- 设置可视区域宽度与设备屏幕缩放比例为1.0 -->\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>云智慧</title>\n    <meta name=\"keywords\" content=\"cloudwise,front-end\" />\n    <meta name=\"description\" content=\"front-end\" />\n    <meta name=\"author\" content=\"cloudwiser\" />\n  </head>\n  <body>\n    <div id=\"root\" style=\"height: 100%\"></div>\n    <script>\n      var ajax = new XMLHttpRequest();ajax.open(\"get\",\"./pubKey.json\");ajax.send();ajax.onreadystatechange=function(){if(ajax.readyState==4&&ajax.status==200){window.PublicKey=JSON.parse(ajax.responseText).publicKey}};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "omp_web/public/pubKey.json",
    "content": "{\"publicKey\":\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwv4dqlvcYtrPJsCL/VuX\\n0u4FZm2E0du1m01gUnp3afSkx+u2GTXptpS7dNfTLguu1HjJUzkEIGaJGG/x/PR2\\nZs6I/UmIFWj6tdmfBBlrVRETnm8tCAdO9/1zjzz4wB2yuHduBK6TYwhXfZOCg3LO\\nj+QVpUYqyq3lqjPN+C6QbFzgk8FwMHr+R3OzZe9nsaNZOHRbSmu6NU5zkIdnScQw\\nIiWIe9nZMpoTUe45FtYPj7SHiCItDOtbXGbyNOP5k5RhIQtJiEJgVOzGeRSaQAj6\\nUfLClsa43ZXfXIZ+BfOO8GZBXmHeRHRs/Prw3Io4n0gXKpgrd6MxwfpoxmXEyoRU\\nGwIDAQAB\"}"
  },
  {
    "path": "omp_web/src/App.js",
    "content": "import Router from \"./router.js\";\nimport { Provider } from \"react-redux\";\nimport store from \"@/store_redux/reduxStore\";\n// 国际化\nimport zhCN from 'antd/es/locale/zh_CN';\nimport { ConfigProvider } from \"antd\";\nimport 'moment/locale/zh-cn'\n\nconst App = () => {\n  return (\n    <ConfigProvider locale={zhCN}>\n      <Provider store={store}>\n        <Router />\n      </Provider>\n    </ConfigProvider>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/index.js",
    "content": "import { Breadcrumb, message } from \"antd\";\nimport React, { useState, useEffect, useContext, useLayoutEffect } from \"react\";\nimport { withRouter } from \"react-router-dom\";\nimport styles from \"./index.module.less\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse, refreshTime } from \"@/utils/utils\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport { AlertFilled } from \"@ant-design/icons\";\nimport { OmpMessageModal } from \"@/components\";\nimport { ExclamationCircleOutlined } from \"@ant-design/icons\";\nimport { getMaintenanceChangeAction } from \"@/pages/SystemManagement/store/actionsCreators\";\n/*eslint-disable*/\n//跟路由路径保持一致\nconst breadcrumbNameMap = {\n  404: \"404\",\n  //\"/\":\"仪表盘\",\n  homepage: \"仪表盘\",\n  \"resource-management\": \"资源管理\",\n  \"machine-management\": \"主机管理\",\n  \"system-settings\": \"系统管理\",\n  \"user-management\": \"用户管理\",\n  \"monitoring-settings\": \"监控设置\",\n  \"system-management\": \"系统管理\",\n  \"alarm-log\": \"告警记录\",\n  \"exception-list\": \"异常清单\",\n  \"application-monitoring\": \"应用监控\",\n  application_management: \"应用管理\",\n  app_store: \"应用商店\",\n  \"app-service-detail\": \"服务详情\",\n  \"app-component-detail\": \"组件详情\",\n  \"status-patrol\": \"状态巡检\",\n  \"patrol-inspection-record\": \"巡检记录\",\n  \"patrol-strategy\": \"巡检策略\",\n  \"status-patrol-detail\": \"分析报告\",\n  service_management: \"服务管理\",\n  application_installation: \"应用安装\",\n  component_installation: \"组件安装\",\n  installation: \"安装\",\n  \"email-settings\": \"邮件管理\",\n  \"rule-center\": \"指标中心\",\n  \"default-rule\": \"默认指标\",\n  \"install-record\": \"执行记录\",\n  service_upgrade: \"服务升级\",\n  \"deployment-plan\": \"部署模板\",\n  \"data-backup\": \"数据备份\",\n  \"backup-record\": \"备份记录\",\n  \"operation-record\": \"操作记录\",\n  \"login-log\": \"登录日志\",\n  \"system-log\": \"系统记录\",\n  \"fault-selfHealing\":\"故障自愈\",\n  \"selfHealing-record\":\"自愈记录\",\n  \"selfHealing-strategy\":\"自愈策略\",\n  \"utilitie\":\"实用工具\",\n  \"tool-management\":\"工具管理\",\n  \"tool-management-detail\": \"工具详情\",\n  \"task-record\":\"任务记录\",\n  \"tool-execution-results\":\"执行结果\",\n  \"indicator-rule\": \"指标规则\",\n  \"extend-rule\": \"扩展指标\"\n};\n\n// 基于面包屑组件的一层封装，用于匹配当前路由地址，动态展示页面路径\nconst CustomBreadcrumb = withRouter(({ location, collapsed }) => {\n  const dispatch = useDispatch();\n  const [loading, setLoading] = useState(false);\n  //是否展示维护模式提示词\n  const time = useSelector((state) => state.customBreadcrumb.time);\n\n  const isMaintenance = useSelector(\n    (state) => state.systemManagement.isMaintenance\n  );\n\n  const [closeMaintenanceModal, setCloseMaintenanceModal] = useState(false);\n\n  //const appContext = useContext(context);\n\n  //定义在首页时当前组件展示的时间\n  const [curentTime, setCurentTime] = useState(\"\");\n\n  const pathSnippets = location.pathname;\n\n  const extraBreadcrumbItems = (_, index) => {\n    //console.log(pathSnippets);\n    const url = pathSnippets.split(\"/\"); //`/${pathSnippets.slice(0, index + 1).join(\"/\")}`\n\n    return (\n      <>\n        {url.map((i, idx) => {\n          if (idx == url.length - 3) {\n            if (!breadcrumbNameMap[url[url.length - 2]]) {\n              return (\n                <Breadcrumb.Item\n                  style={{\n                    color: \"#2e7cee\",\n                    fontSize: 14,\n                  }}\n                  key={i}\n                >\n                  {breadcrumbNameMap[i]}\n                </Breadcrumb.Item>\n              );\n            }\n          }\n\n          if (idx == url.length - 2) {\n            // 动态路由的时候url的最后一项不一定能体现当前页面，也有可能是动态参数\n            if (!breadcrumbNameMap[url[url.length - 1]]) {\n              return (\n                <Breadcrumb.Item\n                  style={{\n                    color: \"#2e7cee\",\n                    fontSize: 14,\n                  }}\n                  key={i}\n                >\n                  {breadcrumbNameMap[i]}\n                </Breadcrumb.Item>\n              );\n            }\n          }\n\n          if (idx == url.length - 1) {\n            return (\n              <Breadcrumb.Item\n                style={{\n                  color: \"#2e7cee\",\n                  fontSize: 14,\n                }}\n                key={i}\n              >\n                {breadcrumbNameMap[i]}\n              </Breadcrumb.Item>\n            );\n          } else {\n            return (\n              <Breadcrumb.Item\n                style={{\n                  color: \"#8b8b8b\",\n                  fontSize: 14,\n                }}\n                key={i}\n              >\n                {breadcrumbNameMap[i]}\n              </Breadcrumb.Item>\n            );\n          }\n        })}\n      </>\n    );\n  };\n\n  //在组件初始化时获取当前时间\n  useEffect(() => {\n    //console.log(extraBreadcrumbItems);\n    //因为在首页才会有时间展示，添加判断\n    dispatch(refreshTime());\n  }, []);\n\n  // 更改维护模式\n  const changeMaintain = (e) => {\n    setLoading(true);\n    fetchPost(apiRequest.environment.queryMaintainState, {\n      body: {\n        matcher_name: \"env\",\n        matcher_value: \"default\",\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          //console.log(res)\n          if (res.code == 0) {\n            if (e) {\n              message.success(\"已进入全局维护模式\");\n              dispatch(getMaintenanceChangeAction(true));\n            } else {\n              message.success(\"已退出全局维护模式\");\n              dispatch(getMaintenanceChangeAction(false));\n            }\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setCloseMaintenanceModal(false);\n      });\n  };\n\n  return (\n    <div\n      className={styles.customNav}\n      style={{ marginLeft: collapsed ? 50 : 200 }}\n    >\n      {/* <div> */}\n      <Breadcrumb>{extraBreadcrumbItems()}</Breadcrumb>\n      {/* </div> */}\n      {isMaintenance ? (\n        <span className={styles.timeStampContainer}>\n          <AlertFilled\n            style={{ fontSize: \"14px\", color: \"rgba(247, 207, 54)\" }}\n            type=\"alert\"\n            theme=\"filled\"\n          />{\" \"}\n          当前处于维护模式, 退出维护模式请点击\n          <span\n            onClick={() => {\n              setCloseMaintenanceModal(true);\n            }}\n            style={{ color: \"#2e7cee\", cursor: \"pointer\" }}\n          >\n            {\" \"}\n            这里\n          </span>\n        </span>\n      ) : (\n        <span />\n      )}\n      {/* <span /> */}\n      {/* <span className={styles.timeStampContainer} style={{ paddingRight: 0 }}>\n        刷新时间: {time}\n      </span> */}\n      <OmpMessageModal\n        visibleHandle={[closeMaintenanceModal, setCloseMaintenanceModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          changeMaintain(false);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>确定退出全局维护模式 ？</div>\n      </OmpMessageModal>\n    </div>\n  );\n});\nexport default React.memo(CustomBreadcrumb);\n/*eslint-disable*/\n"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/index.module.less",
    "content": ".customNav {\n  transition: all 0.2s ease-in-out;\n  margin-top: 60px;\n  position:fixed;\n  z-Index:1000;\n  //margin-left:200px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  box-sizing: border-box;\n  width: 100%;\n  background-color: white;\n  height: 45px;\n  padding-left: 20px;\n  padding-right: 20px;\n  margin-bottom: 15px;\n  font-size: 14px;\n  //background-color: #fff;\n  margin-left: 10px;\n  border-left: 1px solid #e7e7e7;\n  // /font-size: 16px;\n  color: black;\n  //padding-left: 20px;\n  // fontWeight: 400,\n  // color: \"black\",\n  // paddingLeft: 20,\n  //height: 52px;\n  padding-top: 5px;\n  padding-bottom: 5px;\n  // display: \"flex\",\n  // alignItems: \"center\",\n  // borderBottom: \"1px solid #dcdee5\",\n  & > div:last-child {\n    margin-left: auto;\n  }\n}\n\n.timeStampContainer {\n  font-size: 13px;\n  color: #8b8b8b;\n  padding-right: 220px;\n  // position: relative;\n  // left: -100px;\n}"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/store/actionsCreators.js",
    "content": "import * as actionTypes from \"./constants\";\n\nexport const getMaintenanceChangeAction = (value) => ({\n    type: actionTypes.CHANGE_MAINTENANCE,\n    payload: {\n        isMaintenance:value\n    }\n});\n\nexport const getRefreshTimeChangeAction = (value) => ({\n    type: actionTypes.CHANGE_REFRESHTIME,\n    payload: {\n        time:value\n    }\n});"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/store/constants.js",
    "content": "export const CHANGE_MAINTENANCE = \"CHANGE_MAINTENANCE\";\n\nexport const CHANGE_REFRESHTIME = \"CHANGE_REFRESHTIME\";"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/store/index.js",
    "content": "import reducer from \"./reduer\";\n\n export {\n    reducer\n };"
  },
  {
    "path": "omp_web/src/components/CustomBreadcrumb/store/reduer.js",
    "content": "import * as actionTypes from \"./constants\";\n\nconst defaultState = {\n    isMaintenance:false\n};\n\nfunction reducer(state = defaultState,action){\n    switch(action.type){\n        case actionTypes.CHANGE_MAINTENANCE:\n            return {...state, isMaintenance: action.payload.isMaintenance};\n        case actionTypes.CHANGE_REFRESHTIME:\n            return {...state, time: action.payload.time};\n        default:\n            return state;\n    }\n}\n\nexport default reducer;"
  },
  {
    "path": "omp_web/src/components/OmpButton/index.js",
    "content": "import { Button } from \"antd\";\n\nconst OmpButton = (props) => {\n  return (\n    <Button\n      ref={\n        ((node) => {\n          if (node) {\n            if(props.disabled){\n                node.style.setProperty(\"color\", \"#d7d7d7\", \"important\")\n                node.style.setProperty(\"background-color\", \"#f3f3f3\", \"important\")\n            }else{\n                node.style.setProperty(\"color\", null)\n                node.style.setProperty(\"background-color\", null)\n            }\n          }\n        })\n      }\n      {...props}\n    />\n  );\n};\n\nexport default OmpButton;\n"
  },
  {
    "path": "omp_web/src/components/OmpCollapseWrapper/index.js",
    "content": "import { useState, useEffect } from \"react\";\nimport styles from \"./index.module.less\";\nimport { Form, Button, Select, Drawer, Tag } from \"antd\";\nimport { FunnelPlotFilled, CloseOutlined } from \"@ant-design/icons\";\n\nconst OmpCollapseWrapper = ({\n  children,\n  onFinish,\n  form,\n  onReset,\n  initialValues = {},\n  operation,\n}) => {\n  const [defaultForm] = Form.useForm();\n  const formInstance = form ? form : defaultForm;\n  const [visible, setVisible] = useState(false);\n\n  const [searchTags, setSearchTags] = useState({});\n\n  let childrenArr = Array.isArray(children) ? children : [children];\n\n  // 提取form表单中的数据，将name和label关联起来,如果是select框因为value和text不一致，也要存进来\n  // 为了获取检索项的渲染数据\n  let dictionary = childrenArr.map((item) => {\n    return {\n      label: item.props.label,\n      name: item.props.name,\n      //有children代表是select\n      children: item.props.children?.map((i) => i.props),\n    };\n  });\n\n  const renderButtonGroup = () => {\n    return (\n      <div style={{ width: \"100%\", marginLeft: \"190px\", marginTop: \"60px\" }}>\n        <Form.Item>\n          <Button type=\"primary\" htmlType=\"submit\" style={{ marginRight: 10 }}>\n            查询\n          </Button>\n          <Button\n            style={{ marginRight: 10 }}\n            onClick={() => {\n              setSearchTags({});\n              formInstance.resetFields();\n              onReset && onReset();\n              setVisible(false);\n            }}\n          >\n            重置\n          </Button>\n        </Form.Item>\n      </div>\n    );\n  };\n\n  useEffect(() => {\n    console.log(searchTags);\n  }, [searchTags]);\n\n  return (\n    <div className={styles.OmpCollapseWrapper}>\n      <div\n        style={{\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div>{operation}</div>\n\n        <Form\n          form={formInstance}\n          name=\"basic\"\n          className={styles.OmpCollapseFormWrapper}\n          layout=\"inline\"\n          initialValues={initialValues}\n          onFinish={() => {\n            let formData = formInstance.getFieldValue();\n            console.log(formData);\n            onFinish();\n          }}\n        >\n          <Form.Item\n            label=\"IP地址\"\n            key=\"alert_ip\"\n            name=\"alert_ip\"\n            labelAlign=\"right\"\n          >\n            <Select placeholder=\"请选择IP地址\" style={{ width: 200 }}>\n              <Select.Option value=\"10.0.9.61\">10.0.9.61</Select.Option>\n              <Select.Option value=\"10.0.9.62\">10.0.9.62</Select.Option>\n              <Select.Option value=\"10.0.9.63\">10.0.9.63</Select.Option>\n              <Select.Option value=\"all\">全部</Select.Option>\n            </Select>\n          </Form.Item>\n          <Button\n            onClick={() => {\n              setVisible(true);\n            }}\n            style={{\n              margin: 10,\n              marginLeft: 0,\n              marginRight: 15,\n              paddingLeft: 10,\n              paddingRight: 10,\n            }}\n          >\n            <FunnelPlotFilled style={{ position: \"relative\", top: 1 }} />\n          </Button>\n        </Form>\n      </div>\n      {Object.keys(searchTags).length > 0 && (\n        <div\n          style={{\n            position: \"relative\",\n            top: -5,\n            paddingLeft: 5,\n            fontSize: \"12px\",\n            height: 25,\n            display: \"flex\",\n            alignItems: \"center\",\n          }}\n        >\n          <FunnelPlotFilled />{\" \"}\n          <span style={{ paddingRight: 5, paddingLeft: 2 }}>检索项 : </span>\n          {Object.keys(searchTags).map((key) => {\n            return (\n              <Tag\n                color=\"#eeeff3\"\n                style={{ height: 20, color: \"#63656E\" }}\n                key={key}\n                closable\n                closeIcon={<CloseOutlined style={{ color: \"#63656E\" }} />}\n                onClose={(e) => {\n                  e.preventDefault();\n                  let willDeleteItem = dictionary.filter(\n                    (item) => item.label == key\n                  );\n                  formInstance.resetFields([willDeleteItem[0].name]);\n                  setSearchTags((tags) => {\n                    let newTags = { ...tags };\n                    delete newTags[key];\n                    return newTags;\n                  });\n                }}\n              >\n                {`${key}=${searchTags[key]}`}\n              </Tag>\n            );\n          })}\n        </div>\n      )}\n      <Drawer\n        title=\"高级筛选\"\n        placement=\"right\"\n        onClose={() => setVisible(false)}\n        visible={visible}\n        width={560}\n        bodyStyle={{\n          paddingLeft: 20,\n          paddingRight: 20,\n        }}\n      >\n        <Form\n          form={formInstance}\n          name=\"basic\"\n          className={styles.OmpCollapseFormWrapper}\n          layout=\"inline\"\n          initialValues={initialValues}\n          //onFinish={onFinish}\n          onFinish={(e) => {\n            let formData = formInstance.getFieldValue();\n            //console.log(formData,e,childrenArr)\n            Object.keys(formData).map((i) => {\n              let [info] = dictionary.filter((item) => item.name == i);\n              // console.log(info,formData[i])\n              if (formData[i]) {\n                let value = formData[i];\n                //如果是select，要展示text，根据value检索text\n                if (info.children) {\n                  info.children.map((c) => {\n                    if (c.value == value) {\n                      value = c.children;\n                    }\n                  });\n                }\n                setSearchTags((tags) => {\n                  return {\n                    ...tags,\n                    [info.label]: value,\n                  };\n                });\n              }\n            });\n            setVisible(false);\n            onFinish();\n          }}\n        >\n          {childrenArr.map((item) => {\n            return (\n              <div key={item.props.label}>\n                <Form.Item\n                  label={<span style={{ width: 65 }}>{item.props.label}</span>}\n                  key={item.props.label}\n                  name={item.props.label && item.props.name}\n                  labelAlign=\"right\"\n                  style={{ width: `242px`, marginBottom: 15 }}\n                >\n                  {item}\n                </Form.Item>\n              </div>\n            );\n          })}\n          {renderButtonGroup()}\n        </Form>\n      </Drawer>\n    </div>\n  );\n};\n\nexport default OmpCollapseWrapper;\n"
  },
  {
    "path": "omp_web/src/components/OmpCollapseWrapper/index.module.less",
    "content": ".OmpCollapseWrapper {\n  width: 100%;\n  padding-top: 5px;\n  // background-color: #fff;\n  // border: 1px solid rgb(220, 222, 229);\n  // margin-top: 10px;\n  // margin-bottom: 10px;\n\n  .OmpCollapseFormWrapper {\n    display: flex;\n    position: relative;\n    //left: -20px;\n    left: 15px;\n    //padding-left: 10px;\n    > div {\n      display: flex;\n      flex-wrap: wrap;\n      padding-bottom: 10px;\n      >div {\n        //width: 0px;\n        padding-top: 10px;\n      }\n    }\n  }\n}\n:global {\n  .ant-picker-input {\n    input {\n      text-align: center;\n    }\n  }\n}"
  },
  {
    "path": "omp_web/src/components/OmpCollapseWrapper/indexOld.js",
    "content": "import { useState, useEffect, useLayoutEffect } from \"react\";\nimport styles from \"./index.module.less\";\nimport { Form, Input, Button } from \"antd\";\nimport {\n  DownOutlined,\n} from \"@ant-design/icons\";\nimport { useSelector } from \"react-redux\";\n\nconst OmpCollapseWrapper = ({ children, onFinish, form, onReset, initialValues={} }) => {\n  const [isExpand, setIsExpand] = useState(false)\n  const [ defaultForm ] = Form.useForm();\n  const formInstance = form?form:defaultForm\n  let childrenArr = Array.isArray(children) ? children : [children];\n  // 视口宽度\n  const viewWidth = useSelector(state => state.layouts.viewSize.width);\n  //每个formitem 的初始值定为255\n  const [itemWidth, setItemWidth] = useState(255)\n  // 计算当前组件的宽度 = 窗口宽度 - 左侧asideMenu宽度 - content区域的padding*2\n  let width = viewWidth - 240 - 20*2\n  // 计算在当前窗口宽度下，一行能放置几个formItem\n  let num = (width - 240)/itemWidth\n  let widthSurplus = (width - 240)%itemWidth/num\n\n  //console.log(width,num,widthSurplus)\n\n  useLayoutEffect(()=>{\n    // 根据剩余宽度重新计算每个foritem的宽度（目的是当剩余宽度过多，增加formitem宽度）\n    setItemWidth((w)=>w + Number(widthSurplus) - 28)\n  },[])\n\n  let result = childrenArr.slice(0,num)\n\n  const renderButtonGroup = ()=>{\n    return (\n      <div style={{ position:\"absolute\", right:0,bottom:0 }}>\n          <Form.Item style={{ float: \"right\",marginLeft:\"auto\" }}>\n            <div style={{ float: \"right\" }}>\n              <Button\n                type=\"primary\"\n                htmlType=\"submit\"\n                style={{ marginRight: 10 }}\n              >\n                查询\n              </Button>\n              <Button style={{ marginRight: 10 }} onClick={()=>{formInstance.resetFields();onReset&&onReset()}}>重置</Button>\n              {\n                childrenArr.length > num &&  <span style={{ color: \"#1890ff\", fontSize: 14, position:\"relative\", top:2, cursor:\"pointer\" }} onClick={()=>setIsExpand(!isExpand)}>\n                <DownOutlined style={{position:\"relative\",top:\"1px\"}} rotate={isExpand?180:0}/>\n                 {isExpand?\" 收起\":\" 展开\"}\n              </span>\n              }\n            </div>\n          </Form.Item>\n        </div>\n    )\n  }\n\n  return (\n    <div \n    //ref={measuredRef} \n    //id=\"warpper\" \n    //onClick={()=> console.log(height)} \n    className={styles.OmpCollapseWrapper} \n  //  / style={{height:60,overflow:\"hidden\"}}\n    >\n      <Form\n        form = {formInstance}\n        name=\"basic\"\n        className={styles.OmpCollapseFormWrapper}\n        layout=\"inline\"\n        initialValues={initialValues}\n        onFinish={onFinish}\n      >\n        {(isExpand?childrenArr:result).map((item) => {\n          return (\n            <div key={item.props.label}>\n              <Form.Item\n                label={<span style={{ width: 70 }}>{item.props.label}</span>}\n                key={item.props.label}\n                name={item.props.label && item.props.name}\n                labelAlign=\"right\"\n                style={{width:item.props.width || `${itemWidth}px`}}\n              >\n                {item}\n              </Form.Item>\n            </div>\n          );\n        })}\n        <div style={{height:45,width:itemWidth}}><div></div></div>\n        {renderButtonGroup()}\n      </Form>\n    </div>\n  );\n};\n\nexport default OmpCollapseWrapper;\n"
  },
  {
    "path": "omp_web/src/components/OmpContentNav/index.js",
    "content": "import styles from \"./index.module.less\";\n\n// 用于面包屑导航组件下部的 切换页面content的导航\nconst OmpContentNav = ({ data, currentFocus }) => {\n  const focusedStyle = {\n    color: \"#4986f7\",\n    borderBottom: \"2px solid #4986f7\",\n    //paddingBottom:10\n    height: \"35px\",\n    marginRight: 15,\n    zIndex: 2,\n  };\n\n  return (\n    <>\n      <div className={styles.warningListHeader}>\n        {data.map((item, index) => {\n          return (\n            <div\n              key={index}\n              style={\n                currentFocus === item.name\n                  ? focusedStyle\n                  : { height: \"35px\", marginRight: 15 }\n              }\n              onClick={() => item.handler()}\n            >\n              {item.text}\n            </div>\n          );\n        })}\n      </div>\n      <div style={{ backgroundColor: \"#bfbfbf\", height: 1, zIndex: 1 }} />\n    </>\n  );\n};\nexport default OmpContentNav;\n"
  },
  {
    "path": "omp_web/src/components/OmpContentNav/index.module.less",
    "content": ".warningListHeader {\n  display: flex;\n  flex-flow: row nowrap;\n  align-items: center;\n  //border-bottom: 1px solid rgb(220, 222, 229);\n  font-weight: 500;\n  padding-top: 20px;\n  //color: #333;\n  //padding-bottom: 10px;\n  //padding-right:35px;\n  padding-left: 10px;\n  & > div {\n    height: 28px;\n    padding: 0 10px;\n    margin: 0 5px -1.5px;\n    cursor: pointer;\n  }\n}\n"
  },
  {
    "path": "omp_web/src/components/OmpContentWrapper/index.js",
    "content": "import styles from \"./index.module.less\";\n\nfunction OmpContentWrapper({ children, wrapperStyle }) {\n\n  return (\n    <div style={wrapperStyle} className={styles.contentWrapper}>\n      {children}\n    </div>\n  );\n}\n\nexport default OmpContentWrapper;\n"
  },
  {
    "path": "omp_web/src/components/OmpContentWrapper/index.module.less",
    "content": ".contentWrapper {\n  background-color: white;\n  padding: 10px;\n  padding-bottom: 30px;\n  //height: 100%;\n  // color: rgba(0,0,0,0.65);\n}\n"
  },
  {
    "path": "omp_web/src/components/OmpDatePicker/index.js",
    "content": "import { DatePicker } from \"antd\";\nimport moment from \"moment\";\n\nconst OmpDatePicker = ({...props}) => {\n  return (\n    <DatePicker.RangePicker\n      ranges={{\n        今天: [moment().startOf(\"day\"), moment()],\n        本周: [moment().startOf(\"week\"), moment()],\n        本月: [moment().startOf(\"month\"), moment()],\n      }}\n      disabledDate={(current) => {\n        return current && current >= moment().endOf(\"day\");\n      }}\n      showTime={{\n        hideDisabledOptions: true,\n      }}\n      //value={rangePickerValue}\n      format=\"YYYY-MM-DD HH:mm:ss\"\n      {...props}\n      // onChange={onChange}\n      // onOk={(dates) => {\n      //   const start = moment(dates[0]).format(\"YYYY-MM-DD HH:mm:ss\");\n      //   const end = moment(dates[1]).format(\"YYYY-MM-DD HH:mm:ss\");\n      //   getServiceData(pagination, {\n      //     query_start_time: start,\n      //     query_end_time: end,\n      //     query_content: searchValue,\n      //   });\n      // }}\n    />\n  );\n};\n\nexport default OmpDatePicker;\n"
  },
  {
    "path": "omp_web/src/components/OmpDrawer/index.js",
    "content": "import { Drawer } from \"antd\";\nimport {\n    DesktopOutlined\n  } from \"@ant-design/icons\";\n  import {\n    OmpIframe\n  } from \"@/components\";\n\nconst OmpDrawer = ({ showIframe, setShowIframe }) => {\n  return (\n    <Drawer\n      title={\n        <div style={{ display: \"flex\" }}>\n          <DesktopOutlined\n            style={{ position: \"relative\", top: 3, left: -5 }}\n          />\n          信息面板\n          <span style={{ paddingLeft: 30, fontWeight: 400, fontSize: 15 }}>\n            IP: {showIframe.record?.ip}\n          </span>\n        </div>\n      }\n      headerStyle={{\n        padding:\"19px 24px\"\n      }}\n      placement=\"right\"\n      closable={true}\n      width={`calc(100% - 200px)`}\n      style={{\n        height: \"calc(100%)\",\n        // paddingTop: \"60px\",\n      }}\n      onClose={() => {\n        setShowIframe({\n          ...showIframe,\n          isOpen: false,\n        });\n      }}\n      visible={showIframe.isOpen}\n      bodyStyle={{\n        padding: 10,\n        //paddingLeft:10,\n        backgroundColor: \"#e7e9f0\", //\"#f4f6f8\"\n        height: \"calc(100%)\",\n      }}\n      destroyOnClose={true}\n    >\n      <OmpIframe\n        showIframe={showIframe}\n        setShowIframe={setShowIframe}\n      />\n    </Drawer>\n  );\n};\n\nexport default OmpDrawer;\n"
  },
  {
    "path": "omp_web/src/components/OmpIframe/index.js",
    "content": "import { useEffect } from \"react\";\nconst OmpIframe = ({ showIframe, setShowIframe, iframeSrc }) => {\n  useEffect(() => {\n    let h = document.getElementById(\"root\").clientHeight;\n    document.getElementById(\"omp_iframe\").style.height = h + \"px\";\n    document.getElementById(\"omp_iframe_container\").style.height =\n      h + \"px\";\n  }, []);\n  let href = window.location.href.split(\"#\")[0];\n  return (\n    <>\n      <div\n        id=\"omp_iframe_container\"\n        style={{ overflow: \"hidden\", position: \"relative\", backgroundColor:\"#f6f7f9\" }}\n      >\n        <iframe\n          id=\"omp_iframe\"\n          style={{\n            width:`calc(100% + 70px)`,\n            position: \"absolute\",\n          }}\n          src={showIframe.src}\n          width=\"100%\"\n          // scrolling=\"no\"\n          name=\"omp_iframe\"\n          frameBorder=\"0\"\n        ></iframe>\n      </div>\n    </>\n  );\n};\n\nexport default OmpIframe;\n"
  },
  {
    "path": "omp_web/src/components/OmpMaintenanceModal/index.js",
    "content": "import { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchPut } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\n\nconst OmpMaintenanceModal = ({ control, used }) => {\n  const dispatch = useDispatch();\n  const [isModalLoading, setIsModalLoading] = useState(false);\n  const changeMaintenance = () => {\n    setIsModalLoading(true);\n    fetchPut(apiRequest.systemSettings.modeInfoChange, {\n      body: {\n        used: used,\n        //env_id: Number(updata()().value),\n      },\n    })\n      .then((res) => {\n        res = res.data;\n        handleResponse(res, () => {\n          if (res.code === 0) {\n            //dispatch(getMaintenanceChangeAction(res.data.used));\n            control[1](false);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setIsModalLoading(false);\n      });\n  };\n  return (\n    <div>omp</div>\n    // <OmpModal\n    //   visibleHandle={control}\n    //   title={used?\"进入维护模式\":\"退出维护模式\"}\n    //   loading = {isModalLoading}\n    //   onOk={() => {\n    //     changeMaintenance();\n    //   }}\n    // >\n    //   <span>{used?\"当该环境处于维护模式时,该环境发出的警报将会被抑制。\":\"该环境退出维护模式,则从该环境内发出的警报将不会再被抑制。\"}</span>\n    // </OmpModal>\n  );\n};\n\nexport default OmpMaintenanceModal;\n"
  },
  {
    "path": "omp_web/src/components/OmpMessageModal/index.js",
    "content": "import { Modal, Button } from \"antd\";\n\nconst OmpMessageModal = ({\n  visibleHandle,\n  children,\n  title,\n  onFinish,\n  noFooter,\n  loading = false,\n  afterClose = () => {},\n  disabled = false,\n  ...residualParam\n}) => {\n  return (\n    <Modal\n      {...residualParam}\n      title={title}\n      visible={visibleHandle[0]}\n      //onOk={() => }\n      onCancel={() => visibleHandle[1](false)}\n      footer={null}\n      destroyOnClose\n      loading={loading}\n      afterClose={afterClose}\n    >\n      {children}\n      <div\n        style={{\n          textAlign: \"center\",\n          position: \"relative\",\n          top: 0,\n          paddingTop: 20,\n        }}\n      >\n        {noFooter ? (\n          \"\"\n        ) : (\n          <>\n            <Button\n              style={{ marginRight: 16 }}\n              onClick={() => visibleHandle[1](false)}\n            >\n              取消\n            </Button>\n            <Button\n              loading={loading}\n              type=\"primary\"\n              htmlType=\"submit\"\n              onClick={onFinish}\n              disabled={disabled}\n            >\n              确定\n            </Button>\n          </>\n        )}\n      </div>\n    </Modal>\n  );\n};\n\nexport default OmpMessageModal;\n"
  },
  {
    "path": "omp_web/src/components/OmpModal/index.js",
    "content": "import { Modal, Button, Form } from \"antd\";\nimport React from \"react\";\nimport styles from \"./index.module.less\";\n\nconst OmpModal = ({\n  visibleHandle,\n  children,\n  title,\n  onFinish = () => {},\n  footer,\n  loading = false,\n  afterClose = () => {},\n  form,\n  initialValues = {},\n  setLoading,\n  okBtnText,\n  beForeOk = () => {},\n  formLabelCol = { span: 7 },\n  formWrapperCol = { span: 14 },\n  ...residualParam\n}) => {\n  const [modalForm] = Form.useForm();\n  // 扩展formItem功能,为了能够在formitem的validator校验时获得当前form的实例进行操作\n  // 在这里重写formitem的validator函数，在新函数中注入form实例\n  // console.log(children)\n  let dealChild = Array.isArray(children) ? children : [children];\n  let processedChildren = dealChild?.map((item) => {\n    if (item.props?.useforminstanceinvalidator === \"true\") {\n      // 当前就是需要扩展validator的formItem\n      // 拿到当前项的rules数组并把数组项为{validator:fn}的拿到，重写fn\n      let newRules = item.props.rules.map((r) => {\n        if (r.validator) {\n          //重写validator\n          return {\n            validator: (rule, value, callback) => {\n              return r.validator(rule, value, callback, modalForm);\n            },\n          };\n        } else {\n          return r;\n        }\n      });\n      return React.cloneElement(item, { rules: newRules });\n    } else if (item.props?.useforminstanceinonchange === \"true\") {\n      let newOnChange = (e) => {\n        item.props.onChange(e, modalForm);\n      };\n      return React.cloneElement(item, { onChange: newOnChange });\n    } else {\n      return item;\n    }\n  });\n\n  return (\n    <Modal\n      className={styles.OmpModalWrapper}\n      title={title}\n      visible={visibleHandle[0]}\n      //onOk={() => onOk()}\n      onCancel={() => visibleHandle[1](false)}\n      footer={footer}\n      destroyOnClose\n      loading={loading}\n      afterClose={() => {\n        // 重置表单数据\n        form ? form.resetFields() : modalForm.resetFields();\n        //传入的afterClose\n        afterClose();\n      }}\n      footer={null}\n      {...residualParam}\n    >\n      <Form\n        //style={{ paddingLeft: 50, paddingRight: 50 }}\n        form={form ? form : modalForm}\n        labelCol={formLabelCol}\n        wrapperCol={formWrapperCol}\n        onFinish={onFinish}\n        initialValues={initialValues}\n      >\n        {processedChildren}\n        <Form.Item\n          wrapperCol={{ span: 24 }}\n          style={{ textAlign: \"center\", position: \"relative\", top: 20 }}\n        >\n          <Button\n            style={{ marginRight: 16 }}\n            onClick={() => visibleHandle[1](false)}\n          >\n            取消\n          </Button>\n          <span\n            onClick={() => {\n              beForeOk();\n            }}\n          >\n            <Button loading={loading} type=\"primary\" htmlType=\"submit\">\n              {okBtnText || \"确定\"}\n            </Button>\n          </span>\n        </Form.Item>\n      </Form>\n    </Modal>\n  );\n};\n\nexport default OmpModal;\n"
  },
  {
    "path": "omp_web/src/components/OmpModal/index.module.less",
    "content": ":global {\n    .ant-modal-header {\n        padding-top: 12px;\n        padding-bottom: 10px;\n    }\n    .ant-modal-title {\n        font-size: 14px;\n        font-weight: 500;\n    }\n    .ant-modal-close-x {\n        line-height: 45px;\n    }\n\n    //修改form.item的垂直间距\n    .ant-form-item {\n        margin-bottom: 18px;\n    }\n    //当弹出校验提示文字时，把marginbotton置成0\n    .ant-form-item-with-help {\n        margin-bottom: 0px;\n    }\n}"
  },
  {
    "path": "omp_web/src/components/OmpOperationWrapper/index.js",
    "content": "import styles from \"./index.module.less\"\nconst OmpOperationWrapper = (props)=>{\n    return (\n       <div className={styles.OmpOperationWrapper}>\n           {props.children}\n       </div>\n    )\n}\n\nexport default OmpOperationWrapper"
  },
  {
    "path": "omp_web/src/components/OmpOperationWrapper/index.module.less",
    "content": ".OmpOperationWrapper {\n    //background-color: red;\n    height: 54px;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n}"
  },
  {
    "path": "omp_web/src/components/OmpProgress/index.js",
    "content": "import ReactEcharts from \"echarts-for-react\";\n\nconst OmpProgress = ({ trafficWay, percent }) => {\n  // 判断trafficWay的每一项是否都是0\n  let isAllNull = trafficWay.filter((i) => i.value !== 0);\n  //console.log(isAllNull);\n  if (isAllNull.length == 0) {\n    trafficWay[2].value = 1;\n  }\n\n  var data = [];\n  var color = [\n    \"#ee686e\",\n    \"#ffbe40\",\n    \"rgb(84, 187, 166)\",\n    \"#df7153\",\n    \"#fad83a\",\n    \"#c490bf\",\n    \"#1fe15f\",\n    \"#3087d6\",\n    \"#4be1ff\",\n  ];\n  for (var i = 0; i < trafficWay.length; i++) {\n    data.push(\n      {\n        value: trafficWay[i].value,\n        name: trafficWay[i].name,\n        itemStyle: {\n          normal: {\n            borderWidth: 4,\n            //shadowBlur: 2,\n            borderColor: color[i],\n            //shadowColor: color[i],\n          },\n        },\n      },\n      {\n        value: 0,\n        name: \"\",\n        itemStyle: {\n          normal: {\n            label: {\n              show: false,\n            },\n            labelLine: {\n              show: false,\n            },\n            color: percent == 0 ? \"#f45966\":\"rgba(0, 0, 0, 0)\",\n            borderColor: percent == 0 ? \"#f45966\":\"rgba(0, 0, 0, 0)\",\n            borderWidth: 4,\n          },\n        },\n      }\n    );\n  }\n  var seriesOption = [\n    {\n      name: \"\",\n      type: \"pie\",\n      clockWise: false,\n      radius: [\"75%\", \"77%\"],\n      center: [\"50%\", \"50%\"],\n      hoverAnimation: false,\n      itemStyle: {\n        normal: {\n          label: {\n            show: false,\n            position: \"outside\",\n            color: \"#ddd\",\n            // formatter: function (params) {\n            //   var percent = 0;\n            //   var total = 0;\n            //   for (var i = 0; i < trafficWay.length; i++) {\n            //     total += trafficWay[i].value;\n            //   }\n            //   percent = ((params.value / total) * 100).toFixed(0);\n            //   if (params.name !== \"\") {\n            //     return (\n            //       \"交通方式：\" +\n            //       params.name +\n            //       \"\\n\" +\n            //       \"\\n\" +\n            //       \"占百分比：\" +\n            //       percent +\n            //       \"%\"\n            //     );\n            //   } else {\n            //     return \"\";\n            //   }\n            // },\n          },\n          labelLine: {\n            length: 30,\n            length2: 100,\n            show: false,\n            color: \"#00ffff\",\n          },\n        },\n      },\n      data: data,\n    },\n  ];\n  const option = {\n    // backgroundColor: '#0A2E5D',\n    color: color,\n    title: {\n      text: `${percent == Infinity ? 100 : isNaN(percent) ? 0 : percent}%`,\n      top: \"37%\",\n      left: \"47%\",\n      textAlign: \"center\",\n      textStyle: {\n        color:\n          `${percent == Infinity ? 100 : isNaN(percent) ? 0 : percent}%` ==\n          \"100%\"\n            ? \"rgb(84, 187, 166)\"\n            : \"rgba(0, 0, 0, 0.65)\",\n        fontSize: 18,\n        fontWeight: \"400\",\n      },\n    },\n    graphic: {\n      elements: [\n        {\n          type: \"image\",\n          z: 3,\n          style: {\n            // image: img,\n            width: 178,\n            height: 178,\n          },\n          left: \"center\",\n          top: \"center\",\n          position: [100, 100],\n        },\n      ],\n    },\n    tooltip: {\n      show: false,\n    },\n    // legend: {\n    //   icon: \"circle\",\n    //   orient: \"vertical\",\n    //   // x: 'left',\n    //   data: [\n    //     \"物理机\",\n    //     \"宿主机\",\n    //     \"云主机\",\n    //     \"网络设备\",\n    //     \"安全设备\",\n    //     \"应用系统\",\n    //     \"存储设备\",\n    //     \"网络服务\",\n    //     \"终端PC\",\n    //   ],\n    //   right: \"5%\",\n    //   top: \"center\",\n    //   align: \"left\",\n    //   textStyle: {\n    //     color: \"black\",\n    //   },\n    //   itemGap: 20,\n    //   // formatter: function(name) {\n    //   //      let target,percent;\n    //   //      for (let i = 0; i < dataPie.length; i++) {\n    //   //          if (dataPie[i].name === name) {\n    //   //              target = dataPie[i].value;\n    //   //              percent = ((target/total)*100).toFixed(2);\n    //   //          }\n    //   //      }\n    //   //      let arr = [ percent+'% '+' {yellow|' + target + '}', ' {blue|' + name + '}' ];\n    //   //      return arr.join(\"\\n\")\n\n    //   //  }\n    // },\n    toolbox: {\n      show: false,\n    },\n    series: seriesOption,\n  };\n  return (\n    <ReactEcharts\n      option={option}\n      notMerge={true}\n      lazyUpdate={true}\n      //onEvents={onEvents}\n      style={{ width: \"100px\", height: \"100px\" }}\n    />\n  );\n};\n\nexport default OmpProgress;\n"
  },
  {
    "path": "omp_web/src/components/OmpSelect/index.js",
    "content": "import { Select } from \"antd\";\nimport { useState, useRef, useEffect } from \"react\";\n\nconst OmpSelect = ({\n  searchLoading,\n  selectValue,\n  listSource,\n  setSelectValue,\n  fetchData,\n  ...props\n}) => {\n  const [searchValue, setSearchValue] = useState(\"\");\n\n  //select 的onblur函数拿不到最新的search value,使用useref存(是最新的，但是因为失去焦点时会自动触发清空search，还是得使用ref存)\n  const searchValueRef = useRef(null);\n  \n  useEffect(()=>{\n    if(!selectValue){\n      searchValueRef.current = \"\";\n      setSearchValue();\n    }\n  },[selectValue])\n\n  return (\n    <Select\n      {...props}\n      allowClear\n      onClear={() => {\n        searchValueRef.current = \"\";\n        setSelectValue();\n        setSearchValue();\n        fetchData();\n      }}\n      showSearch\n      placeholder=\"搜索\"\n      loading={searchLoading}\n      style={{ width: 200 }}\n      onInputKeyDown={(e) => {\n        if (e.code == \"Enter\") {\n          //console.log(\"点击了\",searchValueRef.current )\n          setSelectValue(searchValueRef.current);\n          fetchData(searchValueRef.current);\n        }\n      }}\n      searchValue={searchValue}\n      onSelect={(e) => {\n        if (e == searchValue || !searchValue) {\n          //console.log(1)\n          setSelectValue(e);\n          fetchData(e);\n        } else {\n          //console.log(2)\n          setSelectValue(searchValue);\n          fetchData(searchValueRef.current);\n        }\n        searchValueRef.current = \"\";\n      }}\n      value={selectValue}\n      onSearch={(e) => {\n        e && (searchValueRef.current = e);\n        setSearchValue(e);\n      }}\n      onBlur={(e) => {\n        //console.log(searchValueRef.current,\"searchValueRef.current\")\n        if (searchValueRef.current) {\n          setSelectValue(searchValueRef.current);\n          fetchData(searchValueRef.current);\n        }\n      }}\n    >\n      {listSource.map((item) => {\n        return (\n          <Select.Option value={item} key={item}>\n            {item}\n          </Select.Option>\n        );\n      })}\n    </Select>\n  );\n};\n\nexport default OmpSelect;\n"
  },
  {
    "path": "omp_web/src/components/OmpStateBlock/index.js",
    "content": "import { useEffect, useState } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport { Button, Checkbox, Popover } from \"antd\";\nimport styles from \"./index.module.less\";\n\nconst colorObj = {\n  normal: {\n    background: \"#eefaf4\",\n    borderColor: \"#54bba6\",\n  },\n  abnormal: {\n    background: \"#fbe7e6\",\n    borderColor: \"#da4e48\",\n  },\n  noMonitored: {\n    background: \"#e5e5e5\",\n    borderColor: \"#aaaaaa\",\n  },\n  warning: {\n    background: \"rgba(247, 231, 24, 0.2)\",\n    borderColor: \"rgb(245, 199, 115)\",\n  },\n};\n\nfunction OmpStateBlock(props) {\n  const history = useHistory();\n  const { title, data = [] } = props;\n  const [allData, setAllData] = useState([]);\n  //console.log(data)\n  const [currentData, setCurrentData] = useState([]);\n\n  const [isShowAll, setIsShowAll] = useState(false);\n\n  useEffect(() => {\n    if (data && data.length > 0) {\n      let handleData = data.map((item) => {\n        //后端的critical和warning都渲染成红色，在前端对数据进行处理 noMonitored:\"未监控\" abnormal:\"异常\" normal:\"正常\"\n        // let status = \"noMonitored\";\n        // if (item.severity == \"warning\" || item.severity == \"critical\") {\n        //   status = \"abnormal\";\n        // } else if (item.severity == \"normal\") {\n        //   status = \"normal\";\n        // }\n        // return {\n        //   ...item,\n        //   frontendStatus: status,\n        // };\n\n        let status = \"noMonitored\";\n        if (item.severity == \"critical\") {\n          status = \"abnormal\";\n        } else if (item.severity == \"normal\") {\n          status = \"normal\";\n        } else if (item.severity == \"warning\") {\n          status = \"warning\";\n        }\n        return {\n          ...item,\n          frontendStatus: status,\n        };\n      });\n      setCurrentData(sortData(handleData));\n      setAllData(sortData(handleData));\n    }\n  }, [data]);\n\n  const sortData = (data) => {\n    let normalArr = data.filter((i) => i.frontendStatus == \"normal\");\n    let noMonitoredArr = data.filter((i) => i.frontendStatus == \"noMonitored\");\n    let abnormalArr = data.filter((i) => i.frontendStatus == \"abnormal\");\n    let warningArr = data.filter((i) => i.frontendStatus == \"warning\");\n\n    let result = abnormalArr\n      .concat(normalArr)\n      .concat(noMonitoredArr)\n      .concat(warningArr);\n    return result;\n  };\n\n  return (\n    <div className={styles.blockContent}>\n      <div className={styles.checkboxGroup}>\n        <span className={styles.blockTitle}>{title}</span>\n        <div>\n          <Checkbox\n            defaultChecked={true}\n            onChange={(e) => {\n              if (e.target.checked) {\n                setCurrentData(\n                  sortData(\n                    currentData.concat(\n                      allData.filter(\n                        (i) =>\n                          i.frontendStatus == \"abnormal\" ||\n                          i.frontendStatus == \"warning\"\n                      )\n                    )\n                  )\n                );\n              } else {\n                setCurrentData(\n                  sortData(\n                    currentData.filter(\n                      (i) =>\n                        i.frontendStatus !== \"abnormal\" ||\n                        i.frontendStatus == \"warning\"\n                    )\n                  )\n                );\n              }\n            }}\n          >\n            异常\n          </Checkbox>\n          <Checkbox\n            defaultChecked={true}\n            onChange={(e) => {\n              if (e.target.checked) {\n                setCurrentData(\n                  sortData(\n                    currentData.concat(\n                      allData.filter((i) => i.frontendStatus == \"normal\")\n                    )\n                  )\n                );\n              } else {\n                setCurrentData(\n                  sortData(\n                    currentData.filter((i) => i.frontendStatus !== \"normal\")\n                  )\n                );\n              }\n            }}\n          >\n            正常\n          </Checkbox>\n\n          <Checkbox\n            defaultChecked={true}\n            onChange={(e) => {\n              if (e.target.checked) {\n                setCurrentData(\n                  sortData(\n                    currentData.concat(\n                      allData.filter((i) => i.frontendStatus == \"noMonitored\")\n                    )\n                  )\n                );\n              } else {\n                setCurrentData(\n                  sortData(\n                    currentData.filter(\n                      (i) => i.frontendStatus !== \"noMonitored\"\n                    )\n                  )\n                );\n              }\n            }}\n          >\n            未监控\n          </Checkbox>\n\n          <Button\n            className={styles.dropBtn}\n            size={\"small\"}\n            onClick={() => setIsShowAll(!isShowAll)}\n          >\n            {isShowAll ? \"收起\" : \"展开\"}\n          </Button>\n        </div>\n      </div>\n      {currentData.length > 0 ? (\n        <div\n          style={isShowAll ? {} : { maxHeight: 170, overflowY: \"scroll\" }}\n          className={styles.blockItemWrapper}\n        >\n          {currentData.map((item, idx) => {\n            return (\n              <div\n                key={`${title}-${idx}`}\n                onClick={() => {\n                  item.frontendStatus == \"abnormal\"\n                    ? props.criticalLink(item)\n                    : props.link(item);\n                }}\n              >\n                <Popover content={popContent(item || {})} title={\"相关信息\"}>\n                  <Button\n                    className={styles.stateButton}\n                    style={colorObj[item.frontendStatus]}\n                  >\n                    <div>{item.instance_name || item.ip}</div>\n                  </Button>\n                </Popover>\n              </div>\n            );\n          })}\n        </div>\n      ) : (\n        <div className={styles.emptyTable}>暂无数据</div>\n      )}\n    </div>\n  );\n}\n\nexport default OmpStateBlock;\n\nfunction popContent(item) {\n  return (\n    <div>\n      {item.info.map((i) => (\n        <div className={styles.popContent}>\n          {i.ip && (\n            <span\n              className={styles.ip}\n              style={{ color: colorObj[item.frontendStatus]?.borderColor }}\n            >\n              {i.ip}\n            </span>\n          )}\n          <span>\n            {i.date ? (\n              <span>{i.date}</span>\n            ) : (\n              <span\n                style={{ color: colorObj[item.frontendStatus]?.borderColor }}\n              >\n                {item.frontendStatus === \"noMonitored\" ? \"未监控\" : \"正常\"}\n              </span>\n            )}\n          </span>\n          {i.describe && (\n            <span>\n              {i.describe.length > 100\n                ? i.describe.slice(0, 100) + \"...\"\n                : i.describe.slice(0, 100)}\n            </span>\n          )}\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "omp_web/src/components/OmpStateBlock/index.module.less",
    "content": ".homepageWrapper {\n  padding: 15px;\n\n  .pageBlock {\n    .blockTitle {\n      font-weight: 500;\n      //color: #333;\n      margin-bottom: 10px;\n    }\n  }\n}\n\n.blockContent {\n  //border: 1px solid  #DCDEE5;\n  background-color: #fff;\n  border-radius: 5px;\n  padding: 10px;\n  margin-bottom: 15px;\n}\n\n.blockOverviewItem {\n  flex: 1;\n  display: flex;\n  justify-content: center;\n  flex-flow: row nowrap;\n  padding: 10px;\n  margin-right: 20px;\n  border-radius: 5px;\n\n  & > div:nth-child(1) {\n    margin-right: 15px;\n    align-self: center;\n    & > div:nth-child(1) {\n      width: 80px !important;\n      height: 80px !important;\n      font-size: 16px !important;\n    }\n  }\n\n  .progressInfo {\n    color: #333;\n    & > div:nth-child(1) {\n      font-size: 14px;\n      font-weight: 500;\n      margin-bottom: 15px;\n    }\n    & > div:nth-child(2) {\n      margin-bottom: 10px;\n    }\n  }\n}\n\n.checkboxGroup {\n  display: flex;\n  justify-content: space-between;\n  width: 100%;\n  margin-bottom: 10px;\n  .blockTitle {\n    font-weight: 500;\n  }\n  div {\n    display: flex;\n  }\n}\n\n.blockItemWrapper {\n  display: flex;\n  flex-flow: row wrap;\n  padding-top: 5px;\n  //justify-content: space-between;\n}\n\n// .blockItemWrapper>div:last-child {\n//   flex:1\n// }\n\n.stateButton:hover {\n  top: -2px;\n  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);\n  color: black !important;\n  // color: white;\n  // /* 设置字体阴影 */\n  // text-shadow: 1px 1px 2px #35cac2;\n  // /* 改变按钮边界 */\n  // border: 1px solid #35cac2;\n  /* 设置按钮阴影 */\n  //box-shadow: 5px 5px 50px rgba(255, 255, 255, 0.4) inset;\n}\n\n.stateButton {\n  position: relative;\n  top: 0px;\n  color: rgba(0, 0, 0, 0.65);\n  transition: all 0.2s ease-in-out;\n  width: 174px;\n  margin-right: 10px;\n  margin-bottom: 10px;\n  font-size: 13px;\n  & > div {\n    max-width: 145px;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n}\n\n.popContent {\n  & > span {\n    font-size: 13px;\n    margin-right: 15px;\n  }\n\n  .ip {\n    //display: inline-flex;\n    // todo 这个地方最大宽度不确定，此处为ip地址最大长度，但会导致ip地址较短时页面空一大块\n    // /min-width: 60px;\n  }\n}\n\n.dropBtn {\n  height: 24px;\n  font-size: 12px;\n  // position: relative;\n  // top: -2px;\n}\n\n.emptyTable {\n  background-color: #fff;\n  border-radius: 5px;\n  padding: 10px;\n  margin-bottom: 15px;\n  height: 20px;\n  font-size: 12px;\n  color: rgba(0, 0, 0, 0.25);\n  line-height: 0px;\n  text-align: center;\n  //overflow-y: scroll\n}\n"
  },
  {
    "path": "omp_web/src/components/OmpTable/components/OmpTableFilter.js",
    "content": "import { Dropdown, Menu } from \"antd\";\nimport { FilterFilled } from \"@ant-design/icons\";\nimport { useState } from \"react\";\n\nconst OmpTableFilter = ({ dataIndex, filterMenuList, queryRequest, initfilter }) => {\n  // 表格的筛选控制器\n  const [filterControl, setFilterControl] = useState(initfilter);\n\n  // 展开菜单是否\n  const [dropDownIsOpen, setDropDownIsOpen] = useState(false);\n\n  return (\n    <Dropdown\n      visible={dropDownIsOpen}\n      onVisibleChange={(e) => {\n        if (filterControl) {\n          queryRequest({ [dataIndex]: null });\n          setFilterControl(\"\");\n        } else {\n          setDropDownIsOpen(e);\n        }\n      }}\n      overlay={\n        <Menu\n          style={{\n            maxHeight:200,\n            overflow:\"hidden\",\n            overflowY: \"auto\",\n          }}\n          selectedKeys={[filterControl]}\n          onClick={(e) => {\n            setFilterControl(e.key);\n            queryRequest({ [dataIndex]: e.key });\n            setDropDownIsOpen(false);\n          }}\n        >\n          {filterMenuList?.map((item) => {\n            return <Menu.Item key={item.value}>{item.text}</Menu.Item>;\n          })}\n        </Menu>\n      }\n      placement=\"bottomCenter\"\n      trigger=\"click\"\n    >\n      <FilterFilled style={{ color: filterControl ? \"#4986f7\" : null }} />\n    </Dropdown>\n  );\n};\n\nexport default OmpTableFilter;\n"
  },
  {
    "path": "omp_web/src/components/OmpTable/index.js",
    "content": "import { Table, Tree } from \"antd\";\nimport styles from \"./index.module.less\";\nimport { useLayoutEffect, useState } from \"react\";\nimport { useSelector } from \"react-redux\";\nimport OmpTableFilter from \"./components/OmpTableFilter\";\nimport { SettingOutlined } from \"@ant-design/icons\";\n\nconst OmpTable = ({\n  checkedState,\n  columns,\n  notSelectable,\n  noScroll,\n  ...residualParam\n}) => {\n  const [checkedList, setCheckedList] = checkedState ? checkedState : [];\n  // 视口高度\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  // 视口宽度\n  const viewWidth = useSelector((state) => state.layouts.viewSize.width);\n  const [maxWidth, setMaxWidth] = useState(1900);\n\n  // 表格项的筛选selectKey\n  const [selectKeys, setSelectKeys] = useState(\n    columns.map((item) => item.dataIndex)\n  );\n\n  // 当columns传入usefilter时，对该项做处理\n  const extensionsColumns = columns.map((item, idx) => {\n    // // 复制一下item\n    // let item = R.clone(i);\n\n    // // 当columns的width未设置时，直接添加width200,然后计算最大宽度\n    // if (!item.width) {\n    //   item = {\n    //     width: 120,\n    //     ellipsis: true,\n    //     ...item,\n    //   };\n    // }\n\n    //item.isShow = true;\n\n    let lastIndex = columns.length - 1;\n    if (idx == lastIndex) {\n      return {\n        ...item,\n        filterIcon: (filtered) => <SettingOutlined />,\n        filterDropdown: ({ confirm, clearFilters }) => (\n          <div style={{ padding: 8, overflow: \"hidden\" }}>\n            <Tree\n              style={{\n                left: -20,\n                fontSize: 13,\n              }}\n              checkable\n              switcherIcon={<SettingOutlined />}\n              selectable={false}\n              onCheck={(checkedKeys, info) => {\n                setSelectKeys(checkedKeys);\n              }}\n              treeData={columns.map((item, idx) => {\n                return {\n                  ...item,\n                  disabled: idx == columns.length - 1 ? true : false,\n                };\n              })}\n              checkedKeys={selectKeys}\n            />\n          </div>\n        ),\n      };\n    }\n\n    // 筛选功能\n    if (item.usefilter) {\n      return {\n        ...item,\n        filterIcon: () => {\n          return (\n            <OmpTableFilter\n              initfilter={item?.initfilter}\n              dataIndex={item.dataIndex}\n              filterMenuList={item.filterMenuList}\n              queryRequest={item.queryRequest}\n            />\n          );\n        },\n        filters: [{ text: \"mock\", value: \"mock\" }],\n        filterDropdown: () => {\n          return <span key=\"mock_\"></span>;\n        },\n      };\n    }\n    return {\n      ...item,\n    };\n  });\n\n  // 计算表格实际横向宽度（最大）\n  // useLayoutEffect(() => {\n  //   let maxW = 0\n  //   extensionsColumns.map((item) => {\n  //     maxW += item.width\n  //   });\n  //   console.log(maxW)\n  //   setMaxWidth(maxW)\n  //   //console.log(extensionsColumns)\n  // }, []);\n\n  // useEffect(()=>{\n\n  // },[selectKeys])\n\n  useLayoutEffect(() => {\n    //console.log(viewHeight);\n    // 为了能够让omptable能够根据视口高度进行自适应\n    // 订出如下标准 视口高度大于955 设置 表格cell的padding为1rem\n    // 视口高度大于 760 设置cell的padding为0.72rem\n    let cellPadding = \".5\";\n    if (viewHeight > 955) {\n      cellPadding = \".9\";\n    } else if (viewHeight <= 955 && viewHeight > 860) {\n      cellPadding = \".75\";\n    } else if (viewHeight <= 860 && viewHeight > 760) {\n      cellPadding = \".6\";\n    }\n    try {\n      //window.style = \"body{background-color:blue;}\";\n      var stylee = document.createElement(\"style\");\n      stylee.type = \"text/css\";\n      var sHtml = `\n      .ant-table-thead > tr > th, .ant-table-tbody\n      > tr > td, .ant-table tfoot >\n      tr > th, .ant-table tfoot > tr > td {\n            padding: ${cellPadding}rem;\n        }`;\n      stylee.innerHTML = sHtml;\n      document.getElementsByTagName(\"head\").item(0).appendChild(stylee);\n    } catch (error) {\n      console.log(error);\n    }\n  }, []);\n\n  return (\n    <Table\n      showSorterTooltip={false}\n      //scroll={(viewWidth - 300) > maxWidth ? null : { x: (maxWidth + 30) }}\n      scroll={viewWidth > 1900 ? null : { x: noScroll ? null : 1500 }}\n      {...residualParam}\n      columns={extensionsColumns.filter((i) => {\n        return selectKeys.includes(i.dataIndex);\n      })}\n      //size=\"small\"\n      rowSelection={\n        checkedState && {\n          onSelect: (record, selected, selectedRows) => {\n            if (selected) {\n              setCheckedList([...checkedList, record]);\n            } else {\n              setCheckedList(checkedList.filter((m) => m.id !== record.id));\n            }\n          },\n          onSelectAll: (selected, selectedRows, changeRows) => {\n            if (selected) {\n              setCheckedList([...checkedList, ...changeRows]);\n            } else {\n              setCheckedList((ls) => {\n                return ls.filter((l) => {\n                  let ids = changeRows.map((m) => m.id);\n                  return !ids.includes(l.id);\n                });\n              });\n            }\n          },\n          getCheckboxProps:\n            notSelectable ||\n            ((record) => ({\n              disabled: record.is_read === 1,\n            })),\n          selectedRowKeys: checkedList.map((item) => item?.id),\n          // 传入rowselect优先使用传入的\n          ...residualParam.rowSelection,\n        }\n      }\n    />\n  );\n};\n\nexport default OmpTable;\n"
  },
  {
    "path": "omp_web/src/components/OmpTable/index.module.less",
    "content": ".OmpTableWrapper {\n  //background-color: red;\n  // .OmpTableRow {\n  //   background-color: aqua !important;\n  // }\n  //margin-bottom: 100px;\n  \n}\n\n:global {\n  .ant-table-small .ant-table-thead > tr > th {\n    background-color: #fafbfd;\n  }\n  // .ant-table {\n  //   color:rgba(0, 0, 0, 0.65);\n  // }\n  // .ant-badge {\n  //   color:rgba(0, 0, 0, 0.65);\n  // }\n  .ant-table-tbody > tr.ant-table-row-selected > td {\n    border-color:rgba(0, 0, 0, 0.03);\n    background-color:#fff\n  }\n  table tr th.ant-table-selection-column, table tr td.ant-table-selection-column {\n    padding-left: 15px;\n  }\n\n  .ant-table-content { \n   // border-top: 1px solid rgb(220, 222, 229)!important;\n  }\n  //  之后即使是全局样式也配置在相应的组件里\n  .ant-table-wrapper {\n    //border: 1px solid #dcdee5;\n  }\n  .ant-table-body {\n    overflow-y: auto !important;\n  }\n  .ant-table-header {\n    border-bottom: 1px dashed #c4c6cc;\n  }\n  .ant-table-thead {\n    color: #313238;\n    font-weight: 400;\n    font-size: 12px;\n    tr > th {\n      background-color: #fafbfd;\n      background-color: #eaedf4;\n      background-color: rgba(73,134,247,0.1);\n      background-color: #fafbfd;\n      border-bottom: 1px solid rgb(220, 222, 229)!important;\n      color: rgba(0,0,0,0.65);\n    }\n    // .ant-table-cell :after {\n    //   background-color: red;\n    // }\n  }\n  .ant-table-tbody {\n    font-size: 12px;\n  }\n  .ant-table-pagination.ant-pagination {\n    //border-top: 1px dashed #c4c6cc;\n    margin: 0px;\n  }\n  .ant-table-pagination-right {\n    padding: 12px;\n    font-size: 12px;\n    background-color: #fff;\n  }\n  .ant-pagination-options-size-changer {\n    font-size: 12px;\n  }\n  // .ant-table.ant-table-middle .ant-table-title,\n  // .ant-table.ant-table-middle .ant-table-footer,\n  // .ant-table.ant-table-middle .ant-table-thead > tr > th,\n  // .ant-table.ant-table-middle .ant-table-tbody > tr > td,\n  // .ant-table.ant-table-middle tfoot > tr > th,\n  // .ant-table.ant-table-middle tfoot > tr > td {\n  //   padding: 1rem;\n  // }\n  .ant-select-item-option-content{\n    //font-size: 12px !important;\n  }\n  //表格排序按钮紧贴title\n  .ant-table-column-sorters {\n    display:inline-flex;\n    // align-items: center;\n    .ant-table-column-title{\n      position: relative;\n      top:1px;\n      left: -3px;\n    }\n    .ant-table-column-sorter {\n      position: relative;\n      left: 3px;\n    }\n    // .ant-table-column-sorter-full {}\n  }\n  //表格筛选按钮紧贴title\n  .ant-table-filter-column {\n    display:inline-flex;\n    .ant-table-column-title{\n      position: relative;\n      top:1px;\n      left: -4px;\n    }\n    .ant-dropdown-trigger {\n      position: relative;\n      left: 0px;\n      //top:1px\n    }\n    //是为了解决过滤图标和选中背景生效范围不一致问题\n    .ant-table-filter-trigger {\n      padding: 0px;\n      margin: 0;\n      position: relative;\n      left: 3px;\n    }\n  }\n}\n\n.warningSearch {\n  display: flex;\n  justify-content: flex-end;\n  margin-top: 10px;\n  margin-bottom: 10px;\n\n  & > div:nth-child(1) {\n    margin-right: auto;\n  }\n\n  .rangePicker {\n    margin-right: 15px;\n  }\n}\n\n.contentLeftMenuWrapper {\n  display: flex;\n\n  .leftMenu {\n    background-color: #fafafa;\n    padding: 15px;\n    margin-top: 10px;\n    margin-right: 10px;\n    height: 82vh;\n    overflow-y: auto;\n  }\n\n  .leftMenuListContent {\n    display: flex;\n    flex-direction: column;\n    font-size: 16px;\n    // label :nth-child(1){\n    //   padding-top:5px\n    // }\n    label {\n      font-size: 16px!important;\n      padding-top: 10px;\n    }\n    :global {\n      .ant-checkbox-group-item {\n        //margin-bottom: 7px;\n        font-size: 14px!important;\n      }\n      .ant-checkbox-checked {\n        position: relative;\n        top: 1px;\n      }\n    }\n  }\n}\n\n.trendContentWrapper {\n  width: calc(100% - 0px);\n  padding: 10px;\n\n  .title {\n    margin-bottom: 5px;\n    margin-left: 15px;\n    color: #333;\n    font-size: 16px;\n    font-weight: 500;\n    padding: 10px 0;\n  }\n\n  .trendItemContent {\n    background-color: #fff;\n    border-radius: 10px;\n\n    .header {\n      display: flex;\n      align-items: center;\n      padding: 8px 8px 8px 15px;\n      color: #333;\n      border-bottom: 1px solid #bfbfbf;\n\n      & > div:nth-child(2) {\n        display: flex;\n        align-items: center;\n        width: 100%;\n        height: 35px;\n        margin-left: 5px;\n      }\n    }\n\n    .rangPicker {\n      display: flex;\n      justify-content: flex-end;\n      width: 100%;\n      padding: 10px 60px 10px;\n    }\n  }\n}\n\n.pageInfo {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding: 10px;\n\n  & > div:nth-child(1) {\n    margin-right: 10px;\n  }\n}\n\n.panelItem {\n  background: #fafafa;\n  border-radius: 4px;\n  border: 0;\n  overflow: hidden;\n  border-bottom: 0 !important;\n\n  & > div:nth-child(1) {\n    padding: 8px 15px;\n  }\n\n  // 覆盖展开后的content背景色\n  & > div:nth-child(2) {\n    background-color: #fff !important;\n\n  }\n}\n\n// .reportContentWrapper {\n\n// }\n\n.reportContent {\n  padding: 15px;\n\n  .reportTitle {\n    display: flex;\n    justify-content: center;\n    color: #1890ff;\n    margin-bottom: 20px;\n    height: 40px;\n    align-items: center;\n    position: relative;\n\n    & > div:nth-child(1) {\n      font-size: 22px;\n      font-weight: 500;\n      color: #333;\n    }\n\n    & > div:nth-child(2) {\n      position: absolute;\n      right: 0;\n      display: flex;\n      cursor: pointer;\n\n      & > div:nth-child(1) {\n        margin-right: 10px;\n      }\n    }\n  }\n}\n\n.overviewItemWrapper {\n  display: flex;\n  justify-content: space-between;\n  flex-flow: wrap;\n  margin: 10px 0;\n\n  & > div:nth-child(even) {\n    margin-right: 0;\n  }\n}\n\n\n.overviewItem {\n  display: flex;\n  width: 49.5%;\n  color: #333;\n  margin-bottom: 10px;\n\n  & > div {\n    border: 1px solid #E8E8E8;\n    padding: 10px;\n  }\n\n  & > div:nth-child(1) {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    border-right: none;\n    width: 200px;\n  }\n\n  & > div:nth-child(2) {\n    width: 100%;\n  }\n}\n\n.planChartWrapper {\n  margin-top: 10px;\n  width: 100%;\n  border:1px solid #E8E8E8;\n  border-radius: 2px;\n  // /padding:10px;\n  .planChartTitle {\n    background-color: #fafbfd;\n    font-weight: 500;\n    height: 30px;\n    line-height: 30px;\n    border-bottom: solid 1px #E8E8E8;\n    .planChartTitleCircular {\n      display: inline-block;\n      width: 10px;\n      height: 10px;\n      background-color: #54bba6;\n      border-radius: 50%;\n      margin-right: 10px;\n      margin-left: 20px;\n    }\n  }\n  .planChartBlockWrapper {\n      display: flex;\n      flex-flow: row wrap;\n      max-height: 240px; \n      overflow-y: auto;\n      padding-top:15px;\n      padding:20px;\n      .stateButton {\n        position: relative;\n        top: 0px;\n        //background-color: #eefaf4;\n        border:1px solid #333;\n        transition: all .2s ease-in-out;\n        width: 178px;\n        margin-right: 32px;\n        margin-bottom: 10px;\n        height: 32px;\n        & > div {\n          margin: auto;\n          width: 100%;\n          height: 100%;\n          line-height: 30px;\n          text-align: center;\n          overflow: hidden;\n          white-space: nowrap;\n          text-overflow: ellipsis;\n        }\n      }\n  }\n}\n\n.topologyWrapper {\n  display: flex;\n  align-items: center;\n  margin-right: 80px;\n  margin-bottom: 80px;\n\n\n  .topologyChildren {\n    display: flex;\n    flex-direction: column;\n\n    & > div:nth-child(1) {\n      position: relative;\n\n      .verticalLine {\n        position: absolute;\n        background-color: #333;\n        left: 0;\n        top: 22px;\n        height: calc(100% - 55px);\n        width: 2px;\n        border-radius: 1px;\n      }\n    }\n\n  }\n\n\n  .topologyItem {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    padding: 10px;\n    border: 2px solid #333;\n    border-radius: 5px;\n  }\n\n  .rootItemBox {\n    display: flex;\n    align-items: center;\n    margin-bottom: 10px;\n\n    .topologyTitle {\n\n    }\n  }\n\n  .connectLine {\n    display: inline-block;\n    width: 30px;\n    background-color: #333;\n    height: 2px;\n    border-radius: 1px;\n  }\n}\n// .expandedRowWrapper {\n//   width: 100% ;\n// }\n.basicCardWrapper {\n  display: flex;\n  flex-flow: row wrap;\n\n  .basicCardItem {\n    width: 40%;\n    padding: 5px;\n    margin-right: 10px;\n    margin-bottom: 10px;\n  }\n}\n\n\n.buttonContainer {\n  width: 100%;\n  .greenType {\n    background-color: #5ba165;\n    color: #fff;\n    border-color: #5ba165;\n  }\n  .redType {\n    background-color: #ff4d4f;\n    color: #fff;\n    border-color: #ff4d4f;\n  }\n  & > button {\n    margin-right: 15px;\n  }\n}\n\n._bigfontSize{\n  font-size: 14px;\n}"
  },
  {
    "path": "omp_web/src/components/OmpToolTip/index.js",
    "content": "import { Tooltip } from \"antd\";\n\nconst OmpToolTip = ({ children, maxLength, ...props }) => {\n  children = children || \"-\";\n  if (children.length > maxLength) {\n    return (\n      <Tooltip title={children} {...props}>\n        {children.substring(0, maxLength)}...\n      </Tooltip>\n    );\n  } else {\n    return children;\n  }\n};\n\nexport default OmpToolTip;\n"
  },
  {
    "path": "omp_web/src/components/index.js",
    "content": "import OmpContentNav from \"./OmpContentNav\";\nimport OmpContentWrapper from \"./OmpContentWrapper\";\nimport OmpOperationWrapper from \"./OmpOperationWrapper\";\nimport OmpTable from \"./OmpTable\";\nimport OmpModal from \"./OmpModal\";\nimport OmpCollapseWrapper from \"./OmpCollapseWrapper\";\nimport OmpStateBlock from \"./OmpStateBlock\";\nimport CustomBreadcrumb from \"./CustomBreadcrumb\";\n// import OmpEnvSelect from \"./OmpEnvSelect\";\nimport OmpDatePicker from \"./OmpDatePicker\";\nimport OmpButton from \"./OmpButton\";\nimport OmpMessageModal from \"./OmpMessageModal\";\nimport OmpProgress from \"./OmpProgress\";\nimport OmpIframe from \"./OmpIframe\";\nimport OmpSelect from \"./OmpSelect\";\nimport OmpDrawer from \"./OmpDrawer\";\nimport OmpToolTip from \"./OmpToolTip\";\n\nexport {\n  OmpContentNav,\n  OmpContentWrapper,\n  OmpOperationWrapper,\n  OmpTable,\n  OmpModal,\n  OmpCollapseWrapper,\n  OmpStateBlock,\n  CustomBreadcrumb,\n  // OmpEnvSelect,\n  OmpDatePicker,\n  OmpButton,\n  OmpMessageModal,\n  OmpProgress,\n  OmpIframe,\n  OmpSelect,\n  OmpDrawer,\n  OmpToolTip,\n};\n"
  },
  {
    "path": "omp_web/src/config/requestApi.js",
    "content": "export const apiRequest = {\n  environment: {\n    //环境数据查询\n    //queryEnvList: \"/api/v1/env\"\n    // 查询全局维护模式\n    queryMaintainState: \"/api/promemonitor/globalMaintain/\",\n  },\n  auth: {\n    // 用户认证\n    login: \"/api/login/\", // 登入\n    // 首次请求用于验证登出\n    users: \"/api/users/users/\",\n    // 修改密码\n    changePassword: \"/api/users/updatePassword/\",\n  },\n  homepage: {\n    instrumentPanel: \"/api/promemonitor/instrumentPanel/\",\n  },\n  machineManagement: {\n    // 主机管理\n    hosts: \"/api/hosts/hosts/\",\n    ipList: \"/api/hosts/ips/\",\n    // 主机详情\n    hostDetail: \"/api/hosts/hostsDetail/\",\n    // 主机名和ip地址校验接口\n    checkHost: \"/api/hosts/fields/\",\n    operateLog: \"/api/hosts/operateLog/\",\n    // 重启主机agent\n    restartHostAgent: \"/api/hosts/restartHostAgent/\",\n    // 重启监控agent\n    restartMonitorAgent: \"/api/promemonitor/restartMonitorAgent/\",\n    // 重装主机agent\n    reInstallHostAgent: \"/api/hosts/hostReinstall/\",\n    // 重装监控agent\n    reInstallMonitorAgent: \"/api/hosts/monitorReinstall/\",\n    // 主机进入退出维护模式\n    hostsMaintain: \"/api/hosts/maintain/\",\n    // 主机初始化脚本下载地址\n    downInitScript: \"/api/hosts/hostInit/\",\n    // 主机初始化\n    hostInit: \"/api/hosts/hostInit/\",\n    // 主机agent状态查询\n    hostsAgentStatus: \"/api/hosts/hostsAgentStatus/\",\n\n    // 主机批量导入模版下载地址\n    downTemplate: \"/api/hosts/batchValidate/\",\n    // 主机批量导入文件解析后的校验接口(post)\n    batchValidate: \"/api/hosts/batchValidate/\",\n    // 主机批量导入创建主机\n    batchImport: \"/api/hosts/batchImport/\",\n\n    // 主机删除\n    deleteHost: \"/api/hosts/hostUninstall/\",\n  },\n  MonitoringSettings: {\n    // 配置初始查询监控\n    monitorurl: \"/api/promemonitor/monitorurl/\",\n    // 修改配置监控\n    multiple_update: \"/api/promemonitor/monitorurl/multiple_update/\",\n    // 查询推送配置\n    queryPushConfig: \"/api/promemonitor/getSendAlertSetting/\",\n    // 更新邮件配置\n    updatePushConfig: \"/api/promemonitor/updateSendAlertSetting/\",\n  },\n  Alert: {\n    // 告警记录页面查询\n    listAlert: \"/api/promemonitor/listAlert/\",\n    // 告警记录已读\n    updateAlert: \"/api/promemonitor/updateAlert/\",\n    // 告警记录筛选实例名称\n    instanceNameList: \"/api/promemonitor/instanceNameList/\",\n  },\n  ExceptionList: {\n    // 异常清单列表查询\n    exceptionList: \"/api/promemonitor/grafanaurl/\",\n  },\n  appStore: {\n    // 组件查询\n    queryComponents: \"/api/appStore/components/\",\n    // 服务查询\n    queryServices: \"/api/appStore/services/\",\n    // 服务筛选条件列表\n    queryLabels: \"/api/appStore/labels/\",\n    // 基础组件详情\n    ProductDetail: \"/api/appStore/componentDetail/\",\n    // 应用服务详情\n    ApplicationDetail: \"/api/appStore/serviceDetail/\",\n    // 安装包删除\n    remove: \"/api/appStore/remove/\",\n    // 安装包校验结果\n    pack_verification_results: \"/api/appStore/pack_verification_results/\",\n    // 发布命令下发\n    publish: \"/api/appStore/publish/\",\n    // 扫描服务端命令下发\n    executeLocalPackageScan: \"/api/appStore/executeLocalPackageScan/\",\n    // 扫描状态查询\n    localPackageScanResult: \"/api/appStore/localPackageScanResult/\",\n\n    // 服务列表\n    services: \"/api/services/services/\",\n    // 服务详情查询\n    servicesDetail: \"/api/services/services\",\n    // 服务的操作\n    servicesAction: \"/api/services/action/\",\n    // 服务的删除提示信息\n    servicesDeleteMsg: \"/api/services/delete/\",\n    // 模版下载\n    applicationTemplate: \"/api/appStore/applicationTemplate/\",\n    // 组件安装查询接口\n    componentEntrance: \"/api/appStore/componentEntrance/\",\n    // 产品安装查询接口\n    productEntrance: \"/api/appStore/productEntrance/\",\n\n    // 服务安装校验\n    executeInstall: \"/api/appStore/executeInstall/\",\n    // 服务校验通过后的状态查询接口\n    installHistory: \"/api/appStore/installHistory/\",\n\n    // 服务查询安装记录\n    serviceInstallHistoryDetail: \"/api/appStore/serviceInstallHistoryDetail/\",\n\n    // 服务批量安装选择应用服务列表\n    queryBatchInstallationServiceList: \"/api/appStore/batchInstallEntrance/\",\n    // 服务批量安装选择应用确认操作\n    createInstallInfo: \"/api/appStore/createInstallInfo/\",\n    // 批量安装\n    checkInstallInfo: \"/api/appStore/checkInstallInfo/\",\n    // 服务分布数据查询\n    createServiceDistribution: \"/api/appStore/createServiceDistribution/\",\n    // 服务分布已安装服务查询\n    queryListServiceByIp: \"/api/appStore/listServiceByIp/\",\n    // 服务批量安装服务分布确认操作\n    checkServiceDistribution: \"/api/appStore/checkServiceDistribution/\",\n\n    // 服务批量安装修改配置ip初始查询\n    getInstallHostRange: \"/api/appStore/getInstallHostRange/\",\n    // 服务批量安装根据ip查询安装相应参数\n    getInstallArgsByIp: \"/api/appStore/getInstallArgsByIp/\",\n    // 服务批量安装开始安装操作\n    createInstallPlan: \"/api/appStore/createInstallPlan/\",\n    // 批量安装-开始安装信息查询\n    queryInstallProcess: \"/api/appStore/showInstallProcess\",\n    // 批量安装-服务安装进度详情查询\n    showSingleServiceInstallLog: \"/api/appStore/showSingleServiceInstallLog/\",\n\n    // 批量安装-组件安装操作下发\n    createComponentInstallInfo: \"/api/appStore/createComponentInstallInfo/\",\n\n    // 批量安装-安装重试操作\n    retryInstall: \"/api/appStore/retryInstall/\",\n\n    // 服务升级选择应用服务列表\n    canUpgrade: \"/api/upgrade/can-upgrade\",\n\n    // 服务升级动作下发\n    doUpgrade: \"/api/upgrade/do-upgrade\",\n    // 服务升级页面列表进度查询\n    queryUpgradeProcess: \"/api/upgrade/history\",\n\n    // 服务回退选择应用服务列表\n    canRollback: \"/api/rollback/can-rollback\",\n\n    // 服务回退动作下发\n    doRollback: \"/api/rollback/do-rollback\",\n    // 服务回退页面列表进度查询\n    queryRollbackProcess: \"/api/rollback/history\",\n    // 删除应用商店\n    deleteServer: \"/api/appStore/delete/\",\n\n    // 获取可纳管服务列表\n    queryAppList: \"/api/services/appList/\",\n    // 校验服务纳管配置信息\n    appConfCheck: \"/api/services/appConfCheck/\",\n  },\n  installHistoryPage: {\n    queryInstallHistoryList: \"/api/appStore/mainInstallHistory\",\n    queryUpgradeHistoryList: \"/api/upgrade/history\",\n    queryRollbackHistoryList: \"/api/rollback/history\",\n    queryAllList: \"/api/appStore/executionRecord/\",\n  },\n  inspection: {\n    inspectionList: \"/api/inspection/history/\",\n    // 巡检记录详情\n    reportDetail: \"/api/inspection/report\",\n    // 巡检策略查询\n    queryPatrolStrategy: \"/api/inspection/crontab/0/\",\n    // 创建巡检任务\n    createPatrolStrategy: \"/api/inspection/crontab/\",\n    // 修改巡检任务\n    updatePatrolStrategy: \"/api/inspection/crontab/0/\",\n    // 深度分析｜主机巡检｜组件巡检 任务下发\n    taskDistribution: \"/api/inspection/history/\",\n    // 巡检渲染组件列表\n    servicesList: \"/api/inspection/services/\",\n    // 巡检导出\n    download: \"/download-inspection\",\n    // 查询推送配置\n    queryPushConfig: \"/api/inspection/inspectionSendEmailSetting/\",\n    // 更新邮件配置\n    updatePushConfig: \"/api/inspection/inspectionSendEmailSetting/\",\n    // 推送邮件\n    pushEmail: \"/api/inspection/inspectionSendEmail/\",\n  },\n  emailSetting: {\n    // 查询邮件全局设置\n    querySetting: \"/api/promemonitor/getSendEmailConfig/\",\n    // 更新邮件全局设置\n    updateSetting: \"/api/promemonitor/updateSendEmailConfig/\",\n  },\n  // 指标中心\n  ruleCenter: {\n    // 主机指标\n    hostThreshold: \"/api/promemonitor/hostThreshold/\",\n    // 服务指标\n    serviceThreshold: \"/api/promemonitor/serviceThreshold/\",\n    // 定制化指标\n    queryCustomThreshold: \"/api/promemonitor/customThreshold/\",\n\n    // 请求指标规则列表\n    queryPromemonitor: \"/api/promemonitor/quota/\",\n    // 内置指标\n    queryBuiltinsQuota: \"/api/promemonitor/builtinRule/\",\n\n    // 添加，修改规则\n    addQuota: \"/api/promemonitor/quota/\",\n\n    // 测试\n    testPromSql: \"/api/promemonitor/testPromSql/\",\n\n    // 删除\n    deleteQuota: \"/api/promemonitor/quota/\",\n\n    // 启动，停用\n    batchUpdateRule: \"/api/promemonitor/batchUpdateRule/\",\n\n    // 扩展指标列表查询\n    queryExtendRuleList: \"/api/promemonitor/customScript/\",\n\n    // 扩展指标详情查询\n    queryDetail: \"/api/promemonitor/customScriptJobInfo/\",\n  },\n  // 部署计划\n  deloymentPlan: {\n    // 服务验证\n    serviceValidate: \"/api/appStore/deploymentPlanValidate/\",\n    // 部署计划导入\n    serviceImport: \"/api/appStore/deploymentPlanImport/\",\n    // 部署计划列表\n    deploymentList: \"/api/appStore/deploymentPlanList/\",\n    // 部署是否可用\n    deploymentOperable: \"/api/appStore/deploymentOperable\",\n    // 下载部署计划模板\n    deploymentTemplate: \"/api/appStore/deploymentTemplate/\",\n  },\n  // 数据备份\n  dataBackup: {\n    // 数据库备份策略\n    strategySetting: \"api/backups/backupSettings/\",\n    // 自定义参数\n    backupCustom: \"api/backups/backupCustom/\",\n    // 可备份实例\n    queryCanBackup: \"api/backups/canBackupInstances/\",\n    // 查询自定义参数是否存在实例使用\n    backupRepeatCustom: \"api/backups/backupRepeatCustom/\",\n    // 备份记录\n    queryBackupHistory: \"api/backups/backupHistory/\",\n  },\n  operationRecord: {\n    // 登录日志\n    queryLoginLog: \"/api/users/UserLoginLog/\",\n    // 系统记录\n    querySystemLog: \"/api/users/operateLog/\",\n  },\n  faultSelfHealing: {\n    // 自愈记录\n    querySelfHealingList: \"/api/services/ListSelfHealingHistory/\",\n    // \"/api/services/ListSelfHealingHistory/\",\n    // 自愈已读\n    selfHeadlingIsRead: \"/api/services/UpdateSelfHealingHistory/\",\n    // 自愈策略\n    selfHealingStrategy: \"/api/services/SelfHealingSetting/\",\n  },\n  // 实用工具\n  utilitie: {\n    queryList: \"/api/tool/toolList/\",\n    queryFormConf: \"/api/tool/form/\",\n    uploadFile: \"/api/common/upload_file/\",\n    queryResult: \"/api/tool/result/\",\n    queryHistory: \"/api/tool/execute-history\",\n    queryBuiltinsQuota: \"/api/promemonitor/builtinRule/\",\n  },\n};\n"
  },
  {
    "path": "omp_web/src/config/router.config.js",
    "content": "import AppStore from \"@/pages/AppStore\";\nimport AppStoreDetail from \"@/pages/AppStore/config/detail\";\n//import VersionManagement from \"@/pages/ProductsManagement/VersionManagement\";\nimport MachineManagement from \"@/pages/MachineManagement\";\nimport UserManagement from \"@/pages/UserManagement\";\nimport MonitoringSettings from \"@/pages/MonitoringSettings\";\nimport SystemManagement from \"@/pages/SystemManagement\";\nimport AlarmLog from \"@/pages/AlarmLog\";\nimport ExceptionList from \"@/pages/ExceptionList\";\nimport PatrolInspectionRecord from \"@/pages/PatrolInspectionRecord\";\nimport PatrolStrategy from \"@/pages/PatrolStrategy\";\nimport PatrolInspectionDetail from \"@/pages/PatrolInspectionRecord/config/detail\";\nimport ServiceManagement from \"@/pages/ServiceManagement\";\nimport ComponentInstallation from \"@/pages/AppStore/config/ComponentInstallation\";\nimport ApplicationInstallation from \"@/pages/AppStore/config/ApplicationInstallation\";\nimport Installation from \"@/pages/AppStore/config/Installation\";\nimport EmailSettings from \"src/pages/EmailSettings\";\nimport RuleCenter from \"src/pages/RuleCenter\";\nimport InstallationRecord from \"@/pages/InstallationRecord\";\nimport Upgrade from \"@/pages/AppStore/config/Upgrade\";\nimport Rollback from \"@/pages/AppStore/config/Rollback\";\nimport DeploymentPlan from \"@/pages/DeploymentPlan\";\nimport BackupRecords from \"@/pages/BackupRecords\";\nimport BackupStrategy from \"@/pages/BackupStrategy\";\nimport LoginLog from \"@/pages/LoginLog\";\nimport SystemLog from \"@/pages/SystemLog\";\nimport SelfHealingRecord from \"@/pages/SelfHealingRecord\";\nimport SelfHealingStrategy from \"@/pages/SelfHealingStrategy\";\nimport ToolManagement from \"@/pages/ToolManagement\";\nimport TaskRecord from \"@/pages/TaskRecord\";\nimport ToolDetails from \"@/pages/ToolManagement/detail\";\nimport ToolExecution from \"@/pages/ToolExecution\";\nimport ToolExecutionResults from \"@/pages/ToolExecutionResults\";\nimport RuleIndicator from \"@/pages/RuleIndicator\";\nimport RuleExtend from \"@/pages/RuleExtend\";\nimport GetService from \"@/pages/AppStore/config/GetService\";\n\nimport {\n  DesktopOutlined,\n  ClusterOutlined,\n  ProfileOutlined,\n  SettingOutlined,\n  LineChartOutlined,\n  AppstoreOutlined,\n  EyeOutlined,\n  UnorderedListOutlined,\n  SaveOutlined,\n  SolutionOutlined,\n  InteractionOutlined,\n  ToolOutlined,\n} from \"@ant-design/icons\";\n\nexport default [\n  {\n    menuTitle: \"资源管理\",\n    menuIcon: <DesktopOutlined />,\n    menuKey: \"/resource-management\",\n    children: [\n      {\n        title: \"主机管理\",\n        path: \"/resource-management/machine-management\",\n        component: MachineManagement,\n      },\n    ],\n  },\n  {\n    menuTitle: \"应用管理\",\n    menuIcon: <AppstoreOutlined />,\n    menuKey: \"/application_management\",\n    children: [\n      {\n        title: \"服务管理\",\n        path: \"/application_management/service_management\",\n        component: ServiceManagement,\n      },\n      {\n        title: \"应用商店\",\n        path: \"/application_management/app_store\",\n        component: AppStore,\n      },\n      {\n        title: \"组件安装\",\n        path: \"/application_management/app_store/component_installation/:name\",\n        notInMenu: true,\n        component: ComponentInstallation,\n      },\n      {\n        title: \"应用安装\",\n        path: \"/application_management/app_store/application_installation/:name\",\n        notInMenu: true,\n        component: ApplicationInstallation,\n      },\n      {\n        title: \"应用商店服务详情\",\n        path: \"/application_management/app_store/app-service-detail/:name/:verson\",\n        notInMenu: true,\n        component: AppStoreDetail,\n      },\n      {\n        title: \"应用商店组件详情\",\n        path: \"/application_management/app_store/app-component-detail/:name/:verson\",\n        notInMenu: true,\n        component: AppStoreDetail,\n      },\n      {\n        title: \"批量安装\",\n        path: \"/application_management/app_store/installation\",\n        notInMenu: true,\n        component: Installation,\n      },\n      // {\n      //   title: \"服务安装\",\n      //   path: \"/application_management/app_store/installation-service\",\n      //   notInMenu: true,\n      //   component: Installation,\n      // },\n      {\n        title: \"执行记录\",\n        path: \"/application_management/install-record\",\n        component: InstallationRecord,\n      },\n      {\n        title: \"服务升级\",\n        path: \"/application_management/app_store/service_upgrade\",\n        notInMenu: true,\n        component: Upgrade,\n      },\n      {\n        title: \"服务回滚\",\n        path: \"/application_management/app_store/service_rollback\",\n        notInMenu: true,\n        component: Rollback,\n      },\n      {\n        title: \"部署模板\",\n        path: \"/application_management/deployment-plan\",\n        component: DeploymentPlan,\n      },\n      {\n        title: \"服务纳管\",\n        path: \"/application_management/get-service\",\n        notInMenu: true,\n        component: GetService,\n      },\n    ],\n  },\n  {\n    menuTitle: \"应用监控\",\n    menuIcon: <LineChartOutlined />,\n    menuKey: \"/application-monitoring\",\n    children: [\n      {\n        title: \"异常清单\",\n        path: \"/application-monitoring/exception-list\",\n        component: ExceptionList,\n      },\n      {\n        title: \"告警记录\",\n        path: \"/application-monitoring/alarm-log\",\n        component: AlarmLog,\n      },\n      {\n        title: \"监控设置\",\n        path: \"/application-monitoring/monitoring-settings\",\n        component: MonitoringSettings,\n      },\n    ],\n  },\n  {\n    menuTitle: \"故障自愈\",\n    menuIcon: <InteractionOutlined />,\n    menuKey: \"/fault-selfHealing\",\n    children: [\n      {\n        title: \"自愈策略\",\n        path: \"/fault-selfHealing/selfHealing-strategy\",\n        component: SelfHealingStrategy,\n      },\n      {\n        title: \"自愈记录\",\n        path: \"/fault-selfHealing/selfHealing-record\",\n        component: SelfHealingRecord,\n      },\n    ],\n  },\n  {\n    menuTitle: \"状态巡检\",\n    menuIcon: <EyeOutlined />,\n    menuKey: \"/status-patrol\",\n    children: [\n      {\n        title: \"巡检记录\",\n        path: \"/status-patrol/patrol-inspection-record\",\n        component: PatrolInspectionRecord,\n      },\n      {\n        title: \"巡检记录详情\",\n        path: \"/status-patrol/patrol-inspection-record/status-patrol-detail/:id\",\n        notInMenu: true,\n        component: PatrolInspectionDetail,\n      },\n      {\n        title: \"巡检策略\",\n        path: \"/status-patrol/patrol-strategy\",\n        component: PatrolStrategy,\n      },\n    ],\n  },\n  {\n    menuTitle: \"指标中心\",\n    menuIcon: <UnorderedListOutlined />,\n    menuKey: \"/rule-center\",\n    children: [\n      // {\n      //   title: \"默认指标\",\n      //   path: \"/rule-center/default-rule\",\n      //   component: RuleCenter,\n      // },\n      {\n        title: \"指标规则\",\n        path: \"/rule-center/indicator-rule\",\n        component: RuleIndicator,\n      },\n      {\n        title: \"扩展指标\",\n        path: \"/rule-center/extend-rule\",\n        component: RuleExtend,\n      },\n    ],\n  },\n  {\n    menuTitle: \"数据备份\",\n    menuIcon: <SaveOutlined />,\n    menuKey: \"/data-backup\",\n    children: [\n      {\n        title: \"备份策略\",\n        path: \"/data-backup/backup-strategy\",\n        component: BackupStrategy,\n      },\n      {\n        title: \"备份记录\",\n        path: \"/data-backup/backup-record\",\n        component: BackupRecords,\n      },\n    ],\n  },\n  {\n    menuTitle: \"实用工具\",\n    menuIcon: <ToolOutlined />,\n    menuKey: \"/utilitie\",\n    children: [\n      {\n        title: \"工具管理\",\n        path: \"/utilitie/tool-management\",\n        component: ToolManagement,\n      },\n      {\n        title: \"工具详情\",\n        path: \"/utilitie/tool-management/tool-management-detail/:id\",\n        notInMenu: true,\n        component: ToolDetails,\n      },\n      {\n        title: \"工具执行\",\n        path: \"/utilitie/tool-management/tool-execution/:id\",\n        notInMenu: true,\n        component: ToolExecution,\n      },\n      {\n        title: \"执行结果\",\n        path: \"/utilitie/tool-management/tool-execution-results/:id\",\n        notInMenu: true,\n        component: ToolExecutionResults,\n      },\n      {\n        title: \"任务记录\",\n        path: \"/utilitie/task-record\",\n        component: TaskRecord,\n      },\n    ],\n  },\n  {\n    menuTitle: \"操作记录\",\n    menuIcon: <SolutionOutlined />,\n    menuKey: \"/operation-record\",\n    children: [\n      {\n        title: \"登录日志\",\n        path: \"/operation-record/login-log\",\n        component: LoginLog,\n      },\n      {\n        title: \"系统记录\",\n        path: \"/operation-record/system-log\",\n        component: SystemLog,\n      },\n    ],\n  },\n  {\n    menuTitle: \"系统设置\",\n    menuIcon: <SettingOutlined />,\n    menuKey: \"/system-settings\",\n    children: [\n      {\n        title: \"用户管理\",\n        path: \"/system-settings/user-management\",\n        component: UserManagement,\n      },\n      {\n        title: \"系统管理\",\n        path: \"/system-settings/system-management\",\n        component: SystemManagement,\n      },\n      {\n        title: \"邮件管理\",\n        path: \"/system-settings/email-settings\",\n        component: EmailSettings,\n      },\n    ],\n  },\n];\n"
  },
  {
    "path": "omp_web/src/index.js",
    "content": "import ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(\n    <App />,\n  document.getElementById('root')\n);"
  },
  {
    "path": "omp_web/src/layouts/container/container.js",
    "content": "import React, { useEffect, useState } from \"react\";\n\nconst Container = ({ children, ...arg }) => {\n\n  let extendChild = React.cloneElement(children, arg);\n\n  return (\n    <div\n      style={{\n        padding: 20,\n        paddingTop: 0,\n        margin: 10,\n        height: \"100%\",\n        overflowY: \"auto\",\n        backgroundColor: \"#fff\",\n      }}\n    >\n      {extendChild}\n    </div>\n  );\n};\nexport default Container;\n"
  },
  {
    "path": "omp_web/src/layouts/index.js",
    "content": "import { Layout, Menu, Dropdown, message, Form, Input } from \"antd\";\nimport {\n  MenuUnfoldOutlined,\n  MenuFoldOutlined,\n  DashboardOutlined,\n  CaretDownOutlined,\n  QuestionCircleOutlined,\n  CaretUpOutlined,\n} from \"@ant-design/icons\";\nimport React, { useState, useEffect, useRef } from \"react\";\nimport img from \"@/config/logo/logo.svg\";\nimport styles from \"./index.module.less\";\nimport routerConfig from \"@/config/router.config\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { CustomBreadcrumb, OmpModal } from \"@/components\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport {\n  handleResponse,\n  _idxInit,\n  logout,\n  isPassword,\n  encrypt,\n} from \"@/utils/utils\";\nimport { useDispatch } from \"react-redux\";\nimport { getSetViewSizeAction } from \"./store/actionsCreators\";\nimport { getMaintenanceChangeAction } from \"@/pages/SystemManagement/store/actionsCreators\";\n\nconst { Header, Content, Footer, Sider } = Layout;\nconst { SubMenu } = Menu;\n\nconst OmpLayout = (props) => {\n  const reduxDispatch = useDispatch();\n  const history = useHistory();\n  const location = useLocation();\n  //不可用状态是一个全局状态，放在layout\n  const [disabled, setDisabled] = useState(false);\n  const [isLoading, setLoading] = useState(false);\n  const [collapsed, setCollapsed] = useState(false);\n  const rootSubmenuKeys = [\n    \"/machine-management\",\n    \"/products-management\",\n    \"/operation-management\",\n    \"/actions-record\",\n    \"/product-settings\",\n    \"/system-settings\",\n  ];\n\n  const headerLink = [\n    // { title: \"仪表盘\", path: \"/homepage\" },\n    // { title: \"应用商店\", path: \"/application_management/app_store\" },\n    { title: \"快速部署\", path: \"/application_management/deployment-plan\" },\n    // { title: \"数据上传\", path: \"/products-management/version/upload\" },\n    // { title: \"深度分析\", path: \"/operation-management/report\" },\n    {\n      title: \"监控平台\",\n      path: \"/proxy/v1/grafana/d/XrwAXz_Mz/mian-ban-lie-biao\",\n    },\n  ];\n\n  const [currentOpenedKeys, setCurrentOpenedKeys] = useState([]);\n  const [selectedKeys, setSelectedKeys] = useState([]);\n  //修改密码弹框\n  const [showModal, setShowModal] = useState(false);\n  //用户相关信息\n  const [userInfo, setUserInfo] = useState({});\n\n  const menu = (\n    <Menu className=\"menu\">\n      <Menu.Item key=\"changePass\" onClick={() => setShowModal(true)}>\n        修改密码\n      </Menu.Item>\n      <Menu.Item key=\"logout\" onClick={() => logout()}>\n        <span>退出登录</span>\n      </Menu.Item>\n    </Menu>\n  );\n\n  const toggle = () => {\n    setCollapsed(!collapsed);\n  };\n\n  const onPathChange = (e) => {\n    //console.log(e);\n    if (e.key === history.location.pathname) {\n      return;\n    }\n    // homepage没有submenu\n    if (e.key === \"/homepage\") {\n      setCurrentOpenedKeys([]);\n    }\n    history.push(e.key);\n  };\n\n  const onOpenChange = (openKeys) => {\n    const latestOpenKey = openKeys.find(\n      (key) => currentOpenedKeys.indexOf(key) === -1\n    );\n    //console.log(latestOpenKey)\n    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {\n      setCurrentOpenedKeys(openKeys);\n    } else {\n      setCurrentOpenedKeys(latestOpenKey ? [latestOpenKey] : []);\n    }\n  };\n\n  const onPassWordChange = (data) => {\n    setLoading(true);\n    fetchPost(apiRequest.auth.changePassword, {\n      body: {\n        username: encrypt(localStorage.getItem(\"username\")),\n        old_password: encrypt(data.old_password),\n        new_password: encrypt(data.new_password2),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          if (res.code == 0) {\n            message.success(\"修改密码成功, 请重新登录\");\n            setShowModal(false);\n            setTimeout(() => {\n              logout();\n            }, 1000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 相应路由跳转，submenu打开\n  useEffect(() => {\n    try {\n      let pathArr = location.pathname.split(\"/\");\n      console.log(pathArr);\n      if (pathArr[1] == \"homepage\") {\n        setSelectedKeys([\"/homepage\"]);\n      } else {\n        setSelectedKeys([`/${pathArr[1]}/${pathArr[2]}`]);\n      }\n      let newPath = `/${pathArr[1]}`;\n      setCurrentOpenedKeys([newPath]);\n    } catch (e) {\n      console.log(e);\n    }\n  }, [location]);\n\n  useEffect(() => {\n    window.__history__ = history;\n    fetchGet(apiRequest.auth.users)\n      .then((res) => {\n        if (res && res.data.code == 1 && res.data.message == \"未认证\") {\n        }\n        res.data && res.data.data && setUserInfo(res.data.data[0]);\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally(() => setLoading(false));\n  }, []);\n\n  const antiShakeRef = useRef(null);\n\n  const getViewSize = () => {\n    reduxDispatch(\n      // 这里做一个视口查询，存入store, 其他组件可以根据视口大小进行自适应\n      getSetViewSizeAction({\n        height: document.documentElement.clientHeight,\n        width: document.documentElement.clientWidth,\n      })\n    );\n  };\n\n  getViewSize();\n\n  useEffect(() => {\n    window.onresize = () => {\n      if (!antiShakeRef.current) {\n        antiShakeRef.current = true;\n        setTimeout(() => {\n          getViewSize();\n          antiShakeRef.current = false;\n        }, 300);\n      }\n    };\n  }, []);\n\n  // 防止在校验进入死循环\n  const flag = useRef(null);\n\n  // 查询全局维护模式状态\n  const queryMaintainState = () => {\n    fetchGet(apiRequest.environment.queryMaintainState)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          //console.log(res)\n          if (res.data) {\n            reduxDispatch(getMaintenanceChangeAction(res.data.length !== 0));\n          }\n        });\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally();\n  };\n\n  useEffect(() => {\n    queryMaintainState();\n  }, []);\n\n  return (\n    <Layout style={{ minHeight: \"100vh\" }}>\n      <Sider\n        trigger={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}\n        collapsible\n        collapsed={collapsed}\n        onCollapse={toggle}\n        collapsedWidth={50}\n      >\n        <div\n          style={{\n            position: \"relative\",\n            left: collapsed ? 0 : -15,\n            display: \"flex\",\n            height: 60,\n            color: \"white\",\n            justifyContent: \"center\",\n            // /marginBottom:10,\n            backgroundColor: \"#151a21\",\n          }}\n        >\n          <div className={styles.headerLogo}>\n            <img src={img} />\n          </div>\n          {!collapsed && (\n            <div\n              style={{\n                cursor: \"pointer\",\n                position: \"relative\",\n                top: 1,\n                fontSize: 18,\n                fontWeight: 600,\n                display: \"flex\",\n                alignItems: \"center\",\n              }}\n              onClick={() => history.push(\"/homepage\")}\n            >\n              {/* 运维管理平台 */}\n              运维工具包\n            </div>\n          )}\n        </div>\n        <Menu\n          mode=\"inline\"\n          style={{\n            height: \"calc(100% - 60px)\",\n            //paddingTop:3,\n            // borderRight: \"1px solid #d7d9e1\",\n            color: \"rgba(0,0,0,0.65)\",\n            overflowY: \"auto\",\n            // position:\"fixed\",\n            // zIndex:1000,\n          }}\n          //theme=\"dark\"\n          onClick={onPathChange}\n          onOpenChange={onOpenChange}\n          openKeys={currentOpenedKeys}\n          selectedKeys={selectedKeys}\n          //theme=\"red\"\n          expandIcon={(e) => {\n            if (e.isOpen) {\n              return <CaretUpOutlined />;\n            } else {\n              return (\n                <CaretDownOutlined style={{ color: \"rgba(0,0,0,0.65)\" }} />\n              );\n            }\n          }}\n        >\n          <Menu.Item key=\"/homepage\" icon={<DashboardOutlined />}>\n            仪表盘\n          </Menu.Item>\n          {routerConfig.map((item) => {\n            return (\n              <SubMenu\n                key={item.menuKey}\n                icon={item.menuIcon}\n                title={item.menuTitle}\n              >\n                {item.children.map((i) => {\n                  if (!i.notInMenu) {\n                    return <Menu.Item key={i.path}>{i.title}</Menu.Item>;\n                  }\n                })}\n              </SubMenu>\n            );\n          })}\n        </Menu>\n      </Sider>\n      <Layout className=\"site-layout\" style={{ width: \"100%\" }}>\n        <Header\n          className=\"site-layout-background\"\n          style={{\n            padding: 0,\n            display: \"flex\",\n            justifyContent: \"space-between\",\n            position: \"fixed\",\n            zIndex: 1000,\n            transition: collapsed ? \"all 0.1s ease-out\" : \"all 0.4s ease-out\",\n            width: collapsed ? \"calc(100% - 49px)\" : \"calc(100% - 199px)\",\n            marginLeft: collapsed ? 49 : 199,\n          }}\n        >\n          <div style={{ display: \"flex\" }}>\n            {headerLink.map((item, idx) => {\n              return (\n                <div\n                  style={\n                    window.location.hash.includes(item.path)\n                      ? { background: \"#0C1423\", color: \"#fff\" }\n                      : { cursor: disabled ? \"not-allowed\" : null }\n                  }\n                  className={\n                    !disabled || item.title === \"快速部署\"\n                      ? styles.headerLink\n                      : styles.headerLinkNohover\n                  }\n                  key={idx}\n                  onClick={() => {\n                    if (!disabled || item.title === \"快速部署\") {\n                      if (item.title === \"监控平台\") {\n                        window.open(\n                          \"/proxy/v1/grafana/d/XrwAXz_Mz/mian-ban-lie-biao\"\n                        );\n                      } else {\n                        history.push(item.path);\n                      }\n                    }\n                  }}\n                >\n                  {item.title}\n                </div>\n              );\n            })}\n          </div>\n          <div\n            className={styles.userAvatar}\n            style={{ display: \"flex\", position: \"relative\", top: 2 }}\n          >\n            <Dropdown overlay={menu} trigger={[\"click\"]}>\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontWeight: 500,\n                  fontSize: 14,\n                  cursor: \"pointer\",\n                }}\n              >\n                {localStorage.getItem(\"username\")}{\" \"}\n                <CaretDownOutlined\n                  style={{ position: \"relative\", top: 1, left: 3 }}\n                />\n              </div>\n            </Dropdown>\n            <Dropdown\n              trigger={[\"click\"]}\n              placement=\"bottomCenter\"\n              overlay={\n                <Menu className=\"menu\">\n                  <Menu.Item>版本信息：V1.1</Menu.Item>\n                </Menu>\n              }\n            >\n              <div\n                style={{\n                  margin: \"0 25px 0 22px\",\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontSize: 14,\n                }}\n              >\n                <QuestionCircleOutlined />\n              </div>\n            </Dropdown>\n          </div>\n        </Header>\n        <CustomBreadcrumb collapsed={collapsed} />\n        <Content style={{ margin: \"0 16px\", color: \"rgba(0,0,0,0.65)\" }}>\n          <div\n            style={{\n              transition: \"all 0.2s ease-in-out\",\n              marginTop: 120,\n              marginLeft: collapsed ? 50 : 200,\n              padding: 0,\n              paddingBottom: 30,\n              height: \"calc(100% - 130px)\",\n              // 应用商店content大背景不是白色，特殊处理\n              backgroundColor:\n                location.pathname == \"/application_management/app_store\" ||\n                location.pathname.includes(\"installation\") ||\n                location.pathname.includes(\"service_upgrade\") ||\n                location.pathname.includes(\"service_rollback\") ||\n                (location.pathname.includes(\"tool-management\") &&\n                  !location.pathname.includes(\"tool-execution\")) ||\n                location.pathname.includes(\"/homepage\")\n                  ? undefined\n                  : \"#fff\",\n            }}\n          >\n            {props.children}\n          </div>\n        </Content>\n        <Footer\n          style={{\n            backgroundColor: \"rgba(0,0,0,0)\",\n            textAlign: \"center\",\n            height: 30,\n            padding: 0,\n            paddingTop: 0,\n            paddingLeft: 195,\n          }}\n        >\n          Copyright © 2020-2023 Cloudwise.All Rights Reserved{\" \"}\n        </Footer>\n      </Layout>\n      <OmpModal\n        loading={isLoading}\n        onFinish={onPassWordChange}\n        visibleHandle={[showModal, setShowModal]}\n        title=\"修改密码\"\n        beForeOk={() => {\n          flag.current = true;\n        }}\n        afterClose={() => {\n          flag.current = null;\n        }}\n      >\n        <Form.Item\n          label=\"当前密码\"\n          name=\"old_password\"\n          key=\"old_password\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入当前用户密码\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (value) {\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    return Promise.resolve(\"success\");\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请输入当前密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"新密码\"\n          name=\"new_password1\"\n          key=\"new_password1\"\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入新密码\",\n            },\n            {\n              validator: (rule, value, callback, passwordModalForm) => {\n                if (value) {\n                  if (!flag.current) {\n                    passwordModalForm.validateFields([\"new_password2\"]);\n                  }\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    return Promise.resolve(\"success\");\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请设置新密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"确认密码\"\n          name=\"new_password2\"\n          key=\"new_password2\"\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            {\n              required: true,\n              message: \"请再次输入新密码\",\n            },\n            {\n              validator: (rule, value, callback, passwordModalForm) => {\n                if (value) {\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    if (\n                      passwordModalForm.getFieldValue().new_password1 ===\n                        value ||\n                      !value\n                    ) {\n                      return Promise.resolve(\"success\");\n                    } else {\n                      return Promise.reject(\"两次密码输入不一致\");\n                    }\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请再次输入新密码\" />\n        </Form.Item>\n      </OmpModal>\n    </Layout>\n  );\n};\n\nexport default OmpLayout;\n"
  },
  {
    "path": "omp_web/src/layouts/index.module.less",
    "content": ".headerLogo {\n  width: 60px;\n  display: flex;\n  height: 100%;\n  align-items: center;\n  justify-content: center;\n  & > img:nth-child(1) {\n    width: 30px;\n    height: 30px;\n  }\n}\n.trigge {\n  padding: 0 24px;\n  font-size: 18px;\n  line-height: 64px;\n  cursor: pointer;\n  transition: color 0.3s;\n}\n.OmpLayoutContainer {\n  color: #63656e;\n  height: 100%;\n  //height: 58px;\n  //background-color: red;\n  .siteLayoutBackground {\n    border-right: solid 1px #dcdee5;\n    //padding-left:20px;\n    //padding-top:0px ;\n  }\n  .OmpHeader {\n    display: flex;\n    align-items: center;\n    width: 100%;\n    height: 58px;\n    color: white;\n    font-size: 14px;\n    white-space: nowrap;\n    background-color: #1a2131;\n    .headerSelect {\n      width: 160px;\n    }\n    .headerDropDown {\n      font-size: 14px;\n      margin-left: 20px;\n      cursor: pointer;\n      padding: 7px 10px;\n      padding-right: 90px;\n      border: 1px solid #979aa4;\n      .headerDropDownIcon {\n        position: relative;\n        left: 70px;\n      }\n    }\n    .envMenuWrapper {\n      margin-left: auto;\n    }\n    .headerLogo {\n      width: 60px;\n      display: flex;\n      height: 100%;\n      align-items: center;\n      justify-content: center;\n      & > img:nth-child(1) {\n        width: 30px;\n        height: 30px;\n      }\n    }\n\n    div:nth-child(2) {\n      margin-right: 75px;\n      font-size: 16px;\n      font-weight: 500;\n    }\n\n    .userAvatar {\n      width: 100px;\n      //margin-right: 70px;\n      margin-left: auto;\n      display: flex;\n      align-items: center;\n      //width: 100%;\n      //justify-content: flex-end;\n      position: relative;\n      top: 1px;\n    }\n    .userAvatarIcon {\n      position: relative;\n      top: 1px;\n      left: 3px;\n    }\n  }\n  .MenuTop {\n    height: 52px;\n    background-color: white;\n    border-bottom: 1px solid #dcdee5;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  }\n  //   .siteLayoutBackground {\n  //     background-color: red;\n  //   }\n}\n\n.headerLink {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 0 20px;\n  color: #8d919c;\n  color: rgba(255, 255, 255, 0.75);\n  font-weight: 500;\n  font-size: 15px;\n  cursor: pointer;\n  position: relative;\n  top:1px\n}\n\n.headerLinkNohover {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 0 15px;\n  color: #8d919c;\n  color: rgba(255, 255, 255, 0.75);\n  font-weight: 500;\n  font-size: 15px;\n  cursor: pointer;\n}\n\n.headerLink:hover {\n  background: #121925;\n}\n\n:global {\n  .ant-layout-sider-has-trigger {\n    position: fixed;\n    height: 100%;\n  }\n  .ant-pagination {\n    color: rgba(0, 0, 0, 0.65) !important;\n  }\n  .ant-btn {\n    color: rgba(0, 0, 0, 0.65);\n  }\n  .ant-btn-primary {\n    color: #fff;\n  }\n  .ant-menu {\n    color: rgba(0, 0, 0, 0.65);\n  }\n  .ant-select {\n    color: rgba(0, 0, 0, 0.65) !important;\n  }\n  .site-layout .site-layout-background {\n    // background: #fff;\n    background-color: #161c25;\n    color: white;\n    height: 60px;\n  }\n  #components-layout-demo-custom-trigger .trigger {\n    padding: 0 24px;\n    font-size: 18px;\n    line-height: 64px;\n    cursor: pointer;\n    transition: color 0.3s;\n  }\n\n  #components-layout-demo-custom-trigger .trigger:hover {\n    color: #1890ff;\n  }\n\n  #components-layout-demo-custom-trigger .logo {\n    height: 32px;\n    margin: 16px;\n    background: rgba(255, 255, 255, 0.3);\n  }\n  .ant-layout-sider-trigger {\n    background: #fff;\n    color: black;\n    font-size: 18px;\n    // border-right: 1px solid #f0f0f0\n  }\n  .ant-layout-sider-children {\n    background: #151a21;\n  }\n  // .site-layout .site-layout-background {\n  //   background: #fff;\n  //   height: 50px;\n  // }\n\n  // 修改antd menu的原生选中样式\n  // 选中文字的颜色\n  // .ant-menu.ant-menu-dark, .ant-menu-dark .ant-menu-sub, .ant-menu.ant-menu-dark .ant-menu-sub {\n  //   background-color: #192031;\n  //   color: #fff;\n  // }\n  // .ant-layout-sider {\n  //   background-color: #2e4254;\n  // }\n  // .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {\n  //   background-color: #4a6782;\n  //   color: #36bebc;\n  // }\n\n  // .ant-menu-dark .ant-menu-item:hover, .ant-menu-dark .ant-menu-item-active, .ant-menu-dark .ant-menu-submenu-active, .ant-menu-dark .ant-menu-submenu-open, .ant-menu-dark .ant-menu-submenu-selected, .ant-menu-dark .ant-menu-submenu-title:hover {\n  //   color: #36bebc;\n  // }\n  // .ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::after, .ant-menu-dark .ant-menu-item:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-item-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-open > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before, .ant-menu-dark .ant-menu-submenu-title:hover > .ant-menu-submenu-title > .ant-menu-submenu-arrow::before {\n  //   background-color: #36bebc;\n  // }\n  // .ant-menu-dark .ant-menu-item, .ant-menu-dark .ant-menu-item-group-title, .ant-menu-dark .ant-menu-item > a, .ant-menu-dark .ant-menu-item > span > a {\n  //   color: #fff\n  // }\n  // .ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon, .ant-menu-dark .ant-menu-item-selected .anticon {\n  //   color: #36bebc;\n  // }\n  // .ant-menu-dark .ant-menu-item-selected .ant-menu-item-icon + span, .ant-menu-dark .ant-menu-item-selected .anticon + span {\n  //   color: #36bebc;\n  // }\n  // .ant-menu.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow, .ant-menu.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow {\n  //   opacity: 1;\n  // }\n  .ant-spin-nested-loading {\n    //height: 100%;\n    width: 100%;\n  }\n  .ant-spin-container {\n    //height: 100%;\n    width: 100%;\n  }\n\n  // search 的搜索\n  .ant-input-search\n    > .ant-input-group\n    > .ant-input-group-addon:last-child\n    .ant-input-search-button {\n    padding-top: 1px;\n  }\n\n  // 按钮高度统一调整\n  .ant-btn {\n    padding: 4px 20px;\n    border-radius: 4px;\n  }\n  .ant-btn-sm {\n    padding: 1px 7px;\n  }\n  // 翻页组件样式修改\n  // .ant-pagination-prev,\n  // .ant-pagination-next,\n  // .ant-pagination-total-text,\n  // .ant-pagination-jump-prev,\n  // .ant-pagination-jump-next {\n  //   height: 30px !important;\n  //   min-width: 30px !important;\n  //   line-height: 30px !important;\n  // }\n  // .ant-pagination-item {\n  //   height: 30px !important;\n  //   min-width: 30px !important;\n  //   line-height: 30px !important;\n  // }\n  // .ant-pagination-options {\n  //   .ant-select-selector {\n  //     height: 30px !important;\n  //     min-width: 30px !important;\n  //     line-height: 30px !important;\n  //   }\n  // }\n  .ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left {\n    border-right: none;\n  }\n  // .ant-table-pagination {\n  //   color: red!important;\n  // }\n  // .ant-menu-vertical .ant-menu-item::after, .ant-menu-vertical-left .ant-menu-item::after, .ant-menu-vertical-right .ant-menu-item::after, .ant-menu-inline .ant-menu-item::after {\n  //   border-right:3px solid #4986f7\n  // }\n  .ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "omp_web/src/layouts/indexOld.js",
    "content": "import { Layout, Menu, Dropdown, message, Form, Input, Button } from \"antd\";\nimport {\n  MenuUnfoldOutlined,\n  MenuFoldOutlined,\n  DashboardOutlined,\n  CaretDownOutlined,\n  QuestionCircleOutlined,\n} from \"@ant-design/icons\";\nimport React, { useState, useEffect } from \"react\";\nimport img from \"@/config/logo/logo.svg\";\nimport styles from \"./index.module.less\";\nimport routerConfig from \"@/config/router.config\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { CustomBreadcrumb, OmpModal } from \"@/components\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport {\n  handleResponse,\n  _idxInit,\n  logout\n} from \"@/utils/utils\";\n\nconst { Header, Content, Footer, Sider } = Layout;\nconst { SubMenu } = Menu;\n\nconst OmpLayout = (props) => {\n  const history = useHistory();\n  const location = useLocation();\n  //不可用状态是一个全局状态，放在layout\n  const [disabled, setDisabled] = useState(false);\n  const [isLoading, setLoading] = useState(false);\n  const [collapsed, setCollapsed] = useState(false);\n  const rootSubmenuKeys = [\n    \"/machine-management\",\n    \"/products-management\",\n    \"/operation-management\",\n    \"/actions-record\",\n    \"/product-settings\",\n    \"/system-settings\",\n  ];\n  const menu = (\n    <Menu className=\"menu\">\n      <Menu.Item key=\"changePass\" onClick={() => setShowModal(true)}>\n        修改密码\n      </Menu.Item>\n      <Menu.Item key=\"logout\" onClick={() => logout()}>\n        <span>退出登录</span>\n      </Menu.Item>\n    </Menu>\n  );\n  const [currentOpenedKeys, setCurrentOpenedKeys] = useState([]);\n  const [selectedKeys, setSelectedKeys] = useState([]);\n  //修改密码弹框\n  const [showModal, setShowModal] = useState(false);\n  //用户相关信息\n  const [userInfo, setUserInfo] = useState({})\n\n  const toggle = () => {\n    setCollapsed(!collapsed);\n  };\n\n  const onPathChange = (e) => {\n    console.log(e);\n    if (e.key === history.location.pathname) {\n      return;\n    }\n    // homepage没有submenu\n    if (e.key === \"/homepage\") {\n      setCurrentOpenedKeys([]);\n    }\n    history.push(e.key);\n  };\n\n  const onOpenChange = (openKeys) => {\n    const latestOpenKey = openKeys.find(\n      (key) => currentOpenedKeys.indexOf(key) === -1\n    );\n    //console.log(latestOpenKey)\n    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {\n      setCurrentOpenedKeys(openKeys);\n    } else {\n      setCurrentOpenedKeys(latestOpenKey ? [latestOpenKey] : []);\n    }\n  };\n\n  const onPassWordChange = (data) => {\n    setLoading(true);\n    fetchPost(apiRequest.auth.password, {\n      body: {\n        ...data,\n      },\n    })\n      .then((res) => {\n        handleResponse(res);\n        if (res.code == 0) {\n          setShowModal(false);\n          logout();\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 相应路由跳转，submenu打开\n  useEffect(() => {\n    try {\n      if (\n        location.pathname == \"/products-management/version/rapidDeployment\" ||\n        location.pathname == \"/products-management/version/upload\" ||\n        location.pathname == \"/products-management/version/installationDetails\"\n      ) {\n        setSelectedKeys([\"/products-management/version\"]);\n      } else {\n        setSelectedKeys([location.pathname]);\n      }\n      let pathArr = location.pathname.split(\"/\");\n      let newPath = `/${pathArr[1]}`;\n      setCurrentOpenedKeys([newPath]);\n    } catch (e) {\n      console.log(e);\n    }\n  }, [location]);\n\n  useEffect(() => {\n    window.__history__ = history;\n    fetchGet(apiRequest.auth.users)\n      .then((res) => {\n        if (res && res.data.code == 1 && res.data.message == \"未认证\") {\n          //message.warning(\"登录失效,请重新登录\")\n          //history.replace(\"/login\");\n        }\n        console.log(res.data)\n        res.data && setUserInfo(res.data.data[0])\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally(() => setLoading(false));\n  }, []);\n\n  return (\n    <Layout style={{ minHeight: \"100vh\" }}>\n      <Sider trigger={null} collapsible collapsed={collapsed}>\n        <div\n          style={{\n            position: \"relative\",\n            left: collapsed ? 0 : -15,\n            display: \"flex\",\n            height: 46,\n            color: \"white\",\n            justifyContent: \"center\",\n            marginBottom:10\n          }}\n        >\n          <div className={styles.headerLogo}>\n            <img src={img} />\n          </div>\n          {!collapsed && (\n            <div\n              style={{\n                cursor: \"pointer\",\n                position: \"relative\",\n                top: 1,\n                fontSize: 16,\n                fontWeight: 600,\n                display: \"flex\",\n                alignItems: \"center\",\n              }}\n              onClick={() => history.push(\"/homepage\")}\n            >\n              运维管理平台\n            </div>\n          )}\n        </div>\n        <Menu\n          mode=\"inline\"\n          style={{\n            height: \"calc(100% - 60px)\",\n            //paddingTop:3,\n            borderRight: 0,\n          }}\n          theme=\"dark\"\n          onClick={onPathChange}\n          onOpenChange={onOpenChange}\n          openKeys={currentOpenedKeys}\n          selectedKeys={selectedKeys}\n          //theme=\"red\"\n        >\n          <Menu.Item key=\"/homepage\" icon={<DashboardOutlined />}>\n            仪表盘\n          </Menu.Item>\n          {routerConfig.map((item) => {\n            return (\n              <SubMenu\n                key={item.menuKey}\n                icon={item.menuIcon}\n                title={item.menuTitle}\n              >\n                {item.children.map((i) => {\n                  return <Menu.Item key={i.path}>{i.title}</Menu.Item>;\n                })}\n              </SubMenu>\n            );\n          })}\n        </Menu>\n      </Sider>\n      <Layout className=\"site-layout\">\n        <Header\n          className=\"site-layout-background\"\n          style={{\n            padding: 0,\n            display: \"flex\",\n            justifyContent: \"space-between\",\n          }}\n        >\n          {React.createElement(\n            collapsed ? MenuUnfoldOutlined : MenuFoldOutlined,\n            {\n              style: {\n                padding: \"0 24px\",\n                fontSize: \"18px\",\n                lineHeight: \"60px\",\n                cursor: \"pointer\",\n                transition: \"color 0.3s\",\n              },\n              onClick: toggle,\n            }\n          )}\n          <div className={styles.userAvatar} style={{ display: \"flex\" }}>\n            <Dropdown overlay={menu} trigger={[\"click\"]}>\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontWeight: 500,\n                  fontSize: 14,\n                  cursor: \"pointer\",\n                }}\n              >\n                {userInfo?.username}{\" \"}\n                <CaretDownOutlined\n                  style={{ position: \"relative\", top: 1, left: 3 }}\n                />\n              </div>\n            </Dropdown>\n            <Dropdown\n              trigger={[\"click\"]}\n              placement=\"bottomCenter\"\n              overlay={\n                <Menu className=\"menu\">\n                  <Menu.Item>版本信息：V0.1.0</Menu.Item>\n                </Menu>\n              }\n            >\n              <div\n                style={{\n                  margin: \"0 25px 0 22px\",\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontSize: 14,\n                }}\n              >\n                <QuestionCircleOutlined />\n              </div>\n            </Dropdown>\n          </div>\n        </Header>\n        <Content style={{ margin: \"0 16px\" }}>\n          <CustomBreadcrumb />\n          <div\n            //className=\"site-layout-background\"\n            style={{ padding: 0, paddingBottom: 30, height:\"calc(100% - 30px)\",backgroundColor:\"#fff\" }}\n          >\n            {props.children}\n          </div>\n        </Content>\n        <Footer\n          style={{\n            //color: \"#acb5ba\",\n            backgroundColor: \"rgba(0,0,0,0)\",\n            textAlign: \"center\",\n            height: 40,\n            padding: 0,\n            paddingTop: 10,\n            // position:\"absolute\",\n            // bottom:0\n          }}\n        >\n          Copyright © 2020-2021 Cloudwise.All Rights Reserved{\" \"}\n        </Footer>\n      </Layout>\n      <OmpModal\n        loading={isLoading}\n        onFinish={onPassWordChange}\n        visibleHandle={[showModal, setShowModal]}\n        title=\"修改密码\"\n      >\n        <Form.Item\n          label=\"当前密码\"\n          name=\"old_password\"\n          key=\"old_password\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入当前用户密码\",\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请输入当前密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"新密码\"\n          name=\"new_password1\"\n          key=\"new_password1\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入新密码\",\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请设置新密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"确认密码\"\n          name=\"new_password2\"\n          key=\"new_password2\"\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            {\n              required: true,\n              message: \"请再次输入新密码\",\n            },\n            {\n              validator: (rule, value, callback, passwordModalForm) => {\n                if (\n                  passwordModalForm.getFieldValue().new_password1 === value ||\n                  !value\n                ) {\n                  return Promise.resolve(\"success\");\n                } else {\n                  return Promise.reject(\"两次密码输入不一致\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请再次输入新密码\" />\n        </Form.Item>\n      </OmpModal>\n    </Layout>\n  );\n};\n\nexport default OmpLayout;\n"
  },
  {
    "path": "omp_web/src/layouts/store/actionsCreators.js",
    "content": "import * as actionTypes from \"./constants\";\n\nexport const getSetViewSizeAction = (viewSize) => ({\n    type: actionTypes.SET_VIEWSIZE,\n    payload: {\n        viewSize:viewSize\n    }\n});"
  },
  {
    "path": "omp_web/src/layouts/store/constants.js",
    "content": "export const SET_VIEWSIZE = \"SET_VIEWSIZE\";\n//export const CHANGE_ENVINFO = \"CHANGE_ENVINFO\";"
  },
  {
    "path": "omp_web/src/layouts/store/index.js",
    "content": "import reducer from \"./reduer\";\n\n export {\n    reducer\n };"
  },
  {
    "path": "omp_web/src/layouts/store/reduer.js",
    "content": "import * as actionTypes from \"./constants\";\n\nconst defaultState = {\n    viewSize:{ height:0, width:0 },   \n    a:\"1\"\n};\n\nfunction reducer(state = defaultState,action){\n    switch(action.type){\n        case actionTypes.SET_VIEWSIZE:\n            return {...state, viewSize: action.payload.viewSize};\n        case \"c\":\n            return {...state, a: \"123\"};\n        default:\n            return state;\n    }\n}\n\nexport default reducer;"
  },
  {
    "path": "omp_web/src/pages/AlarmLog/config/columns.js",
    "content": "import { colorConfig } from \"@/utils/utils\";\nimport { Tooltip, Badge } from \"antd\";\nimport moment from \"moment\";\n\nconst getColumnsConfig = (\n  queryRequest,\n  setShowIframe,\n  updateAlertRead,\n  history\n) => {\n  return [\n    {\n      title: \"实例名称\",\n      key: \"alert_instance_name\",\n      dataIndex: \"alert_instance_name\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      fixed: \"left\",\n      // sorter: (a, b) => a.alert_instance_name - b.alert_instance_name,\n      // sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <Badge dot={record.is_read === 0} offset={[5, 2]}>\n              <span style={{ fontSize: 12 }}>\n                {record.alert_instance_name ? record.alert_instance_name : \"-\"}\n              </span>\n            </Badge>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"alert_host_ip\",\n      width: 200,\n      dataIndex: \"alert_host_ip\",\n      ellipsis: true,\n      sorter: (a, b) => a.alert_host_ip - b.alert_host_ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        return (\n          <a\n            onClick={() => {\n              text &&\n                history.push({\n                  pathname: \"/resource-management/machine-management\",\n                  state: {\n                    ip: text,\n                  },\n                });\n            }}\n          >\n            {text}\n          </a>\n        );\n      },\n    },\n    {\n      title: \"级别\",\n      key: \"alert_level\",\n      dataIndex: \"alert_level\",\n      align: \"center\",\n      width: 120,\n      // sorter: (a, b) => a.severity - b.severity,\n      // sortDirections: [\"descend\", \"ascend\"],\n      //ellipsis: true,\n      //width:120,\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"critical\",\n          text: \"严重\",\n        },\n        {\n          value: \"warning\",\n          text: \"警告\",\n        },\n      ],\n      render: (text) => {\n        switch (text) {\n          case \"critical\":\n            return <span style={{ color: colorConfig[text] }}>严重</span>;\n          case \"warning\":\n            return <span style={{ color: colorConfig[text] }}>警告</span>;\n          default:\n            return \"-\";\n        }\n      },\n    },\n    {\n      title: \"告警类型\",\n      key: \"alert_type\",\n      dataIndex: \"alert_type\",\n      // usefilter: true,\n      // queryRequest: queryRequest,\n      // filterMenuList: [\n      //   {\n      //     value: \"service\",\n      //     text: \"服务\",\n      //   },\n      //   {\n      //     value: \"host\",\n      //     text: \"主机\",\n      //   },\n      // ],\n      align: \"center\",\n      //ellipsis: true,\n      width: 150,\n      render: (text) => {\n        if (text == \"host\") {\n          return \"主机\";\n        } else if (text == \"service\") {\n          return \"服务\";\n        } else if (text == \"component\") {\n          return \"组件\";\n        } else if (text == \"database\") {\n          return \"数据库\";\n        }\n      },\n    },\n    {\n      title: \"告警描述\",\n      key: \"alert_describe\",\n      dataIndex: \"alert_describe\",\n      align: \"center\",\n      width: 420,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"告警时间\",\n      width: 180,\n      key: \"alert_time\",\n      dataIndex: \"alert_time\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.alert_time - b.alert_time,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        return str;\n      },\n    },\n    {\n      title: \"更新时间\",\n      width: 180,\n      key: \"create_time\",\n      dataIndex: \"create_time\",\n      align: \"center\",\n      //ellipsis: true,\n      // sorter: (a, b) => a.create_time - b.create_time,\n      // sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        return str;\n      },\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      fixed: \"right\",\n      align: \"center\",\n      render: function renderFunc(text, record, index) {\n        //console.log(record);\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              {record.monitor_path ? (\n                <a\n                  onClick={() => {\n                    record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_path,\n                      record: {\n                        ...record,\n                        ip: record.alert_host_ip,\n                      },\n                      isLog: false,\n                    });\n                  }}\n                >\n                  监控\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n              )}\n\n              {record.alert_type == \"host\" ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() =>\n                    history.push({\n                      pathname: \"/status-patrol/patrol-inspection-record\",\n                    })\n                  }\n                >\n                  分析\n                </a>\n              ) : record.monitor_log ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() => {\n                    record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_log,\n                      record: {\n                        ...record,\n                        ip: record.alert_host_ip,\n                      },\n                      isLog: true,\n                    });\n                  }}\n                >\n                  日志\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  日志\n                </span>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/AlarmLog/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpSelect,\n  OmpDatePicker,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, Select, message, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport getColumnsConfig from \"./config/columns\";\nimport { SearchOutlined } from \"@ant-design/icons\";\nimport moment from \"moment\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst AlarmLog = () => {\n  const history = useHistory();\n\n  const location = useLocation();\n\n  const initTime = location.state?.time;\n\n  const [time, setTime] = useState([]);\n\n  const initIp = location.state?.ip;\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [ipListSource, setIpListSource] = useState([]);\n\n  const [selectValue, setSelectValue] = useState(initIp);\n\n  const [instanceSelectValue, setInstanceSelectValue] = useState(\n    location.state?.alert_instance_name\n  );\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 筛选label\n  const [labelControl, setLabelControl] = useState(\n    initIp ? \"ip\" : \"instance_name\"\n  );\n\n  const [showIframe, setShowIframe] = useState({});\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams = {},\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.Alert.listAlert, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchIPlist();\n        //fetchNameList();\n      });\n  }\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  const updateAlertRead = (ids = []) => {\n    setLoading(true);\n    fetchPost(apiRequest.Alert.updateAlert, {\n      body: {\n        ids: ids,\n        is_read: 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          message.success(\"已读成功\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setCheckedList([]);\n        setLoading(false);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ...pagination.searchParams },\n          pagination.ordering\n        );\n      });\n  };\n\n  useEffect(() => {\n    if (initTime) {\n      setTime([moment(initTime), moment(initTime)]);\n      fetchData(pagination, {\n        alert_host_ip: location.state?.ip,\n        alert_instance_name: location.state?.alert_instance_name,\n        start_alert_time: moment(initTime).format(\"YYYY-MM-DD HH:mm:ss\"),\n        end_alert_time: moment(initTime).format(\"YYYY-MM-DD HH:mm:ss\"),\n      });\n    } else {\n      let currentTime = moment();\n      let aMonthAgoTime = moment().subtract(1, \"months\");\n      console.log(currentTime, aMonthAgoTime);\n      setTime([aMonthAgoTime, currentTime]);\n      fetchData(pagination, {\n        alert_host_ip: location.state?.ip,\n        alert_instance_name: location.state?.alert_instance_name,\n        start_alert_time: aMonthAgoTime.format(\"YYYY-MM-DD HH:mm:ss\"),\n        end_alert_time: currentTime.format(\"YYYY-MM-DD HH:mm:ss\"),\n      });\n    }\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n        <Button\n          type=\"primary\"\n          disabled={checkedList.length == 0}\n          onClick={() => {\n            let ids = checkedList.map((item) => item.id);\n            updateAlertRead(ids);\n          }}\n        >\n          批量已读\n        </Button>\n        <div style={{ display: \"flex\" }}>\n          <OmpDatePicker\n            onChange={(e) => {\n              if (!e) {\n                setTime([]);\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    start_alert_time: null,\n                    end_alert_time: null,\n                  },\n                  pagination.ordering\n                );\n              } else {\n                let result = e.filter((item) => item);\n                if (result.length == 2) {\n                  setTime([moment(e[0]), moment(e[1])]);\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      start_alert_time: moment(e[0]).format(\n                        \"YYYY-MM-DD HH:mm:ss\"\n                      ),\n                      end_alert_time: moment(e[1]).format(\n                        \"YYYY-MM-DD HH:mm:ss\"\n                      ),\n                    },\n                    pagination.ordering\n                  );\n                }\n              }\n            }}\n            value={time}\n          />\n          <div style={{ display: \"flex\", marginLeft: \"10px\" }}>\n            <Input.Group compact style={{ display: \"flex\" }}>\n              <Select\n                value={labelControl}\n                style={{ width: 100 }}\n                onChange={(e) => {\n                  setLabelControl(e);\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      alert_host_ip: null,\n                      alert_instance_name: null,\n                    },\n                    pagination.ordering\n                  );\n                  setInstanceSelectValue();\n                  setSelectValue();\n                }}\n              >\n                <Select.Option value=\"ip\"> IP地址</Select.Option>\n                <Select.Option value=\"instance_name\">实例名称</Select.Option>\n              </Select>\n              {labelControl === \"ip\" && (\n                <OmpSelect\n                  searchLoading={searchLoading}\n                  selectValue={selectValue}\n                  listSource={ipListSource}\n                  setSelectValue={setSelectValue}\n                  fetchData={(value) => {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      { ...pagination.searchParams, alert_host_ip: value },\n                      pagination.ordering\n                    );\n                  }}\n                  pagination={pagination}\n                />\n              )}\n              {labelControl === \"instance_name\" && (\n                <Input\n                  placeholder=\"输入实例名称\"\n                  style={{ width: 200 }}\n                  allowClear\n                  value={instanceSelectValue}\n                  onChange={(e) => {\n                    setInstanceSelectValue(e.target.value);\n                    if (!e.target.value) {\n                      fetchData(\n                        {\n                          current: 1,\n                          pageSize: pagination.pageSize,\n                        },\n                        {\n                          ...pagination.searchParams,\n                          alert_instance_name: null,\n                        },\n                        pagination.ordering\n                      );\n                    }\n                  }}\n                  onBlur={() => {\n                    if (instanceSelectValue) {\n                      fetchData(\n                        {\n                          current: 1,\n                          pageSize: pagination.pageSize,\n                        },\n                        {\n                          ...pagination.searchParams,\n                          alert_instance_name: instanceSelectValue,\n                        },\n                        pagination.ordering\n                      );\n                    }\n                  }}\n                  onPressEnter={() => {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      {\n                        ...pagination.searchParams,\n                        alert_instance_name: instanceSelectValue,\n                      },\n                      pagination.ordering\n                    );\n                  }}\n                  suffix={\n                    !instanceSelectValue && (\n                      <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n                    )\n                  }\n                />\n              )}\n            </Input.Group>\n\n            <Button\n              style={{ marginLeft: 10 }}\n              onClick={() => {\n                //   dispatch(refreshTime());\n                fetchData(\n                  {\n                    current: pagination.current,\n                    pageSize: pagination.pageSize,\n                  },\n                  { ...pagination.searchParams },\n                  pagination.ordering\n                );\n              }}\n            >\n              刷新\n            </Button>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //scroll={{ x: 1400 }}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(\n            (params) => {\n              // console.log(pagination.searchParams)\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                { ...pagination.searchParams, ...params },\n                pagination.ordering\n              );\n            },\n            setShowIframe,\n            updateAlertRead,\n            history\n          )}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  justifyContent: \"space-between\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n    </OmpContentWrapper>\n  );\n};\n\nexport default AlarmLog;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ApplicationInstallation.js",
    "content": "import { useEffect, useState, useRef } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { Steps, Form, Input, Button, Select, Tooltip, message } from \"antd\";\nimport {\n  LeftOutlined,\n  DownOutlined,\n  InfoCircleOutlined,\n  LoadingOutlined,\n} from \"@ant-design/icons\";\nimport styles from \"./index.module.less\";\nimport RenderComDependence from \"./component/RenderComDependence\";\nimport { useDispatch } from \"react-redux\";\nimport { getTabKeyChangeAction } from \"../store/actionsCreators\";\n\nconst step2Open = (num) => ({\n  marginTop: 10,\n  minHeight: 30,\n  height: num * 75,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n});\n\nconst step2NotOpen = () => ({\n  height: 0,\n  minHeight: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n});\n\nconst step3Open = () => ({\n  marginTop: 10,\n  padding: 10,\n  minHeight: 30,\n  height: 240,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  color: \"#fff\",\n  backgroundColor: \"#222222\",\n  wordWrap: \"break-word\",\n  wordBreak: \"break-all\",\n  whiteSpace: \"pre-line\",\n  overflowY: \"auto\",\n  overflowX: \"hidden\",\n});\n\nconst step3NotOpen = () => ({\n  height: 0,\n  minHeight: 0,\n  padding: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  color: \"#fff\",\n  backgroundColor: \"#222222\",\n  wordWrap: \"break-word\",\n  wordBreak: \"break-all\",\n  whiteSpace: \"pre-line\",\n  overflowY: \"auto\",\n  overflowX: \"hidden\",\n});\n\nconst ApplicationInstallation = () => {\n  const [form] = Form.useForm();\n\n  const dispatch = useDispatch();\n\n  const [processContinue, setProcessContinue] = useState(true);\n  //定义校验弹出msessage函数\n  const verificationError = (arr) => {\n    // console.log(arr);\n    for (const item of arr) {\n      if (item.process_continue == false) {\n        setProcessContinue(false);\n        message.warning(item.process_message);\n        return false;\n        break;\n      }\n    }\n    setProcessContinue(true);\n    return true;\n  };\n\n  const history = useHistory();\n  let pathArr = history.location.pathname.split(\"/\");\n  let name = pathArr[pathArr.length - 1];\n\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState([]);\n\n  const [stepNum, setStepNum] = useState(0);\n\n  // setp2的查看更多配置是否是展开状态\n  const [isOpen, setIsOpen] = useState({\n    [name]: false,\n  });\n\n  // step3的安装详情是否是展开状态 因为多个所以为对象\n  const [isDetailOpen, setIsDetailOpen] = useState({});\n\n  const [versionCurrent, setVersionCurrent] = useState(\"\");\n\n  const [step1Data, setStep1Data] = useState({});\n\n  const [step2Data, setStep2Data] = useState({});\n\n  const [step3Data, setStep3Data] = useState({});\n\n  // 第二步校验通过后，存储数据\n  const [vPassedresData, setVPassedresData] = useState({});\n\n  //\n\n  const containerRef = useRef(null);\n\n  const timer = useRef(null);\n\n  function fetchData() {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.productEntrance, {\n      params: {\n        pro_name: name,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res.data);\n          setDataSource(res.data);\n\n          // 设置版本默认选中第一个\n          //console.log(form);\n          verificationError(res.data[0]?.pro_services);\n          setVersionCurrent(res.data[0]?.pro_version);\n          form.setFieldsValue({ version: res.data[0]?.pro_version });\n          form.setFieldsValue({\n            clusterMode: \"single\",\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  let currentproDependenceData = dataSource.filter(\n    (item) => item.pro_version == versionCurrent\n  )[0];\n\n  const [ipListSource, setIpListSource] = useState([]);\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  const use_exist_servicesRef = useRef([]);\n  const install_servicesRef = useRef([]);\n\n  // 定义选中的ip是否包含在instance_info\n  const hasSameIp = (item, ip) => {\n    return (\n      item.is_base_env &&\n      item.instance_info &&\n      item.instance_info.filter((b) => b.ip == ip)\n    );\n  };\n\n  const [showJdk, setShowJdk] = useState(false);\n\n  const jdkHandleInitData = (data = [], ip) => {\n    console.log(\"jdk 尝试设置默认值\", data);\n    //data.map((item) => {\n    if (hasSameIp(data, ip).length == 0) {\n      console.log(\"不存在相同\");\n      setShowJdk(true);\n      install_servicesRef.current = [\n        {\n          ...data,\n        },\n      ];\n      use_exist_servicesRef.current = [];\n\n      data?.app_install_args?.map((i) => {\n        setIsOpen({\n          ...isOpen,\n          [i.name]: false,\n        });\n        form.setFieldsValue({\n          [`install|${data.name}|${JSON.stringify({\n            name: i.name,\n            key: i.key,\n            dir_key: i.dir_key,\n          })}`]: i.default,\n        });\n      });\n\n      data?.app_port?.map((i) => {\n        setIsOpen({\n          ...isOpen,\n          [i.name]: false,\n        });\n        form.setFieldsValue({\n          [`port|${data.name}|${JSON.stringify({\n            name: i.name,\n            key: i.key,\n            //dir_key: i.dir_key,\n          })}`]: i.default,\n        });\n      });\n\n      form.setFieldsValue({\n        [`${data.name}|ip`]: ip,\n        [`${data.name}|instanceName`]: `${data.name}-${ip[ip.length - 2]}-${\n          ip[ip.length - 1]\n        }`,\n      });\n    } else {\n      setShowJdk(false);\n      //console.log(\"存在相同\");\n      let isSame = hasSameIp(data, ip)[0];\n      use_exist_servicesRef.current = [isSame];\n      install_servicesRef.current = [];\n    }\n    //});\n  };\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n          const firstIP = res.data[0].split(\".\");\n          form.setFieldsValue({\n            ip: res.data[0],\n            instanceName: `${name}-${firstIP[firstIP.length - 2]}-${\n              firstIP[firstIP.length - 1]\n            }`,\n          });\n          let firstIp = res.data[0];\n          // jdk 数据默认设置\n          jdkHandleInitData(\n            currentproDependenceData?.dependence_services_info[0],\n            firstIp\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  // 开始安装get\n  const queryInstallationInfo = (operateId) => {\n    fetchGet(apiRequest.appStore.installHistory, {\n      params: {\n        operation_uuid: operateId,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setStep3Data(res.data[0]);\n          if (!timer.current) {\n            res.data[0].detail_lst.map((item) => {\n              setIsDetailOpen({\n                ...isDetailOpen,\n                [item.service_name]: false,\n              });\n            });\n          }\n\n          if (\n            res.data[0]?.install_status == 1 ||\n            res.data[0]?.install_status == 0\n          ) {\n            timer.current = setTimeout(() => {\n              queryInstallationInfo(operateId);\n            }, 2000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        containerRef.current.scrollTop = containerRef.current.scrollHeight;\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n    return () => {\n      clearTimeout(timer.current);\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            className={styles.backIcon}\n            onClick={() => {\n              dispatch(getTabKeyChangeAction(\"service\"));\n              history?.goBack();\n              // history?.push({\n              //   pathname: `/application_management/app_store`,\n              // });\n            }}\n          />\n          {name}\n        </div>\n        <div style={{ width: 600, position: \"relative\", left: -60 }}>\n          <Steps size=\"small\" current={stepNum}>\n            <Steps.Step title=\"基本信息\" />\n            <Steps.Step title=\"部署信息\" />\n            <Steps.Step title=\"开始安装\" />\n          </Steps>\n        </div>\n        <div />\n      </div>\n\n      {/* 第一步 */}\n      {stepNum == 0 && (\n        <>\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                基本信息\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div style={{ paddingLeft: 20, marginTop: 10, paddingBottom: 40 }}>\n              <Form form={form} layout=\"inline\" name=\"basic\">\n                <Form.Item label=\"选择版本\" name=\"version\">\n                  <Select\n                    style={{ width: 200 }}\n                    onChange={(e) => {\n                      setVersionCurrent(e);\n                      verificationError(\n                        dataSource.filter((item) => item.pro_version == e)[0]\n                          ?.pro_services\n                      );\n                    }}\n                  >\n                    {dataSource?.map((item) => (\n                      <Select.Option\n                        key={item.pro_version}\n                        value={item.pro_version}\n                      >\n                        {item.pro_version}\n                      </Select.Option>\n                    ))}\n                  </Select>\n                </Form.Item>\n                <Form.Item\n                  label=\"集群模式\"\n                  name=\"clusterMode\"\n                  style={{ marginLeft: 30 }}\n                >\n                  <Select style={{ width: 200 }}>\n                    <Select.Option key={\"single\"} value={\"single\"}>\n                      单实例\n                    </Select.Option>\n                    {/* {currentproDependenceData?.deploy_mode?.map((item) => {\n                      return (\n                        <Select.Option\n                          key={JSON.stringify(item)}\n                          value={JSON.stringify(item)}\n                        >\n                          {item.name}\n                        </Select.Option>\n                      );\n                    })} */}\n                  </Select>\n                </Form.Item>\n              </Form>\n            </div>\n          </div>\n\n          {/* 服务信息 */}\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                服务信息\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div\n              style={{\n                paddingLeft: 20,\n                marginTop: 10,\n                paddingBottom: 40,\n                paddingTop: 10,\n              }}\n            >\n              {currentproDependenceData &&\n              currentproDependenceData.pro_services &&\n              currentproDependenceData.pro_services.length == 0 ? (\n                <div>无</div>\n              ) : (\n                currentproDependenceData?.pro_services?.map((item) => {\n                  return (\n                    <div\n                      key={item.name}\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        paddingBottom: 20,\n                      }}\n                    >\n                      <span\n                        style={{\n                          //width: 100,\n                          paddingRight: 15,\n                          textAlign: \"right\",\n                        }}\n                      >\n                        {item.name}:\n                      </span>\n                      <Select\n                        style={{ width: 240 }}\n                        defaultValue={item.version}\n                        // onSelect={(e) => {\n                        // setDeploy_mode(e);\n                        // }}\n                      >\n                        <Select.Option value={item.version} key={item.version}>\n                          {item.version}\n                        </Select.Option>\n                      </Select>\n                    </div>\n                  );\n                })\n              )}\n            </div>\n          </div>\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                依赖信息\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div\n              style={{\n                paddingLeft: 20,\n                marginTop: 10,\n                paddingBottom: 40,\n                paddingTop: 10,\n              }}\n            >\n              {currentproDependenceData &&\n              currentproDependenceData.dependence_services_info &&\n              currentproDependenceData.dependence_services_info.length == 0 ? (\n                <div>无</div>\n              ) : (\n                currentproDependenceData?.dependence_services_info?.map(\n                  (item) => (\n                    <RenderComDependence\n                      key={item.name}\n                      data={item}\n                      form={form}\n                    />\n                  )\n                )\n              )}\n            </div>\n          </div>\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 80,\n            }}\n          >\n            <div />\n            <Button\n              disabled={!processContinue}\n              type=\"primary\"\n              onClick={() => {\n                currentproDependenceData?.pro_services[0].app_install_args?.map(\n                  (item) => {\n                    form.setFieldsValue({\n                      [`install|${\n                        currentproDependenceData?.pro_services[0]?.name\n                      }|${JSON.stringify({\n                        name: item.name,\n                        key: item.key,\n                        dir_key: item.dir_key,\n                      })}`]: item.default,\n                    });\n                  }\n                );\n                currentproDependenceData?.pro_services[0].app_port?.map(\n                  (item) => {\n                    //console.log(item.default, item);\n                    form.setFieldsValue({\n                      [`port|${\n                        currentproDependenceData?.pro_services[0]?.name\n                      }|${JSON.stringify({\n                        name: item.name,\n                        key: item.key,\n                        //dir_key: item.dir_key,\n                      })}`]: item.default,\n                    });\n                  }\n                );\n\n                setStep1Data(form.getFieldsValue());\n\n                fetchIPlist();\n                setStepNum(1);\n              }}\n            >\n              下一步\n            </Button>\n          </div>\n        </>\n      )}\n      {/* 第二步 */}\n      {stepNum == 1 && (\n        <>\n          {/* 不遍历了，直接用第一项 */}\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                {currentproDependenceData?.pro_services[0]?.name}\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div style={{ paddingLeft: 20, marginTop: 10, paddingBottom: 40 }}>\n              <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n                <Form\n                  form={form}\n                  layout=\"inline\"\n                  name=\"basic\"\n                  // initialValues={{\n                  //   clusterMode: \"singleInstance\",\n                  // }}\n                >\n                  <Form.Item label=\"选择主机\" name=\"ip\">\n                    <Select\n                      style={{ width: 200 }}\n                      onChange={(e) => {\n                        const IpArr = e.split(\".\");\n                        jdkHandleInitData(\n                          currentproDependenceData?.dependence_services_info[0],\n                          e\n                        );\n                        form.setFieldsValue({\n                          instanceName: `${\n                            currentproDependenceData?.pro_services[0]?.name\n                          }-${IpArr[IpArr.length - 2]}-${\n                            IpArr[IpArr.length - 1]\n                          }`,\n                        });\n                      }}\n                    >\n                      {ipListSource?.map((item) => (\n                        <Select.Option key={item} value={item}>\n                          {item}\n                        </Select.Option>\n                      ))}\n                    </Select>\n                  </Form.Item>\n                  <Form.Item\n                    label=\"实例名称\"\n                    name=\"instanceName\"\n                    style={{ marginLeft: 30 }}\n                    rules={[\n                      {\n                        required: true,\n                        message: \"请填写实例名称\",\n                      },\n                    ]}\n                  >\n                    <Input />\n                  </Form.Item>\n                </Form>\n                <a\n                  style={{\n                    fontSize: 13,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    flexDirection: \"row-reverse\",\n                    paddingRight: 100,\n                  }}\n                  onClick={() => {\n                    setIsOpen({\n                      ...isOpen,\n                      [currentproDependenceData?.pro_services[0]?.name]:\n                        !isOpen[\n                          currentproDependenceData?.pro_services[0]?.name\n                        ],\n                    });\n                    // setIsDetailOpen({\n                    //   ...isDetailOpen,\n                    //   [item.ip]: !isDetailOpen[item.ip],\n                    // });\n                  }}\n                >\n                  <DownOutlined\n                    style={{\n                      transform: `rotate(${\n                        isOpen[currentproDependenceData?.pro_services[0]?.name]\n                          ? 180\n                          : 0\n                      }deg)`,\n                      position: \"relative\",\n                      top: isOpen[name] ? -1 : 1,\n                      left: 3,\n                    }}\n                  />\n                  查看更多配置\n                </a>\n              </div>\n              <div\n                //className={styles.backIcon}\n                style={\n                  isOpen[currentproDependenceData?.pro_services[0]?.name]\n                    ? step2Open(\n                        currentproDependenceData?.pro_services[0]\n                          ?.app_install_args?.length +\n                          currentproDependenceData?.pro_services[0]?.app_port\n                            ?.length\n                      )\n                    : step2NotOpen()\n                }\n              >\n                <Form\n                  form={form}\n                  //layout=\"inline\"\n                  name=\"basic\"\n                  style={{\n                    marginTop: 20,\n                  }}\n                >\n                  {currentproDependenceData?.pro_services[0]?.app_install_args?.map(\n                    (item) => {\n                      return (\n                        <Form.Item\n                          key={item.key}\n                          style={{ paddingLeft: 15, paddingBottom: 15 }}\n                          label={<span style={{ width: 60 }}>{item.name}</span>}\n                          name={`install|${\n                            currentproDependenceData?.pro_services[0]?.name\n                          }|${JSON.stringify({\n                            name: item.name,\n                            key: item.key,\n                            dir_key: item.dir_key,\n                          })}`}\n                          rules={[\n                            {\n                              required: true,\n                              message: `请填写${item.name}`,\n                            },\n                          ]}\n                        >\n                          <Input\n                            addonBefore={item.dir_key ? \"/ 数据分区\" : null}\n                            style={{ width: 420 }}\n                            suffix={\n                              item.dir_key ? (\n                                <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                  <InfoCircleOutlined\n                                    style={{ color: \"rgba(0,0,0,.45)\" }}\n                                  />\n                                </Tooltip>\n                              ) : null\n                            }\n                          />\n                        </Form.Item>\n                      );\n                    }\n                  )}\n                  {currentproDependenceData?.pro_services[0]?.app_port?.map(\n                    (item) => {\n                      return (\n                        <Form.Item\n                          key={item.key}\n                          style={{ paddingLeft: 15 }}\n                          label={<span style={{ width: 60 }}>{item.name}</span>}\n                          name={`port|${\n                            currentproDependenceData?.pro_services[0]?.name\n                          }|${JSON.stringify({\n                            name: item.name,\n                            key: item.key,\n                            //dir_key: item.dir_key,\n                          })}`}\n                          rules={[\n                            {\n                              required: true,\n                              message: `请填写${item.name}`,\n                            },\n                          ]}\n                        >\n                          <Input\n                            addonBefore={item.dir_key ? \"/ 数据分区\" : null}\n                            style={{ width: 420 }}\n                            suffix={\n                              item.dir_key ? (\n                                <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                  <InfoCircleOutlined\n                                    style={{ color: \"rgba(0,0,0,.45)\" }}\n                                  />\n                                </Tooltip>\n                              ) : null\n                            }\n                          />\n                        </Form.Item>\n                      );\n                    }\n                  )}\n                </Form>\n              </div>\n            </div>\n          </div>\n\n          {/* 展示jdk */}\n          {showJdk ? (\n            <>\n              <div\n                key={\n                  currentproDependenceData?.dependence_services_info[0]?.name\n                }\n                style={{\n                  marginTop: 20,\n                  backgroundColor: \"#fff\",\n                  padding: 10,\n                }}\n              >\n                <div\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    width: \"100%\",\n                    position: \"relative\",\n                    height: 30,\n                  }}\n                >\n                  <div\n                    style={{\n                      fontWeight: 500,\n                      position: \"absolute\",\n                      left: 30,\n                      backgroundColor: \"#fff\",\n                      paddingLeft: 20,\n                      paddingRight: 20,\n                    }}\n                  >\n                    {\n                      currentproDependenceData?.dependence_services_info[0]\n                        ?.name\n                    }\n                  </div>\n                  <div\n                    style={{\n                      height: 1,\n                      backgroundColor: \"#b3b2b3\",\n                      width: \"100%\",\n                    }}\n                  />\n                </div>\n                <div\n                  style={{\n                    paddingLeft: 20,\n                    marginTop: 10,\n                    paddingBottom: 40,\n                  }}\n                >\n                  {/* {currentproDependenceData?.dependence_services_info[0]\n                    ?.is_pack_exist ? ( \"\" ) : (\n                      <p style={{color:\"red\"}}>{currentproDependenceData?.dependence_services_info[0]?.name} 服务的安装包不存在 ！</p>\n                    )} */}\n                  <div\n                    style={{\n                      display: \"flex\",\n                      justifyContent: \"space-between\",\n                    }}\n                  >\n                    <Form\n                      form={form}\n                      layout=\"inline\"\n                      name=\"basic\"\n                      // initialValues={{\n                      //   clusterMode: \"singleInstance\",\n                      // }}\n                    >\n                      <Form.Item\n                        label=\"选择主机\"\n                        name={`${currentproDependenceData?.dependence_services_info[0]?.name}|ip`}\n                      >\n                        <Select\n                          disabled={true}\n                          style={{ width: 200 }}\n                          // onChange={(e) => {\n                          //   const IpArr = e.split(\".\");\n                          //   form.setFieldsValue({\n                          //     [`${currentproDependenceData?.dependence_services_info[0]?.name}|instanceName`]: `${\n                          //       currentproDependenceData\n                          //         ?.dependence_services_info[0]?.name\n                          //     }-${IpArr[IpArr.length - 2]}-${\n                          //       IpArr[IpArr.length - 1]\n                          //     }`,\n                          //   });\n                          // }}\n                        >\n                          {ipListSource?.map((item) => (\n                            <Select.Option key={item} value={item}>\n                              {item}\n                            </Select.Option>\n                          ))}\n                        </Select>\n                      </Form.Item>\n                      <Form.Item\n                        label=\"实例名称\"\n                        name={`${currentproDependenceData?.dependence_services_info[0]?.name}|instanceName`}\n                        style={{ marginLeft: 30 }}\n                        rules={[\n                          {\n                            required: true,\n                            message: \"请填写实例名称\",\n                          },\n                        ]}\n                      >\n                        <Input />\n                      </Form.Item>\n                    </Form>\n                    <a\n                      style={{\n                        fontSize: 13,\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        flexDirection: \"row-reverse\",\n                        paddingRight: 100,\n                      }}\n                      onClick={() => {\n                        setIsOpen({\n                          ...isOpen,\n                          [currentproDependenceData?.dependence_services_info[0]\n                            ?.name]:\n                            !isOpen[\n                              currentproDependenceData\n                                ?.dependence_services_info[0]?.name\n                            ],\n                        });\n                      }}\n                    >\n                      <DownOutlined\n                        style={{\n                          transform: `rotate(${\n                            isOpen[\n                              currentproDependenceData\n                                ?.dependence_services_info[0]?.name\n                            ]\n                              ? 180\n                              : 0\n                          }deg)`,\n                          position: \"relative\",\n                          top: isOpen[\n                            currentproDependenceData\n                              ?.dependence_services_info[0]?.name\n                          ]\n                            ? -1\n                            : 1,\n                          left: 3,\n                        }}\n                      />\n                      查看更多配置\n                    </a>\n                  </div>\n\n                  <div\n                    //className={styles.backIcon}\n                    style={\n                      isOpen[\n                        currentproDependenceData?.dependence_services_info[0]\n                          ?.name\n                      ]\n                        ? step2Open(\n                            currentproDependenceData\n                              ?.dependence_services_info[0]?.app_install_args\n                              ?.length +\n                              currentproDependenceData\n                                ?.dependence_services_info[0]?.app_port.length\n                          )\n                        : step2NotOpen()\n                    }\n                  >\n                    <Form\n                      form={form}\n                      //layout=\"inline\"\n                      name=\"basic\"\n                      style={{\n                        marginTop: 20,\n                      }}\n                    >\n                      {currentproDependenceData?.dependence_services_info[0]?.app_install_args?.map(\n                        (i) => {\n                          return (\n                            <Form.Item\n                              key={i.key}\n                              style={{ paddingLeft: 15, paddingBottom: 15 }}\n                              label={\n                                <span style={{ width: 60 }}>{i.name}</span>\n                              }\n                              name={`install|${\n                                currentproDependenceData\n                                  ?.dependence_services_info[0]?.name\n                              }|${JSON.stringify({\n                                name: i.name,\n                                key: i.key,\n                                dir_key: i.dir_key,\n                              })}`}\n                              rules={[\n                                {\n                                  required: true,\n                                  message: `请填写${i.name}`,\n                                },\n                              ]}\n                            >\n                              <Input\n                                addonBefore={i.dir_key ? \"/ 数据分区\" : null}\n                                style={{ width: 420 }}\n                                suffix={\n                                  i.dir_key ? (\n                                    <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                      <InfoCircleOutlined\n                                        style={{ color: \"rgba(0,0,0,.45)\" }}\n                                      />\n                                    </Tooltip>\n                                  ) : null\n                                }\n                              />\n                            </Form.Item>\n                          );\n                        }\n                      )}\n                      {currentproDependenceData?.dependence_services_info[0]?.app_port?.map(\n                        (i) => {\n                          return (\n                            <Form.Item\n                              key={i.key}\n                              style={{ paddingLeft: 15, paddingBottom: 15 }}\n                              label={\n                                <span style={{ width: 60 }}>{i.name}</span>\n                              }\n                              name={`port|${\n                                currentproDependenceData\n                                  ?.dependence_services_info[0]?.name\n                              }|${JSON.stringify({\n                                name: i.name,\n                                key: i.key,\n                              })}`}\n                              rules={[\n                                {\n                                  required: true,\n                                  message: `请填写${i.name}`,\n                                },\n                              ]}\n                            >\n                              <Input\n                                addonBefore={i.dir_key ? \"/ 数据分区\" : null}\n                                style={{ width: 420 }}\n                                suffix={\n                                  i.dir_key ? (\n                                    <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                      <InfoCircleOutlined\n                                        style={{ color: \"rgba(0,0,0,.45)\" }}\n                                      />\n                                    </Tooltip>\n                                  ) : null\n                                }\n                              />\n                            </Form.Item>\n                          );\n                        }\n                      )}\n                    </Form>\n                  </div>\n                </div>\n              </div>\n            </>\n          ) : (\n            \"\"\n          )}\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 40,\n            }}\n          >\n            <div style={{ display: \"flex\", alignItems: \"center\" }}>\n              分布主机数量: 1台\n            </div>\n            <div>\n              <Button\n                style={{\n                  marginRight: 15,\n                }}\n                type=\"primary\"\n                onClick={() => {\n                  setStepNum(0);\n                }}\n              >\n                上一步\n              </Button>\n              <Button\n                type=\"primary\"\n                loading={loading}\n                onClick={() => {\n                  // 先出发表单校验\n                  form\n                    .validateFields()\n                    .then((values) => {\n                      setLoading(true);\n                      setStep2Data(form.getFieldsValue());\n                      let st2 = form.getFieldsValue();\n                      //setStepNum(2);\n\n                      let parameterCreate = (\n                        step2Data,\n                        type,\n                        length = 3,\n                        type2\n                      ) => {\n                        console.log(step2Data, type, (length = 3), type2);\n                        let arr = [];\n                        Object.keys(step2Data).map((key) => {\n                          if (\n                            key.split(\"|\")[0] == type &&\n                            key.split(\"|\").length == length &&\n                            key.split(\"|\")[1] == type2\n                          ) {\n                            let data = JSON.parse(key.split(\"|\")[length - 1]);\n                            arr.push({\n                              ...data,\n                              default: step2Data[key],\n                            });\n                          }\n                        });\n                        return arr;\n                      };\n\n                      const analysisJdk = (st2, type, length = 3, type2) => {\n                        let result = {};\n                        for (const key in st2) {\n                          let keyArr = key.split(\"|\");\n                          if (keyArr.length == length) {\n                            if (keyArr[0] == type && keyArr[1] == type2) {\n                              result[keyArr[2]] = st2[key];\n                            }\n                          }\n                        }\n                        return result;\n                      };\n\n                      let installArr = install_servicesRef.current;\n                      let app_install_args = install_servicesRef.current[0]\n                        ? analysisJdk(\n                            st2,\n                            \"install\",\n                            3,\n                            install_servicesRef.current[0]?.name\n                          )\n                        : {};\n\n                      let ipAndInstanceName = {};\n                      Object.keys(st2).map((o) => {\n                        let arr = o.split(\"|\");\n                        if (arr.length == 2 && arr[0] == installArr[0]?.name) {\n                          ipAndInstanceName[arr[1]] = st2[o];\n                        }\n                      });\n\n                      if (installArr.length > 0) {\n                        install_servicesRef.current[0].ip =\n                          ipAndInstanceName.ip;\n                        install_servicesRef.current[0].service_instance_name =\n                          ipAndInstanceName.instanceName;\n                        install_servicesRef.current[0].app_install_args =\n                          install_servicesRef.current[0]?.app_install_args?.map(\n                            (item) => {\n                              let key = JSON.stringify({\n                                name: item.name,\n                                key: item.key,\n                                dir_key: item.dir_key,\n                              });\n                              return {\n                                ...item,\n                                default: app_install_args[key],\n                              };\n                            }\n                          );\n                        install_servicesRef.current[0].app_port = [];\n                      }\n\n                      use_exist_servicesRef.current =\n                        use_exist_servicesRef.current.map((item) => {\n                          return {\n                            ...item,\n                            type: \"single\",\n                          };\n                        });\n\n                      let data = {\n                        install_type: 0,\n                        use_exist_services: use_exist_servicesRef.current,\n                        install_services: [\n                          {\n                            name: currentproDependenceData?.pro_services[0]\n                              ?.name,\n                            version: versionCurrent,\n                            ip: st2.ip,\n                            app_install_args: parameterCreate(\n                              st2,\n                              \"install\",\n                              3,\n                              currentproDependenceData?.pro_services[0]?.name\n                            ),\n                            app_port: parameterCreate(\n                              st2,\n                              \"port\",\n                              3,\n                              currentproDependenceData?.pro_services[0]?.name\n                            ),\n                            service_instance_name: st2.instanceName,\n                            //deploy_mode: JSON.parse(step1Data.clusterMode),\n                          },\n                          ...install_servicesRef.current,\n                        ],\n                      };\n                      console.log(data);\n                      //return;\n                      fetchPost(apiRequest.appStore.executeInstall, {\n                        body: {\n                          ...data,\n                        },\n                      })\n                        .then((res) => {\n                          handleResponse(res, (res) => {\n                            if (res.data && res.data.install_services) {\n                              if (!res.data.is_valid_flag) {\n                                // 打开全部的展开栏\n                                let isOpenCopy = JSON.parse(\n                                  JSON.stringify(isOpen)\n                                );\n                                for (const key in isOpenCopy) {\n                                  isOpenCopy[key] = true;\n                                }\n                                setIsOpen({\n                                  ...isOpenCopy,\n                                  [currentproDependenceData\n                                    ?.dependence_services_info[0]?.name]: true,\n                                });\n\n                                res.data.install_services?.map((item, idx) => {\n                                  if (\n                                    item.check_flag == false &&\n                                    item.check_msg\n                                  ) {\n                                    if (idx == 0) {\n                                      form.setFields([\n                                        {\n                                          name: \"instanceName\",\n                                          errors: [\n                                            res.data.install_services[0]\n                                              .check_msg,\n                                          ],\n                                        },\n                                      ]);\n                                    } else {\n                                      form.setFields([\n                                        {\n                                          name: `${item.name}|instanceName`,\n                                          errors: [item.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  }\n                                  item.app_port?.map((i) => {\n                                    if (i.check_flag == false) {\n                                      form.setFields([\n                                        {\n                                          name: `port|${\n                                            item.name\n                                          }|${JSON.stringify({\n                                            name: i.name,\n                                            key: i.key,\n                                          })}`,\n                                          errors: [i.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  });\n                                  item.app_install_args?.map((i) => {\n                                    if (i.check_flag == false) {\n                                      form.setFields([\n                                        {\n                                          name: `install|${\n                                            item.name\n                                          }|${JSON.stringify({\n                                            name: i.name,\n                                            key: i.key,\n                                            dir_key: i.dir_key,\n                                          })}`,\n                                          errors: [i.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  });\n                                });\n                              } else {\n                                // 后端校验通过\n                                setVPassedresData(res.data);\n                                queryInstallationInfo(res.data.operation_uuid);\n                                setStepNum(2);\n                              }\n                            }\n                          });\n                        })\n                        .catch((e) => console.log(e))\n                        .finally(() => {\n                          setLoading(false);\n                        });\n                    })\n                    .catch((errorInfo) => {\n                      console.log(errorInfo);\n                      let isOpenCopy = JSON.parse(JSON.stringify(isOpen));\n                      for (const key in isOpenCopy) {\n                        isOpenCopy[key] = true;\n                      }\n                      setIsOpen({\n                        ...isOpenCopy,\n                      });\n                    });\n                }}\n              >\n                开始安装\n              </Button>\n            </div>\n          </div>\n        </>\n      )}\n      {stepNum == 2 && (\n        <>\n          {step3Data?.detail_lst?.map((item) => {\n            return (\n              <div\n                key={item?.service_instance_name}\n                style={{\n                  marginTop: 20,\n                  backgroundColor: \"#fff\",\n                  padding: 10,\n                }}\n              >\n                <div\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    width: \"100%\",\n                    position: \"relative\",\n                    height: 30,\n                  }}\n                >\n                  <div\n                    style={{\n                      fontWeight: 500,\n                      position: \"absolute\",\n                      left: 30,\n                      backgroundColor: \"#fff\",\n                      paddingLeft: 20,\n                      paddingRight: 20,\n                    }}\n                  >\n                    {item.service_name}\n                  </div>\n                  <div\n                    style={{\n                      height: 1,\n                      backgroundColor: \"#b3b2b3\",\n                      width: \"100%\",\n                    }}\n                  />\n                </div>\n\n                <div\n                  style={{\n                    paddingLeft: 20,\n                    marginTop: 10,\n                    paddingBottom: 40,\n                  }}\n                >\n                  <div\n                    style={{\n                      display: \"flex\",\n                      alignItems: \"center\",\n                      justifyContent: \"space-between\",\n                    }}\n                  >\n                    <div\n                      style={{\n                        width: 320,\n                        display: \"flex\",\n                        alignItems: \"center\",\n                      }}\n                    >\n                      {item.ip}\n                    </div>\n                    <a\n                      style={{\n                        fontSize: 13,\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        flexDirection: \"row-reverse\",\n                        paddingRight: 100,\n                      }}\n                      onClick={() => {\n                        setIsDetailOpen({\n                          ...isDetailOpen,\n                          [item.service_name]: !isDetailOpen[item.service_name],\n                        });\n                        //setIsDetailOpen(!isDetailOpen);\n                      }}\n                    >\n                      <DownOutlined\n                        style={{\n                          transform: `rotate(${\n                            isDetailOpen[item.ip] ? 180 : 0\n                          }deg)`,\n                          position: \"relative\",\n                          top: isDetailOpen[item.ip] ? -1 : 1,\n                          left: 3,\n                        }}\n                      />\n                      查看详细安装信息\n                    </a>\n                  </div>\n\n                  <div\n                    //className={styles.backIcon}\n                    ref={containerRef}\n                    style={\n                      isDetailOpen[item.service_name]\n                        ? step3Open(2)\n                        : step3NotOpen()\n                    }\n                  >\n                    {item.log ? item.log : \"正在安装...\"}\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 80,\n            }}\n          >\n            <div style={{ display: \"flex\", alignItems: \"center\" }}>\n              {step3Data.install_status_msg}{\" \"}\n              {(step3Data.install_status == 0 ||\n                step3Data.install_status == 1) && (\n                <LoadingOutlined\n                  style={{ fontSize: 20, fontWeight: 600, marginLeft: 10 }}\n                />\n              )}\n            </div>\n            <div>\n              <Button\n                type=\"primary\"\n                onClick={() => {\n                  history.push(\"/application_management/service_management\");\n                }}\n              >\n                完成\n              </Button>\n            </div>\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport default ApplicationInstallation;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/BatchInstallationModal.js",
    "content": "import { Button, Modal, Select, Switch } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { CopyOutlined } from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { OmpTable } from \"@/components\";\nimport { useHistory } from \"react-router-dom\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport { getStep1ChangeAction } from \"./Installation/store/actionsCreators\";\nimport { getUniqueKeyChangeAction } from \"../store/actionsCreators\";\n\nconst BatchInstallationModal = ({\n  bIModalVisibility,\n  setBIModalVisibility,\n  dataSource,\n  installTitle,\n  initLoading,\n}) => {\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  const reduxDispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const history = useHistory();\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n  // console.log(checkedList)\n  //应用服务选择的版本号\n  const versionInfo = useRef({});\n\n  const columns = [\n    {\n      title: \"名称\",\n      key: \"name\",\n      dataIndex: \"name\",\n      align: \"center\",\n      ellipsis: true,\n      width: 80,\n      render: (text, record) => {\n        return text;\n      },\n    },\n    {\n      title: \"版本\",\n      key: \"version\",\n      dataIndex: \"version\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        return (\n          <Select\n            bordered={false}\n            defaultValue={text[0]}\n            style={{ width: 120 }}\n            onSelect={(v) => {\n              versionInfo.current[record.name] = v;\n            }}\n          >\n            {text.map((item) => {\n              return (\n                <Select.Option value={item} key={`${item}-${record.name}`}>\n                  {item}\n                </Select.Option>\n              );\n            })}\n          </Select>\n        );\n      },\n    },\n  ];\n\n  // 高可用是否开启\n  const [highAvailabilityCheck, setHighAvailabilityCheck] = useState(false);\n\n  // 批量安装/服务安装选择确认请求\n  const createInstallInfo = (install_product) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createInstallInfo, {\n      body: {\n        high_availability: highAvailabilityCheck,\n        install_product: install_product,\n        unique_key: uniqueKey,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            reduxDispatch(getStep1ChangeAction(res.data.data));\n          }\n          history.push(\"/application_management/app_store/installation\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 组件安装\n  const createComponentInstallInfo = (install_product) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createComponentInstallInfo, {\n      body: {\n        high_availability: highAvailabilityCheck,\n        install_component: install_product,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            reduxDispatch(getStep1ChangeAction(res.data.data));\n            reduxDispatch(getUniqueKeyChangeAction(res.data.unique_key));\n          }\n          history.push(\"/application_management/app_store/installation\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    // 选中全部\n    setCheckedList(dataSource.filter((item) => item.is_continue));\n  }, [dataSource]);\n  // console.log(checkedList)\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <CopyOutlined />\n          </span>\n          <span>\n            {installTitle == \"服务\"\n              ? \"服务安装-选择版本\"\n              : installTitle == \"组件\"\n              ? \"组件安装-选择版本\"\n              : \"批量安装-选择应用服务\"}\n          </span>\n        </span>\n      }\n      afterClose={() => {\n        setCheckedList([]);\n        setHighAvailabilityCheck(false);\n      }}\n      onCancel={() => {\n        setBIModalVisibility(false);\n      }}\n      visible={bIModalVisibility}\n      footer={null}\n      //width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n    >\n      <div>\n        <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n          <OmpTable\n            size=\"small\"\n            scroll={{ y: 270 }}\n            loading={loading || initLoading}\n            //scroll={{ x: 1900 }}\n            columns={columns}\n            dataSource={dataSource}\n            rowKey={(record) => {\n              return record.name;\n            }}\n            checkedState={[checkedList, setCheckedList]}\n            pagination={false}\n            notSelectable={(record) => ({\n              // is_continue的不能选中\n              disabled: !record.is_continue,\n            })}\n            rowSelection={{\n              selectedRowKeys: checkedList?.map((item) => item?.name),\n            }}\n          />\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            marginTop: 20,\n            justifyContent: \"space-between\",\n            padding: \"0px 20px\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            高可用\n            <Switch\n              style={{ marginLeft: 10 }}\n              checked={highAvailabilityCheck}\n              onChange={(e) => {\n                setHighAvailabilityCheck(e);\n              }}\n            />\n          </div>\n          <div style={{ display: \"flex\" }}>\n            <div style={{ marginRight: 15 }}>\n              已选择 {checkedList.length} 个\n            </div>\n            <div>共 {dataSource.length} 个</div>\n          </div>\n        </div>\n        <div\n          style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n        >\n          <div\n            style={{\n              width: 170,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          >\n            <Button onClick={() => setBIModalVisibility(false)}>取消</Button>\n            <Button\n              type=\"primary\"\n              style={{ marginLeft: 16 }}\n              loading={loading || initLoading}\n              disabled={checkedList.length == 0}\n              onClick={() => {\n                if (installTitle == \"组件\") {\n                  let install_product = checkedList.map((item) => {\n                    return {\n                      name: item.name,\n                      version:\n                        versionInfo.current[item.name] || item.version[0],\n                    };\n                  });\n                  createComponentInstallInfo(install_product);\n                } else {\n                  let install_product = checkedList.map((item) => {\n                    return {\n                      name: item.name,\n                      version:\n                        versionInfo.current[item.name] || item.version[0],\n                    };\n                  });\n                  createInstallInfo(install_product);\n                  // history.push(\"/application_management/app_store/installation\")\n                }\n              }}\n            >\n              确认选择\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default BatchInstallationModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ComponentInstallation.js",
    "content": "import { useEffect, useState, useRef } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { Steps, Form, Input, Button, Select, Tooltip, message } from \"antd\";\nimport {\n  LeftOutlined,\n  DownOutlined,\n  InfoCircleOutlined,\n  LoadingOutlined,\n} from \"@ant-design/icons\";\nimport styles from \"./index.module.less\";\nimport RenderComDependence from \"./component/RenderComDependence\";\n\nconst step2Open = (num) => ({\n  marginTop: 10,\n  minHeight: 30,\n  height: num * 75,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n});\n\nconst step2NotOpen = () => ({\n  height: 0,\n  minHeight: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n});\n\nconst step3Open = () => ({\n  marginTop: 10,\n  padding: 10,\n  minHeight: 30,\n  height: 240,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  color: \"#fff\",\n  backgroundColor: \"#222222\",\n  wordWrap: \"break-word\",\n  wordBreak: \"break-all\",\n  whiteSpace: \"pre-line\",\n  overflowY: \"auto\",\n  overflowX: \"hidden\",\n});\n\nconst step3NotOpen = () => ({\n  height: 0,\n  minHeight: 0,\n  padding: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  color: \"#fff\",\n  backgroundColor: \"#222222\",\n  wordWrap: \"break-word\",\n  wordBreak: \"break-all\",\n  whiteSpace: \"pre-line\",\n  overflowY: \"auto\",\n  overflowX: \"hidden\",\n});\n\nconst ComponentInstallation = () => {\n  const [form] = Form.useForm();\n\n  const history = useHistory();\n  let pathArr = history.location.pathname.split(\"/\");\n  let name = pathArr[pathArr.length - 1];\n\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState([]);\n\n  const [stepNum, setStepNum] = useState(0);\n\n  // setp2的查看更多配置是否是展开状态\n  const [isOpen, setIsOpen] = useState({\n    [name]: false,\n  });\n\n  // step3的安装详情是否是展开状态 因为多个所以为对象\n  const [isDetailOpen, setIsDetailOpen] = useState({});\n\n  const [versionCurrent, setVersionCurrent] = useState(\"\");\n\n  const [step1Data, setStep1Data] = useState({});\n\n  const [step2Data, setStep2Data] = useState({});\n\n  const [step3Data, setStep3Data] = useState({});\n\n  // 第二步校验通过后，存储数据\n  const [vPassedresData, setVPassedresData] = useState({});\n\n  const [processContinue, setProcessContinue] = useState(true);\n  //定义校验弹出msessage函数\n  const verificationError = (item) => {\n    if (item.process_continue == false) {\n      setProcessContinue(false);\n      message.warning(item.process_message);\n      return false;\n    } else {\n      setProcessContinue(true);\n      return true;\n    }\n  };\n\n  const containerRef = useRef(null);\n\n  const timer = useRef(null);\n\n  function fetchData() {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.componentEntrance, {\n      params: {\n        app_name: name,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          verificationError(res.data[0]);\n          //console.log(res.data);\n          setDataSource(res.data);\n          // 设置版本默认选中第一个\n          //console.log(form);\n          setVersionCurrent(res.data[0].app_version);\n          form.setFieldsValue({ version: res.data[0].app_version });\n          form.setFieldsValue({\n            clusterMode: JSON.stringify(res.data[0].deploy_mode[0]),\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  let currentAppDependenceData = dataSource.filter(\n    (item) => item.app_version == versionCurrent\n  )[0];\n\n  const [ipListSource, setIpListSource] = useState([]);\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  const use_exist_servicesRef = useRef([]);\n  const install_servicesRef = useRef([]);\n\n  // 定义选中的ip是否包含在instance_info\n  const hasSameIp = (item, ip) => {\n    return (\n      item.is_base_env &&\n      item.instance_info &&\n      item.instance_info.filter((b) => b.ip == ip)\n    );\n  };\n\n  const [showJdk, setShowJdk] = useState(false);\n\n  const jdkHandleInitData = (data = [], ip) => {\n    //console.log(\"jdk 尝试设置默认值\");\n    data.map((item) => {\n      if (hasSameIp(item, ip).length == 0) {\n        //console.log(\"不存在相同\");\n        setShowJdk(true);\n        install_servicesRef.current = [\n          {\n            ...item,\n          },\n        ];\n        use_exist_servicesRef.current = [];\n\n        item?.app_install_args?.map((i) => {\n          setIsOpen({\n            ...isOpen,\n            [i.name]: false,\n          });\n          form.setFieldsValue({\n            [`install|${item.name}|${JSON.stringify({\n              name: i.name,\n              key: i.key,\n              dir_key: i.dir_key,\n            })}`]: i.default,\n          });\n        });\n\n        item?.app_port?.map((i) => {\n          setIsOpen({\n            ...isOpen,\n            [i.name]: false,\n          });\n          form.setFieldsValue({\n            [`port|${item.name}|${JSON.stringify({\n              name: i.name,\n              key: i.key,\n              dir_key: i.dir_key,\n            })}`]: i.default,\n          });\n        });\n\n        form.setFieldsValue({\n          [`${item.name}|ip`]: ip,\n          [`${item.name}|instanceName`]: `${item.name}-${ip[ip.length - 2]}-${\n            ip[ip.length - 1]\n          }`,\n        });\n      } else {\n        setShowJdk(false);\n        //console.log(\"存在相同\");\n        let isSame = hasSameIp(item, ip)[0];\n        use_exist_servicesRef.current = [isSame];\n        install_servicesRef.current = [];\n      }\n    });\n  };\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n          const firstIP = res.data[0].split(\".\");\n          form.setFieldsValue({\n            ip: res.data[0],\n            instanceName: `${name}-${firstIP[firstIP.length - 2]}-${\n              firstIP[firstIP.length - 1]\n            }`,\n          });\n          let firstIp = res.data[0];\n          // jdk 数据默认设置\n          jdkHandleInitData(currentAppDependenceData?.app_dependence, firstIp);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  // 开始安装get\n  const queryInstallationInfo = (operateId) => {\n    fetchGet(apiRequest.appStore.installHistory, {\n      params: {\n        operation_uuid: operateId,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setStep3Data(res.data[0]);\n          if (!timer.current) {\n            res.data[0].detail_lst.map((item) => {\n              setIsDetailOpen({\n                ...isDetailOpen,\n                [item.service_name]: false,\n              });\n            });\n          }\n          if (\n            res.data[0]?.install_status == 1 ||\n            res.data[0]?.install_status == 0\n          ) {\n            timer.current = setTimeout(() => {\n              queryInstallationInfo(operateId);\n            }, 2000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        containerRef.current.scrollTop = containerRef.current.scrollHeight;\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n    return () => {\n      clearTimeout(timer.current);\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            className={styles.backIcon}\n            onClick={() => {\n              history?.goBack();\n              // history?.push({\n              //   pathname: `/application_management/app_store`,\n              // });\n            }}\n          />\n          {name}\n        </div>\n        <div style={{ width: 600, position: \"relative\", left: -60 }}>\n          <Steps size=\"small\" current={stepNum}>\n            <Steps.Step title=\"基本信息\" />\n            <Steps.Step title=\"部署信息\" />\n            <Steps.Step title=\"开始安装\" />\n          </Steps>\n        </div>\n        <div />\n      </div>\n\n      {/* 第一步 */}\n      {stepNum == 0 && (\n        <>\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                基本信息\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div style={{ paddingLeft: 20, marginTop: 10, paddingBottom: 40 }}>\n              <Form form={form} layout=\"inline\" name=\"basic\">\n                <Form.Item label=\"选择版本\" name=\"version\">\n                  <Select\n                    style={{ width: 200 }}\n                    onChange={(e) => {\n                      verificationError(\n                        dataSource.filter((item) => item.app_version == e)[0]\n                      );\n                      setVersionCurrent(e);\n                    }}\n                  >\n                    {dataSource?.map((item) => (\n                      <Select.Option\n                        key={item.app_version}\n                        value={item.app_version}\n                      >\n                        {item.app_version}\n                      </Select.Option>\n                    ))}\n                  </Select>\n                </Form.Item>\n                <Form.Item\n                  label=\"集群模式\"\n                  name=\"clusterMode\"\n                  style={{ marginLeft: 30 }}\n                >\n                  <Select style={{ width: 200 }}>\n                    {currentAppDependenceData?.deploy_mode?.map((item) => {\n                      return (\n                        <Select.Option\n                          key={JSON.stringify(item)}\n                          value={JSON.stringify(item)}\n                        >\n                          {item.name}\n                        </Select.Option>\n                      );\n                    })}\n                  </Select>\n                </Form.Item>\n              </Form>\n            </div>\n          </div>\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                依赖信息\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div\n              style={{\n                paddingLeft: 20,\n                marginTop: 10,\n                paddingBottom: 40,\n                paddingTop: 10,\n              }}\n            >\n              {currentAppDependenceData &&\n              currentAppDependenceData.app_dependence &&\n              currentAppDependenceData.app_dependence.length == 0 ? (\n                <div>无</div>\n              ) : (\n                currentAppDependenceData?.app_dependence?.map((item) => (\n                  <RenderComDependence\n                    key={item.name}\n                    data={item}\n                    form={form}\n                  />\n                ))\n              )}\n            </div>\n          </div>\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 80,\n            }}\n          >\n            <div />\n            <Button\n              disabled={!processContinue}\n              type=\"primary\"\n              onClick={() => {\n                currentAppDependenceData?.app_install_args?.map((item) => {\n                  form.setFieldsValue({\n                    [`install|${\n                      currentAppDependenceData?.app_name\n                    }|${JSON.stringify({\n                      name: item.name,\n                      key: item.key,\n                      dir_key: item.dir_key,\n                    })}`]: item.default,\n                  });\n                });\n                currentAppDependenceData?.app_port?.map((item) => {\n                  //console.log(item.default, item);\n                  form.setFieldsValue({\n                    [`port|${\n                      currentAppDependenceData?.app_name\n                    }|${JSON.stringify({\n                      name: item.name,\n                      key: item.key,\n                      //dir_key: item.dir_key,\n                    })}`]: item.default,\n                  });\n                });\n\n                setStep1Data(form.getFieldsValue());\n\n                fetchIPlist();\n                setStepNum(1);\n              }}\n            >\n              下一步\n            </Button>\n          </div>\n        </>\n      )}\n      {/* 第二步 */}\n      {stepNum == 1 && (\n        <>\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 10,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                width: \"100%\",\n                position: \"relative\",\n                height: 30,\n              }}\n            >\n              <div\n                style={{\n                  fontWeight: 500,\n                  position: \"absolute\",\n                  left: 30,\n                  backgroundColor: \"#fff\",\n                  paddingLeft: 20,\n                  paddingRight: 20,\n                }}\n              >\n                {name}\n              </div>\n              <div\n                style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n              />\n            </div>\n            <div style={{ paddingLeft: 20, marginTop: 10, paddingBottom: 40 }}>\n              <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n                <Form\n                  form={form}\n                  layout=\"inline\"\n                  name=\"basic\"\n                  // initialValues={{\n                  //   clusterMode: \"singleInstance\",\n                  // }}\n                >\n                  <Form.Item label=\"选择主机\" name=\"ip\">\n                    <Select\n                      style={{ width: 200 }}\n                      onChange={(e) => {\n                        const IpArr = e.split(\".\");\n                        jdkHandleInitData(\n                          currentAppDependenceData?.app_dependence,\n                          e\n                        );\n                        form.setFieldsValue({\n                          instanceName: `${name}-${IpArr[IpArr.length - 2]}-${\n                            IpArr[IpArr.length - 1]\n                          }`,\n                        });\n                      }}\n                    >\n                      {ipListSource?.map((item) => (\n                        <Select.Option key={item} value={item}>\n                          {item}\n                        </Select.Option>\n                      ))}\n                    </Select>\n                  </Form.Item>\n                  <Form.Item\n                    label=\"实例名称\"\n                    name=\"instanceName\"\n                    style={{ marginLeft: 30 }}\n                    rules={[\n                      {\n                        required: true,\n                        message: \"请填写实例名称\",\n                      },\n                    ]}\n                  >\n                    <Input />\n                  </Form.Item>\n                </Form>\n                <a\n                  style={{\n                    fontSize: 13,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    flexDirection: \"row-reverse\",\n                    paddingRight: 100,\n                  }}\n                  onClick={() => {\n                    setIsOpen({\n                      ...isOpen,\n                      [name]: !isOpen[name],\n                    });\n                    // setIsDetailOpen({\n                    //   ...isDetailOpen,\n                    //   [item.ip]: !isDetailOpen[item.ip],\n                    // });\n                  }}\n                >\n                  <DownOutlined\n                    style={{\n                      transform: `rotate(${isOpen[name] ? 180 : 0}deg)`,\n                      position: \"relative\",\n                      top: isOpen[name] ? -1 : 1,\n                      left: 3,\n                    }}\n                  />\n                  查看更多配置\n                </a>\n              </div>\n\n              <div\n                //className={styles.backIcon}\n                style={\n                  isOpen[name]\n                    ? step2Open(\n                        currentAppDependenceData?.app_install_args?.length +\n                          currentAppDependenceData?.app_port.length\n                      )\n                    : step2NotOpen()\n                }\n              >\n                <Form\n                  form={form}\n                  //layout=\"inline\"\n                  name=\"basic\"\n                  style={{\n                    marginTop: 20,\n                  }}\n                >\n                  {currentAppDependenceData?.app_install_args?.map((item) => {\n                    return (\n                      <Form.Item\n                        key={item.key}\n                        style={{ paddingLeft: 15, paddingBottom: 15 }}\n                        label={<span style={{ width: 60 }}>{item.name}</span>}\n                        name={`install|${\n                          currentAppDependenceData?.app_name\n                        }|${JSON.stringify({\n                          name: item.name,\n                          key: item.key,\n                          dir_key: item.dir_key,\n                        })}`}\n                        rules={[\n                          {\n                            required: true,\n                            message: `请填写${item.name}`,\n                          },\n                        ]}\n                      >\n                        <Input\n                          addonBefore={item.dir_key ? \"/ 数据分区\" : null}\n                          style={{ width: 420 }}\n                          suffix={\n                            item.dir_key ? (\n                              <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                <InfoCircleOutlined\n                                  style={{ color: \"rgba(0,0,0,.45)\" }}\n                                />\n                              </Tooltip>\n                            ) : null\n                          }\n                        />\n                      </Form.Item>\n                    );\n                  })}\n                  {currentAppDependenceData?.app_port?.map((item) => {\n                    return (\n                      <Form.Item\n                        key={item.key}\n                        style={{ paddingLeft: 15 }}\n                        label={<span style={{ width: 60 }}>{item.name}</span>}\n                        name={`port|${\n                          currentAppDependenceData?.app_name\n                        }|${JSON.stringify({\n                          name: item.name,\n                          key: item.key,\n                          //dir_key: item.dir_key,\n                        })}`}\n                        rules={[\n                          {\n                            required: true,\n                            message: `请填写${item.name}`,\n                          },\n                        ]}\n                      >\n                        <Input\n                          addonBefore={item.dir_key ? \"/ 数据分区\" : null}\n                          style={{ width: 420 }}\n                          suffix={\n                            item.dir_key ? (\n                              <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                <InfoCircleOutlined\n                                  style={{ color: \"rgba(0,0,0,.45)\" }}\n                                />\n                              </Tooltip>\n                            ) : null\n                          }\n                        />\n                      </Form.Item>\n                    );\n                  })}\n                </Form>\n              </div>\n            </div>\n          </div>\n          {/* 渲染jdk */}\n          {showJdk ? (\n            <>\n              {currentAppDependenceData?.app_dependence?.map((item) => {\n                return (\n                  <div\n                    key={item.name}\n                    style={{\n                      marginTop: 20,\n                      backgroundColor: \"#fff\",\n                      padding: 10,\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        width: \"100%\",\n                        position: \"relative\",\n                        height: 30,\n                      }}\n                    >\n                      <div\n                        style={{\n                          fontWeight: 500,\n                          position: \"absolute\",\n                          left: 30,\n                          backgroundColor: \"#fff\",\n                          paddingLeft: 20,\n                          paddingRight: 20,\n                        }}\n                      >\n                        {item.name}\n                      </div>\n                      <div\n                        style={{\n                          height: 1,\n                          backgroundColor: \"#b3b2b3\",\n                          width: \"100%\",\n                        }}\n                      />\n                    </div>\n                    <div\n                      style={{\n                        paddingLeft: 20,\n                        marginTop: 10,\n                        paddingBottom: 40,\n                      }}\n                    >\n                      {/* {item.is_pack_exist ? (\n                        \"\"\n                      ) : (\n                        <p style={{ color: \"red\" }}>\n                          {item.name} 服务的安装包不存在 ！\n                        </p>\n                      )} */}\n                      <div\n                        style={{\n                          display: \"flex\",\n                          justifyContent: \"space-between\",\n                        }}\n                      >\n                        <Form\n                          form={form}\n                          layout=\"inline\"\n                          name=\"basic\"\n                          // initialValues={{\n                          //   clusterMode: \"singleInstance\",\n                          // }}\n                        >\n                          <Form.Item label=\"选择主机\" name={`${item.name}|ip`}>\n                            <Select\n                              disabled={true}\n                              style={{ width: 200 }}\n                              // onChange={(e) => {\n                              //   const IpArr = e.split(\".\");\n                              //   form.setFieldsValue({\n                              //     [`${item.name}|instanceName`]: `${\n                              //       item.name\n                              //     }-${IpArr[IpArr.length - 2]}-${\n                              //       IpArr[IpArr.length - 1]\n                              //     }`,\n                              //   });\n                              // }}\n                            >\n                              {ipListSource?.map((item) => (\n                                <Select.Option key={item} value={item}>\n                                  {item}\n                                </Select.Option>\n                              ))}\n                            </Select>\n                          </Form.Item>\n                          <Form.Item\n                            label=\"实例名称\"\n                            name={`${item.name}|instanceName`}\n                            style={{ marginLeft: 30 }}\n                            rules={[\n                              {\n                                required: true,\n                                message: \"请填写实例名称\",\n                              },\n                            ]}\n                          >\n                            <Input />\n                          </Form.Item>\n                        </Form>\n                        <a\n                          style={{\n                            fontSize: 13,\n                            display: \"flex\",\n                            alignItems: \"center\",\n                            flexDirection: \"row-reverse\",\n                            paddingRight: 100,\n                          }}\n                          onClick={() => {\n                            setIsOpen({\n                              ...isOpen,\n                              [item.name]: !isOpen[item.name],\n                            });\n                          }}\n                        >\n                          <DownOutlined\n                            style={{\n                              transform: `rotate(${\n                                isOpen[item.name] ? 180 : 0\n                              }deg)`,\n                              position: \"relative\",\n                              top: isOpen[item.name] ? -1 : 1,\n                              left: 3,\n                            }}\n                          />\n                          查看更多配置\n                        </a>\n                      </div>\n\n                      <div\n                        //className={styles.backIcon}\n                        style={\n                          isOpen[item.name]\n                            ? step2Open(\n                                item.app_install_args?.length +\n                                  item.app_port?.length\n                              )\n                            : step2NotOpen()\n                        }\n                      >\n                        <Form\n                          form={form}\n                          //layout=\"inline\"\n                          name=\"basic\"\n                          style={{\n                            marginTop: 20,\n                          }}\n                        >\n                          {item?.app_install_args?.map((i) => {\n                            return (\n                              <Form.Item\n                                key={i.key}\n                                style={{ paddingLeft: 15, paddingBottom: 15 }}\n                                label={\n                                  <span style={{ width: 60 }}>{i.name}</span>\n                                }\n                                name={`install|${item.name}|${JSON.stringify({\n                                  name: i.name,\n                                  key: i.key,\n                                  dir_key: i.dir_key,\n                                })}`}\n                                rules={[\n                                  {\n                                    required: true,\n                                    message: `请填写${i.name}`,\n                                  },\n                                ]}\n                              >\n                                <Input\n                                  addonBefore={i.dir_key ? \"/ 数据分区\" : null}\n                                  style={{ width: 420 }}\n                                  suffix={\n                                    i.dir_key ? (\n                                      <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                        <InfoCircleOutlined\n                                          style={{ color: \"rgba(0,0,0,.45)\" }}\n                                        />\n                                      </Tooltip>\n                                    ) : null\n                                  }\n                                />\n                              </Form.Item>\n                            );\n                          })}\n                          {item?.app_port?.map((i) => {\n                            return (\n                              <Form.Item\n                                key={i.key}\n                                style={{ paddingLeft: 15, paddingBottom: 15 }}\n                                label={\n                                  <span style={{ width: 60 }}>{i.name}</span>\n                                }\n                                name={`port|${item.name}|${JSON.stringify({\n                                  name: i.name,\n                                  key: i.key,\n                                })}`}\n                                rules={[\n                                  {\n                                    required: true,\n                                    message: `请填写${i.name}`,\n                                  },\n                                ]}\n                              >\n                                <Input\n                                  addonBefore={i.dir_key ? \"/ 数据分区\" : null}\n                                  style={{ width: 420 }}\n                                  suffix={\n                                    i.dir_key ? (\n                                      <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                                        <InfoCircleOutlined\n                                          style={{ color: \"rgba(0,0,0,.45)\" }}\n                                        />\n                                      </Tooltip>\n                                    ) : null\n                                  }\n                                />\n                              </Form.Item>\n                            );\n                          })}\n                        </Form>\n                      </div>\n                    </div>\n                  </div>\n                );\n              })}\n            </>\n          ) : (\n            \"\"\n          )}\n\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 40,\n            }}\n          >\n            <div style={{ display: \"flex\", alignItems: \"center\" }}>\n              分布主机数量: 1台\n            </div>\n            <div>\n              <Button\n                style={{\n                  marginRight: 15,\n                }}\n                type=\"primary\"\n                onClick={() => {\n                  setStepNum(0);\n                }}\n              >\n                上一步\n              </Button>\n              <Button\n                type=\"primary\"\n                loading={loading}\n                onClick={() => {\n                  // 先出发表单校验\n                  form\n                    .validateFields()\n                    .then((values) => {\n                      setLoading(true);\n                      setStep2Data(form.getFieldsValue());\n                      let st2 = form.getFieldsValue();\n                      //setStepNum(2);\n\n                      let parameterCreate = (\n                        step2Data,\n                        type,\n                        length = 3,\n                        type2\n                      ) => {\n                        let arr = [];\n                        Object.keys(step2Data)?.map((key) => {\n                          if (\n                            key.split(\"|\")[0] == type &&\n                            key.split(\"|\").length == length &&\n                            key.split(\"|\")[1] == type2\n                          ) {\n                            let data = JSON.parse(key.split(\"|\")[length - 1]);\n                            arr.push({\n                              ...data,\n                              default: step2Data[key],\n                            });\n                          }\n                        });\n                        return arr;\n                      };\n\n                      const analysisJdk = (st2, type, length = 3, type2) => {\n                        let result = {};\n                        for (const key in st2) {\n                          let keyArr = key.split(\"|\");\n                          if (keyArr.length == length) {\n                            if (keyArr[0] == type && keyArr[1] == type2) {\n                              result[keyArr[2]] = st2[key];\n                            }\n                          }\n                        }\n                        return result;\n                      };\n\n                      let installArr = install_servicesRef.current;\n                      let app_install_args = install_servicesRef.current[0]\n                        ? analysisJdk(\n                            st2,\n                            \"install\",\n                            3,\n                            install_servicesRef.current[0].name\n                          )\n                        : {};\n\n                      let ipAndInstanceName = {};\n                      Object.keys(st2)?.map((o) => {\n                        let arr = o.split(\"|\");\n                        if (arr.length == 2 && arr[0] == installArr[0]?.name) {\n                          ipAndInstanceName[arr[1]] = st2[o];\n                        }\n                      });\n\n                      if (installArr.length > 0) {\n                        install_servicesRef.current[0].ip =\n                          ipAndInstanceName.ip;\n                        install_servicesRef.current[0].service_instance_name =\n                          ipAndInstanceName.instanceName;\n                        install_servicesRef.current[0].app_install_args =\n                          install_servicesRef.current[0]?.app_install_args?.map(\n                            (item) => {\n                              let key = JSON.stringify({\n                                name: item.name,\n                                key: item.key,\n                                dir_key: item.dir_key,\n                              });\n                              return {\n                                ...item,\n                                default: app_install_args[key],\n                              };\n                            }\n                          );\n                        install_servicesRef.current[0].app_port = [];\n                      }\n\n                      use_exist_servicesRef.current =\n                        use_exist_servicesRef.current?.map((item) => {\n                          return {\n                            ...item,\n                            type: \"single\",\n                          };\n                        });\n\n                      let data = {\n                        install_type: 0,\n                        use_exist_services: use_exist_servicesRef.current,\n                        install_services: [\n                          {\n                            name: name,\n                            version: versionCurrent,\n                            ip: st2.ip,\n                            app_install_args: parameterCreate(\n                              st2,\n                              \"install\",\n                              3,\n                              currentAppDependenceData?.app_name\n                            ),\n                            app_port: parameterCreate(\n                              st2,\n                              \"port\",\n                              3,\n                              currentAppDependenceData?.app_name\n                            ),\n                            service_instance_name: st2.instanceName,\n                            deploy_mode: JSON.parse(step1Data.clusterMode),\n                          },\n                          ...install_servicesRef.current,\n                        ],\n                      };\n                      //return;\n                      fetchPost(apiRequest.appStore.executeInstall, {\n                        body: {\n                          ...data,\n                        },\n                      })\n                        .then((res) => {\n                          handleResponse(res, (res) => {\n                            if (res.data && res.data.install_services) {\n                              if (!res.data.is_valid_flag) {\n                                // 打开全部的展开栏\n                                let isOpenCopy = JSON.parse(\n                                  JSON.stringify(isOpen)\n                                );\n                                console.log(isOpenCopy);\n                                for (const key in isOpenCopy) {\n                                  isOpenCopy[key] = true;\n                                }\n                                console.log(isOpenCopy);\n                                setIsOpen({\n                                  ...isOpenCopy,\n                                  [currentAppDependenceData?.app_dependence[0]\n                                    ?.name]: true,\n                                });\n\n                                if (\n                                  res.data.use_exist_services &&\n                                  res.data.use_exist_services.length > 0\n                                ) {\n                                  if (\n                                    res.data.use_exist_services[0].check_flag ==\n                                    false\n                                  ) {\n                                    message.warn(\n                                      res.data.use_exist_services[0].check_msg\n                                    );\n                                    //setProcessContinue(false)\n                                  }\n                                }\n\n                                res.data.install_services?.map((item, idx) => {\n                                  if (\n                                    item.check_flag == false &&\n                                    item.check_msg\n                                  ) {\n                                    if (idx == 0) {\n                                      form.setFields([\n                                        {\n                                          name: \"instanceName\",\n                                          errors: [\n                                            res.data.install_services[0]\n                                              .check_msg,\n                                          ],\n                                        },\n                                      ]);\n                                    } else {\n                                      form.setFields([\n                                        {\n                                          name: `${item.name}|instanceName`,\n                                          errors: [item.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  }\n                                  item.app_port?.map((i) => {\n                                    if (i.check_flag == false) {\n                                      form.setFields([\n                                        {\n                                          name: `port|${\n                                            item.name\n                                          }|${JSON.stringify({\n                                            name: i.name,\n                                            key: i.key,\n                                          })}`,\n                                          errors: [i.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  });\n                                  item.app_install_args?.map((i) => {\n                                    if (i.check_flag == false) {\n                                      form.setFields([\n                                        {\n                                          name: `install|${\n                                            item.name\n                                          }|${JSON.stringify({\n                                            name: i.name,\n                                            key: i.key,\n                                            dir_key: i.dir_key,\n                                          })}`,\n                                          errors: [i.check_msg],\n                                        },\n                                      ]);\n                                    }\n                                  });\n                                });\n                              } else {\n                                // 后端校验通过\n                                setVPassedresData(res.data);\n                                queryInstallationInfo(res.data.operation_uuid);\n                                setStepNum(2);\n                              }\n                            }\n                          });\n                        })\n                        .catch((e) => console.log(e))\n                        .finally(() => {\n                          setLoading(false);\n                        });\n                    })\n                    .catch((errorInfo) => {\n                      let isOpenCopy = JSON.parse(JSON.stringify(isOpen));\n                      for (const key in isOpenCopy) {\n                        isOpenCopy[key] = true;\n                      }\n                      setIsOpen({\n                        ...isOpenCopy,\n                      });\n                    });\n                }}\n              >\n                开始安装\n              </Button>\n            </div>\n          </div>\n        </>\n      )}\n      {stepNum == 2 && (\n        <>\n          {step3Data?.detail_lst?.map((item) => {\n            return (\n              <div\n                key={item.service_instance_name}\n                style={{\n                  marginTop: 20,\n                  backgroundColor: \"#fff\",\n                  padding: 10,\n                }}\n              >\n                <div\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    width: \"100%\",\n                    position: \"relative\",\n                    height: 30,\n                  }}\n                >\n                  <div\n                    style={{\n                      fontWeight: 500,\n                      position: \"absolute\",\n                      left: 30,\n                      backgroundColor: \"#fff\",\n                      paddingLeft: 20,\n                      paddingRight: 20,\n                    }}\n                  >\n                    {item.service_name}\n                  </div>\n                  <div\n                    style={{\n                      height: 1,\n                      backgroundColor: \"#b3b2b3\",\n                      width: \"100%\",\n                    }}\n                  />\n                </div>\n\n                <div\n                  style={{\n                    paddingLeft: 20,\n                    marginTop: 10,\n                    paddingBottom: 40,\n                  }}\n                >\n                  <div\n                    style={{\n                      display: \"flex\",\n                      alignItems: \"center\",\n                      justifyContent: \"space-between\",\n                    }}\n                  >\n                    <div\n                      style={{\n                        width: 320,\n                        display: \"flex\",\n                        alignItems: \"center\",\n                      }}\n                    >\n                      {item.ip}\n                    </div>\n                    <a\n                      style={{\n                        fontSize: 13,\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        flexDirection: \"row-reverse\",\n                        paddingRight: 100,\n                      }}\n                      onClick={() => {\n                        setIsDetailOpen({\n                          ...isDetailOpen,\n                          [item.service_name]: !isDetailOpen[item.service_name],\n                        });\n                        //setIsDetailOpen(!isDetailOpen);\n                      }}\n                    >\n                      <DownOutlined\n                        style={{\n                          transform: `rotate(${\n                            isDetailOpen[item.ip] ? 180 : 0\n                          }deg)`,\n                          position: \"relative\",\n                          top: isDetailOpen[item.ip] ? -1 : 1,\n                          left: 3,\n                        }}\n                      />\n                      查看详细安装信息\n                    </a>\n                  </div>\n\n                  <div\n                    //className={styles.backIcon}\n                    ref={containerRef}\n                    style={\n                      isDetailOpen[item.service_name]\n                        ? step3Open(2)\n                        : step3NotOpen()\n                    }\n                  >\n                    {item.log ? item.log : \"正在安装...\"}\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n          <div\n            style={{\n              marginTop: 20,\n              backgroundColor: \"#fff\",\n              padding: 25,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              paddingRight: 80,\n            }}\n          >\n            <div style={{ display: \"flex\", alignItems: \"center\" }}>\n              {step3Data.install_status_msg}{\" \"}\n              {(step3Data.install_status == 0 ||\n                step3Data.install_status == 1) && (\n                <LoadingOutlined\n                  style={{ fontSize: 20, fontWeight: 600, marginLeft: 10 }}\n                />\n              )}\n            </div>\n            <div>\n              <Button\n                type=\"primary\"\n                onClick={() => {\n                  history.push(\"/application_management/service_management\");\n                }}\n              >\n                完成\n              </Button>\n            </div>\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport default ComponentInstallation;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/DeleteServerModal.js",
    "content": "import { Modal, Cascader, message } from \"antd\";\nimport { useEffect, useState } from \"react\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\n\nconst DeleteServerModal = ({\n  deleteServerVisibility,\n  setDeleteServerVisibility,\n  tabKey,\n  refresh,\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const [options, setOptions] = useState([]);\n\n  const [resApp, setResApp] = useState([]);\n\n  const [initData, setInitData] = useState([]);\n\n  // 获取可删除选项\n  const queryData = () => {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.deleteServer, {\n      params: {\n        type: tabKey === \"component\" ? \"component\" : \"product\",\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setInitData(res.data.data);\n          setOptions(\n            res.data.data.map((e) => {\n              return {\n                label: (\n                  <>\n                    {e.name.includes(\"|\") ? (\n                      <>\n                        {e.name.split(\"|\")[0]}\n                        <span style={{ float: \"right\", marginLeft: 20 }}>\n                          {e.name.split(\"|\")[1]}\n                        </span>\n                      </>\n                    ) : (\n                      e.name\n                    )}\n                  </>\n                ),\n                value: e.name,\n                children: e.versions.map((i) => {\n                  return {\n                    label: (\n                      <>\n                        {i.includes(\"|\") ? (\n                          <>\n                            {i.split(\"|\")[0]}\n                            <span style={{ float: \"right\", marginLeft: 20 }}>\n                              {i.split(\"|\")[1]}\n                            </span>\n                          </>\n                        ) : (\n                          i\n                        )}\n                      </>\n                    ),\n                    value: i,\n                  };\n                }),\n              };\n            })\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 删除操作\n  const doDelete = () => {\n    if (resApp.length === 0) {\n      message.info(\"请先选择要删除的服务\");\n      return;\n    }\n    const allList = [];\n    const someList = {};\n    const resData = [];\n    for (let i = 0; i < resApp.length; i++) {\n      const e = resApp[i];\n      if (e.length === 1) {\n        allList.push(e[0]);\n      } else {\n        if (someList.hasOwnProperty(e[0])) {\n          someList[e[0]].push(e[1]);\n        } else {\n          someList[e[0]] = [e[1]];\n        }\n      }\n    }\n    for (let i = 0; i < initData.length; i++) {\n      const e = initData[i];\n      if (allList.includes(e.name)) {\n        resData.push(e);\n      } else if (someList.hasOwnProperty(e.name)) {\n        resData.push({\n          name: e.name,\n          versions: someList[e.name],\n        });\n      }\n    }\n    setLoading(true);\n    fetchPost(apiRequest.appStore.deleteServer, {\n      body: {\n        type: tabKey === \"component\" ? \"component\" : \"product\",\n        data: resData,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code === 0) {\n            message.success(\"删除成功\");\n            setDeleteServerVisibility(false);\n            queryData();\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryData();\n  }, [tabKey]);\n\n  return (\n    <Modal\n      zIndex={1000}\n      title={\n        <span>删除{tabKey === \"component\" ? \"基础组件\" : \"自研服务\"}</span>\n      }\n      afterClose={() => {\n        setResApp([]);\n        refresh();\n      }}\n      onCancel={() => {\n        setDeleteServerVisibility(false);\n      }}\n      visible={deleteServerVisibility}\n      width={480}\n      confirmLoading={loading}\n      okText={loading ? \"稍候\" : \"删除\"}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n      onOk={() => doDelete()}\n    >\n      <div\n        style={{\n          marginLeft: 10,\n          marginBottom: 20,\n        }}\n      >\n        选择服务：\n        <Cascader\n          style={{\n            width: 300,\n            marginLeft: 6,\n          }}\n          options={options}\n          onChange={(value) => setResApp(value)}\n          multiple\n          maxTagCount=\"responsive\"\n          placeholder={tabKey === \"component\" ? \"选择基础组件\" : \"选择自研服务\"}\n        />\n      </div>\n    </Modal>\n  );\n};\n\nexport default DeleteServerModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/GetService.js",
    "content": "import { useHistory, useLocation } from \"react-router-dom\";\nimport styles from \"./index.module.less\";\nimport { useSelector } from \"react-redux\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport {\n  Radio,\n  Steps,\n  Table,\n  Button,\n  Form,\n  Checkbox,\n  Row,\n  Input,\n  Tooltip,\n  Collapse,\n  Tag,\n  Modal,\n  Spin,\n  Result,\n  message,\n  Select,\n  Switch,\n} from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  LeftOutlined,\n  CheckCircleFilled,\n  CloseCircleFilled,\n  CaretRightOutlined,\n  InfoCircleOutlined,\n  SyncOutlined,\n  ExclamationCircleFilled,\n  ZoomInOutlined,\n} from \"@ant-design/icons\";\n\nconst { Panel } = Collapse;\n\nconst ServiceItem = ({ form, itemData, errInfo, productName }) => {\n  const version =\n    typeof itemData?.version === \"string\"\n      ? itemData.version\n      : itemData.version[0];\n\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${itemData.name}-base_dir`]: itemData.base_dir?.replace(\n        \"{data_path}\",\n        \"\"\n      ),\n      [`${itemData.name}-data_dir`]: itemData.data_dir?.replace(\n        \"{data_path}\",\n        \"\"\n      ),\n      [`${itemData.name}-log_dir`]: itemData.log_dir?.replace(\n        \"{data_path}\",\n        \"\"\n      ),\n      [`${itemData.name}-run_user`]: itemData.run_user,\n      [`${itemData.name}-service_port`]: itemData.service_port,\n    });\n  }, []);\n\n  return (\n    <div style={{ padding: 10, paddingTop: 0 }}>\n      <Collapse\n        bordered={false}\n        defaultActiveKey={[\"1\"]}\n        expandIcon={({ isActive }) => (\n          <CaretRightOutlined rotate={isActive ? 90 : 0} />\n        )}\n      >\n        <Panel\n          header={\n            <>\n              {productName ? (\n                <span\n                  style={{ color: \"rgb(73,134,247)\", marginRight: 12 }}\n                >{`[ ${productName} ]`}</span>\n              ) : (\n                \"\"\n              )}\n              {itemData.name}\n              {errInfo && (\n                <span style={{ color: \"red\", marginLeft: 20 }}>{errInfo}</span>\n              )}\n            </>\n          }\n          extra={version}\n          key=\"1\"\n          className={\"panelItem\"}\n          style={{ backgroundColor: \"#f6f6f6\" }}\n        >\n          <Form.Item\n            label=\"安装目录\"\n            key=\"base_dir\"\n            name={`${itemData.name}-base_dir`}\n            style={{ marginTop: 10, width: 600 }}\n          >\n            <Input\n              addonBefore={<span style={{ color: \"#b1b1b1\" }}>/ 数据分区</span>}\n              placeholder=\"请输入安装目录\"\n              suffix={\n                <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                  <InfoCircleOutlined style={{ color: \"rgba(0,0,0,.45)\" }} />\n                </Tooltip>\n              }\n            />\n          </Form.Item>\n          <Form.Item\n            label=\"数据目录\"\n            key=\"data_dir\"\n            name={`${itemData.name}-data_dir`}\n            style={{ marginTop: 10, width: 600 }}\n          >\n            <Input\n              addonBefore={<span style={{ color: \"#b1b1b1\" }}>/ 数据分区</span>}\n              placeholder=\"请输入数据目录\"\n              suffix={\n                <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                  <InfoCircleOutlined style={{ color: \"rgba(0,0,0,.45)\" }} />\n                </Tooltip>\n              }\n            />\n          </Form.Item>\n          <Form.Item\n            label=\"日志目录\"\n            key=\"log_dir\"\n            name={`${itemData.name}-log_dir`}\n            style={{ marginTop: 10, width: 600 }}\n          >\n            <Input\n              addonBefore={<span style={{ color: \"#b1b1b1\" }}>/ 数据分区</span>}\n              placeholder=\"请输入数据目录\"\n              suffix={\n                <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                  <InfoCircleOutlined style={{ color: \"rgba(0,0,0,.45)\" }} />\n                </Tooltip>\n              }\n            />\n          </Form.Item>\n          <Form.Item\n            label=\"运行用户\"\n            key=\"run_user\"\n            name={`${itemData.name}-run_user`}\n            style={{ marginTop: 10, width: 600 }}\n          >\n            <Input placeholder=\"请输入运行用户\" />\n          </Form.Item>\n          <Form.Item\n            label=\"服务端口\"\n            key=\"service_port\"\n            name={`${itemData.name}-service_port`}\n            style={{ marginTop: 10, width: 600 }}\n          >\n            <Input placeholder=\"请输入服务端口\" />\n          </Form.Item>\n        </Panel>\n      </Collapse>\n    </div>\n  );\n};\n\n// 第一步\nconst Step1 = ({\n  setStepNum,\n  getServiceData,\n  setCheckServiceData,\n  setServiceConnectionData,\n}) => {\n  const ipArr = Object.keys(getServiceData?.ips || []);\n\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n\n  // 选中ip数据\n  const [getServiceIpArr, setGetServiceIpArr] = useState([]);\n\n  // 选中所有ip\n  const [selectAllIp, setSelectAllIp] = useState(false);\n\n  // 服务数据表单\n  const [serviceForm] = Form.useForm();\n\n  // 扫描中对话框\n  const [ingVisible, setIngVisible] = useState(false);\n\n  // 加载\n  const [loading, setLoading] = useState(false);\n\n  const getPathValue = (name, dirType) => {\n    const value = serviceForm.getFieldValue(name + \"-\" + dirType);\n    return value !== \"\" ? \"{data_path}\" + value : \"\";\n  };\n\n  // 服务纳管\n  const startGetService = () => {\n    setLoading(true);\n    setIngVisible(true);\n    const serviceData = [...getServiceData.service];\n    const resArr = [];\n    // 处理服务信息\n    for (let i = 0; i < serviceData.length; i++) {\n      const element = serviceData[i];\n      if (element.hasOwnProperty(\"child\")) {\n        const childInfo = {};\n        childInfo[element.version[0]] = element.child[element.version[0]].map(\n          (i) => {\n            return {\n              name: i.name,\n              version: i.version,\n              base_dir: getPathValue(i.name, \"base_dir\"),\n              data_dir: getPathValue(i.name, \"data_dir\"),\n              log_dir: getPathValue(i.name, \"log_dir\"),\n              run_user: serviceForm.getFieldValue(`${i.name}-run_user`),\n              service_port: serviceForm.getFieldValue(`${i.name}-service_port`),\n            };\n          }\n        );\n        resArr.push({\n          name: element.name,\n          version: element.version,\n          child: childInfo,\n        });\n      } else {\n        resArr.push({\n          name: element.name,\n          version: element.version,\n          base_dir: getPathValue(element.name, \"base_dir\"),\n          data_dir: getPathValue(element.name, \"data_dir\"),\n          log_dir: getPathValue(element.name, \"log_dir\"),\n          run_user: serviceForm.getFieldValue(`${element.name}-run_user`),\n          service_port: serviceForm.getFieldValue(\n            `${element.name}-service_port`\n          ),\n        });\n      }\n    }\n    // 处理ip信息\n    const ipsRes = {};\n    for (const key in getServiceData.ips) {\n      if (Object.hasOwnProperty.call(getServiceData.ips, key)) {\n        const element = getServiceData.ips[key];\n        if (getServiceIpArr.indexOf(key) !== -1) {\n          ipsRes[key] = element;\n        }\n      }\n    }\n    fetchPost(apiRequest.appStore.appConfCheck, {\n      body: {\n        data: {\n          service: resArr,\n          ips: ipsRes,\n          is_continue: getServiceData.is_continue,\n        },\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setCheckServiceData(res.data);\n          let connectData = {};\n          const targetData = res.data.ser_info;\n          for (let index = 0; index < targetData.length; index++) {\n            const element = targetData[index];\n            connectData[element.name] = {\n              is_use_exist: element.is_use_exist,\n              exist_instance:\n                element.exist_instance.length === 0\n                  ? []\n                  : [element.exist_instance[0]],\n            };\n          }\n          setServiceConnectionData(connectData);\n          setStepNum(1);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setIngVisible(false);\n      });\n  };\n\n  return (\n    <div>\n      <div\n        style={{\n          marginTop: 20,\n          padding: 10,\n          paddingLeft: 30,\n          display: \"flex\",\n          alignItems: \"center\",\n          backgroundColor: \"#fff\",\n        }}\n      >\n        <ExclamationCircleFilled\n          style={{ fontSize: 16, marginRight: 10, color: \"rgb(72,134,247)\" }}\n        />\n        请选择本次纳管的主机范围，填写服务相关信息\n      </div>\n      <Spin spinning={loading}>\n        <div\n          style={{\n            marginTop: 15,\n            display: \"flex\",\n            backgroundColor: \"#fff\",\n          }}\n        >\n          <div\n            style={{\n              width: 240,\n            }}\n          >\n            <div\n              style={{\n                padding: \"15px 5px 10px 5px\",\n              }}\n            >\n              <p style={{ marginLeft: 20 }}>\n                选择主机 :\n                <Radio\n                  style={{ float: \"right\" }}\n                  disabled={ipArr.length === 0}\n                  checked={selectAllIp}\n                  onClick={() => {\n                    const target = !selectAllIp;\n                    setSelectAllIp(target);\n                    setGetServiceIpArr(target ? ipArr : []);\n                  }}\n                >\n                  全选\n                </Radio>\n              </p>\n\n              {ipArr?.length === 0 ? (\n                <span style={{ marginLeft: 30, color: \"#a7abb7\" }}>\n                  暂无可选的主机\n                </span>\n              ) : (\n                <Checkbox.Group\n                  value={getServiceIpArr}\n                  onChange={(checkedValues) => {\n                    setGetServiceIpArr(checkedValues);\n                    setSelectAllIp(\n                      checkedValues.length === ipArr.length ? true : false\n                    );\n                  }}\n                  style={{\n                    marginLeft: 30,\n                  }}\n                >\n                  {ipArr.map((e) => {\n                    return (\n                      <Row>\n                        <Checkbox\n                          key={e}\n                          value={e}\n                          style={{ lineHeight: \"32px\" }}\n                        >\n                          {e}\n                        </Checkbox>\n                      </Row>\n                    );\n                  })}\n                </Checkbox.Group>\n              )}\n            </div>\n\n            <div\n              style={{\n                overflowY: \"auto\",\n              }}\n            >\n              <div\n                style={{\n                  cursor: \"pointer\",\n                  borderRight: \"0px\",\n                  height: viewHeight - 390,\n                }}\n              >\n                <div style={{ height: 100 }}></div>\n              </div>\n            </div>\n          </div>\n          <div\n            style={{\n              flex: 1,\n              borderLeft: \"1px solid #d9d9d9\",\n              overflowY: \"auto\",\n              paddingTop: 10,\n            }}\n          >\n            <Form\n              form={serviceForm}\n              name=\"serviceForm\"\n              labelCol={{ span: 8 }}\n              wrapperCol={{ span: 40 }}\n            >\n              {getServiceData?.service?.map((item) => {\n                if (item.hasOwnProperty(\"child\")) {\n                  const childArr = item.child[item.version[0]];\n                  return childArr.map((i) => {\n                    return (\n                      <ServiceItem\n                        itemData={i}\n                        form={serviceForm}\n                        errInfo={item.error}\n                        productName={item.name}\n                      />\n                    );\n                  });\n                }\n                return (\n                  <ServiceItem\n                    itemData={item}\n                    form={serviceForm}\n                    errInfo={item.error}\n                  />\n                );\n              })}\n            </Form>\n          </div>\n        </div>\n      </Spin>\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 4,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20 }}>\n          分布主机: {getServiceIpArr.length} 台\n        </div>\n        <div>\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            disabled={\n              !getServiceData?.is_continue || getServiceIpArr.length === 0\n            }\n            loading={loading}\n            onClick={() => {\n              startGetService();\n            }}\n          >\n            扫描服务\n          </Button>\n        </div>\n      </div>\n\n      <Modal\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <ZoomInOutlined />\n            </span>\n            <span>服务纳管</span>\n          </span>\n        }\n        visible={ingVisible}\n        width={600}\n        bodyStyle={{\n          fontSize: 14,\n        }}\n        onCancel={() => {\n          setIngVisible(false);\n        }}\n        footer={null}\n      >\n        <div style={{ margin: 20 }}>\n          <SyncOutlined\n            spin\n            style={{\n              marginRight: 16,\n              fontSize: 16,\n            }}\n          />\n          正在按照服务相关信息，对指定服务器进行扫描，请稍后...\n        </div>\n      </Modal>\n    </div>\n  );\n};\n\n// 第二步\nconst Step2 = ({\n  setStepNum,\n  getServiceData,\n  checkServiceData,\n  serviceConnectionData,\n  setServiceConnectionData,\n}) => {\n  const tableData = checkServiceData?.ser_info;\n\n  // 加载\n  const [loading, setLoading] = useState(false);\n\n  const addService = () => {\n    const resSerInfo = [];\n    const sourceData = checkServiceData.ser_info;\n    for (let index = 0; index < sourceData.length; index++) {\n      const element = sourceData[index];\n      resSerInfo.push({\n        name: element.name,\n        error: element.error,\n        ip: element.ip,\n        is_use_exist: serviceConnectionData[element.name].is_use_exist,\n        exist_instance: serviceConnectionData[element.name].exist_instance,\n      });\n    }\n    setLoading(true);\n    fetchPost(apiRequest.appStore.appConfCheck, {\n      body: {\n        data: {\n          ser_info: resSerInfo,\n          uuid: checkServiceData.uuid,\n        },\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.data.is_error) {\n            message.warning(res.data.message);\n          } else {\n            setStepNum(2);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => setLoading(false));\n  };\n\n  const tableColumn = [\n    {\n      title: \"服务名称\",\n      dataIndex: \"name\",\n      key: \"name\",\n      align: \"center\",\n      width: 200,\n    },\n    {\n      title: \"扫描到该服务的IP\",\n      dataIndex: \"ip\",\n      key: \"ip\",\n      align: \"center\",\n      width: 300,\n      filters: Object.keys(getServiceData?.ips || []).map((i) => {\n        return {\n          value: i,\n          text: i,\n        };\n      }),\n      onFilter: (value, record) => record.ip.indexOf(value) !== -1,\n      render: (text) => {\n        if (typeof text === \"string\") {\n          return text;\n        } else if (text.length === 0) {\n          return \"-\";\n        } else {\n          return (\n            <>\n              {text.map((i) => (\n                <Tag>{i}</Tag>\n              ))}\n            </>\n          );\n        }\n      },\n    },\n    {\n      title: \"关联集群\",\n      dataIndex: \"is_use_exist\",\n      key: \"is_use_exist\",\n      align: \"center\",\n      width: 80,\n      render: (text, record) => {\n        return (\n          <Switch\n            defaultChecked={record.is_use_exist}\n            disabled={!record.is_use_exist}\n            onChange={(value) => {\n              const resData = {};\n              for (const key in serviceConnectionData) {\n                if (Object.hasOwnProperty.call(serviceConnectionData, key)) {\n                  const element = serviceConnectionData[key];\n                  if (key === record.name) {\n                    resData[key] = {\n                      is_use_exist: value,\n                      exist_instance: element.exist_instance,\n                    };\n                  } else {\n                    resData[key] = element;\n                  }\n                }\n              }\n              setServiceConnectionData(resData);\n            }}\n          />\n        );\n      },\n    },\n    {\n      title: \"可选集群实例\",\n      dataIndex: \"exist_instance\",\n      key: \"exist_instance\",\n      align: \"center\",\n      width: 260,\n      render: (text, record) => {\n        if (record.exist_instance.length === 0) {\n          return \"无可用实例\";\n        } else {\n          return (\n            <Select\n              defaultValue={record.exist_instance[0]}\n              disabled={!serviceConnectionData[record.name].is_use_exist}\n              onChange={(value) => {\n                const resData = {};\n                for (const key in serviceConnectionData) {\n                  if (Object.hasOwnProperty.call(serviceConnectionData, key)) {\n                    const element = serviceConnectionData[key];\n                    if (key === record.name) {\n                      resData[key] = {\n                        is_use_exist: element.is_use_exist,\n                        exist_instance: value,\n                      };\n                    } else {\n                      resData[key] = element;\n                    }\n                  }\n                }\n                setServiceConnectionData(resData);\n              }}\n              options={record.exist_instance.map((e) => {\n                return {\n                  value: e,\n                  label: e,\n                };\n              })}\n            />\n          );\n        }\n      },\n    },\n    {\n      title: \"校验结果\",\n      dataIndex: \"check\",\n      key: \"check\",\n      align: \"center\",\n      width: 100,\n      render: (text, record) => {\n        return record.error ? (\n          <CloseCircleFilled\n            style={{ color: \"rgb(247,77,80)\", fontSize: 16 }}\n          />\n        ) : (\n          <CheckCircleFilled\n            style={{ color: \"rgb(82,196,27)\", fontSize: 16 }}\n          />\n        );\n      },\n    },\n    {\n      title: \"错误信息\",\n      dataIndex: \"error\",\n      key: \"error\",\n      align: \"center\",\n      render: (text) => {\n        return text || \"-\";\n      },\n    },\n  ];\n\n  return (\n    <div>\n      <div\n        style={{\n          marginTop: 20,\n          padding: 10,\n          paddingLeft: 30,\n          display: \"flex\",\n          alignItems: \"center\",\n          backgroundColor: \"#fff\",\n        }}\n      >\n        <ExclamationCircleFilled\n          style={{ fontSize: 16, marginRight: 10, color: \"rgb(72,134,247)\" }}\n        />\n        扫描结果如下\n      </div>\n\n      <div\n        style={{\n          width: \"100%\",\n          marginTop: 15,\n          display: \"flex\",\n          backgroundColor: \"#fff\",\n          padding: 20,\n        }}\n      >\n        <Table\n          columns={tableColumn}\n          dataSource={tableData}\n          bordered\n          style={{ width: \"100%\" }}\n          pagination={{\n            pageSize: 10,\n          }}\n        />\n      </div>\n\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 4,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div />\n        <div>\n          <Button\n            type=\"primary\"\n            onClick={() => {\n              setStepNum(0);\n            }}\n          >\n            上一步\n          </Button>\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            disabled={!checkServiceData?.is_continue}\n            loading={loading}\n            onClick={() => {\n              addService();\n            }}\n          >\n            纳管\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst GetService = () => {\n  const history = useHistory();\n  const location = useLocation();\n\n  // 服务纳管初始数据\n  const getServiceData = location.state?.getServiceData;\n  // 服务扫描信息\n  const [checkServiceData, setCheckServiceData] = useState(null);\n  // 服务关联信息\n  const [serviceConnectionData, setServiceConnectionData] = useState({});\n  // 步骤\n  const [stepNum, setStepNum] = useState(0);\n\n  return (\n    <div\n      style={{\n        backgroundColor: \"rgb(240, 242, 245)\",\n      }}\n    >\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            onClick={() => {\n              history?.goBack();\n            }}\n          />\n          服务纳管\n        </div>\n        <div style={{ width: 600, position: \"relative\", left: -60 }}>\n          <Steps size=\"small\" current={stepNum}>\n            <Steps.Step title=\"基本信息\" />\n            <Steps.Step title=\"扫描结果\" />\n            <Steps.Step title=\"服务纳管\" />\n          </Steps>\n        </div>\n        <div />\n      </div>\n      {stepNum == 0 && (\n        <Step1\n          setStepNum={setStepNum}\n          getServiceData={getServiceData}\n          setCheckServiceData={setCheckServiceData}\n          setServiceConnectionData={setServiceConnectionData}\n        />\n      )}\n      {stepNum == 1 && (\n        <Step2\n          setStepNum={setStepNum}\n          getServiceData={getServiceData}\n          checkServiceData={checkServiceData}\n          serviceConnectionData={serviceConnectionData}\n          setServiceConnectionData={setServiceConnectionData}\n        />\n      )}\n      {stepNum == 2 && (\n        <Result\n          style={{\n            paddingTop: \"10%\",\n            backgroundColor: \"#fff\",\n          }}\n          status=\"success\"\n          title=\"服务纳管执行成功\"\n          extra={[\n            <Button\n              onClick={() => {\n                history?.goBack();\n              }}\n            >\n              返回\n            </Button>,\n            <Button\n              type=\"primary\"\n              onClick={() => {\n                history?.push(\"/application_management/service_management\");\n              }}\n            >\n              查看服务\n            </Button>,\n          ]}\n        />\n      )}\n    </div>\n  );\n};\n\nexport default GetService;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/GetServiceModal.js",
    "content": "import { Button, Modal, Input, Select } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { CopyOutlined, SearchOutlined } from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { OmpTable } from \"@/components\";\nimport { useHistory } from \"react-router-dom\";\n\nconst GetServiceModal = ({\n  modalVisibility,\n  setModalVisibility,\n  initData,\n  dataSource,\n  setDataSource,\n  initLoading,\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const history = useHistory();\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //应用服务选择的版本号\n  const versionInfo = useRef({});\n\n  const [searchName, setSearchName] = useState(\"\");\n\n  const columns = [\n    {\n      title: \"名称\",\n      key: \"name\",\n      dataIndex: \"name\",\n      align: \"center\",\n      ellipsis: true,\n      width: 80,\n      render: (text, record) => {\n        return text;\n      },\n    },\n    {\n      title: \"版本\",\n      key: \"version\",\n      dataIndex: \"version\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        if (typeof text === \"string\") {\n          return text;\n        } else if (text.length === 1) {\n          return text[0];\n        } else {\n          return (\n            <Select\n              defaultValue={text[0]}\n              style={{ width: 120, textAlign: \"left\" }}\n              onSelect={(newVersion) => {\n                const oldVersion =\n                  versionInfo.current[record.name] || record.version[0];\n                versionInfo.current[record.name] = newVersion;\n                // 如果版本发生变化，改变数据，清空选中，关闭展开\n                if (oldVersion != newVersion) {\n                  for (let i = 0; i < dataSource.length; i++) {\n                    const element = dataSource[i];\n                    if (\n                      element.name === record.name &&\n                      element.hasOwnProperty(\"child\")\n                    ) {\n                      element.children = element.child[newVersion];\n\n                      let arr = element.child[oldVersion].map((i) => i.name);\n                      arr = [...arr, record.name];\n                      setCheckedList(\n                        checkedList.filter((i) => arr.indexOf(i.name) === -1)\n                      );\n                    }\n                  }\n                }\n              }}\n            >\n              {text.map((item) => {\n                return (\n                  <Select.Option value={item} key={`${item}-${record.name}`}>\n                    {item}\n                  </Select.Option>\n                );\n              })}\n            </Select>\n          );\n        }\n      },\n    },\n  ];\n\n  const checkServiceData = () => {\n    // setLoading(true);\n    const copyCheckData = [...checkedList];\n    // 处理选中数据\n\n    const childArr = copyCheckData.filter((i) => typeof i.version === \"string\");\n\n    const resData = copyCheckData\n      .filter((i) => typeof i.version !== \"string\")\n      .map((i) => {\n        const version = versionInfo.current[i.name] || i.version[0];\n        const itemData = {\n          name: i.name,\n          version: [version],\n        };\n        if (i.hasOwnProperty(\"child\")) {\n          const childOjb = {};\n          childOjb[version] = i.child[version]?.filter(\n            (i) => childArr.indexOf(i) !== -1\n          );\n          itemData.child = childOjb;\n        }\n        return itemData;\n      });\n    fetchPost(apiRequest.appStore.queryAppList, {\n      body: {\n        data: resData,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setLoading(false);\n          if (res.data) {\n            history.push({\n              pathname: \"/application_management/get-service\",\n              state: {\n                getServiceData: res.data,\n              },\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {}, []);\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <CopyOutlined />\n          </span>\n          <span>选择纳管服务</span>\n        </span>\n      }\n      afterClose={() => {\n        setCheckedList([]);\n        versionInfo.current = {};\n      }}\n      onCancel={() => {\n        setModalVisibility(false);\n      }}\n      visible={modalVisibility}\n      footer={null}\n      //width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n        marginTop: -10,\n      }}\n      destroyOnClose\n    >\n      <div\n        style={{\n          display: \"flex\",\n          marginBottom: 10,\n        }}\n      >\n        <div style={{ flex: 1 }}>\n          <Input\n            placeholder=\"请输入名称\"\n            suffix={<SearchOutlined style={{ color: \"#b6b6b6\" }} />}\n            style={{\n              width: 220,\n            }}\n            value={searchName}\n            onChange={(e) => {\n              setSearchName(e.target.value);\n              if (e.target.value === \"\") {\n                setDataSource(initData);\n              }\n            }}\n            onPressEnter={() => {\n              setDataSource(\n                initData.filter((i) => i.name.includes(searchName))\n              );\n            }}\n          />\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            justifyContent: \"space-between\",\n            flex: 1,\n            textAlign: \"right\",\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              width: \"100%\",\n              justifyContent: \"flex-end\",\n              paddingTop: 6,\n            }}\n          >\n            <div style={{ marginRight: 10 }}>\n              已选择{\" \"}\n              {checkedList.filter((i) => typeof i.version !== \"string\").length}{\" \"}\n              个\n            </div>\n          </div>\n        </div>\n      </div>\n      <div>\n        <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n          <OmpTable\n            size=\"small\"\n            scroll={{ y: 270 }}\n            loading={loading || initLoading}\n            //scroll={{ x: 1900 }}\n            columns={columns}\n            dataSource={dataSource}\n            rowKey={(record) => {\n              return record.name;\n            }}\n            checkedState={[checkedList, setCheckedList]}\n            pagination={false}\n            rowSelection={{\n              selectedRowKeys: checkedList?.map((item) => item?.name),\n              onSelect: (record, selected, selectedRows, nativeEvent) => {\n                if (selected) {\n                  // 如果有子项则全部选中\n                  let childArr = [];\n                  if (record.hasOwnProperty(\"child\")) {\n                    const version =\n                      versionInfo.current[record.name] || record.version[0];\n                    childArr = record.child[version];\n                  }\n                  // 如果有父则选中\n                  let fatherArr = [];\n                  if (typeof record.version === \"string\") {\n                    for (let i = 0; i < dataSource.length; i++) {\n                      const element = dataSource[i];\n                      if (element.hasOwnProperty(\"child\")) {\n                        const version =\n                          versionInfo.current[element.name] ||\n                          element.version[0];\n                        if (element.child[version].indexOf(record) !== -1) {\n                          fatherArr.push(element);\n                          break;\n                        }\n                      }\n                    }\n                  }\n                  setCheckedList(\n                    Array.from(\n                      new Set([\n                        ...checkedList,\n                        ...childArr,\n                        ...fatherArr,\n                        record,\n                      ])\n                    )\n                  );\n                } else {\n                  let arr = [record.name];\n                  if (record.hasOwnProperty(\"child\")) {\n                    const version =\n                      versionInfo.current[record.name] || record.version[0];\n                    arr = [...arr, ...record.child[version].map((i) => i.name)];\n                  }\n                  setCheckedList(\n                    checkedList.filter((i) => arr.indexOf(i.name) === -1)\n                  );\n                }\n              },\n              onSelectAll: (selected, selectedRows, changeRows) => {\n                if (selected) {\n                  let resArr = [];\n                  if (\n                    !(changeRows.length === 1 && changeRows[0] === undefined)\n                  ) {\n                    for (let i = 0; i < dataSource.length; i++) {\n                      const element = dataSource[i];\n                      resArr.push(element);\n                      const version =\n                        versionInfo.current[element.name] || element.version[0];\n\n                      if (element.hasOwnProperty(\"child\")) {\n                        resArr = [...resArr, ...element.child[version]];\n                      }\n                    }\n                  }\n                  setCheckedList(Array.from(new Set(resArr)));\n                } else {\n                  setCheckedList([]);\n                }\n              },\n            }}\n          />\n        </div>\n        <div\n          style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n        >\n          <div\n            style={{\n              width: 170,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          >\n            <Button onClick={() => setModalVisibility(false)}>取消</Button>\n            <Button\n              type=\"primary\"\n              style={{ marginLeft: 16 }}\n              loading={loading || initLoading}\n              disabled={checkedList.length == 0}\n              onClick={() => {\n                checkServiceData();\n              }}\n            >\n              确认选择\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default GetServiceModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/BasicInfoItem/index.js",
    "content": "import styles from \"../index.module.less\";\nimport { Form, Input, InputNumber } from \"antd\";\nimport { useEffect, useState, useRef } from \"react\";\nimport { randomNumber } from \"@/utils/utils\";\nimport { DownOutlined } from \"@ant-design/icons\";\n\nconst BasicInfoItem = ({ data, form }) => {\n  // step3的安装详情是否是展开状态 因为多个所以为对象\n  const [isOpen, setIsOpen] = useState(false);\n\n  const numRef = useRef({});\n\n  const step2Open = (num) => ({\n    marginTop: 10,\n    minHeight: 30,\n    height: num * 55,\n    transition: \"all .2s ease-in\",\n    overflow: \"hidden\",\n    backgroundColor: \"#f9f9f9\",\n    display: \"flex\",\n    padding: 10,\n    flexWrap: \"wrap\",\n  });\n\n  const step2NotOpen = () => ({\n    height: 0,\n    minHeight: 0,\n    transition: \"all .2s ease-in\",\n    overflow: \"hidden\",\n    backgroundColor: \"#f9f9f9\",\n    display: \"flex\",\n  });\n\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${data.name}`]: `${data.name}-${randomNumber()}`,\n    });\n    data.services_list.map((item) => {\n      numRef.current[\n        `${data.name}=${item.name}`\n      ] = `${item.deploy_mode.default}`;\n      form.setFieldsValue({\n        [`${data.name}=${item.name}`]: `${item.deploy_mode.default}`,\n      });\n    });\n  }, []);\n\n  return (\n    <>\n      <div className={styles.basicInfoItem}>\n        <div style={{ flex: 2 }}>{data.name}</div>\n        <div style={{ flex: 2 }}>{data.version}</div>\n        <div style={{ flex: 3 }}>\n          <Form.Item\n            label=\"实例名称\"\n            name={`${data.name}`}\n            style={{ marginBottom: 0 }}\n            rules={[\n              {\n                required: true,\n                message: \"请输入密码\",\n              },\n            ]}\n          >\n            <Input />\n          </Form.Item>\n        </div>\n        <div\n          style={{ flex: 7, display: \"flex\", justifyContent: \"space-between\" }}\n        >\n          <div />\n          <div style={{ paddingRight: 20 }}>\n            <a\n              style={{\n                fontSize: 13,\n                display: \"flex\",\n                alignItems: \"center\",\n                flexDirection: \"row-reverse\",\n                paddingRight: 50,\n              }}\n              onClick={() => {\n                setIsOpen(!isOpen);\n              }}\n            >\n              <DownOutlined\n                style={{\n                  transform: `rotate(${isOpen ? 180 : 0}deg)`,\n                  position: \"relative\",\n                  top: isOpen ? -1 : 1,\n                  left: 3,\n                }}\n              />\n              更改服务信息\n            </a>\n          </div>\n        </div>\n      </div>\n      <div\n        //className={styles.backIcon}\n        style={\n          isOpen\n            ? step2Open(Math.ceil(data.services_list.length / 3))\n            : step2NotOpen()\n        }\n      >\n        {data.services_list.map((item) => {\n          return (\n            <div style={{ width: 360 }} key={item.name}>\n              <Form.Item\n                label={<span style={{ width: 180 }}>{item.name}</span>}\n                name={`${data.name}=${item.name}`}\n                style={{ marginBottom: 0 }}\n              >\n                <InputNumber\n                  min={1}\n                  max={32}\n                  onChange={(e) => {\n                    if (\n                      e &&\n                      (e - item.deploy_mode.step ==\n                        numRef.current[`${data.name}=${item.name}`] ||\n                        e + item.deploy_mode.step ==\n                          numRef.current[`${data.name}=${item.name}`])\n                    ) {\n                      numRef.current[`${data.name}=${item.name}`] = e;\n                    } else {\n                      form.setFieldsValue({\n                        [`${data.name}=${item.name}`]:\n                          numRef.current[`${data.name}=${item.name}`],\n                      });\n                    }\n                  }}\n                  keyboard={false}\n                  disabled={item.deploy_mode.step == 0}\n                  step={item.deploy_mode.step}\n                />\n              </Form.Item>\n            </div>\n          );\n        })}\n      </div>\n      <div style={{ marginTop: 5, color: \"red\", whiteSpace: \"pre-line\" }}>\n        {data.error_msg}\n      </div>\n    </>\n  );\n};\n\nexport default BasicInfoItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/DeployInstanceRow.js",
    "content": "import { Select, Form, Checkbox } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport RenderArr from \"./RenderArr\";\nimport RenderNum from \"./RenderNum\";\n\nconst DeployInstanceRow = ({ data, form }) => {\n  const [check, setCheck] = useState(true);\n\n  useEffect(() => {\n    if (check) {\n      form.setFieldsValue({\n        [`${data.name}`]: JSON.stringify({\n          name: data.exist_instance[0]?.name,\n          id: data.exist_instance[0]?.id,\n          type: data.exist_instance[0]?.type,\n        }),\n      });\n    }\n  }, [check]);\n\n  return (\n    <>\n      <div style={{ flex: 1 }}>{data.name}</div>\n      <div style={{ flex: 1 }}>{data.version}</div>\n      {check ? (\n        <>\n          <div style={{ flex: 3 }}>\n            <Form.Item\n              label=\"选择实例\"\n              name={`${data.name}`}\n              style={{ marginBottom: 0, width: 180 }}\n            >\n              <Select>\n                {data.exist_instance.map((item) => (\n                  <Select.Option\n                    key={item.name}\n                    value={JSON.stringify({\n                      name: item.name,\n                      id: item.id,\n                      type: item.type,\n                    })}\n                  >\n                    {item.name}\n                  </Select.Option>\n                ))}\n              </Select>\n            </Form.Item>\n          </div>\n          <div\n            style={{\n              flex: 5,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          ></div>\n        </>\n      ) : Array.isArray(data.deploy_mode) ? (\n        <RenderArr data={data} form={form} />\n      ) : (\n        <RenderNum data={data} form={form} />\n      )}\n\n      <div\n        style={{ flex: 2, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        <div />\n        <div\n          style={{\n            fontSize: 13,\n            display: \"flex\",\n            alignItems: \"center\",\n            flexDirection: \"row-reverse\",\n            paddingRight: 70,\n          }}\n        >\n          <Checkbox\n            checked={check}\n            onChange={(e) => {\n              setCheck(e.target.checked);\n            }}\n          >\n            复用依赖\n          </Checkbox>\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default DeployInstanceRow;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/DeployNumRow.js",
    "content": "import RenderArr from \"./RenderArr\";\nimport RenderNum from \"./RenderNum\";\n\nconst DeployNumRow = ({ data, form }) => {\n\n  return (\n    <>\n      <div style={{ flex:  1 }}>{data.name}</div>\n      <div style={{ flex: 1 }}>{data.version}</div>\n      {Array.isArray(data.deploy_mode) ? (\n        <RenderArr data={data} form={form} />\n      ) : (\n        <RenderNum data={data} form={form} />\n      )}\n      <div\n        style={{ flex: 2, display: \"flex\", justifyContent: \"space-between\" }}\n      ></div>\n    </>\n  );\n};\n\nexport default DeployNumRow;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/DeployRow.js",
    "content": "import DeployNumRow from \"./DeployNumRow\";\nimport DeployInstanceRow from \"./DeployInstanceRow\";\n\nconst DeployRow = ({ data, form }) => {\n  return <>{data.is_use_exist ? <DeployInstanceRow data={data} form={form} /> : <DeployNumRow data={data} form={form} />}</>;\n};\n\nexport default DeployRow;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/JdkRow.js",
    "content": "import { Checkbox } from \"antd\";\nconst JdkRow = ({ data, isBaseEnv }) => {\n  return (\n    <>\n      <div style={{ flex: 1 }}>{data.name}</div>\n      <div style={{ flex: 1 }}>{data.version}</div>\n      <div style={{ flex: 3 }}></div>\n      <div\n        style={{ flex: 5, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        <div />\n      </div>\n      <div\n        style={{ flex: 2, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        <div />\n        <div\n          style={{\n            fontSize: 13,\n            display: \"flex\",\n            alignItems: \"center\",\n            flexDirection: \"row-reverse\",\n            paddingRight: 70,\n          }}\n        >\n          {!isBaseEnv && (\n            <Checkbox checked disabled>\n              安装依赖\n            </Checkbox>\n          )}\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default JdkRow;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/RenderArr.js",
    "content": "import { Select, Form, Input } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { randomNumber } from \"@/utils/utils\";\n\nconst RenderArr = ({ data, form }) => {\n  const [deployValue, setDeployValue] = useState(data.deploy_mode[0]?.key);\n\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${data.name}=num`]: deployValue,\n    });\n    if (deployValue == \"master-slave\" || deployValue == \"master-master\") {\n      form.setFieldsValue({\n        [`${data.name}=name`]: `${data.name}-cluster-${randomNumber(7)}`,\n      });\n    }\n  }, [deployValue]);\n  return (\n    <>\n      <div style={{ flex: 3 }}>\n        <Form.Item\n          label=\"部署数量\"\n          name={`${data.name}=num`}\n          style={{ marginBottom: 0, width: 100 }}\n        >\n          <Select\n            onChange={(e) => {\n              setDeployValue(e);\n            }}\n          >\n            {data.deploy_mode.map((item) => {\n              return (\n                <Select.Option key={item.key} value={item.key}>\n                  {item.name}\n                </Select.Option>\n              );\n            })}\n          </Select>\n        </Form.Item>\n      </div>\n      <div\n        style={{ flex: 3, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        {(deployValue == \"master-slave\" || deployValue == \"master-master\") && (\n          <Form.Item\n            label=\"集群名称\"\n            name={`${data.name}=name`}\n            style={{ marginBottom: 0, width: 240 }}\n            rules={[\n              {\n                required: true,\n                message: \"请输入集群名称\",\n              },\n            ]}\n          >\n            <Input placeholder=\"请输入集群名称\" />\n          </Form.Item>\n        )}\n      </div>\n      <div\n        style={{ flex: 2, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        {deployValue == \"master-master\" && (\n          <Form.Item\n            label=\"vip\"\n            name={`${data.name}=vip`}\n            style={{ marginBottom: 0, width: 180 }}\n            rules={[\n              {\n                required: true,\n                message: \"请输入vip\",\n              },\n            ]}\n          >\n            <Input placeholder=\"请输入vip\" />\n          </Form.Item>\n        )}\n      </div>\n    </>\n  );\n};\n\nexport default RenderArr;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/component/RenderNum.js",
    "content": "import { Form, InputNumber, Input } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { randomNumber } from \"@/utils/utils\";\n\nconst RenderNum = ({ data, form }) => {\n  const [num, setNum] = useState(data.deploy_mode.default);\n  const numRef = useRef(data.deploy_mode.default);\n\n  useEffect(() => {\n    if (num == 1) {\n      form.setFieldsValue({\n        [`${data.name}=num`]: `${data.deploy_mode.default}`,\n      });\n    }\n\n    if (num > 1) {\n      form.setFieldsValue({\n        [`${data.name}=num`]: `${data.deploy_mode.default}`,\n      });\n      form.setFieldsValue({\n        [`${data.name}=name`]: `${data.name}-cluster-${randomNumber(7)}`,\n      });\n    }\n  }, []);\n\n  return (\n    <>\n      <div style={{ flex: 3 }}>\n        <Form.Item\n          label=\"部署数量\"\n          name={`${data.name}=num`}\n          style={{ marginBottom: 0, width: 180 }}\n        >\n          <InputNumber\n            onChange={(e) => {\n              if (e) {\n                if (\n                  e - data.deploy_mode.step == numRef.current ||\n                  e + data.deploy_mode.step == numRef.current\n                ) {\n                  numRef.current = e;\n                  setNum(e);\n                } else {\n                  form.setFieldsValue({\n                    [`${data.name}=num`]: numRef.current,\n                  });\n                }\n              }\n\n              if (e > 1) {\n                form.setFieldsValue({\n                  [`${data.name}=name`]: `${data.name}-cluster-${randomNumber(\n                    7\n                  )}`,\n                });\n              }\n            }}\n            keyboard={false}\n            disabled={data.deploy_mode.step == 0}\n            step={data.deploy_mode.step}\n            min={1}\n            max={32}\n            style={{\n              width: 100,\n            }}\n          />\n        </Form.Item>\n      </div>\n      <div\n        style={{ flex: 5, display: \"flex\", justifyContent: \"space-between\" }}\n      >\n        {num > 1 && (\n          <Form.Item\n            label=\"集群名称\"\n            name={`${data.name}=name`}\n            style={{ marginBottom: 0, width: 240 }}\n            rules={[\n              {\n                required: true,\n                message: \"请输入集群名称\",\n              },\n            ]}\n          >\n            <Input placeholder=\"请输入集群名称\" />\n          </Form.Item>\n        )}\n      </div>\n    </>\n  );\n};\n\nexport default RenderNum;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/DependentinfoItem/index.js",
    "content": "import styles from \"../index.module.less\";\nimport JdkRow from \"./component/JdkRow\";\nimport DeployRow from \"./component/DeployRow\";\n\nconst DependentInfoItem = ({ data, form, isBaseEnv }) => {\n  //   useEffect(() => {\n  //     form.setFieldsValue({\n  //       [`${data.name}`]: `${data.name}-${randomNumber()}`,\n  //     });\n  //     data.services_list.map((item) => {\n  //       form.setFieldsValue({\n  //         [`${data.name}=${item.name}`]: `${item.deploy_mode.default}`,\n  //       });\n  //     });\n  //   }, []);\n\n  return (\n    <>\n      <div className={styles.dependentinfoItem}>\n        {data.is_base_env ? (\n          <JdkRow data={data} isBaseEnv={isBaseEnv} />\n        ) : (\n          <DeployRow data={data} form={form} />\n        )}\n      </div>\n      <div\n        style={{\n          marginTop: 5,\n          color: \"red\",\n          height: data.error_msg ? 20 : 0,\n          transition: \"all .2s ease-in\",\n        }}\n      >\n        {data.error_msg}\n      </div>\n    </>\n  );\n};\n\nexport default DependentInfoItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/InstallInfoItem/component/InstallDetail.js",
    "content": "import { DownOutlined } from \"@ant-design/icons\";\nimport { useEffect, useRef } from \"react\";\n\nconst stepOpen = {\n  marginTop: 10,\n  minHeight: 30,\n  height: 300,\n  transition: \"all .2s ease-in\",\n  //overflow: \"hidden\",\n  backgroundColor: \"#000\",\n  color: \"#fff\",\n  padding: 10,\n  overflowY: \"auto\",\n  whiteSpace: \"pre-line\"\n};\nconst stepNotOpen = {\n  height: 0,\n  minHeight: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n};\n\n// 状态渲染规则\n  const renderStatus = {\n    0: <span style={{ color: \"#f0c242\" }}>等待安装</span>,\n    1: <span style={{ color: \"rgba(0, 0, 0, 0.85)\" }}>正在安装</span>,\n    2: <span style={{ color: \"rgb(118,204,104)\" }}>安装成功</span>,\n    3: <span style={{ color: \"#da4e48\" }}>安装失败</span>,\n  };\n\nconst InstallDetail = ({ title, ip, status, openName, setOpenName,log }) => {\n\n  const containerRef = useRef(null)\n\n  useEffect(()=>{\n    containerRef.current.scrollTop =\n    containerRef.current.scrollHeight;\n  },[log])\n\n  return (\n    <div\n      style={{\n        padding: 10,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          justifyContent: \"space-between\",\n        }}\n      >\n        <div style={{ flex: 2 }}>{ip}</div>\n        <div style={{ flex: 1 }}>{renderStatus[status]}</div>\n        <div style={{ flex: 6, textAlign: \"right\", paddingRight: 50 }}>\n          <a\n            onClick={() => {\n              if (openName == `${title}=${ip}`) {\n                setOpenName(\"\");\n              } else {\n                setOpenName(`${title}=${ip}`);\n              }\n            }}\n          >\n            查看详细安装信息\n            <DownOutlined\n              style={{\n                transform: `rotate(${\n                  openName == `${title}=${ip}` ? 180 : 0\n                }deg)`,\n                position: \"relative\",\n                top: openName == `${title}=${ip}` ? -1 : 1,\n                left: 3,\n              }}\n            />\n          </a>\n        </div>\n      </div>\n      <div ref={containerRef} style={openName == `${title}=${ip}` ? stepOpen : stepNotOpen}>\n        {log || \"暂无数据\"}\n      </div>\n    </div>\n  );\n};\n\nexport default InstallDetail;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/InstallInfoItem/index.js",
    "content": "import InstallDetail from \"./component/InstallDetail\";\n\nconst InstallInfoItem = ({\n  id,\n  data,\n  title,\n  openName,\n  setOpenName,\n  log,\n  idx,\n}) => {\n  return (\n    <div\n      id={id}\n      style={{\n        //marginTop: 20,\n        backgroundColor: \"#fff\",\n        padding: 10,\n        //marginBottom: 15,\n        marginTop: idx !== 0 && 15,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          width: \"100%\",\n          position: \"relative\",\n          height: 40,\n          paddingTop: 10,\n        }}\n      >\n        <div\n          style={{\n            fontWeight: 500,\n            position: \"absolute\",\n            left: 30,\n            backgroundColor: \"#fff\",\n            paddingLeft: 20,\n            paddingRight: 20,\n          }}\n        >\n          {title}\n        </div>\n        <div style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }} />\n      </div>\n      <div\n        style={{\n          paddingLeft: 20,\n          marginTop: 10,\n          paddingBottom: 5,\n          // paddingTop: 20,\n        }}\n      >\n        {data.map((item) => {\n          return (\n            <InstallDetail\n              title={title}\n              openName={openName}\n              setOpenName={setOpenName}\n              key={`${title}=${item.ip}`}\n              status={item.status}\n              ip={item.ip}\n              log={log}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\nexport default InstallInfoItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/ServiceConfigItem/index.js",
    "content": "import { Collapse, Form, Input, Tooltip, Spin } from \"antd\";\nimport { CaretRightOutlined, InfoCircleOutlined } from \"@ant-design/icons\";\nimport { useEffect } from \"react\";\nimport { useDispatch, useSelector } from \"react-redux\";\n\nconst { Panel } = Collapse;\n\nconst ServiceConfigItem = ({ form, loading, ip, idx }) => {\n  let data = useSelector((state) => state.installation.step3Data)[ip][idx];\n\n  // console.log(data)\n\n  let portData = data.ports || [];\n  let installArgsData = data.install_args || [];\n  const renderData = [...installArgsData, ...portData];\n  // let instanceName = data.instance_name;\n\n  const errInfo = useSelector((state) => state.installation.step3ErrorData);\n\n  useEffect(() => {\n    // 设置默认值\n    // form.setFieldsValue({\n    //   [`${data.name}=instance_name`]: data.instance_name,\n    // });\n    renderData.map((i) => {\n      form.setFieldsValue({\n        [`${data.name}=${i.key}`]: i.default,\n      });\n    });\n  }, [data]);\n\n  useEffect(() => {\n    if (errInfo[ip] && errInfo[ip][data.name]) {\n      for (const key in errInfo[ip][data.name]) {\n        if (errInfo[ip][data.name][key]) {\n          form.setFields([\n            {\n              name: `${data.name}=${key}`,\n              errors: [errInfo[ip][data.name][key]],\n            },\n          ]);\n        }\n      }\n    }\n    return () => {\n      form.resetFields();\n    };\n  }, [errInfo[ip]]);\n\n  return (\n    <div style={{ padding: 10 }}>\n      <Spin spinning={loading}>\n        <Collapse\n          bordered={false}\n          defaultActiveKey={[\"1\"]}\n          expandIcon={({ isActive }) => (\n            <CaretRightOutlined rotate={isActive ? 90 : 0} />\n          )}\n        >\n          <Panel\n            header={data.name}\n            key=\"1\"\n            className={\"panelItem\"}\n            style={{ backgroundColor: \"#f6f6f6\" }}\n          >\n            {renderData.map((item) => {\n              return (\n                <Form.Item\n                  key={item.key}\n                  label={item?.name}\n                  name={`${data.name}=${item?.key}`}\n                  style={{ marginTop: 10, width: 600 }}\n                  rules={[\n                    {\n                      required: item.key !== \"vip\",\n                      message: `请输入${item.name}`,\n                    },\n                  ]}\n                >\n                  <Input\n                    disabled={!item.editable}\n                    // onChange={(e) => {\n                    //   //console.log(e.target.value);\n                    //   dispatch(\n                    //     getStep3ServiceChangeAction(\n                    //       ip,\n                    //       data.name,\n                    //       item.key,\n                    //       e.target.value\n                    //     )\n                    //   );\n                    // }}\n                    addonBefore={\n                      item.dir_key ? (\n                        <span style={{ color: \"#b1b1b1\" }}>/ 数据分区</span>\n                      ) : null\n                    }\n                    //style={{ width: 420 }}\n                    placeholder={`请输入${item.name}`}\n                    suffix={\n                      item.dir_key ? (\n                        <Tooltip title=\"数据分区：主机所设置的数据分区\">\n                          <InfoCircleOutlined\n                            style={{ color: \"rgba(0,0,0,.45)\" }}\n                          />\n                        </Tooltip>\n                      ) : null\n                    }\n                  />\n                </Form.Item>\n              );\n            })}\n          </Panel>\n        </Collapse>\n      </Spin>\n    </div>\n  );\n};\n\nexport default ServiceConfigItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/ServiceDistributionItem/component/HasInstallService.js",
    "content": "import { Tooltip, Spin } from \"antd\";\n\nconst HasInstallService = ({ children, ip, installService }) => {\n  \n  return (\n    <Tooltip\n      mouseEnterDelay={0.3}\n      placement=\"right\"\n      color=\"#fff\"\n      overlayStyle={{\n        minWidth:300\n      }}\n      title={\n        <div\n          style={{\n            color: \"rgba(0, 0, 0, 0.85)\",\n            padding: 5,\n            height: 330\n          }}\n        >\n          <div\n            style={{\n              borderBottom: \"1px solid #d9d9d9\",\n              fontSize: 14,\n              paddingBottom: 5,\n            }}\n          >\n            已选安装服务\n          </div>\n          <Spin spinning={!installService}>\n            <div\n              style={{\n                overflowY: \"auto\",\n                height: 300,\n              }}\n            >\n            {installService[ip]?.map((item) => {\n              return (\n                <div\n                  style={{ paddingTop: 5, fontSize: 14 }}\n                  key={item.service_instance_name}\n                >\n                  {item.service_instance_name}\n                </div>\n              );\n            })}\n            </div>\n          </Spin>\n        </div>\n      }\n    >\n      <a>{children}</a>\n    </Tooltip>\n  );\n};\n\nexport default HasInstallService;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/ServiceDistributionItem/index.js",
    "content": "import { Cascader, Form, Tag, Tooltip } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport {\n  getDataSourceChangeAction,\n  getIpListChangeAction,\n} from \"../../store/actionsCreators\";\nimport * as R from \"ramda\";\nimport HasInstallService from \"./component/HasInstallService\";\n\n// 当前组件一次value改变，伴随着三个动作\n// 1. 组件state的value 的改变\n// 2. redux中dataSource 的改变\n//   （1）ipList 的改变，影响页面中`已分配主机数量`\n//    (2) dataSource的改变 （对应组件中的option,决定组件展开框的渲染）\n// 3. form中数据的变更\n\n// 当前组件触发value改变的情况有\n// 1. 点击展开栏的check或者item容器或者文字\n// 2. 点击展示框的tag\n// 3. 展示框关于with的处理\n\n// update 2021.12.30\n// 为解决关于with项关联元素异常展示问题\n// 在onclick 事件中添加两个动作\n// 在执行onclick正常逻辑之前判断当前元素内是否全部子项都是不可选中状态，如果是直接return,不再执行正常逻辑（或者直接判断当前元素类名是否有disable相关）\n// 在执行onclick正常逻辑或者执行完关联with之后，查询其他元素内是否存在子元素全是不可选中状态的元素，将这种元素设置成不可选中状态\n\nconst ServiceDistributionItem = ({ form, data, info, installService }) => {\n  const [options, setOption] = useState([]);\n\n  const [value, setValue] = useState([]);\n\n  const allDataPool = useSelector((state) => state.installation.dataSource);\n\n  const ipList = useSelector((state) => state.installation.ipList);\n\n  const errorList = useSelector((state) => state.installation.errorList);\n\n  const errorMsg = errorList.filter((i) => i.ip == info.ip)[0]?.error_msg;\n\n  const reduxDispatch = useDispatch();\n\n  // 对value进行处理(因为当把某一级菜单下对应的全部二级菜单选中后，value会合并成一个，只会存展示一级菜单)\n  const dealColumnsData = (value) => {\n    let result = [];\n    value.map((item) => {\n      //console.log(item);\n      switch (item.length) {\n        case 1:\n          // value长度为1时，代表选中全部的一级菜单\n          let checkedItem = options\n            .filter((i) => {\n              return i.label == item[0];\n            })[0]\n            .children.map((i) => {\n              return i.label;\n            });\n          result = result.concat([...checkedItem]);\n          break;\n        case 2:\n          // console.log(item);\n          result.push(item[1]);\n          break;\n        default:\n          return [];\n          break;\n      }\n    });\n    return result;\n  };\n\n  const handleDataSourceData = (key, num) => {\n    if (Array.isArray(key)) {\n      key.forEach((item) => {\n        if (allDataPool[item].num >= 0) {\n          let n =\n            allDataPool[item].num + num <= 0 ? 0 : allDataPool[item].num + num;\n          allDataPool[item].num = n;\n        }\n      });\n      return allDataPool;\n    } else {\n      if (allDataPool[key].num >= 0) {\n        let n =\n          allDataPool[key].num + num <= 0 ? 0 : allDataPool[key].num + num;\n        allDataPool[key].num = n;\n      }\n      return allDataPool;\n    }\n  };\n\n  // 当选中这项时同时拿到所有with这项的数据\n  const getWithItem = (label) => {\n    // 全部with当前项的数据\n    const result = [];\n    for (const key in allDataPool) {\n      // console.log(allDataPool[key]);\n      if (allDataPool[key].with && allDataPool[key].with == label) {\n        result.push(key);\n      }\n    }\n    return result;\n  };\n\n  // 封装对选中被其他项with关联的数据的处理（选中）\n  const handleWithData = (label, withItem, isCheck) => {\n    if (withItem.length > 0) {\n      withItem.map((w) => {\n        let formData = R.clone(form.getFieldsValue());\n        // 确认with这项是属于哪个product\n        let optionsCopy = R.clone(options);\n        let productItem = optionsCopy.filter((item) => {\n          let f = item.children.filter((i) => {\n            return i.label == w;\n          });\n          return f.length !== 0;\n        });\n        if (isCheck) {\n          formData[info.ip].push([productItem[0].label, w]);\n          // form.setFieldsValue({\n          //   [`${info.ip}`]: formData[info.ip],\n          // });\n\n          setValue((value) => {\n            let strArr = [\n              ...formData[info.ip],\n              ...value,\n              [productItem[0].label, w],\n            ].map((item) => JSON.stringify(item));\n            let delStrArr = Array.from(new Set([...strArr]));\n            return [...delStrArr.map((item) => JSON.parse(item))];\n          });\n          // 数据池子中数量变更\n          let data = handleDataSourceData(w, -1);\n          reduxDispatch(getDataSourceChangeAction(data));\n        } else {\n          let withI = [productItem[0]?.label, w];\n          let result = formData[info.ip].filter((item) => {\n            return item[0] != withI[0] || item[1] != withI[1];\n          });\n\n          // form.setFieldsValue({\n          //   [`${info.ip}`]: result,\n          // });\n\n          let arr = [];\n          result.map((v) => {\n            Object.keys(allDataPool).map((i) => {\n              if (v[1] == allDataPool[i].with) {\n                arr.push([v[0], i]);\n              }\n            });\n          });\n          setValue([...result, ...arr]);\n          let dataC = handleDataSourceData(w, 1);\n          reduxDispatch(getDataSourceChangeAction(dataC));\n        }\n      });\n    } else {\n      handleValueAndForm(label, isCheck);\n    }\n  };\n\n  const handleValueAndForm = (label, isCheck) => {\n    // value的格式[[1,1],[1,2]]\n    // setVal1ue()\n    // form的格式\n    // 当前ip下和value一致\n    // 判断一下当前label是一级还是二级\n    if (allDataPool[label]) {\n      // 二级\n      if (isCheck === true) {\n        let result = [...value];\n        options.map((item) => {\n          item.children.map((i) => {\n            if (i.value == label) {\n              result.push([item.label, i.label]);\n            }\n          });\n        });\n        setValue(result);\n        // form.setFieldsValue({\n        //   [`${info.ip}`]: result,\n        // });\n      } else if (isCheck === false) {\n        // 取消二级选中\n        let result = [...value];\n        let deleteItem = [];\n        options.map((item) => {\n          item.children.map((i) => {\n            if (i.value == label) {\n              deleteItem = [item.label, i.label];\n            }\n          });\n        });\n        result = result.filter((item) => {\n          return item[0] != deleteItem[0] || item[1] != deleteItem[1];\n        });\n        setValue(result);\n        // form.setFieldsValue({\n        //   [`${info.ip}`]: result,\n        // });\n      }\n    } else {\n      // 一级\n      if (isCheck === true) {\n        // console.log(\"点击到了一级的选中\");\n        // console.log(options, isCheck);\n        // 拿到一级下的全部子项\n        let checkedArr = options\n          .filter((i) => {\n            return i.label == label;\n          })[0]\n          .children.map((i) => {\n            return i.label;\n          });\n        // 去除其中带有with的\n        checkedArr = checkedArr.filter((i) => {\n          if (allDataPool[i] && allDataPool[i].with) {\n            return false;\n          }\n          return true;\n        });\n\n        let result = checkedArr.map((item) => {\n          return [label, item];\n        });\n\n        // 查找是否子项中有被其他项with的\n        let withArr = [];\n        options.map((item) => {\n          item.children.map((i) => {\n            // if(!allDataPool[i.label]){\n            //   console.log(i.label)\n            // }\n            let withI = allDataPool[i.label].with;\n            let idx = checkedArr.indexOf(withI);\n            if (withI && idx !== -1) {\n              result.push([item.label, i.label]);\n              withArr.push(i.label);\n            }\n          });\n        });\n        let data = handleDataSourceData(withArr, -1);\n        reduxDispatch(getDataSourceChangeAction(data));\n        // console.log(value, result, label)\n        let dealValue = value.filter((i) => {\n          if (i[0] !== label) {\n            return true;\n          }\n          //console.log(i)\n          return false;\n        });\n\n        setValue((v) => {\n          let arr = Array.from(\n            new Set([...dealValue, ...result].map((i) => JSON.stringify(i)))\n          );\n          return arr.map((m) => JSON.parse(m));\n          // Array.from(new Set([...dealValue, ...result]))\n        });\n        // form.setFieldsValue({\n        //   [`${info.ip}`]: Array.from(new Set([...dealValue,...result])),\n        // });\n      } else if (isCheck === false) {\n        // console.log(\"点击到了一级的取消选中\");\n        let withArr = [];\n        let v = value.filter((i) => {\n          // 当这项包含with直接留在这里不动，with项的改变在与被with项\n          if (allDataPool[i[1]] && allDataPool[i[1]].with) {\n            // 判断这项的with是否包涵在value\n            let arr = value.filter((a) => {\n              return a[1] == allDataPool[i[1]].with;\n            });\n            withArr.push(i[1]);\n            return arr.length == 0;\n          }\n          return i[0] !== label;\n        });\n        let data = handleDataSourceData(withArr, 1);\n        reduxDispatch(getDataSourceChangeAction(data));\n        setValue(v);\n        // form.setFieldsValue({\n        //   [`${info.ip}`]: v,\n        // });\n      }\n    }\n  };\n\n  // 获得子项全部是disable的项的lable\n  // 并且当前已选中项中不存在\n  const getDisableItem = (options) => {\n    let disableItem = [];\n    options.forEach((i) => {\n      if (i.children.filter((a) => !a.disabled) == 0) {\n        disableItem.push(i.value);\n      }\n    });\n    console.log(disableItem, value, \"要禁用的项\");\n    return disableItem;\n  };\n\n  // 根据lable,将其dom设置为不可用状态\n  const setDisableItem = (disableItem) => {\n    if (disableItem.length > 0) {\n      let arr = [...document.getElementsByClassName(\"ant-cascader-menus\")];\n      arr.forEach((a) => {\n        let dom = a?.childNodes;\n        if (dom && dom[0].childNodes) {\n          [...dom[0].childNodes].map((v) => {\n            if (v && v.childNodes[1]) {\n              if (disableItem.indexOf(v.childNodes[1].innerHTML) !== -1) {\n                // v.childNodes[0].removeEventListener(\"click\")\n                // console.log(v.childNodes[0].getAttribute())\n                v.childNodes[0].onclick = (e) => {\n                  e.stopPropagation();\n                };\n                v.childNodes[0].classList.add(\"ant-cascader-checkbox-disabled\");\n                v.style.cssText =\n                  \"color: rgba(0,0,0,0.25);font-weight:400 !important;cursor:not-allowed\";\n              }\n            }\n          });\n        }\n      });\n    }\n  };\n\n  // remakeDom重置dom\n  const remakeDom = () => {\n    let arr = [...document.getElementsByClassName(\"ant-cascader-menus\")];\n    arr.forEach((a) => {\n      let dom = a?.childNodes;\n      if (dom && dom[0].childNodes) {\n        [...dom[0].childNodes].map((v) => {\n          if (v && v.childNodes[1]) {\n            // v.childNodes[0].removeEventListener(\"click\")\n            // console.log(v.childNodes[0].getAttribute())\n            v.childNodes[0].onclick = null;\n            v.childNodes[0].classList.remove(\"ant-cascader-checkbox-disabled\");\n            v.style.cssText = \"color: rgba(0, 0, 0, 0.65);font-weight:400;\";\n          }\n        });\n      }\n    });\n  };\n\n  useEffect(() => {\n    let disabledItem = getDisableItem(options);\n\n    let isDelete = Object.keys(allDataPool).filter((k) => {\n      // 当前组件实例已经选中了，那即使在数据池中该数据num已经为0,也不应影响组件对该数据的展示,在这里过滤掉\n      // 并且没有with项\n      return (\n        allDataPool[k].num == 0 &&\n        !dealColumnsData(value).includes(k) &&\n        !allDataPool[k].with\n      );\n      // && disabledItem.indexOf(k) !== -1;\n    });\n    let c = [...data];\n    c = c.map((i) => {\n      // 去除在child对应数据池num为0的项\n      let child = [...i.child];\n      // if(disabledItem.indexOf(i.name) == -1) {\n      child = i.child.filter((e) => {\n        // let withI = data\n        //   .filter((f) => disabledItem.indexOf(f.name) !== -1)\n        //   .map((c) => {\n        //     console.log(c)\n        //     return c.child\n        //   })\n        //   .flat();\n        return isDelete.indexOf(e) == -1;\n        // || withI.indexOf(e) !== -1;\n      });\n      // }\n      return {\n        ...i,\n        child: child,\n      };\n    });\n    setOption(() => {\n      let result = [];\n      c.map((item) => {\n        let i = {\n          label: item.name,\n          value: item.name,\n        };\n        // 当前项如果存在with就不可选中\n        i.children = item.child.map((n) => {\n          let disabled = false;\n          if (allDataPool[n] && allDataPool[n].with) {\n            disabled = true;\n          }\n          return {\n            label: n,\n            value: n,\n            disabled: disabled,\n          };\n        });\n\n        if (\n          item.child.length > 0 &&\n          dealColumnsData(value)\n          // ||  disabledItem.indexOf(i.label) !== -1\n        ) {\n          // 当选中后，全部数据中有子项都是disable的\n          result.push(i);\n        }\n      });\n      return result;\n    });\n  }, [allDataPool]);\n\n  // 当value值发生改变时触发事件，用来判断当前组件所对应主机是否已经选择服务\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${info.ip}`]: value,\n    });\n\n    let idx = ipList.indexOf(info.ip);\n\n    // console.log(ipList, value, idx);\n    if (value.length == 0) {\n      if (idx !== -1) {\n        //console.log([...ipList], info.ip);\n        let newIpList = [...ipList];\n        newIpList.splice(idx, 1);\n        reduxDispatch(getIpListChangeAction(newIpList));\n      }\n    } else {\n      if (idx == -1) {\n        let newIpList = [...ipList];\n        newIpList.push(info.ip);\n        reduxDispatch(getIpListChangeAction(newIpList));\n      }\n    }\n  }, [value]);\n\n  return (\n    <div style={{ marginBottom: 40, width: \"45%\" }}>\n      <Form form={form} name=\"service\">\n        <div style={{ display: \"flex\", justifyContent: \"center\" }}>\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              position: \"relative\",\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                top: errorMsg ? 40 : 25,\n                color: \"red\",\n                height: errorMsg ? 20 : 0,\n                transition: \"all .1s ease-in\",\n              }}\n            >\n              {errorMsg}\n            </div>\n            <Form.Item\n              label={info.ip}\n              name={info.ip}\n              style={{ marginBottom: 0 }}\n            >\n              <Cascader\n                placeholder=\"请选择\"\n                style={{ width: 240, marginTop: 0, paddingLeft: 10 }}\n                options={options}\n                expandTrigger={\"hover\"}\n                value={value}\n                allowClear={false}\n                tagRender={(e) => {\n                  const { value, onClose, label } = e;\n                  return (\n                    <Tag closable={false}>\n                      {allDataPool[label] ? label : `${label}-集合`}\n                    </Tag>\n                  );\n                }}\n                // tagRender={(e) => {\n                //   // console.log(e.onClose);\n                //   // console.log(e);\n                //   const { value, onClose, label } = e;\n                //   console.log(value);\n                //   if (value.includes(\"__RC_CASCADER_SPLIT__\")) {\n                //     return (\n                //       <Tag\n                //         // closable\n                //         // onClose={(event) => {\n                //         //   onClose(event);\n                //         // }}\n                //       >\n                //         {label}\n                //       </Tag>\n                //     );\n                //   } else {\n                //     // 选中了一级菜单\n                //     console.log(value);\n                //     let checkedItem = options\n                //       .filter((i) => {\n                //         return i.label == value;\n                //       })[0]\n                //       .children.map((i) => {\n                //         return i.label;\n                //       });\n                //     console.log(checkedItem);\n                //     return (\n                //       <>\n                //         {checkedItem.map((item) => {\n                //           return (\n                //             <Tag\n                //               key={item}\n                //               // closable\n                //               // onClose={(event) => {\n                //               //   onClose(event);\n                //               // }}\n                //             >\n                //               {item}\n                //             </Tag>\n                //           );\n                //         })}\n                //       </>\n                //     );\n                //   }\n                // }}\n                onClick={(e) => {\n                  // 会出现options的子项已经为空，但是一级还在的情况，在这里过滤\n                  // setOption((op)=>{\n                  //   return op.filter(i=>{\n                  //     return i.children.length\n                  //   })\n                  // })\n\n                  // 使用onclick的原因是因为onchange在每次点击后会触发两次，\n                  // 每次onchange执行都是一次单独逻辑,不能在每次onchange时，故不能准确对应整个数据池的num增减情况\n                  // 点击总共会出现三种情况\n                  // 1. 点击了checkbox\n                  // 2. 点击了背后的容器\n                  // 3. 点击了文字\n                  // console.log(e.target);\n                  // 因为antd是复用展开框，所以在设置禁用前先重制，然后再根据情况去确定是否设置禁用\n                  remakeDom();\n                  // 设置禁用项\n                  let disabledItem = getDisableItem(options);\n                  setDisableItem(disabledItem);\n                  // 1. 点击了checkbox(这个比较特殊，也可能是点击了一级菜单触发)\n                  if (e.target.className == \"ant-cascader-checkbox-inner\") {\n                    // return\n                    // 选中（可能是一级也可能是二级,点击了checkbox）\n                    // 判断是点击的一级还是二级\n                    let label =\n                      e.target.parentNode.parentNode.childNodes[1].innerHTML;\n                    if (allDataPool[label]) {\n                      // 点的是二级\n                      if (allDataPool[label] && allDataPool[label].with) {\n                        return;\n                      }\n                      // with的判断不光是选中这项，还有判断当前选中这项有没有被其他的项with\n                      let withItem = getWithItem(label);\n                      // 有其他通过with关联选中项的数据都要进行选中处理\n                      handleWithData(label, withItem, true);\n\n                      handleValueAndForm(label);\n\n                      let data = handleDataSourceData(label, -1);\n\n                      // console.log(data);\n                      reduxDispatch(getDataSourceChangeAction(data));\n                    } else {\n                      // 点的是一级\n                      // 在这个条件中还有一个情况是半选中状态\n                      //console.log(\"点击一级\");\n                      if (disabledItem.indexOf(label) !== -1) {\n                        setDisableItem(disabledItem);\n                        return;\n                      } else {\n                        // 在点击前\n                        let checkedArr = options\n                          .filter((i) => {\n                            return i.label == label;\n                          })[0]\n                          .children.map((i) => {\n                            return i.label;\n                          });\n                        // 对checkedArr再做一次过滤，过滤掉含有with的项\n                        checkedArr = checkedArr.filter((i) => {\n                          if (allDataPool[i] && allDataPool[i].with) {\n                            return false;\n                          }\n                          return true;\n                        });\n                        // 还要过滤掉已经选中状态的\n                        // value适配数据只要第二级数据\n                        const hasCheck = value.map((item) => {\n                          return item[1];\n                        });\n\n                        checkedArr = checkedArr.filter((i) => {\n                          if (hasCheck.includes(i)) {\n                            return false;\n                          }\n                          return true;\n                        });\n\n                        handleValueAndForm(label, true);\n                        let data = handleDataSourceData(checkedArr, -1);\n                        reduxDispatch(getDataSourceChangeAction(data));\n                      }\n                    }\n                    // console.log(\n                    //   \"选中\",\n                    //   e.target.parentNode.parentNode.childNodes[1].innerHTML\n                    // );\n                  } else if (\n                    e.target.className ==\n                    \"ant-cascader-checkbox ant-cascader-checkbox-checked\"\n                  ) {\n                    // reduxDispatch(getDataSourceChangeAction(data));\n                    // 取消选中（可能是一级也可能是二级,点击了checkbox\n                    // 判断是点击的一级还是二级\n                    let label = e.target.parentNode.childNodes[1].innerHTML;\n                    if (allDataPool[label]) {\n                      if (allDataPool[label] && allDataPool[label].with) {\n                        return;\n                      }\n                      // 点的是二级\n                      // console.log(\"取消选中了一级\")\n                      let withItem = getWithItem(label);\n                      // 有其他通过with关联选中项的数据都要进行选中处理\n                      handleWithData(label, withItem, false);\n\n                      let data = handleDataSourceData(label, 1);\n                      reduxDispatch(getDataSourceChangeAction(data));\n                    } else {\n                      // 点的是一级\n                      let checkedArr = options\n                        .filter((i) => {\n                          return i.label == label;\n                        })[0]\n                        .children.map((i) => {\n                          return i.label;\n                        });\n                      // 对checkedArr再做一次过滤，过滤掉含有with的项\n                      checkedArr = checkedArr.filter((i) => {\n                        if (allDataPool[i] && allDataPool[i].with) {\n                          return false;\n                        }\n                        return true;\n                      });\n                      handleValueAndForm(label, false);\n                      let data = handleDataSourceData(checkedArr, 1);\n                      reduxDispatch(getDataSourceChangeAction(data));\n                    }\n                    // console.log(\n                    //   \"取消选中\",\n                    //   e.target.parentNode.childNodes[1].innerHTML\n                    // );\n                  }\n\n                  // 2. 点击了背后的容器\n                  if (\n                    e.target.className == \"ant-cascader-menu-item\" ||\n                    e.target.className ==\n                      \"ant-cascader-menu-item ant-cascader-menu-item-active\"\n                  ) {\n                    if (e.target.getAttribute(\"aria-checked\") === \"true\") {\n                      if (\n                        allDataPool[e.target.lastChild.innerHTML] &&\n                        allDataPool[e.target.lastChild.innerHTML].with\n                      ) {\n                        return;\n                      }\n                      // with的判断不光是选中这项，还有判断当前选中这项有没有被其他的项with\n                      let withItem = getWithItem(e.target.lastChild.innerHTML);\n                      // 有其他通过with关联选中项的数据都要进行选中处理\n                      handleWithData(\n                        e.target.lastChild.innerHTML,\n                        withItem,\n                        false\n                      );\n\n                      let data = handleDataSourceData(\n                        e.target.lastChild.innerHTML,\n                        1\n                      );\n                      reduxDispatch(getDataSourceChangeAction(data));\n                      // console.log(\n                      //   \"点击了背后容器，取消\",\n                      //   e.target.lastChild.innerHTML\n                      // );\n                    } else {\n                      if (\n                        allDataPool[e.target.lastChild.innerHTML] &&\n                        allDataPool[e.target.lastChild.innerHTML].with\n                      ) {\n                        return;\n                      }\n                      // with的判断不光是选中这项，还有判断当前选中这项有没有被其他的项with\n                      let withItem = getWithItem(e.target.lastChild.innerHTML);\n                      // 有其他通过with关联选中项的数据都要进行选中处理\n                      handleWithData(\n                        e.target.lastChild.innerHTML,\n                        withItem,\n                        true\n                      );\n\n                      let data = handleDataSourceData(\n                        e.target.lastChild.innerHTML,\n                        -1\n                      );\n                      reduxDispatch(getDataSourceChangeAction(data));\n                      // console.log(\n                      //   \"点击了背后容器，选中\",\n                      //   e.target.lastChild.innerHTML\n                      // );\n                    }\n                  }\n\n                  // 3. 点击了文字\n                  if (e.target.className == \"ant-cascader-menu-item-content\") {\n                    // 这个文字要判断是一级还是二级\n                    // 判断是选中还是取消选中\n                    if (\n                      e.target.parentNode.getAttribute(\"aria-checked\") ===\n                      \"true\"\n                    ) {\n                      if (\n                        allDataPool[e.target.innerHTML] &&\n                        allDataPool[e.target.innerHTML].with\n                      ) {\n                        return;\n                      }\n\n                      // 取消选中\n                      if (e.target.parentNode.childNodes.length === 2) {\n                        // with的判断不光是选中这项，还有判断当前选中这项有没有被其他的项with\n                        let withItem = getWithItem(e.target.innerHTML);\n                        // 有其他通过with关联选中项的数据都要进行选中处理\n                        handleWithData(e.target.innerHTML, withItem, false);\n                        let data = handleDataSourceData(e.target.innerHTML, 1);\n                        reduxDispatch(getDataSourceChangeAction(data));\n                        // console.log(\"点击的是文字，取消\", e.target.innerHTML);\n                      }\n                    } else {\n                      if (\n                        allDataPool[e.target.innerHTML] &&\n                        allDataPool[e.target.innerHTML].with\n                      ) {\n                        return;\n                      }\n\n                      // 选中\n                      if (e.target.parentNode.childNodes.length === 2) {\n                        let withItem = getWithItem(e.target.innerHTML);\n                        // 有其他通过with关联选中项的数据都要进行选中处理\n                        handleWithData(e.target.innerHTML, withItem, true);\n                        let data = handleDataSourceData(e.target.innerHTML, -1);\n                        reduxDispatch(getDataSourceChangeAction(data));\n                        //console.log(\"点击的是文字，选中\", e.target.innerHTML);\n                      }\n                    }\n                  }\n                }}\n                multiple=\"multiple\"\n                maxTagCount=\"responsive\"\n              />\n            </Form.Item>\n          </div>\n          <div style={{ paddingLeft: 15 }}>\n            <div\n              style={{ fontSize: 13 }}\n              // onClick={() => {\n              //   form.setFieldsValue({\n              //     [`${info.ip}`]: value,\n              //   });\n              // }}\n            >\n              选择服务数:{\" \"}\n              {value.length == 0 ? (\n                <span>0个</span>\n              ) : (\n                <Tooltip\n                  mouseEnterDelay={0.3}\n                  placement=\"right\"\n                  color=\"#fff\"\n                  overlayStyle={{\n                    minWidth: 300,\n                  }}\n                  title={\n                    <div\n                      style={{\n                        color: \"rgba(0, 0, 0, 0.85)\",\n                        padding: 5,\n                        height: 330,\n                      }}\n                    >\n                      <div\n                        style={{\n                          borderBottom: \"1px solid #d9d9d9\",\n                          fontSize: 14,\n                          paddingBottom: 5,\n                        }}\n                      >\n                        已选服务\n                      </div>\n                      <div\n                        style={{\n                          overflowY: \"auto\",\n                          height: 300,\n                        }}\n                      >\n                        {value.map((item) => {\n                          return (\n                            <div\n                              style={{ paddingTop: 5, fontSize: 14 }}\n                              key={`${item[0]}/${item[1]}`}\n                            >\n                              {`${item[0]} / ${item[1]}`}\n                            </div>\n                          );\n                        })}\n                      </div>\n                    </div>\n                  }\n                >\n                  <a>{value.length}个</a>\n                </Tooltip>\n              )}\n            </div>\n            <div style={{ fontSize: 12 }}>\n              已安装服务数:{\" \"}\n              {info.num == 0 ? (\n                <span>0个</span>\n              ) : (\n                <HasInstallService ip={info.ip} installService={installService}>\n                  {info.num}个\n                </HasInstallService>\n              )}\n            </div>\n          </div>\n        </div>\n      </Form>\n    </div>\n  );\n};\n\nexport default ServiceDistributionItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/component/index.module.less",
    "content": ".basicInfoItem {\n    display: flex;\n    margin-top: 25px;\n    align-items: center;\n}\n\n.dependentinfoItem {\n    display: flex;\n    margin-top: 35px;\n    align-items: center;\n}\n\n:global{\n    .ant-form-vertical .ant-form-item-label, .ant-col-24.ant-form-item-label, .ant-col-xl-24.ant-form-item-label {\n        padding:0\n    }\n}"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/index.js",
    "content": "import { useHistory, useLocation } from \"react-router-dom\";\nimport { useDispatch } from \"react-redux\";\nimport { Steps } from \"antd\";\nimport { useState } from \"react\";\nimport styles from \"./index.module.less\";\nimport Step1 from \"./steps/Step1\";\nimport Step2 from \"./steps/Step2\";\nimport Step3 from \"./steps/Step3\";\nimport Step4 from \"./steps/Step4\";\n\nimport { LeftOutlined } from \"@ant-design/icons\";\n// 安装页面\nconst Installation = () => {\n\n  const dispatch = useDispatch();\n  const history = useHistory();\n  const location = useLocation()\n\n  const defaultStep = location.state?.step\n  //console.log(location, defaultStep)\n\n  const [stepNum, setStepNum] = useState(defaultStep || 0);\n\n  return (\n    <div>\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            className={styles.backIcon}\n            onClick={() => {\n              // dispatch(getTabKeyChangeAction(\"service\"));\n              history?.goBack();\n              // history?.push({\n              //   pathname: `/application_management/app_store`,\n              // });\n            }}\n          />\n          安装\n        </div>\n        <div style={{ width: 600, position: \"relative\", left: -60 }}>\n          <Steps size=\"small\" current={stepNum}>\n            <Steps.Step title=\"基本信息\" />\n            <Steps.Step title=\"服务分布\" />\n            <Steps.Step title=\"修改配置\" />\n            <Steps.Step title=\"开始安装\" />\n          </Steps>\n        </div>\n        <div />\n      </div>\n      {stepNum == 0 && <Step1 setStepNum={setStepNum} />}\n      {stepNum == 1 && <Step2 setStepNum={setStepNum} />}\n      {stepNum == 2 && <Step3 setStepNum={setStepNum} />}\n      {stepNum == 3 && <Step4 />}\n    </div>\n  );\n};\n\nexport default Installation;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/index.module.less",
    "content": "  .backIcon:hover {\n    color: rgb(46, 124, 238);\n  }\n\n  :global {\n    .ant-anchor-ink::before {\n      background-color: #fff!important;\n    }\n  }"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/steps/Step1.js",
    "content": "import BasicInfoItem from \"../component/BasicInfoItem/index\";\nimport DependentInfoItem from \"../component/DependentinfoItem/index\";\nimport { Form, Button, message } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { fetchPost } from \"@/utils/request\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { getStep1ChangeAction } from \"../store/actionsCreators\";\nimport * as R from \"ramda\";\n\nconst Step1 = ({ setStepNum }) => {\n  const data = useSelector((state) => state.installation.step1Data);\n  const reduxDispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  // 定义下一步是否可操作，只在第一次加载时判断\n  const [isContinue, setIsContinue] = useState(false);\n\n  // 基本信息的form实例\n  const [basicForm] = Form.useForm();\n\n  // 依赖信息的form实例\n  const [dependentForm] = Form.useForm();\n\n  const dataProcessing = () => {\n    let formBasicData = basicForm.getFieldsValue();\n    let formDependentData = dependentForm.getFieldsValue();\n    //setStepNum(1);\n    let basic = JSON.parse(JSON.stringify(data.basic));\n    let dependent = JSON.parse(JSON.stringify(data.dependence));\n\n    basic = basic.map((item) => {\n      let services_list = item.services_list;\n      let cluster_name = \"\";\n\n      Object.keys(formBasicData).map((k) => {\n        let kArr = k.split(\"=\");\n        if (kArr.length == 1) {\n          // 长度为1 说明当前key就是实例名称\n          // console.log(k, formBasicData[k]);\n          if (k == item.name) {\n            cluster_name = formBasicData[k];\n          }\n        } else if (kArr.length == 2) {\n          services_list = services_list.map((i) => {\n            // console.log(i);\n            if (i.name == kArr[1]) {\n              return {\n                name: i.name,\n                version: i.version,\n                deploy_mode: Number(formBasicData[k]),\n              };\n            } else {\n              return {\n                ...i,\n              };\n            }\n          });\n        }\n      });\n      return {\n        name: item.name,\n        version: item.version,\n        cluster_name: cluster_name,\n        services_list: services_list,\n      };\n    });\n\n    dependent = dependent.map((item) => {\n      if (item.is_base_env) {\n        // jdk\n        return {\n          ...item,\n        };\n      } else {\n        //if(item.is_use_exist){\n        // deployInstanceRow\n        let exist_instance = item.exist_instance;\n        let deploy_mode = item.deploy_mode;\n        let cluster_name = \"\";\n        let vip = \"\";\n        let is_use_exist = false;\n\n        Object.keys(formDependentData).map((k) => {\n          let kArr = k.split(\"=\");\n          if (kArr[0] == item.name) {\n            if (kArr.length == 1) {\n              // 选中了勾选了说明当前为选择实例信息\n              exist_instance = JSON.parse(formDependentData[k]);\n              is_use_exist = true;\n            } else {\n              // 取消了选中，当前为部署数量信息\n              // 判断部署数量是否是数字\n              if (kArr[1] == \"num\") {\n                // deploy_mode = formDependentData[k];\n                cluster_name = formDependentData[`${item.name}=name`];\n                vip = formDependentData[`${item.name}=vip`];\n                if (isNaN(Number(formDependentData[k]))) {\n                  // 非数字代表单实例，主从，主主\n                  deploy_mode = formDependentData[k];\n                } else {\n                  // 数字代表部署数量\n                  deploy_mode = Number(formDependentData[k]);\n                  // 数量\n                  //cluster_name\n                }\n              }\n            }\n          }\n        });\n\n        return {\n          ...item,\n          exist_instance: exist_instance,\n          deploy_mode: deploy_mode,\n          cluster_name: cluster_name,\n          vip: vip,\n          is_use_exist: is_use_exist,\n        };\n      }\n\n      return {\n        ...item,\n      };\n    });\n\n    return {\n      basic: basic,\n      dependence: dependent,\n    };\n  };\n\n  // const reduxDataProcessing = () => {\n  //   let formBasicData = basicForm.getFieldsValue();\n  //   let formDependentData = dependentForm.getFieldsValue();\n  //   //setStepNum(1);\n  //   let basic = JSON.parse(JSON.stringify(data.basic));\n  //   let dependent = JSON.parse(JSON.stringify(data.dependence));\n\n  //   basic = basic.map((item) => {\n  //     let services_list = item.services_list;\n  //     let cluster_name = \"\";\n\n  //     Object.keys(formBasicData).map((k) => {\n  //       let kArr = k.split(\"=\");\n  //       if (kArr.length == 1) {\n  //         // 长度为1 说明当前key就是实例名称\n  //         // console.log(k, formBasicData[k]);\n  //         if (k == item.name) {\n  //           cluster_name = formBasicData[k];\n  //         }\n  //       } else if (kArr.length == 2) {\n  //         services_list = services_list.map((i) => {\n  //           // console.log(i);\n  //           if (i.name == kArr[1]) {\n  //             return {\n  //               name: i.name,\n  //               version: i.version,\n  //               deploy_mode: {\n  //                 ...i.deploy_mode,\n  //                 default: Number(formBasicData[k]),\n  //               }\n  //             };\n  //           } else {\n  //             return {\n  //               ...i,\n  //             };\n  //           }\n  //         });\n  //       }\n  //     });\n  //     return {\n  //       name: item.name,\n  //       version: item.version,\n  //       cluster_name: cluster_name,\n  //       services_list: services_list,\n  //     };\n  //   });\n\n  //   console.log(data.dependence)\n  //   dependent = dependent.map((item) => {\n  //     if (item.is_base_env) {\n  //       // jdk\n  //       return {\n  //         ...item,\n  //       };\n  //     } else {\n  //       //if(item.is_use_exist){\n  //       // deployInstanceRow\n  //       let exist_instance = item.exist_instance;\n  //       let deploy_mode = item.deploy_mode;\n  //       let cluster_name = \"\";\n  //       let vip = \"\";\n  //       let is_use_exist = false;\n\n  //       Object.keys(formDependentData).map((k) => {\n  //         let kArr = k.split(\"=\");\n  //         if (kArr[0] == item.name) {\n  //           if (kArr.length == 1) {\n  //             // 选中了勾选了说明当前为选择实例信息\n  //             //console.log(formDependentData[k])\n  //             exist_instance = JSON.parse(formDependentData[k]);\n  //             is_use_exist = true;\n  //           } else {\n  //             // 取消了选中，当前为部署数量信息\n  //             // 判断部署数量是否是数字\n  //             if (kArr[1] == \"num\") {\n  //               // deploy_mode = formDependentData[k];\n  //               cluster_name = formDependentData[`${item.name}=name`];\n  //               vip = formDependentData[`${item.name}=vip`];\n  //               if (isNaN(Number(formDependentData[k]))) {\n  //                 // 非数字代表单实例，主从，主主\n  //                 console.log(\"非数字\", formDependentData[k],item)\n  //                 deploy_mode = formDependentData[k];\n  //               } else {\n  //                 console.log(\"数字\", formDependentData[k])\n  //                 // 数字代表部署数量\n  //                 deploy_mode = Number(formDependentData[k]);\n  //                 // 数量\n  //                 //cluster_name\n  //               }\n  //             }\n  //           }\n  //         }\n  //       });\n  //       console.log(exist_instance, deploy_mode, cluster_name, vip, is_use_exist,)\n  //       console.log(item.exist_instance)\n  //       let processedExistInstance = []\n  //       if(typeof exist_instance == \"object\"){\n  //         processedExistInstance = item.exist_instance.map(m=>{\n  //           if(m.id == exist_instance){\n  //             return {\n  //               ...exist_instance,\n  //               isCheck: true\n  //             }\n  //           }else{\n  //             return m\n  //           }\n  //         })\n  //       }\n\n  //       return {\n  //         ...item,\n  //         exist_instance: processedExistInstance,\n  //         deploy_mode: {\n  //           ...item.deploy_mode,\n  //           default:deploy_mode && typeof deploy_mode == \"number\"? deploy_mode:deploy_mode.default,\n  //         },\n  //         cluster_name: cluster_name,\n  //         vip: vip,\n  //         is_use_exist: is_use_exist,\n  //       };\n  //     }\n\n  //     return {\n  //       ...item,\n  //     };\n  //   });\n\n  //   return {\n  //     basic: basic,\n  //     dependence: dependent,\n  //     is_continue: true\n  //   };\n  // };\n\n  // checkInstallInfo 基本信息提交操作，决定是否跳转服务分布以及数据校验回显\n  const checkInstallInfo = (queryData) => {\n    // console.log(uniqueKey,data)\n    // return\n    setLoading(true);\n    fetchPost(apiRequest.appStore.checkInstallInfo, {\n      body: {\n        unique_key: uniqueKey,\n        data: queryData,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            if (res.data.data.is_continue) {\n              // 校验通过，跳转，请求服务分布数据并跳转\n              setStepNum(1);\n            } else {\n              message.warn(\"校验未通过\");\n              // 当校验未通过时不跳转，并回显数据\n              // 使用redux中已有的数据做基础，在其上添加失败的校验信息\n              let { basic, dependence } = R.clone(data);\n\n              let { basic: resBasic, dependence: resDependence } = R.clone(\n                res.data.data\n              );\n              reduxDispatch(\n                getStep1ChangeAction({\n                  basic: basic.map((item) => {\n                    let d = R.clone(item);\n                    resBasic.map((i) => {\n                      if (i.error_msg) {\n                        if (d.name == i.name) {\n                          d.error_msg = i.error_msg;\n                        }\n                      }\n                    });\n                    return d;\n                  }),\n                  dependence: dependence.map((item) => {\n                    let d = R.clone(item);\n                    resDependence.map((i) => {\n                      if (i.error_msg) {\n                        if (d.name == i.name) {\n                          d.error_msg = i.error_msg;\n                        }\n                      }\n                    });\n                    return d;\n                  }),\n                })\n              );\n            }\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    setIsContinue(data.is_continue);\n  }, []);\n  console.log(\"渲染数据\", data);\n  // 组件渲染特殊处理\n  if (data.basic.length == 0) {\n    return (\n      <>\n        <div\n          style={{\n            marginTop: 20,\n            backgroundColor: \"#fff\",\n            padding: 10,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              width: \"100%\",\n              position: \"relative\",\n              height: 30,\n            }}\n          >\n            <div\n              style={{\n                fontWeight: 500,\n                position: \"absolute\",\n                left: 30,\n                backgroundColor: \"#fff\",\n                paddingLeft: 20,\n                paddingRight: 20,\n              }}\n            >\n              基本信息\n            </div>\n            <div\n              style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n            />\n          </div>\n          <div\n            style={{\n              paddingLeft: 20,\n              marginTop: 10,\n              paddingBottom: 40,\n              // paddingTop: 20,\n            }}\n          >\n            <Form form={dependentForm} name=\"dependent\" layout=\"vertical\">\n              <DependentInfoItem\n                key={data.dependence[0]?.name}\n                form={dependentForm}\n                isBaseEnv={true}\n                data={\n                  data.dependence.length > 0\n                    ? data.dependence[0]\n                    : {\n                        deploy_mode: {},\n                      }\n                }\n              />\n            </Form>\n          </div>\n        </div>\n        <div\n          style={{\n            marginTop: 20,\n            backgroundColor: \"#fff\",\n            padding: 10,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              width: \"100%\",\n              position: \"relative\",\n              height: 30,\n            }}\n          >\n            <div\n              style={{\n                fontWeight: 500,\n                position: \"absolute\",\n                left: 30,\n                backgroundColor: \"#fff\",\n                paddingLeft: 20,\n                paddingRight: 20,\n              }}\n            >\n              依赖信息\n            </div>\n            <div\n              style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n            />\n          </div>\n          <div\n            style={{\n              paddingLeft: 20,\n              marginTop: 10,\n              paddingBottom: 40,\n              // paddingTop: 20,\n            }}\n          >\n            {data.dependence.filter((item, idx) => idx !== 0).length == 0 ? (\n              \"无\"\n            ) : (\n              <Form form={dependentForm} name=\"dependent\" layout=\"vertical\">\n                {data.dependence\n                  .filter((item, idx) => idx !== 0)\n                  .map((item) => {\n                    return (\n                      <DependentInfoItem\n                        key={item.name}\n                        form={dependentForm}\n                        data={item}\n                      />\n                    );\n                  })}\n              </Form>\n            )}\n          </div>\n        </div>\n        <div\n          style={{\n            position: \"fixed\",\n            backgroundColor: \"#fff\",\n            width: \"calc(100% - 230px)\",\n            bottom: 10,\n            padding: \"10px 0px\",\n            display: \"flex\",\n            justifyContent: \"space-between\",\n            paddingRight: 30,\n            boxShadow: \"0px 0px 10px #999999\",\n          }}\n        >\n          <div />\n          <div>\n            <Button\n              type=\"primary\"\n              loading={loading}\n              disabled={!isContinue}\n              onClick={() => {\n                Promise.all([\n                  dependentForm.validateFields(),\n                  // basicForm.validateFields(),\n                ])\n                  .then((result) => {\n                    checkInstallInfo(dataProcessing());\n                  })\n                  .catch((e) => {\n                    message.warn(\"校验未通过\");\n                  });\n              }}\n            >\n              下一步\n            </Button>\n          </div>\n        </div>\n      </>\n    );\n  } else {\n    return (\n      <>\n        <div\n          style={{\n            marginTop: 20,\n            backgroundColor: \"#fff\",\n            padding: 10,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              width: \"100%\",\n              position: \"relative\",\n              height: 30,\n            }}\n          >\n            <div\n              style={{\n                fontWeight: 500,\n                position: \"absolute\",\n                left: 30,\n                backgroundColor: \"#fff\",\n                paddingLeft: 20,\n                paddingRight: 20,\n              }}\n            >\n              基本信息\n            </div>\n            <div\n              style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n            />\n          </div>\n          <div\n            style={{\n              paddingLeft: 20,\n              marginTop: 10,\n              paddingBottom: 40,\n              // paddingTop: 20,\n            }}\n          >\n            <Form form={basicForm} name=\"basic\">\n              {data.basic.map((item) => {\n                return (\n                  <BasicInfoItem key={item.name} form={basicForm} data={item} />\n                );\n              })}\n            </Form>\n          </div>\n        </div>\n        <div\n          style={{\n            marginTop: 20,\n            backgroundColor: \"#fff\",\n            padding: 10,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              width: \"100%\",\n              position: \"relative\",\n              height: 30,\n            }}\n          >\n            <div\n              style={{\n                fontWeight: 500,\n                position: \"absolute\",\n                left: 30,\n                backgroundColor: \"#fff\",\n                paddingLeft: 20,\n                paddingRight: 20,\n              }}\n            >\n              依赖信息\n            </div>\n            <div\n              style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }}\n            />\n          </div>\n          <div\n            style={{\n              paddingLeft: 20,\n              marginTop: 10,\n              paddingBottom: 40,\n              // paddingTop: 20,\n            }}\n          >\n            <Form form={dependentForm} name=\"dependent\" layout=\"vertical\">\n              {data.dependence.map((item) => {\n                return (\n                  <DependentInfoItem\n                    key={item.name}\n                    form={dependentForm}\n                    data={item}\n                  />\n                );\n              })}\n            </Form>\n          </div>\n        </div>\n        <div\n          style={{\n            position: \"fixed\",\n            backgroundColor: \"#fff\",\n            width: \"calc(100% - 230px)\",\n            bottom: 10,\n            padding: \"10px 0px\",\n            display: \"flex\",\n            justifyContent: \"space-between\",\n            paddingRight: 30,\n            boxShadow: \"0px 0px 10px #999999\",\n          }}\n        >\n          <div />\n          <div>\n            <Button\n              type=\"primary\"\n              loading={loading}\n              disabled={!isContinue}\n              onClick={() => {\n                // console.log(data, dependentForm.getFieldValue(), basicForm.getFieldValue())\n                // console.log(dataProcessing())\n                // console.log(reduxDataProcessing())\n                // reduxDispatch(getStep1ChangeAction(reduxDataProcessing()))\n                // return\n                Promise.all([\n                  dependentForm.validateFields(),\n                  basicForm.validateFields(),\n                ])\n                  .then((result) => {\n                    checkInstallInfo(dataProcessing());\n                  })\n                  .catch((e) => {\n                    message.warn(\"校验未通过\");\n                  });\n              }}\n            >\n              下一步\n            </Button>\n          </div>\n        </div>\n      </>\n    );\n  }\n};\n\nexport default Step1;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/steps/Step2.js",
    "content": "import { Button, Form, Spin, message } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport ServiceDistributionItem from \"../component/ServiceDistributionItem/index.js\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport {\n  getDataSourceChangeAction,\n  getStep2ErrorLstChangeAction,\n  getIpListChangeAction,\n} from \"../store/actionsCreators\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\n\nconst Step2 = ({ setStepNum }) => {\n  const reduxDispatch = useDispatch();\n\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  // 基本信息的form实例\n  const [form] = Form.useForm();\n\n  const [loading, setLoading] = useState(false);\n\n  const [installService, setInstallService] = useState({});\n\n  const allDataPool = useSelector((state) => state.installation.dataSource);\n  const ipList = useSelector((state) => state.installation.ipList);\n\n  const [data, setData] = useState({\n    host: [],\n    product: [],\n  });\n\n  // 未分配服务个数\n  const unassignedServices = Object.keys(allDataPool).reduce((prev, cur) => {\n    return prev + allDataPool[cur].num;\n  }, 0);\n\n  const queryCreateServiceDistribution = () => {\n    // checkInstallInfo 基本信息提交操作，决定是否跳转服务分布以及数据校验回显\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createServiceDistribution, {\n      body: {\n        unique_key: uniqueKey,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            reduxDispatch(getDataSourceChangeAction(res.data.data.all));\n            setData((data) => {\n              return {\n                ...data,\n                host: res.data.data.host,\n                product: res.data.data.product,\n              };\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 已安装服务列表查询\n  const queryInstallServiceData = () => {\n    fetchGet(apiRequest.appStore.queryListServiceByIp)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setInstallService(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 提交前对数据进行处理\n  const dataProcessing = () => {\n    let data = form.getFieldValue();\n    console.log(data);\n    let result = {};\n    for (const key in data) {\n      if (data[key].length > 0) {\n        result[key] = data[key].map((item) => {\n          return item[1];\n        });\n      }\n    }\n\n    console.log(result);\n    return result;\n  };\n\n  // 最后的提交操作\n  // checkServiceDistribution 服务分布提交操作，决定是否跳转修改配置以及数据校验回显\n  const checkServiceDistribution = (queryData) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.checkServiceDistribution, {\n      body: {\n        unique_key: uniqueKey,\n        data: queryData,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            if (res.data.is_continue) {\n              // 校验通过，跳转，请求服务分布数据并跳转\n              setStepNum(2);\n            } else {\n              message.warn(\"校验未通过\");\n              reduxDispatch(getStep2ErrorLstChangeAction(res.data.error_lst));\n            }\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryCreateServiceDistribution();\n    queryInstallServiceData();\n    return () => {\n      // 销毁时去除error信息\n      reduxDispatch(getStep2ErrorLstChangeAction([]));\n      reduxDispatch(getDataSourceChangeAction([]));\n      reduxDispatch(getIpListChangeAction([]));\n    };\n  }, []);\n\n  return (\n    <>\n      <div\n        style={{\n          marginTop: 20,\n          backgroundColor: \"#fff\",\n          padding: 10,\n          paddingBottom: 30,\n        }}\n      >\n        <Spin spinning={loading}>\n          <div style={{ display: \"flex\" }}>\n            <div\n              style={{\n                paddingLeft: 20,\n                marginTop: 10,\n                paddingBottom: 30,\n              }}\n            >\n              主机总数: {data.host.length}台\n            </div>\n            <div\n              style={{\n                paddingLeft: 40,\n                marginTop: 10,\n                paddingBottom: 30,\n              }}\n            >\n              未分配服务: {unassignedServices}个\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              flexWrap: \"wrap\",\n              paddingLeft: 30,\n              paddingRight: 30,\n            }}\n          >\n            {data.host.map((item) => {\n              return (\n                <ServiceDistributionItem\n                  key={item.ip}\n                  form={form}\n                  info={item}\n                  data={data.product}\n                  installService={installService}\n                />\n              );\n            })}\n          </div>\n        </Spin>\n      </div>\n\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 10,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20 }}>\n          已分配主机数量: {ipList?.length}台\n        </div>\n        <div>\n          <Button\n            type=\"primary\"\n            onClick={() => {\n              setStepNum(0);\n            }}\n          >\n            上一步\n          </Button>\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            disabled={unassignedServices !== 0}\n            loading={loading}\n            onClick={() => {\n              //setStepNum(2);\n              checkServiceDistribution(dataProcessing());\n            }}\n          >\n            下一步\n          </Button>\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default Step2;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/steps/Step3.js",
    "content": "import { Button, Form, Checkbox, Input, Spin, message } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { SearchOutlined, ExclamationOutlined } from \"@ant-design/icons\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport ServiceConfigItem from \"../component/ServiceConfigItem\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport {\n  getStep3IpDataChangeAction,\n  getStep3ErrorInfoChangeAction,\n  getStep3ServiceChangeAction,\n} from \"../store/actionsCreators\";\nimport * as R from \"ramda\";\n\nconst Step3 = ({ setStepNum }) => {\n  const dispatch = useDispatch();\n  // unique_key: \"21e041a9-c9a5-4734-9673-7ed932625d21\"\n  // 服务的loading\n  const [loading, setLoading] = useState(false);\n\n  const [loadingIp, setLoadingIp] = useState(false);\n\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  // redux中取数据\n  const reduxData = useSelector((state) => state.installation.step3Data);\n\n  const errInfo = useSelector((state) => state.installation.step3ErrorData);\n  //console.log(errInfo[\"10.0.12.250\"]);\n\n  const [checked, setChecked] = useState(false);\n\n  const [serviceConfigform] = Form.useForm();\n\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n\n  // 指定本次安装服务运行用户\n  const [runUser, setRunUser] = useState(\"\");\n\n  // 筛选后的ip列表\n  const [currentIpList, setCurrentIpList] = useState([]);\n\n  // ip列表中的选中项\n  const [checkIp, setCheckIp] = useState(\"\");\n\n  // ip 筛选value\n  const [searchIp, setSearchIp] = useState(\"\");\n\n  // ip列表的数据源\n  const [ipList, setIpList] = useState([]);\n\n  function queryIpList() {\n    setLoadingIp(true);\n    fetchGet(apiRequest.appStore.getInstallHostRange, {\n      params: {\n        unique_key: uniqueKey,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpList(res.data.data);\n          setCurrentIpList(res.data.data);\n          setCheckIp(res.data.data[0]);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoadingIp(false);\n      });\n  }\n\n  const queryInstallArgsByIp = (ip) => {\n    // 如果redux中已经存了当前ip的数据就不再请求直接使用redux中的\n    if (reduxData[ip]) {\n      return;\n    }\n    setLoading(true);\n    fetchGet(apiRequest.appStore.getInstallArgsByIp, {\n      params: {\n        unique_key: uniqueKey,\n        ip: ip,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          //setDataSource(res.data.data);\n          dispatch(\n            getStep3IpDataChangeAction({\n              [ip]: res.data.data,\n            })\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 提交前对数据进行处理\n  const dataProcessing = (data) => {\n    let obj = {};\n    ipList.map((ip) => {\n      obj[ip] = [];\n    });\n    //console.log(data);\n    return {\n      ...obj,\n      ...data,\n    };\n  };\n\n  // 有校验失败的信息生成errlist\n  const getErrorInfo = (data) => {\n    let result = {};\n    for (const ip in data) {\n      data[ip].map((service) => {\n        [...service.install_args, ...service.ports].map((serv) => {\n          if (serv.check_flag == false) {\n            if (!result[ip]) {\n              result[ip] = {};\n            }\n            if (result[ip][service.name]) {\n              result[ip][service.name][serv.key] = serv.error_msg;\n            } else {\n              result[ip][service.name] = {};\n              result[ip][service.name][serv.key] = serv.error_msg;\n            }\n          }\n        });\n      });\n    }\n    // console.log(result, \"result\");\n    return result;\n  };\n\n  // 非空校验\n  const nonNullCheck = (queryData) => {\n    let result = {};\n    //console.log(queryData);\n    let data = R.clone(queryData);\n    // 这一步校验是为了解决非当前页面form的必填校验不到的问题\n    // 当key == vip 跳过校验（非必填）\n    // 首先去除当前页面ip的相关数据校验\n    data[checkIp] = [];\n    // console.log(data)\n    for (const ip in data) {\n      data[ip].map((service) => {\n        [...service.install_args, ...service.ports].map((serv) => {\n          // console.log(serv);\n          // 特殊处理，去除vip非空校验\n          if (!serv.default && serv.key !== \"vip\") {\n            if (!result[ip]) {\n              result[ip] = {};\n            }\n            if (result[ip][service.name]) {\n              result[ip][service.name][serv.key] = `请输入${serv.name}`;\n            } else {\n              result[ip][service.name] = {};\n              result[ip][service.name][serv.key] = `请输入${serv.name}`;\n            }\n          }\n        });\n      });\n    }\n\n    return result;\n  };\n\n  const getCurrentData = (queryData) => {\n    let formData = serviceConfigform.getFieldValue();\n    for (const keyStr in formData) {\n      let keyArr = keyStr.split(\"=\");\n      queryData[checkIp] = queryData[checkIp].map((item) => {\n        if (item.name == keyArr[0]) {\n          let obj = { ...item };\n          obj.install_args = obj.install_args.map((i) => {\n            if (i.key == keyArr[1]) {\n              i.default = formData[keyStr];\n            }\n            return i;\n          });\n          obj.ports = obj.ports.map((i) => {\n            if (i.key == keyArr[1]) {\n              i.default = formData[keyStr];\n            }\n            return i;\n          });\n          return obj;\n        }\n        return item;\n      });\n    }\n    return queryData;\n  };\n\n  // 开始安装操作命令下发\n  const createInstallPlan = (queryData) => {\n    // 这个queryData数据是用redux中来的，当前页面的数据是在页面销毁时才同步redux的\n    console.log(getCurrentData(queryData));\n    //return\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createInstallPlan, {\n      body: {\n        unique_key: uniqueKey,\n        run_user: runUser,\n        data: getCurrentData(queryData),\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            if (res.data.is_continue) {\n              // 校验通过，跳转，请求服务分布数据并跳转\n              setStepNum(3);\n            } else {\n              res.data && res.data.error_msg\n                ? message.warn(res.data.error_msg)\n                : message.warn(\"校验未通过，请检查\");\n              dispatch(\n                getStep3ErrorInfoChangeAction(getErrorInfo(res.data.data))\n              );\n\n              //reduxDispatch(getStep2ErrorLstChangeAction(res.data.error_lst));\n            }\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    if (checkIp) {\n      queryInstallArgsByIp(checkIp);\n    }\n  }, [checkIp]);\n\n  useEffect(() => {\n    // 请求ip数据\n    // currentIpList\n    queryIpList();\n    return () => {\n      dispatch(getStep3ErrorInfoChangeAction({}));\n      dispatch(getStep3IpDataChangeAction());\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          marginTop: 20,\n          backgroundColor: \"#fff\",\n          padding: 10,\n          paddingLeft: 30,\n          display: \"flex\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ width: 220 }}>\n          <Checkbox\n            checked={checked}\n            onChange={(e) => {\n              if (!e.target.checked) {\n                setRunUser(\"\");\n              }\n              setChecked(e.target.checked);\n            }}\n          >\n            指定本次安装服务运行用户\n          </Checkbox>\n        </div>\n\n        <Input\n          disabled={!checked}\n          placeholder=\"请输入本次安装服务运行的非root用户\"\n          style={{ width: 300 }}\n          value={runUser}\n          onChange={(e) => {\n            setRunUser(e.target.value);\n          }}\n        />\n      </div>\n\n      <div\n        style={{\n          marginTop: 15,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n        }}\n      >\n        <div style={{ width: 240 }}>\n          <div\n            style={{\n              padding: \"15px 5px 10px 5px\",\n            }}\n          >\n            <Input\n              allowClear\n              onBlur={() => {\n                if (searchIp) {\n                  let result = ipList.filter((i) => {\n                    return i.includes(searchIp);\n                  });\n                  setCurrentIpList(result);\n                  result.length > 0 && setCheckIp(result[0]);\n                } else {\n                  setCurrentIpList(ipList);\n                  setCheckIp(ipList[0]);\n                }\n              }}\n              value={searchIp}\n              onChange={(e) => {\n                setSearchIp(e.target.value);\n                if (!e.target.value) {\n                  setCurrentIpList(ipList);\n                  setCheckIp(ipList[0]);\n                }\n              }}\n              onPressEnter={() => {\n                if (searchIp) {\n                  let result = ipList.filter((i) => {\n                    return i.includes(searchIp);\n                  });\n                  setCurrentIpList(result);\n                  result.length > 0 && setCheckIp(result[0]);\n                } else {\n                  setCurrentIpList(ipList);\n                  setCheckIp(ipList[0]);\n                }\n              }}\n              placeholder=\"搜索IP地址\"\n              suffix={\n                !searchIp && <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n              }\n            />\n          </div>\n          <div\n            style={{\n              overflowY: \"auto\",\n            }}\n          >\n            <div\n              style={{\n                cursor: \"pointer\",\n                borderRight: \"0px\",\n                height: viewHeight - 390,\n              }}\n            >\n              <Spin spinning={loadingIp}>\n                {currentIpList.length == 0 ? (\n                  <div style={{ height: 100 }}></div>\n                ) : (\n                  <>\n                    {currentIpList?.map((item) => {\n                      return (\n                        <div\n                          key={item}\n                          style={{\n                            display: \"flex\",\n                            justifyContent: \"space-between\",\n                          }}\n                        >\n                          <div\n                            style={{\n                              padding: 5,\n                              paddingLeft: 30,\n                              cursor: \"pointer\",\n                              flex: 1,\n                              color: errInfo[item]\n                                ? \"red\"\n                                : checkIp == item\n                                ? \"#4986f7\"\n                                : \"\",\n                              backgroundColor: checkIp == item ? \"#edf8fe\" : \"\",\n                            }}\n                            onClick={() => {\n                              // 点击切换ip时再存redux，避免不必要的性能消耗\n                              let formData = serviceConfigform.getFieldValue();\n                              for (const key in formData) {\n                                let keyArr = key.split(\"=\");\n                                reduxData[checkIp].map((item) => {\n                                  if (keyArr[0] == item.name) {\n                                    dispatch(\n                                      getStep3ServiceChangeAction(\n                                        checkIp,\n                                        item.name,\n                                        keyArr[1],\n                                        formData[key]\n                                      )\n                                    );\n                                  }\n                                });\n                              }\n                              setCheckIp(item);\n                            }}\n                          >\n                            {item}\n                          </div>\n                          {errInfo[item] && (\n                            <div\n                              style={{\n                                width: 30,\n                                padding: 5,\n                                paddingLeft: 0,\n                                backgroundColor:\n                                  checkIp == item ? \"#edf8fe\" : \"\",\n                                color: \"red\",\n                              }}\n                            >\n                              <ExclamationOutlined />\n                            </div>\n                          )}\n                        </div>\n                      );\n                    })}\n                  </>\n                )}\n              </Spin>\n            </div>\n          </div>\n        </div>\n        <div\n          style={{\n            flex: 1,\n            borderLeft: \"1px solid #d9d9d9\",\n            height: viewHeight - 335,\n            overflowY: \"auto\",\n          }}\n        >\n          {!reduxData[checkIp] || reduxData[checkIp].length == 0 ? (\n            <Spin spinning={true} tip=\"数据加载中...\">\n              <div style={{ width: \"100%\", height: 300 }}></div>\n            </Spin>\n          ) : (\n            <Form\n              form={serviceConfigform}\n              name=\"config\"\n              labelCol={{ span: 8 }}\n              wrapperCol={{ span: 40 }}\n            >\n              {reduxData[checkIp]?.map((item, idx) => {\n                return (\n                  <ServiceConfigItem\n                    ip={checkIp}\n                    key={item.name}\n                    data={item}\n                    form={serviceConfigform}\n                    loading={loading}\n                    idx={idx}\n                  />\n                );\n              })}\n            </Form>\n          )}\n        </div>\n      </div>\n\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 10,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20 }}>分布主机数量: {ipList?.length}台</div>\n        <div>\n          <Button\n            type=\"primary\"\n            onClick={() => {\n              setStepNum(1);\n            }}\n          >\n            上一步\n          </Button>\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            loading={loading}\n            onClick={() => {\n              serviceConfigform.validateFields().then(\n                (e) => {\n                  let errData = nonNullCheck(reduxData);\n                  if (Object.keys(errData).length > 0) {\n                    message.warn(\"校验未通过，请检查\");\n                    dispatch(getStep3ErrorInfoChangeAction(errData));\n                  } else {\n                    createInstallPlan(dataProcessing(reduxData));\n                  }\n                },\n                (e) => {\n                  message.warn(\"校验未通过，请检查\");\n                }\n              );\n            }}\n          >\n            开始安装\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default Step3;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/steps/Step4.js",
    "content": "import { Button, Anchor, Spin, Progress } from \"antd\";\nimport { useSelector } from \"react-redux\";\nimport InstallInfoItem from \"../component/InstallInfoItem\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { LoadingOutlined } from \"@ant-design/icons\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { fetchGet } from \"@/utils/request\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { fetchPost } from \"src/utils/request\";\n\nconst { Link } = Anchor;\n// 状态渲染规则\nconst renderStatus = {\n  0: \"等待安装\",\n  1: \"正在安装\",\n  2: \"安装成功\",\n  3: \"安装失败\",\n  4: \"正在注册\",\n};\n\nconst Step4 = () => {\n  const history = useHistory();\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  const [openName, setOpenName] = useState(\"\");\n  // 在轮训时使用ref存值\n  const openNameRef = useRef(null);\n  const location = useLocation();\n  const defaultUniqueKey = location.state?.uniqueKey;\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  const [loading, setLoading] = useState(true);\n\n  const [retryLoading, setRetryLoading] = useState(false);\n\n  // 主机 agent 状态标识\n  const [hostAgentFlag, setHostAgentFlag] = useState(false);\n  // 轮训控制器\n  const hostAgentTimer = useRef(null);\n  // start 按钮加载\n  const [startLoading, setStartLoading] = useState(false);\n\n  const [data, setData] = useState({\n    detail: {},\n    status: 0,\n  });\n\n  const [log, setLog] = useState(\"\");\n\n  // 轮训的timer控制器\n  const timer = useRef(null);\n\n  const queryInstallProcess = () => {\n    !timer.current && setLoading(true);\n    fetchGet(apiRequest.appStore.queryInstallProcess, {\n      params: {\n        unique_key: defaultUniqueKey || uniqueKey,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setData(res.data);\n          if (\n            res.data.status == 0 ||\n            res.data.status == 1 ||\n            res.data.status == 4\n          ) {\n            // 状态为未安装或者安装中\n            if (openNameRef.current) {\n              let arr = openNameRef.current.split(\"=\");\n              queryDetailInfo(defaultUniqueKey || uniqueKey, arr[1], arr[0]);\n            }\n\n            timer.current = setTimeout(() => {\n              queryInstallProcess();\n            }, 5000);\n          } else {\n            if (openNameRef.current) {\n              let arr = openNameRef.current.split(\"=\");\n              queryDetailInfo(defaultUniqueKey || uniqueKey, arr[1], arr[0]);\n            }\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 请求详细信息\n  const queryDetailInfo = (uniqueKey, ip, app_name) => {\n    fetchGet(apiRequest.appStore.showSingleServiceInstallLog, {\n      params: {\n        unique_key: uniqueKey,\n        app_name: app_name,\n        ip: ip,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setLog(res.data.log);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        //setLoading(false);\n      });\n  };\n\n  const retryInstall = () => {\n    setRetryLoading(true);\n    setOpenName(\"\");\n    openNameRef.current = \"\";\n    fetchPost(apiRequest.appStore.retryInstall, {\n      body: {\n        unique_key: defaultUniqueKey || uniqueKey,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            queryInstallProcess();\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setRetryLoading(false);\n      });\n  };\n\n  // 查询主机 agent 状态\n  const queryHostAgent = () => {\n    // 构造 ip 集合\n    let ipSet = new Set();\n    Object.keys(data.detail).map((key, idx) => {\n      data.detail[key].forEach((e) => {\n        if (e.ip !== \"postAction\") {\n          ipSet.add(e.ip);\n        }\n      });\n    });\n    fetchPost(apiRequest.machineManagement.hostsAgentStatus, {\n      body: {\n        ip_list: Array.from(ipSet),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code === 0 && res.data) {\n            // 调用安装\n            retryInstall();\n            // 清除定时器\n            clearInterval(hostAgentTimer.current);\n            setStartLoading(false);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 开始安装\n  const startInstall = () => {\n    setStartLoading(true);\n    hostAgentTimer.current = setInterval(() => {\n      queryHostAgent();\n    }, 1000);\n  };\n\n  useEffect(() => {\n    queryInstallProcess();\n    return () => {\n      // 页面销毁时清除延时器\n      clearTimeout(timer.current);\n      clearInterval(hostAgentTimer.current);\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          display: \"flex\",\n          paddingTop: 20,\n        }}\n      >\n        <Spin spinning={loading}>\n          <div\n            id=\"Step4Wrapper\"\n            style={{\n              flex: 1,\n              //backgroundColor: \"#fff\",\n              height: viewHeight - 270,\n              overflowY: \"auto\",\n            }}\n          >\n            <div style={{}}>\n              {Object.keys(data.detail).map((key, idx) => {\n                return (\n                  <InstallInfoItem\n                    openName={openName}\n                    setOpenName={(n) => {\n                      setLog(\"\");\n                      if (n) {\n                        let arr = n.split(\"=\");\n                        queryDetailInfo(\n                          defaultUniqueKey || uniqueKey,\n                          arr[1],\n                          arr[0]\n                        );\n                      }\n                      // console.log(n);\n                      // queryDetailInfo(uniqueKey, arr[1], arr[0]);\n                      setOpenName(n);\n                      openNameRef.current = n;\n                    }}\n                    id={`a${key}`}\n                    key={key}\n                    title={key}\n                    data={data.detail[key]}\n                    log={log}\n                    idx={idx}\n                  />\n                );\n              })}\n            </div>\n          </div>\n        </Spin>\n\n        <div\n          style={{\n            //height: 300,\n            width: 200,\n            backgroundColor: \"#fff\",\n            marginLeft: 20,\n            paddingTop: 10,\n          }}\n        >\n          <div style={{ paddingLeft: 5,\n            \n          }}>\n            <Anchor\n              style={{}}\n              affix={false}\n              getContainer={() => {\n                let con = document.getElementById(\"Step4Wrapper\");\n                return con;\n              }}\n              style={{\n                height: viewHeight - 270,\n                overflowY: \"auto\",\n              }}\n              onClick={(e) => {\n                e.preventDefault();\n              }}\n            >\n              {Object.keys(data.detail).map((key) => {\n                // console.log(data.detail[key]);\n                let hasError =\n                  data.detail[key].filter((a) => a.status == 3).length !== 0;\n                return (\n                  <div style={{ padding: 5 }}>\n                    <Link\n                      href={`#a${key}`}\n                      title={\n                        <span style={{ color: hasError && \"rgb(218, 78, 72)\" }}>\n                          {key}\n                        </span>\n                      }\n                    />\n                  </div>\n                );\n              })}\n            </Anchor>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 10,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20, display: \"flex\" }}>\n          <div style={{ width: 100 }}>\n            {renderStatus[data.status]}\n            {(data.status == 0 || data.status == 1 || data.status == 4) && (\n              <LoadingOutlined style={{ marginLeft: 10, fontWeight: 600 }} />\n            )}\n          </div>\n        </div>\n        <div style={{ width: \"70%\" }}>\n          <Progress\n            percent={data.percentage}\n            status={data.status == 3 && \"exception\"}\n          />\n        </div>\n        <div style={{ paddingLeft: 60 }}>\n          {data.status == 0 && (\n            <Button\n              loading={startLoading}\n              style={{ marginLeft: 10 }}\n              type=\"primary\"\n              //disabled={unassignedServices !== 0}\n              onClick={() => {\n                startInstall();\n              }}\n            >\n              开始\n            </Button>\n          )}\n          {data.status == 3 && (\n            <Button\n              loading={retryLoading}\n              style={{ marginLeft: 10 }}\n              type=\"primary\"\n              //disabled={unassignedServices !== 0}\n              onClick={() => {\n                retryInstall();\n              }}\n            >\n              重试\n            </Button>\n          )}\n\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            //disabled={unassignedServices !== 0}\n            onClick={() => {\n              history?.push(\"/application_management/install-record\");\n            }}\n          >\n            完成\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default Step4;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/store/actionsCreators.js",
    "content": "import * as actionTypes from \"./constants\";\nimport * as R from \"ramda\";\n\nexport const getDataSourceChangeAction = (value) => {\n  const dataSource = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_DATASOURCE,\n    payload: {\n      dataSource: dataSource,\n    },\n  };\n};\n\nexport const getIpListChangeAction = (value) => {\n  const list = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_IPLIST,\n    payload: {\n      ipList: list,\n    },\n  };\n};\n\nexport const getStep1ChangeAction = (value) => {\n  const data = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_STEP1DATA,\n    payload: {\n      step1Data: data,\n    },\n  };\n};\n\nexport const getStep2ChangeAction = (value) => {\n  const data = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_STEP2DATA,\n    payload: {\n      step2Data: data,\n    },\n  };\n};\n\nexport const getStep2ErrorLstChangeAction = (value) => {\n  const data = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_STEP2ERRORLISTDATA,\n    payload: {\n      errorList: data,\n    },\n  };\n};\n\nexport const getStep3IpDataChangeAction = (value) => {\n  const data = R.clone(value);\n  return {\n    type: actionTypes.CHANGE_STEP3DATA,\n    payload: {\n      ipData: data,\n    },\n  };\n};\n\nexport const getStep3ServiceChangeAction = (ip, name, key, value) => {\n  return {\n    type: actionTypes.CHANGE_STEP3SERVERDATA,\n    payload: {\n      ip,\n      name,\n      key,\n      value,\n    },\n  };\n};\n\nexport const getStep3ErrorInfoChangeAction = (value) => {\n  console.log(value)\n  return {\n    type: actionTypes.CHANGE_STEP3ERRORDATA,\n    payload: {\n      err: value,\n    },\n  };\n};"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/store/constants.js",
    "content": "export const CHANGE_DATASOURCE = \"CHANGE_DATASOURCE\";\n\nexport const CHANGE_IPLIST = \"CHANGE_IPLIST\";\n\nexport const CHANGE_STEP1DATA = \"CHANGE_STEP1DATA\";\n\nexport const CHANGE_STEP2DATA = \"CHANGE_STEP2DATA\";\nexport const CHANGE_STEP2ERRORLISTDATA = \"CHANGE_STEP2ERRORLISTDATA\";\n\nexport const CHANGE_STEP3DATA = \"CHANGE_STEP3DATA\";\nexport const CHANGE_STEP3SERVERDATA = \"CHANGE_STEP3SERVERDATA\";\nexport const CHANGE_STEP3ERRORDATA = \"CHANGE_STEP3ERRORDATA\";\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/store/index.js",
    "content": "import reducer from \"./reduer\";\n\n export {\n    reducer\n };"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Installation/store/reduer.js",
    "content": "import * as actionTypes from \"./constants\";\nimport * as R from \"ramda\";\n\nconst defaultState = {\n  dataSource: {},\n  ipList: [],\n  step1Data: {\n    basic: [],\n    dependence: [],\n  },\n  step2Data: {},\n  errorList: [],\n\n  step3Data: {},\n  // step3Data数据结构\n  // {\n  //    ip10.012.1:[{\n  //       name:doucZabbixApi\n  //       log_dir:/abc/d,\n  //        ...\n  //    },\n  //    ...]\n  //  }\n  step3ErrorData: {},\n};\n\nfunction reducer(state = defaultState, action) {\n  const stateCopy = R.clone(state);\n  switch (action.type) {\n    case actionTypes.CHANGE_DATASOURCE:\n      return { ...state, dataSource: action.payload.dataSource };\n    case actionTypes.CHANGE_IPLIST:\n      return { ...state, ipList: action.payload.ipList };\n    case actionTypes.CHANGE_STEP1DATA:\n      return { ...state, step1Data: action.payload.step1Data };\n    case actionTypes.CHANGE_STEP2DATA:\n      return { ...state, step2Data: action.payload.step2Data };\n    case actionTypes.CHANGE_STEP2ERRORLISTDATA:\n      return { ...state, errorList: action.payload.errorList };\n    case actionTypes.CHANGE_STEP3DATA:\n      if(!action.payload.ipData){\n        return {\n          ...state,\n          step3Data:{}\n        }\n      }\n      return {\n        ...state,\n        step3Data: {\n          ...state.step3Data,\n          ...action.payload.ipData,\n        },\n      };\n    case actionTypes.CHANGE_STEP3SERVERDATA:\n      let { ip, name, key, value } = action.payload;\n      let serviceData = stateCopy.step3Data[ip];\n      serviceData = serviceData.map((item) => {\n        if (item.name == name) {\n          let obj = { ...item };\n          obj.install_args = obj.install_args.map((i) => {\n            if (i.key == key) {\n              return {\n                ...i,\n                default: value,\n              };\n            }\n            return i;\n          });\n          obj.ports = obj.ports.map((i) => {\n            if (i.key == key) {\n              return {\n                ...i,\n                default: value,\n              };\n            }\n            return i;\n          });\n          return {\n            ...obj,\n          };\n        }\n        return item;\n      });\n      return {\n        ...stateCopy,\n        step3Data: {\n          ...stateCopy.step3Data,\n          [ip]: serviceData,\n        },\n      };\n\n    case actionTypes.CHANGE_STEP3ERRORDATA:\n      return {\n        ...stateCopy,\n        step3ErrorData: action.payload.err,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport default reducer;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ReleaseModal.js",
    "content": "import { Button, Modal, Upload, message, Steps, Tooltip } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  CloudUploadOutlined,\n  ExclamationCircleOutlined,\n  CloseCircleFilled,\n  CheckCircleFilled,\n  SyncOutlined,\n  ClockCircleFilled,\n  SendOutlined,\n} from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { OmpMessageModal } from \"@/components\";\n\n//let bmf = new BMF();\n\nconst ReleaseModal = ({\n  setReleaseModalVisibility,\n  releaseModalVisibility,\n  timeUnix,\n  refresh,\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const [filesList, setFilesList] = useState([]);\n\n  const [deleteModal, setDeleteModal] = useState(false);\n\n  const [dataSource, setDataSource] = useState([]);\n\n  const [stepNum, setStepNum] = useState(0);\n\n  const isRun = useRef(null);\n\n  const timer = useRef(null);\n\n  // 失败时的轮训次数标识\n  const trainingInRotationNum = useRef(0);\n\n  function checkData() {\n    // 防止在弹窗关闭后还继续轮训\n    if (!isRun.current) {\n      return;\n    }\n    fetchGet(apiRequest.appStore.pack_verification_results, {\n      params: {\n        operation_uuid: timeUnix,\n      },\n    })\n      .then((res) => {\n        if (res)\n          handleResponse(res, (res) => {\n            setDataSource(res.data);\n            if (res.data) {\n              let checkRunningArr = res.data.filter((item) => {\n                return item.package_status == 2;\n              });\n              let publishRunningArr = res.data.filter((item) => {\n                return item.package_status == 5;\n              });\n              if (checkRunningArr.length > 0 || publishRunningArr.length > 0) {\n                timer.current = setTimeout(() => {\n                  checkData();\n                }, 2000);\n              }\n            }\n          });\n      })\n      .catch((e) => {\n        console.log(e);\n        trainingInRotationNum.current++;\n        if (trainingInRotationNum.current < 3) {\n          setTimeout(() => {\n            checkData();\n          }, 5000);\n        } else {\n          setDataSource((data) => {\n            console.log(data);\n            return data.map((item) => {\n              return {\n                ...item,\n                package_status: 9,\n              };\n            });\n          });\n        }\n      })\n      .finally((e) => {});\n  }\n\n  // 发布的查询\n  function publishCheckData() {\n    // 防止在弹窗关闭后还继续轮训\n    if (!isRun.current) {\n      return;\n    }\n    fetchGet(apiRequest.appStore.publish, {\n      params: {\n        operation_uuid: timeUnix,\n      },\n    })\n      .then((res) => {\n        if (res)\n          handleResponse(res, (res) => {\n            setDataSource(res.data);\n            if (res.data) {\n              let checkRunningArr = res.data.filter((item) => {\n                return item.package_status == 2;\n              });\n              let publishRunningArr = res.data.filter((item) => {\n                return item.package_status == 5;\n              });\n              if (checkRunningArr.length > 0 || publishRunningArr.length > 0) {\n                setTimeout(() => {\n                  publishCheckData();\n                }, 2000);\n              }\n            }\n          });\n      })\n      .catch((e) => {\n        console.log(e);\n        trainingInRotationNum.current++;\n        if (trainingInRotationNum.current < 3) {\n          setTimeout(() => {\n            publishCheckData();\n          }, 5000);\n        } else {\n          setDataSource((data) => {\n            //console.log(data);\n            return data.map((item) => {\n              return {\n                ...item,\n                package_status: 9,\n              };\n            });\n          });\n        }\n      })\n      .finally((e) => {});\n  }\n\n  // 发布\n  const publishApp = () => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.publish, {\n      body: {\n        uuid: timeUnix,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 0) {\n            setStepNum(2);\n            publishCheckData();\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    isRun.current = releaseModalVisibility;\n  }, [releaseModalVisibility]);\n\n  return (\n    <>\n      <Modal\n        zIndex={1000}\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <SendOutlined />\n            </span>\n            <span>发布</span>\n          </span>\n        }\n        afterClose={() => {\n          clearTimeout(timer.current);\n          setFilesList([]);\n          setStepNum(0);\n          setDataSource([]);\n          refresh();\n        }}\n        onCancel={() => {\n          if (\n            stepNum == 2 &&\n            dataSource.filter((item) => item.package_status == 5).length == 0\n          ) {\n            setReleaseModalVisibility(false);\n            return;\n          }\n          if (filesList.length == 0) {\n            setReleaseModalVisibility(false);\n          } else {\n            setDeleteModal(true);\n          }\n        }}\n        visible={releaseModalVisibility}\n        footer={null}\n        width={1000}\n        loading={loading}\n        bodyStyle={{\n          paddingLeft: 30,\n          paddingRight: 30,\n        }}\n        destroyOnClose\n      >\n        <Steps size=\"small\" current={stepNum}>\n          <Steps.Step title=\"上传文件\" />\n          <Steps.Step title=\"数据校验\" />\n          <Steps.Step title=\"发布结果\" />\n        </Steps>\n        {stepNum == 0 && (\n          <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n            <div\n              style={{\n                visibility: stepNum == 0 ? \"visible\" : \"hidden\",\n                height: stepNum == 0 ? null : 0,\n                overflow: \"hidden\",\n                paddingBottom: 20,\n              }}\n            >\n              <div>\n                <Upload.Dragger\n                  name=\"file\"\n                  action=\"/api/appStore/upload/\"\n                  accept=\".tar,.gz\"\n                  multiple={true}\n                  data={(file, ...arg) => {\n                    //console.log(arg,file)\n                    return {\n                      uuid: timeUnix,\n                      operation_user: localStorage.getItem(\"username\"),\n                      md5: file.uid,\n                    };\n                  }}\n                  maxCount={5}\n                  beforeUpload={(file, fileList) => {\n                    const fileSize = file.size / 1024 / 1024 / 1024; //单位是G\n                    if (Math.ceil(fileSize) > 4) {\n                      message.error(\"仅支持传入4G以内文件\");\n                      return Upload.LIST_IGNORE;\n                    }\n\n                    if (fileList.length > 5) {\n                      if (file == fileList[0]) {\n                        message.error(\"单次发布操作，最多支持上传5个文件\");\n                      }\n                      return Upload.LIST_IGNORE;\n                    }\n\n                    if (filesList.length + fileList.length > 5) {\n                      if (file == fileList[0]) {\n                        message.error(\"单次发布操作，最多支持上传5个文件\");\n                      }\n                      return Upload.LIST_IGNORE;\n                    }\n\n                    if (filesList.length >= 5) {\n                      if (file == fileList[0]) {\n                        message.error(\"单次发布操作，最多支持上传5个文件\");\n                      }\n                      return Upload.LIST_IGNORE;\n                    }\n\n                    var reg = /[^a-zA-Z0-9\\_\\-\\.]/g;\n                    if (reg.test(file.name)) {\n                      message.error(`文件名仅支持字母、数字、\"_\"、\"-\"和\".\"`);\n                      return Upload.LIST_IGNORE;\n                    }\n\n                    let fileNameArr =\n                      fileList.length == 1\n                        ? [...filesList, file].map((i) => i.name)\n                        : [...filesList, ...fileList].map((i) => i.name);\n                    let uniqueArr = [...new Set(fileNameArr)];\n                    if (fileNameArr.length !== uniqueArr.length) {\n                      if (fileList[0].uid == file.uid) {\n                        message.error(\"上传文件存在同名\");\n                      }\n                      return Upload.LIST_IGNORE;\n                    }\n                  }}\n                  onChange={(e) => {\n                    setFilesList(e.fileList);\n                  }}\n                  onRemove={(e) => {\n                    if (e && e.response && e.response.code == 0) {\n                      fetchPost(apiRequest.appStore.remove, {\n                        body: {\n                          uuid: timeUnix,\n                          package_names: [e.name],\n                        },\n                      })\n                        .then((res) => {\n                          handleResponse(res, (res) => {});\n                        })\n                        .catch((e) => console.log(e))\n                        .finally(() => {});\n                    }\n                  }}\n                >\n                  <div style={{ textAlign: \"center\", color: \"#575757\" }}>\n                    <p className=\"ant-upload-drag-icon\">\n                      <CloudUploadOutlined />\n                    </p>\n                    <p style={{ textAlign: \"center\", color: \"#575757\" }}>\n                      点击或将文件拖拽到这里上传\n                    </p>\n                    <p\n                      style={{\n                        textAlign: \"center\",\n                        color: \"#8e8e8e\",\n                        fontSize: 13,\n                        paddingTop: 10,\n                      }}\n                    >\n                      支持扩展名: .tar 或 .tar.gz 文件大小不超过4G\n                    </p>\n                    <p\n                      style={{\n                        textAlign: \"center\",\n                        color: \"#8e8e8e\",\n                        fontSize: 13,\n                        paddingTop: 10,\n                      }}\n                    >\n                      最多上传5个文件\n                    </p>\n                  </div>\n                </Upload.Dragger>\n              </div>\n            </div>\n\n            <div\n              style={{\n                display: \"flex\",\n                justifyContent: \"center\",\n                marginTop: 20,\n              }}\n            >\n              <Button\n                type=\"primary\"\n                disabled={\n                  filesList.filter((i) => i.status !== \"done\").length !== 0 ||\n                  filesList.length == 0\n                }\n                onClick={() => {\n                  setStepNum(1);\n                  checkData();\n                }}\n              >\n                校验\n              </Button>\n            </div>\n          </div>\n        )}\n        {stepNum == 1 && (\n          <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n            <p style={{ marginBottom: 30 }}>\n              校验失败的安装包不会在应用商店创建，如相同名称应用已存在，发布后会替换原有安装包\n            </p>\n            {dataSource.map((item, idx) => {\n              return (\n                <div\n                  key={idx}\n                  style={{\n                    marginBottom: 10,\n                    display: \"flex\",\n                    justifyContent: \"space-around\",\n                    paddingRight: 80,\n                  }}\n                >\n                  <div style={{ flex: 2 }}>{item.package_name}</div>\n                  <div style={{ flex: 1, textAlign: \"right\" }}>\n                    {item.package_status == 0 && (\n                      <span style={{ color: \"#3cbd35\" }}>\n                        校验通过\n                        <CheckCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#3cbd35\",\n                          }}\n                        />\n                      </span>\n                    )}\n                    {item.package_status == 1 && (\n                      <span style={{ color: \"#fe1937\" }}>\n                        校验失败\n                        <CloseCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n\n                    {item.package_status == 2 && (\n                      <span>\n                        校验中...\n                        <SyncOutlined\n                          spin\n                          style={{\n                            fontSize: 16,\n                            position: \"relative\",\n                            top: 1,\n\n                            marginLeft: 10,\n                            //color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n\n                    {item.package_status == 9 && (\n                      <span style={{ color: \"#fe1937\" }}>\n                        网络错误\n                        <CloseCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n                  </div>\n                  <Tooltip placement=\"right\" title={item.error_msg}>\n                    <div\n                      style={{\n                        flex: 3,\n                        textAlign: \"right\",\n                        overflow: \"hidden\",\n                        paddingLeft: 20,\n                        textOverflow: \"ellipsis\",\n                        whiteSpace: \"nowrap\",\n                      }}\n                    >\n                      {item.error_msg}\n                    </div>\n                  </Tooltip>\n                </div>\n              );\n            })}\n            <div\n              style={{\n                display: \"flex\",\n                justifyContent: \"center\",\n                marginTop: 60,\n                position: \"relative\",\n                left: -20,\n              }}\n            >\n              <Button\n                type=\"primary\"\n                disabled={\n                  dataSource.filter((i) => i.package_status == 0).length == 0 ||\n                  dataSource.filter((i) => i.package_status == 2).length !== 0\n                }\n                loading={loading}\n                onClick={() => {\n                  publishApp();\n                }}\n              >\n                发布\n              </Button>\n            </div>\n          </div>\n        )}\n        {stepNum == 2 && (\n          <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n            {dataSource.filter((item) => item.package_status == 5).length >\n            0 ? (\n              <div\n                style={{\n                  paddingBottom: 20,\n                  position: \"relative\",\n                  left: -20,\n                }}\n              >\n                <p style={{ textAlign: \"center\", fontSize: 20 }}>\n                  <ClockCircleFilled\n                    style={{\n                      paddingRight: 10,\n                      color: \"#ffb436\",\n                      fontSize: 24,\n                      position: \"relative\",\n                      top: 1,\n                    }}\n                  />\n                  发布中\n                </p>\n              </div>\n            ) : (\n              <div\n                style={{\n                  marginBottom: 20,\n                  position: \"relative\",\n                  left: -20,\n                }}\n              >\n                <p style={{ textAlign: \"center\", fontSize: 20 }}>\n                  <CheckCircleFilled\n                    style={{\n                      paddingRight: 10,\n                      color: \"#3cbd35\",\n                      fontSize: 24,\n                      position: \"relative\",\n                      top: 1,\n                    }}\n                  />\n                  发布完成\n                </p>\n                <p style={{ textAlign: \"center\" }}>\n                  本次成功发布{\" \"}\n                  {dataSource.filter((item) => item.package_status == 3).length}\n                  个 服务\n                </p>\n                <p style={{ textAlign: \"center\" }}>\n                  发布完成的安装包存放路径:{\" \"}\n                  <span style={{ fontWeight: 500, color: \"rgba(0,0,0,0.8)\" }}>\n                    omp/package_hub/verified/\n                  </span>{\" \"}\n                </p>\n              </div>\n            )}\n\n            {dataSource.map((item, idx) => {\n              return (\n                <div\n                  key={idx}\n                  style={{\n                    marginBottom: 10,\n                    display: \"flex\",\n                    justifyContent: \"space-around\",\n                    paddingRight: 80,\n                  }}\n                >\n                  <div style={{ flex: 2 }}>{item.package_name}</div>\n                  <div style={{ flex: 1, textAlign: \"right\" }}>\n                    {item.package_status == 3 && (\n                      <span style={{ color: \"#3cbd35\" }}>\n                        发布成功\n                        <CheckCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#3cbd35\",\n                          }}\n                        />\n                      </span>\n                    )}\n                    {item.package_status == 4 && (\n                      <span style={{ color: \"#fe1937\" }}>\n                        发布失败\n                        <CloseCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n\n                    {item.package_status == 5 && (\n                      <span>\n                        发布中...\n                        <SyncOutlined\n                          spin\n                          style={{\n                            fontSize: 16,\n                            position: \"relative\",\n                            top: 1,\n\n                            marginLeft: 10,\n                            //color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n\n                    {item.package_status == 9 && (\n                      <span style={{ color: \"#fe1937\" }}>\n                        网络错误\n                        <CloseCircleFilled\n                          style={{\n                            fontSize: 18,\n                            position: \"relative\",\n                            top: 1,\n                            paddingLeft: 10,\n                            color: \"#fe1937\",\n                          }}\n                        />\n                      </span>\n                    )}\n                  </div>\n                  <Tooltip placement=\"right\" title={item.error_msg}>\n                    <div\n                      style={{\n                        flex: 3,\n                        textAlign: \"right\",\n                        overflow: \"hidden\",\n                        paddingLeft: 20,\n                        textOverflow: \"ellipsis\",\n                        whiteSpace: \"nowrap\",\n                      }}\n                    >\n                      {item.error_msg}\n                    </div>\n                  </Tooltip>\n                </div>\n              );\n            })}\n            <div\n              style={{\n                display: \"flex\",\n                justifyContent: \"center\",\n                marginTop: 60,\n                position: \"relative\",\n                left: -20,\n              }}\n            >\n              <Button\n                type=\"primary\"\n                disabled={\n                  dataSource.filter((item) => item.package_status == 5).length >\n                  0\n                }\n                //loading={loading}\n                onClick={() => {\n                  setReleaseModalVisibility(false);\n                }}\n              >\n                完成\n              </Button>\n            </div>\n          </div>\n        )}\n      </Modal>\n      <OmpMessageModal\n        zIndex={1100}\n        style={{ top: 160 }}\n        visibleHandle={[deleteModal, setDeleteModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#fe1937\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        //loading={loading}\n        onFinish={() => {\n          let updatedFile = filesList.filter(\n            (i) => i.response && i.response.code == 0\n          );\n          if (updatedFile.length == 0) {\n            setReleaseModalVisibility(false);\n            setDeleteModal(false);\n            return;\n          }\n          fetchPost(apiRequest.appStore.remove, {\n            body: {\n              uuid: timeUnix,\n              package_names: updatedFile.map((i) => i.name),\n            },\n          })\n            .then((res) => {\n              handleResponse(res, (res) => {});\n            })\n            .catch((e) => console.log(e))\n            .finally(() => {\n              setReleaseModalVisibility(false);\n              setDeleteModal(false);\n            });\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          <p style={{ fontWeight: 500 }}>确认要中断发布操作吗 ？</p>\n          <p style={{ paddingLeft: 20 }}>\n            正在进行发布操作，关闭窗口将会中断，此操作不可逆。\n          </p>\n        </div>\n      </OmpMessageModal>\n    </>\n  );\n};\n\nexport default ReleaseModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Rollback/content/component/RollbackDetail.js",
    "content": "import { DownOutlined } from \"@ant-design/icons\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { OmpToolTip } from \"@/components\";\n\nconst stepOpen = {\n  marginTop: 10,\n  minHeight: 30,\n  height: 300,\n  transition: \"all .2s ease-in\",\n  //overflow: \"hidden\",\n  backgroundColor: \"#000\",\n  color: \"#fff\",\n  padding: 10,\n  overflowY: \"auto\",\n  whiteSpace: \"pre-line\",\n};\nconst stepNotOpen = {\n  height: 0,\n  minHeight: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n};\n\n// 状态渲染规则\nconst renderStatus = {\n  0: <span style={{ color: \"#f0c242\" }}>等待回滚</span>,\n  1: <span style={{ color: \"rgba(0, 0, 0, 0.85)\" }}>正在回滚</span>,\n  2: <span style={{ color: \"rgb(118,204,104)\" }}>回滚成功</span>,\n  3: <span style={{ color: \"#da4e48\" }}>回滚失败</span>,\n};\n\nconst RollbackDetail = ({ title, ip, status, log, instance_name }) => {\n  const containerRef = useRef(null);\n\n  const [openName, setOpenName] = useState(\"\");\n\n  useEffect(() => {\n    containerRef.current.scrollTop = containerRef.current.scrollHeight;\n  }, [log]);\n\n  return (\n    <div\n      style={{\n        padding: 10,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          justifyContent: \"space-between\",\n        }}\n      >\n        <div style={{ flex: 3 }}>\n          <OmpToolTip maxLength={24}>{instance_name}</OmpToolTip>\n        </div>\n        <div style={{ flex: 1 }}>{renderStatus[status]}</div>\n        <div style={{ flex: 6, textAlign: \"right\", paddingRight: 50 }}>\n          <a\n            onClick={() => {\n              if (openName == `${title}=${ip}`) {\n                setOpenName(\"\");\n              } else {\n                setOpenName(`${title}=${ip}`);\n              }\n            }}\n          >\n            查看详细回滚信息\n            <DownOutlined\n              style={{\n                transform: `rotate(${\n                  openName == `${title}=${ip}` ? 180 : 0\n                }deg)`,\n                position: \"relative\",\n                top: openName == `${title}=${ip}` ? -1 : 1,\n                left: 3,\n              }}\n            />\n          </a>\n        </div>\n      </div>\n      <div\n        ref={containerRef}\n        style={openName == `${title}=${ip}` ? stepOpen : stepNotOpen}\n      >\n        {log || \"暂无数据\"}\n      </div>\n    </div>\n  );\n};\n\nexport default RollbackDetail;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Rollback/content/component/RollbackInfoItem.js",
    "content": "import RollbackDetail from \"./RollbackDetail\";\n\nconst RollbackInfoItem = ({ id, data, title, log, idx, instance_name }) => {\n  return (\n    <div\n      id={id}\n      style={{\n        //marginTop: 20,\n        backgroundColor: \"#fff\",\n        padding: 10,\n        //marginBottom: 15,\n        marginTop: idx !== 0 && 15,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          width: \"100%\",\n          position: \"relative\",\n          height: 40,\n          paddingTop: 10,\n        }}\n      >\n        <div\n          style={{\n            fontWeight: 500,\n            position: \"absolute\",\n            left: 30,\n            backgroundColor: \"#fff\",\n            paddingLeft: 20,\n            paddingRight: 20,\n          }}\n        >\n          {title}\n        </div>\n        <div style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }} />\n      </div>\n      <div\n        style={{\n          paddingLeft: 20,\n          marginTop: 10,\n          paddingBottom: 5,\n          // paddingTop: 20,\n        }}\n      >\n        {data?.map((item) => {\n          console.log(item);\n          return (\n            <RollbackDetail\n              title={title}\n              key={`${title}=${item.ip}`}\n              status={item.rollback_state}\n              ip={item.ip}\n              log={item.message}\n              instance_name={item.instance_name}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\nexport default RollbackInfoItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Rollback/content/index.js",
    "content": "import { Button, Anchor, Spin, Progress } from \"antd\";\nimport { useSelector } from \"react-redux\";\nimport RollbackInfoItem from \"./component/RollbackInfoItem\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { LoadingOutlined } from \"@ant-design/icons\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { fetchGet } from \"@/utils/request\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { fetchPut } from \"src/utils/request\";\n\nconst { Link } = Anchor;\n// 状态渲染规则\nconst renderStatus = {\n  0: \"等待回滚\",\n  1: \"正在回滚\",\n  2: \"回滚成功\",\n  3: \"回滚失败\",\n  4: \"正在注册\",\n};\n\nconst Content = () => {\n  const history = useHistory();\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  // 在轮训时使用ref存值\n  const openNameRef = useRef(null);\n  const location = useLocation();\n  console.log(location?.state?.history);\n\n  const [loading, setLoading] = useState(true);\n\n  const [retryLoading, setRetryLoading] = useState(false);\n\n  const [data, setData] = useState({\n    detail: {},\n    rollback_state: 0,\n  });\n\n  // 轮训的timer控制器\n  const timer = useRef(null);\n\n  const queryRollbackProcess = () => {\n    !timer.current && setLoading(true);\n    fetchGet(\n      `${apiRequest.appStore.queryRollbackProcess}/${location?.state?.history}`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setData(res.data);\n          if (\n            res.data.rollback_state == 0 ||\n            res.data.rollback_state == 1 ||\n            res.data.rollback_state == 4\n          ) {\n            // 状态为未安装或者安装中\n            timer.current = setTimeout(() => {\n              queryRollbackProcess();\n            }, 5000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const retryRollback = () => {\n    setRetryLoading(true);\n    fetchPut(\n      `${apiRequest.appStore.queryRollbackProcess}/${location?.state?.history}`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            queryRollbackProcess();\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setRetryLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryRollbackProcess();\n    return () => {\n      // 页面销毁时清除延时器\n      clearTimeout(timer.current);\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          display: \"flex\",\n          paddingTop: 20,\n        }}\n      >\n        <Spin spinning={loading}>\n          <div\n            id=\"Step4Wrapper\"\n            style={{\n              flex: 1,\n              //backgroundColor: \"#fff\",\n              height: viewHeight - 270,\n              overflowY: \"auto\",\n            }}\n          >\n            <div>\n              {data?.rollback_detail?.map((item, idx) => {\n                return (\n                  <RollbackInfoItem\n                    id={`a${idx}`}\n                    key={idx}\n                    title={item.service_name}\n                    data={item.rollback_details}\n                    idx={idx}\n                  />\n                );\n              })}\n            </div>\n          </div>\n        </Spin>\n\n        <div\n          style={{\n            //height: 300,\n            width: 200,\n            backgroundColor: \"#fff\",\n            marginLeft: 20,\n            height: viewHeight - 270,\n            overflowY: \"auto\",\n            paddingTop: 10,\n          }}\n        >\n          <div style={{ paddingLeft: 5 }}>\n            <Anchor\n              style={{}}\n              affix={false}\n              getContainer={() => {\n                let con = document.getElementById(\"Step4Wrapper\");\n                return con;\n              }}\n              onClick={(e) => {\n                e.preventDefault();\n              }}\n            >\n              {data?.rollback_detail?.map((item, idx) => {\n                let hasError =\n                  item.rollback_details.filter((a) => a.rollback_state == 3)\n                    .length !== 0;\n                return (\n                  <div style={{ padding: 5 }} key={idx}>\n                    <Link\n                      href={`#a${idx}`}\n                      title={\n                        <span style={{ color: hasError && \"rgb(218, 78, 72)\" }}>\n                          {item.service_name}\n                        </span>\n                      }\n                    />\n                  </div>\n                );\n              })}\n            </Anchor>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 10,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20, display: \"flex\" }}>\n          <div style={{ width: 100 }}>\n            {renderStatus[data.rollback_state]}\n            {(data.rollback_state == 0 ||\n              data.rollback_state == 1 ||\n              data.rollback_state == 4) && (\n              <LoadingOutlined style={{ marginLeft: 10, fontWeight: 600 }} />\n            )}\n          </div>\n        </div>\n        <div style={{ width: \"70%\" }}>\n          <Progress\n            percent={(data.success_count / data.all_count * 100).toFixed()}\n            status={data.rollback_state == 3 && \"exception\"}\n          />\n        </div>\n        <div style={{ paddingLeft: 60 }}>\n          {data.rollback_state == 3 && (\n            <Button\n              loading={retryLoading}\n              style={{ marginLeft: 10 }}\n              type=\"primary\"\n              //disabled={unassignedServices !== 0}\n              onClick={() => {\n                retryRollback();\n              }}\n            >\n              重试\n            </Button>\n          )}\n\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            //disabled={unassignedServices !== 0}\n            onClick={() => {\n              history.push({\n                pathname: \"/application_management/install-record\",\n                state: {\n                  tabKey: \"backoff\",\n                },\n              });\n            }}\n          >\n            完成\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default Content;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Rollback/index.js",
    "content": "// 服务的升级和回滚\nimport { useHistory } from \"react-router-dom\";\n// import { getTabKeyChangeAction } from \"../../store/actionsCreators\";\nimport styles from \"./index.module.less\";\n\nimport { LeftOutlined } from \"@ant-design/icons\";\nimport Content from \"./content/index.js\";\n// 安装页面\nconst Rollback = () => {\n  // const dispatch = useDispatch();\n  const history = useHistory();\n\n  return (\n    <div>\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            className={styles.backIcon}\n            onClick={() => {\n              history.push({\n                pathname: \"/application_management/install-record\",\n                state: {\n                  tabKey: \"backoff\",\n                },\n              });\n            }}\n          />\n          服务回滚\n        </div>\n        <div />\n      </div>\n      <Content />\n    </div>\n  );\n};\n\nexport default Rollback;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Rollback/index.module.less",
    "content": ".backIcon:hover {\n    color: rgb(46, 124, 238);\n  }\n\n  :global {\n    .ant-anchor-ink::before {\n      background-color: #fff!important;\n    }\n  }"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ScanServerModal.js",
    "content": "import { Button, Modal, Steps, Tooltip, Table } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  LoadingOutlined,\n  CheckCircleFilled,\n  ScanOutlined,\n} from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\n\nconst ScanServerModal = ({\n  scanServerModalVisibility,\n  setScanServerModalVisibility,\n  refresh,\n}) => {\n  const [stepNum, setStepNum] = useState(0);\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState();\n\n  const isOpen = useRef(null);\n\n  const timer = useRef(null);\n\n  // 失败时的轮训次数标识\n  const trainingInRotationNum = useRef(0);\n\n  function fetchData(data) {\n    // 防止在弹窗关闭后还继续轮训\n    if (!isOpen.current) {\n      return;\n    }\n    fetchGet(apiRequest.appStore.localPackageScanResult, {\n      params: {\n        uuid: data.uuid,\n        package_names: data.package_names.join(\",\"),\n      },\n    })\n      .then((res) => {\n        // timer.current = setTimeout(() => {\n        //   fetchData(data);\n        // }, 2000);\n        if (res)\n          handleResponse(res, (res) => {\n            if (res.data.stage_status.includes(\"check\")) {\n              setStepNum(1);\n            }\n            if (res.data.stage_status.includes(\"publish\")) {\n              setStepNum(2);\n            }\n            setDataSource(res.data);\n            if (res.data && res.data.stage_status.includes(\"ing\")) {\n              timer.current = setTimeout(() => {\n                fetchData(data);\n              }, 2000);\n            }\n          });\n      })\n      .catch((e) => {\n        trainingInRotationNum.current++;\n        if (trainingInRotationNum.current < 3) {\n          setTimeout(() => {\n            fetchData(data);\n          }, 5000);\n        } else {\n          setDataSource((dataS) => {\n            // /console.log(dataS);\n            let arr = dataS?.package_detail?.map((item) => {\n              return {\n                ...item,\n                status: 9,\n              };\n            });\n            //console.log(arr);\n            return {\n              ...dataS,\n              package_detail: arr,\n            };\n          });\n        }\n      })\n      .finally((e) => {});\n  }\n\n  // 扫描服务端executeLocalPackageScan\n  const executeLocalPackageScan = () => {\n    setStepNum(0);\n    setLoading(true);\n    fetchPost(apiRequest.appStore.executeLocalPackageScan)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (\n            res.data &&\n            res.data?.package_names?.filter((item) => item).length > 0\n          ) {\n            fetchData(res.data);\n          }\n        });\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally(() => setLoading(false));\n  };\n\n  useEffect(() => {\n    isOpen.current = scanServerModalVisibility;\n    if (scanServerModalVisibility) {\n      executeLocalPackageScan();\n    }\n  }, [scanServerModalVisibility]);\n\n  return (\n    <Modal\n      zIndex={1000}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <ScanOutlined />\n          </span>\n          <span>\n            {stepNum == 0 && \"扫描服务端安装包\"}\n            {stepNum == 1 && \"校验服务端安装包\"}\n            {stepNum == 2 && \"发布服务端安装包\"}\n          </span>\n        </span>\n      }\n      afterClose={() => {\n        setDataSource([]);\n        setStepNum(0);\n        clearTimeout(timer.current);\n        refresh();\n      }}\n      onCancel={() => {\n        setScanServerModalVisibility(false);\n      }}\n      visible={scanServerModalVisibility}\n      footer={null}\n      width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n    >\n      <Steps\n        type=\"navigation\"\n        size=\"small\"\n        current={stepNum}\n        //onChange={this.onChange}\n      >\n        <Steps.Step\n          title=\"扫描服务端安装包\"\n          icon={loading && <LoadingOutlined />}\n        />\n        <Steps.Step\n          title=\"安装包数据校验\"\n          icon={dataSource?.stage_status == \"checking\" && <LoadingOutlined />}\n        />\n        <Steps.Step\n          title=\"发布\"\n          icon={dataSource?.stage_status == \"publishing\" && <LoadingOutlined />}\n        />\n      </Steps>\n      {stepNum == 0 && (\n        <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n          <div\n            style={{\n              overflow: \"hidden\",\n              paddingBottom: 20,\n            }}\n          >\n            {loading ? (\n              <p style={{ textAlign: \"center\" }}>正在扫描服务端...</p>\n            ) : (\n              <p style={{ textAlign: \"center\" }}>\n                <p>扫描结束，服务端暂无安装包！</p>\n                <p>\n                  请将安装包上传至{\" \"}\n                  <span style={{ fontWeight: 500, color: \"rgba(0,0,0,0.8)\" }}>\n                    omp/package_hub/back_end_verified/\n                  </span>{\" \"}\n                  目录后重新扫描\n                </p>\n              </p>\n            )}\n          </div>\n        </div>\n      )}\n      {stepNum == 1 && (\n        <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n          <div\n            style={{\n              overflow: \"hidden\",\n              paddingBottom: 20,\n            }}\n          >\n            <p style={{ textAlign: \"center\" }}>{dataSource?.message}</p>\n          </div>\n          <Table\n            style={{ border: \"1px solid #e3e3e3\" }}\n            size=\"middle\"\n            //hideOnSinglePage\n            pagination={{\n              defaultPageSize: 5,\n            }}\n            columns={[\n              {\n                title: \"安装包名称\",\n                key: \"name\",\n                dataIndex: \"name\",\n                align: \"center\",\n              },\n              {\n                title: \"状态\",\n                key: \"status\",\n                dataIndex: \"status\",\n                align: \"center\",\n                render: (text) => {\n                  switch (text) {\n                    case 2:\n                      return \"校验中\";\n                      break;\n                    case 1:\n                      return \"校验失败\";\n                      break;\n                    case 0:\n                      return \"校验成功\";\n                      break;\n                    case 9:\n                      return \"网络错误\";\n                      break;\n                    default:\n                      break;\n                  }\n                },\n              },\n              {\n                title: \"说明\",\n                key: \"message\",\n                dataIndex: \"message\",\n                align: \"center\",\n                //width:120,\n                ellipsis: true,\n                render: (text) => {\n                  return (\n                    <Tooltip title={text} placement=\"top\">\n                      <div\n                        style={{\n                          overflow: \"hidden\",\n                          textOverflow: \"ellipsis\",\n                          whiteSpace: \"nowrap\",\n                        }}\n                      >\n                        {text ? text : \"-\"}\n                      </div>\n                    </Tooltip>\n                  );\n                },\n              },\n            ]}\n            pagination={false}\n            dataSource={dataSource?.package_detail?.map((item, idx) => {\n              return {\n                ...item,\n                name:\n                  dataSource &&\n                  dataSource.package_names_lst &&\n                  dataSource.package_names_lst[idx],\n              };\n            })}\n          />\n          {dataSource?.stage_status == \"check_all_failed\" && (\n            <div\n              style={{\n                display: \"flex\",\n                justifyContent: \"center\",\n                marginTop: 60,\n                position: \"relative\",\n                left: -20,\n              }}\n            >\n              <Button\n                type=\"primary\"\n                //disabled={dataSource?.stage_status !== \"published\"}\n                //loading={loading}\n                onClick={() => {\n                  setScanServerModalVisibility(false);\n                }}\n              >\n                完成\n              </Button>\n            </div>\n          )}\n        </div>\n      )}\n      {stepNum == 2 && (\n        <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n          <div\n            style={{\n              overflow: \"hidden\",\n              paddingBottom: 20,\n            }}\n          >\n            {dataSource?.stage_status == \"published\" && (\n              <p style={{ textAlign: \"center\", fontSize: 20 }}>\n                <CheckCircleFilled\n                  style={{\n                    paddingRight: 10,\n                    color: \"#3cbd35\",\n                    fontSize: 24,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                />\n                发布完成\n              </p>\n            )}\n            <p style={{ textAlign: \"center\" }}>{dataSource?.message}</p>\n            <p style={{ textAlign: \"center\" }}>\n              发布完成的安装包存放路径:{\" \"}\n              <span style={{ fontWeight: 500, color: \"rgba(0,0,0,0.8)\" }}>\n                omp/package_hub/verified/\n              </span>{\" \"}\n            </p>\n          </div>\n          <Table\n            style={{ border: \"1px solid #e3e3e3\" }}\n            size=\"middle\"\n            //hideOnSinglePage\n            pagination={{\n              defaultPageSize: 5,\n            }}\n            columns={[\n              {\n                title: \"安装包名称\",\n                key: \"name\",\n                dataIndex: \"name\",\n                align: \"center\",\n              },\n              {\n                title: \"状态\",\n                key: \"status\",\n                dataIndex: \"status\",\n                align: \"center\",\n                render: (text) => {\n                  switch (text) {\n                    case 3:\n                      return \"发布成功\";\n                      break;\n                    case 4:\n                      return \"发布失败\";\n                      break;\n                    case 5:\n                      return \"发布中\";\n                      break;\n                    case 9:\n                      return \"网络错误\";\n                      break;\n                    default:\n                      break;\n                  }\n                },\n              },\n              {\n                title: \"说明\",\n                key: \"message\",\n                dataIndex: \"message\",\n                align: \"center\",\n                ellipsis: true,\n                render: (text) => {\n                  return (\n                    <Tooltip title={text} placement=\"top\">\n                      <div\n                        style={{\n                          overflow: \"hidden\",\n                          textOverflow: \"ellipsis\",\n                          whiteSpace: \"nowrap\",\n                        }}\n                      >\n                        {text ? text : \"-\"}\n                      </div>\n                    </Tooltip>\n                  );\n                },\n              },\n            ]}\n            pagination={false}\n            dataSource={dataSource?.package_detail?.map((item, idx) => {\n              return {\n                ...item,\n                name:\n                  dataSource &&\n                  dataSource.package_names_lst &&\n                  dataSource.package_names_lst[idx],\n              };\n            })}\n          />\n          <div\n            style={{\n              display: \"flex\",\n              justifyContent: \"center\",\n              marginTop: 60,\n              position: \"relative\",\n              left: -20,\n            }}\n          >\n            <Button\n              type=\"primary\"\n              disabled={dataSource?.stage_status !== \"published\"}\n              //loading={loading}\n              onClick={() => {\n                setScanServerModalVisibility(false);\n              }}\n            >\n              完成\n            </Button>\n          </div>\n        </div>\n      )}\n    </Modal>\n  );\n};\n\nexport default ScanServerModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ServiceRollbackModal.js",
    "content": "import { Button, Modal, Tooltip, Input, Table } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { SearchOutlined, SyncOutlined } from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { useHistory } from \"react-router-dom\";\n\nconst ServiceRollbackModal = ({\n  sRModalVisibility,\n  setSRModalVisibility,\n  // dataSource,\n  installTitle,\n  initLoading,\n  fixedParams,\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const [selectValue, setSelectValue] = useState();\n\n  const [rows, setRows] = useState([]);\n\n  const history = useHistory();\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n  // console.log(checkedList)\n  //应用服务选择的版本号\n  const versionInfo = useRef({});\n\n  const lock = useRef(false);\n\n  const columns = [\n    {\n      title: \"名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      // align: \"center\",\n      ellipsis: true,\n      width: 150,\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <div style={{ paddingTop: 2 }}>{text}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"当前版本\",\n      key: \"before_rollback_v\",\n      dataIndex: \"before_rollback_v\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v = text || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"回滚版本\",\n      key: \"after_rollback_v\",\n      dataIndex: \"after_rollback_v\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v = text || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n  ];\n\n  const [dataSource, setDataSource] = useState([]);\n  const [allLength, setAllLength] = useState(0);\n\n  const queryDataList = (search) => {\n    // setRows([]);\n    // setCheckedList([]);\n    setLoading(true);\n    fetchGet(`${apiRequest.appStore.canRollback}${fixedParams || \"\"}`, {\n      params: {\n        search: search,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          let result = formatResData(res.data.results);\n          if (!search || search == undefined) {\n            setAllLength(result.map((i) => i.children).flat().length);\n          }\n          setDataSource(result);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const formatResData = (data = []) => {\n    // 遍历数据添加key以及判断父级数据的当前版本\n    // （当其子项的当前版本全部一致时设置父级数据版本为此值）\n    let result = data.map((item) => {\n      let currentVersion = \"-\";\n      let can_rollback = [];\n      let c = Array.from(\n        new Set(\n          item.children.map((i, idx) => {\n            item.children[idx].key = item.children[idx].instance_name;\n            item.children[idx]._id = item.children[idx].id;\n            item.children[idx].id = item.children[idx].instance_name;\n            item.children[idx].isChildren = item.app_name;\n            return i.before_rollback_v;\n          })\n        )\n      );\n      let b = Array.from(\n        new Set(\n          item.children.map((i, idx) => {\n            return i.after_rollback_v;\n          })\n        )\n      );\n      c.length == 1 && (currentVersion = c[0]);\n      b.length == 1 && can_rollback.push(b[0]);\n      return {\n        ...item,\n        before_rollback_v: currentVersion,\n        after_rollback_v: can_rollback,\n        instance_name: item.app_name,\n        id: item.app_name || item.instance_name,\n        key: item.app_name || item.instance_name,\n      };\n    });\n    return result;\n  };\n\n  // 组件的checkedList并不能很好的对应业务数据需求，封装函数进行转换\n  const formatCheckedListData = (data) => {\n    return data.filter((item) => {\n      return item.isChildren;\n    });\n  };\n\n  // 回滚命令下发\n  const doRollback = (checkedList) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.doRollback, {\n      body: {\n        choices: checkedList.map((item) => item._id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            history.push({\n              pathname: \"/application_management/app_store/service_rollback\",\n              state: {\n                history: res.data.history,\n              },\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    sRModalVisibility && queryDataList();\n  }, [sRModalVisibility]);\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <SyncOutlined />\n          </span>\n          <span>服务回滚-选择应用服务</span>\n        </span>\n      }\n      width={600}\n      afterClose={() => {\n        setRows([]);\n        setCheckedList([]);\n      }}\n      onCancel={() => {\n        setSRModalVisibility(false);\n      }}\n      visible={sRModalVisibility}\n      footer={null}\n      //width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n    >\n      <div>\n        <div style={{ display: \"flex\", marginLeft: \"290px\", marginBottom: 15 }}>\n          <span\n            style={{\n              width: 70,\n              display: \"flex\",\n              alignItems: \"center\",\n              fontSize: 14,\n            }}\n          >\n            服务名称:\n          </span>\n          <Input\n            placeholder=\"请输入服务名称\"\n            style={{ width: 180 }}\n            allowClear\n            // size=\"small\"\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                queryDataList();\n              }\n            }}\n            onBlur={() => {\n              queryDataList(selectValue);\n            }}\n            onPressEnter={() => {\n              queryDataList(selectValue);\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n        </div>\n        <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n          <Table\n            size=\"small\"\n            scroll={{ y: 295 }}\n            loading={loading || initLoading}\n            //scroll={{ x: 1900 }}\n            columns={columns}\n            dataSource={dataSource}\n            pagination={false}\n            // notSelectable={(record) => ({\n            //   // is_continue的不能选中\n            //   disabled: !record.is_continue,\n            // })}\n            rowSelection={{\n              onChange: (selectedRowKeys, selectedRows, select, lll) => {\n                // 全选动作交给onchange事件\n                if (lock.current) {\n                  setRows(selectedRowKeys);\n                  setCheckedList(formatCheckedListData(selectedRows));\n                  // 关闭锁，下次事件触发时优先使用onselect处理rowkey变更\n                  lock.current = false;\n                }\n              },\n              selectedRowKeys: rows,\n              onSelect: (record, selected, selectedRows) => {\n                if (record.isChildren) {\n                  if (selected) {\n                    for (const item of dataSource) {\n                      if (item.instance_name == record.isChildren) {\n                        let results = item.children.filter(\n                          (i) => i.ip == record.ip\n                        );\n                        // results是当前点击要变动的\n                        // row中原本的数据不应该改变\n                        // row原本数据不改变,考虑到每次都是直接选中全部ip的项，也不会出现选中已经选中项的情况\n                        setRows((r) => {\n                          return [...r, ...results.map((k) => k.instance_name)];\n                        });\n                        setCheckedList((checks) => {\n                          return formatCheckedListData([...checks, ...results]);\n                        });\n                        break;\n                      }\n                    }\n                  } else {\n                    // 删除掉和record 父级和ip一样的项\n                    let checkedListCopy = JSON.parse(\n                      JSON.stringify(checkedList)\n                    );\n                    let results = formatCheckedListData(\n                      checkedListCopy.filter(\n                        (i) =>\n                          i.ip !== record.ip ||\n                          i.isChildren !== record.isChildren\n                      )\n                    );\n\n                    setRows(results.map((i) => i.instance_name));\n                    setCheckedList(results);\n                  }\n                } else {\n                  // 打开锁，使用onchange事件处理rowkey变更\n                  lock.current = true;\n                }\n              },\n              onSelectAll: (selected, selectedRows, changeRows) => {\n                // 打开锁，使用onchange事件处理rowkey变更\n                lock.current = true;\n              },\n              checkStrictly: false,\n            }}\n          />\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            marginTop: 20,\n            justifyContent: \"space-between\",\n            padding: \"0px 20px\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}></div>\n          <div style={{ display: \"flex\" }}>\n            <div style={{ marginRight: 15 }}>\n              已选择 {checkedList.length} 个\n            </div>\n            <div>共 {allLength} 个</div>\n          </div>\n        </div>\n        <div\n          style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n        >\n          <div\n            style={{\n              width: 170,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          >\n            <Button onClick={() => setSRModalVisibility(false)}>取消</Button>\n            <Button\n              type=\"primary\"\n              style={{ marginLeft: 16 }}\n              loading={loading || initLoading}\n              disabled={checkedList.length == 0}\n              onClick={() => {\n                doRollback(checkedList);\n              }}\n            >\n              确认选择\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default ServiceRollbackModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/ServiceUpgradeModal.js",
    "content": "import {\n  Button,\n  Modal,\n  Upload,\n  message,\n  Steps,\n  Tooltip,\n  Select,\n  Switch,\n  Input,\n  Table,\n} from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { CopyOutlined, SearchOutlined, ArrowUpOutlined } from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost, fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { useHistory } from \"react-router-dom\";\n\nconst ServiceUpgradeModal = ({\n  sUModalVisibility,\n  setSUModalVisibility,\n  // dataSource,\n  installTitle,\n  initLoading,\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const [selectValue, setSelectValue] = useState();\n\n  const [rows, setRows] = useState([]);\n\n  const history = useHistory();\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n  // console.log(checkedList)\n  //应用服务选择的版本号\n  const versionInfo = useRef({});\n\n  const lock = useRef(false);\n\n  const columns = [\n    {\n      title: \"名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      // align: \"center\",\n      ellipsis: true,\n      width: 150,\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <div style={{ paddingTop: 2 }}>{text}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"当前版本\",\n      key: \"version\",\n      dataIndex: \"version\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v =  text || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"升级版本\",\n      key: \"can_upgrade\",\n      dataIndex: \"can_upgrade\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v =  text[0].app_version || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n  ];\n\n  const [dataSource, setDataSource] = useState([]);\n  const [allLength, setAllLength] = useState(0)\n\n  const queryDataList = (search) => {\n    // setRows([]);\n    // setCheckedList([]);\n    setLoading(true);\n    fetchGet(apiRequest.appStore.canUpgrade, {\n      params: {\n        search: search,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          let result = formatResData(res.data.results)\n          if(!search || search == undefined){\n            setAllLength(result.map((i) => i.children).flat().length)\n          }\n          setDataSource(result);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const formatResData = (data = []) => {\n    console.log(data);\n    // 遍历数据添加key以及判断父级数据的当前版本\n    // （当其子项的当前版本全部一致时设置父级数据版本为此值）\n    let result = data.map((item) => {\n      let currentVersion = \"-\";\n      let can_upgrade = [item.can_upgrade];\n      let c = Array.from(\n        new Set(\n          item.children.map((i, idx) => {\n            item.children[idx].key = item.children[idx].instance_name;\n            item.children[idx].id = item.children[idx].instance_name;\n            item.children[idx].isChildren = item.app_name;\n            return i.version;\n          })\n        )\n      );\n      c.length == 1 && (currentVersion = c[0]);\n      return {\n        ...item,\n        version: currentVersion,\n        can_upgrade: can_upgrade,\n        instance_name: item.app_name,\n        id: item.app_name || item.instance_name,\n        key: item.app_name || item.instance_name,\n      };\n    });\n    return result;\n  };\n\n  // 组件的checkedList并不能很好的对应业务数据需求，封装函数进行转换\n  const formatCheckedListData = (data) => {\n    return data.filter((item) => {\n      return item.isChildren;\n    });\n  };\n\n  // 升级命令下发\n  const doUpgrade = (checkedList) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.doUpgrade, {\n      body: {\n        choices: checkedList.map((item) => ({\n          app_id: item.can_upgrade[0].app_id,\n          service_id: item.service_id,\n        })),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            history.push({\n              pathname: \"/application_management/app_store/service_upgrade\",\n              state: {\n                history: res.data.history\n              },\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    sUModalVisibility && queryDataList();\n  }, [sUModalVisibility]);\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <ArrowUpOutlined />\n          </span>\n          <span>服务升级-选择应用服务</span>\n        </span>\n      }\n      width={600}\n      afterClose={() => {\n        setRows([]);\n        setCheckedList([]);\n      }}\n      onCancel={() => {\n        setSUModalVisibility(false);\n      }}\n      visible={sUModalVisibility}\n      footer={null}\n      //width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n    >\n      <div>\n        <div style={{ display: \"flex\", marginLeft: \"290px\", marginBottom: 15 }}>\n          <span\n            style={{\n              width: 70,\n              display: \"flex\",\n              alignItems: \"center\",\n              fontSize: 14,\n            }}\n          >\n            服务名称:\n          </span>\n          <Input\n            placeholder=\"请输入服务名称\"\n            style={{ width: 180 }}\n            allowClear\n            // size=\"small\"\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                queryDataList();\n              }\n            }}\n            onBlur={() => {\n              queryDataList(selectValue);\n            }}\n            onPressEnter={() => {\n              queryDataList(selectValue);\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n        </div>\n        <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n          <Table\n            size=\"small\"\n            scroll={{ y: 295 }}\n            loading={loading || initLoading}\n            //scroll={{ x: 1900 }}\n            columns={columns}\n            dataSource={dataSource}\n            pagination={false}\n            // notSelectable={(record) => ({\n            //   // is_continue的不能选中\n            //   disabled: !record.is_continue,\n            // })}\n            rowSelection={{\n              onChange: (selectedRowKeys, selectedRows, select, lll) => {\n                // 全选动作交给onchange事件\n                if (lock.current) {\n                  setRows(selectedRowKeys);\n                  setCheckedList(formatCheckedListData(selectedRows));\n                  // 关闭锁，下次事件触发时优先使用onselect处理rowkey变更\n                  lock.current = false;\n                }\n              },\n              selectedRowKeys: rows,\n              onSelect: (record, selected, selectedRows) => {\n                if (record.isChildren) {\n                  if (selected) {\n                    for (const item of dataSource) {\n                      if (item.instance_name == record.isChildren) {\n                        let results = item.children.filter(\n                          (i) => i.ip == record.ip\n                        );\n                        // results是当前点击要变动的\n                        // row中原本的数据不应该改变\n                        // row原本数据不改变,考虑到每次都是直接选中全部ip的项，也不会出现选中已经选中项的情况\n                        setRows((r) => {\n                          return [...r, ...results.map((k) => k.instance_name)];\n                        });\n                        setCheckedList((checks) => {\n                          return formatCheckedListData([...checks, ...results]);\n                        });\n                        break;\n                      }\n                    }\n                  } else {\n                    // 删除掉和record 父级和ip一样的项\n                    let checkedListCopy = JSON.parse(\n                      JSON.stringify(checkedList)\n                    );\n                    let results = formatCheckedListData(\n                      checkedListCopy.filter(\n                        (i) =>\n                          i.ip !== record.ip ||\n                          i.isChildren !== record.isChildren\n                      )\n                    );\n\n                    setRows(results.map((i) => i.instance_name));\n                    setCheckedList(results);\n                  }\n                } else {\n                  // 打开锁，使用onchange事件处理rowkey变更\n                  lock.current = true;\n                }\n              },\n              onSelectAll: (selected, selectedRows, changeRows) => {\n                // 打开锁，使用onchange事件处理rowkey变更\n                lock.current = true;\n              },\n              checkStrictly: false,\n            }}\n          />\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            marginTop: 20,\n            justifyContent: \"space-between\",\n            padding: \"0px 20px\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}></div>\n          <div style={{ display: \"flex\" }}>\n            <div style={{ marginRight: 15 }}>\n              已选择 {checkedList.length} 个\n            </div>\n            <div>共 {allLength} 个</div>\n          </div>\n        </div>\n        <div\n          style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n        >\n          <div\n            style={{\n              width: 170,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          >\n            <Button onClick={() => setSUModalVisibility(false)}>取消</Button>\n            <Button\n              type=\"primary\"\n              style={{ marginLeft: 16 }}\n              loading={loading || initLoading}\n              disabled={checkedList.length == 0}\n              onClick={() => {\n                doUpgrade(checkedList);\n              }}\n            >\n              确认选择\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default ServiceUpgradeModal;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Upgrade/content/component/UpgradeDetail.js",
    "content": "import { DownOutlined } from \"@ant-design/icons\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { OmpToolTip } from \"@/components\";\n\nconst stepOpen = {\n  marginTop: 10,\n  minHeight: 30,\n  height: 300,\n  transition: \"all .2s ease-in\",\n  //overflow: \"hidden\",\n  backgroundColor: \"#000\",\n  color: \"#fff\",\n  padding: 10,\n  overflowY: \"auto\",\n  whiteSpace: \"pre-line\",\n};\nconst stepNotOpen = {\n  height: 0,\n  minHeight: 0,\n  transition: \"all .2s ease-in\",\n  overflow: \"hidden\",\n  backgroundColor: \"#f9f9f9\",\n};\n\n// 状态渲染规则\nconst renderStatus = {\n  0: <span style={{ color: \"#f0c242\" }}>等待升级</span>,\n  1: <span style={{ color: \"rgba(0, 0, 0, 0.85)\" }}>正在升级</span>,\n  2: <span style={{ color: \"rgb(118,204,104)\" }}>升级成功</span>,\n  3: <span style={{ color: \"#da4e48\" }}>升级失败</span>,\n};\n\nconst UpgradeDetail = ({ title, ip, status, log, instance_name }) => {\n  const containerRef = useRef(null);\n\n  const [openName, setOpenName] = useState(\"\");\n\n  useEffect(() => {\n    containerRef.current.scrollTop = containerRef.current.scrollHeight;\n  }, [log]);\n\n  return (\n    <div\n      style={{\n        padding: 10,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          justifyContent: \"space-between\",\n        }}\n      >\n        <div style={{ flex: 3 }}>\n          <OmpToolTip maxLength={24}>{instance_name}</OmpToolTip>\n        </div>\n\n        <div style={{ flex: 1 }}>{renderStatus[status]}</div>\n        <div style={{ flex: 6, textAlign: \"right\", paddingRight: 50 }}>\n          <a\n            onClick={() => {\n              if (openName == `${title}=${ip}`) {\n                setOpenName(\"\");\n              } else {\n                setOpenName(`${title}=${ip}`);\n              }\n            }}\n          >\n            查看详细升级信息\n            <DownOutlined\n              style={{\n                transform: `rotate(${\n                  openName == `${title}=${ip}` ? 180 : 0\n                }deg)`,\n                position: \"relative\",\n                top: openName == `${title}=${ip}` ? -1 : 1,\n                left: 3,\n              }}\n            />\n          </a>\n        </div>\n      </div>\n      <div\n        ref={containerRef}\n        style={openName == `${title}=${ip}` ? stepOpen : stepNotOpen}\n      >\n        {log || \"暂无数据\"}\n      </div>\n    </div>\n  );\n};\n\nexport default UpgradeDetail;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Upgrade/content/component/UpgradeInfoItem.js",
    "content": "import UpgradeDetail from \"./UpgradeDetail\";\n\nconst UpgradeInfoItem = ({ id, data, title, log, idx }) => {\n  return (\n    <div\n      id={id}\n      style={{\n        //marginTop: 20,\n        backgroundColor: \"#fff\",\n        padding: 10,\n        //marginBottom: 15,\n        marginTop: idx !== 0 && 15,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          width: \"100%\",\n          position: \"relative\",\n          height: 40,\n          paddingTop: 10,\n        }}\n      >\n        <div\n          style={{\n            fontWeight: 500,\n            position: \"absolute\",\n            left: 30,\n            backgroundColor: \"#fff\",\n            paddingLeft: 20,\n            paddingRight: 20,\n          }}\n        >\n          {title}\n        </div>\n        <div style={{ height: 1, backgroundColor: \"#b3b2b3\", width: \"100%\" }} />\n      </div>\n      <div\n        style={{\n          paddingLeft: 20,\n          marginTop: 10,\n          paddingBottom: 5,\n          // paddingTop: 20,\n        }}\n      >\n        {data?.map((item) => {\n          console.log(item);\n          return (\n            <UpgradeDetail\n              title={title}\n              key={`${title}=${item.ip}=${item.instance_name}`}\n              status={item.upgrade_state}\n              ip={item.ip}\n              log={item.message}\n              instance_name={item.instance_name}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\nexport default UpgradeInfoItem;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Upgrade/content/index.js",
    "content": "import { Button, Anchor, Spin, Progress } from \"antd\";\nimport { useSelector } from \"react-redux\";\nimport UpgradeInfoItem from \"./component/UpgradeInfoItem\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { LoadingOutlined } from \"@ant-design/icons\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { fetchGet } from \"@/utils/request\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { fetchPut } from \"src/utils/request\";\nimport ServiceRollbackModal from \"../../ServiceRollbackModal\";\n\nconst { Link } = Anchor;\n// 状态渲染规则\nconst renderStatus = {\n  0: \"等待升级\",\n  1: \"正在升级\",\n  2: \"升级成功\",\n  3: \"升级失败\",\n  4: \"正在注册\",\n};\n\nconst Content = () => {\n  const history = useHistory();\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  // 在轮训时使用ref存值\n  const openNameRef = useRef(null);\n  const location = useLocation();\n  console.log(location?.state?.history);\n\n  const [loading, setLoading] = useState(true);\n\n  const [retryLoading, setRetryLoading] = useState(false);\n\n  const [data, setData] = useState({\n    detail: {},\n    upgrade_state: 0,\n  });\n\n  // 轮训的timer控制器\n  const timer = useRef(null);\n\n  const [vfModalVisibility, setVfModalVisibility] = useState(false);\n  const [rowId, setRowId] = useState(\"\");\n\n  const queryUpgradeProcess = () => {\n    !timer.current && setLoading(true);\n    fetchGet(\n      `${apiRequest.appStore.queryUpgradeProcess}/${location?.state?.history}`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setData(res.data);\n          if (\n            res.data.upgrade_state == 0 ||\n            res.data.upgrade_state == 1 ||\n            res.data.upgrade_state == 4\n          ) {\n            // 状态为未安装或者安装中\n            timer.current = setTimeout(() => {\n              queryUpgradeProcess();\n            }, 5000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const retryUpgrade = () => {\n    setRetryLoading(true);\n    fetchPut(\n      `${apiRequest.appStore.queryUpgradeProcess}/${location?.state?.history}`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            queryUpgradeProcess();\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setRetryLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryUpgradeProcess();\n    return () => {\n      // 页面销毁时清除延时器\n      clearTimeout(timer.current);\n    };\n  }, []);\n\n  return (\n    <div>\n      <div\n        style={{\n          display: \"flex\",\n          paddingTop: 20,\n        }}\n      >\n        <Spin spinning={loading}>\n          <div\n            id=\"Step4Wrapper\"\n            style={{\n              flex: 1,\n              //backgroundColor: \"#fff\",\n              height: viewHeight - 270,\n              overflowY: \"auto\",\n            }}\n          >\n            <div>\n              {data?.upgrade_detail?.map((item, idx) => {\n                return (\n                  <UpgradeInfoItem\n                    id={`a${idx}`}\n                    key={idx}\n                    title={item.service_name}\n                    data={item.upgrade_details}\n                    idx={idx}\n                  />\n                );\n              })}\n            </div>\n          </div>\n        </Spin>\n\n        <div\n          style={{\n            //height: 300,\n            width: 200,\n            backgroundColor: \"#fff\",\n            marginLeft: 20,\n            height: viewHeight - 270,\n            overflowY: \"auto\",\n            paddingTop: 10,\n          }}\n        >\n          <div style={{ paddingLeft: 5 }}>\n            <Anchor\n              style={{}}\n              affix={false}\n              getContainer={() => {\n                let con = document.getElementById(\"Step4Wrapper\");\n                return con;\n              }}\n              onClick={(e) => {\n                e.preventDefault();\n              }}\n            >\n              {data?.upgrade_detail?.map((item, idx) => {\n                let hasError =\n                  item.upgrade_details.filter((a) => a.upgrade_state == 3)\n                    .length !== 0;\n                return (\n                  <div style={{ padding: 5 }} key={idx}>\n                    <Link\n                      href={`#a${idx}`}\n                      title={\n                        <span style={{ color: hasError && \"rgb(218, 78, 72)\" }}>\n                          {item.service_name}\n                        </span>\n                      }\n                    />\n                  </div>\n                );\n              })}\n            </Anchor>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"fixed\",\n          backgroundColor: \"#fff\",\n          width: \"calc(100% - 230px)\",\n          bottom: 10,\n          padding: \"10px 0px\",\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          paddingRight: 30,\n          boxShadow: \"0px 0px 10px #999999\",\n          alignItems: \"center\",\n          borderRadius: 2,\n        }}\n      >\n        <div style={{ paddingLeft: 20, display: \"flex\" }}>\n          <div style={{ width: 100 }}>\n            {renderStatus[data.upgrade_state]}\n            {(data.upgrade_state == 0 ||\n              data.upgrade_state == 1 ||\n              data.upgrade_state == 4) && (\n              <LoadingOutlined style={{ marginLeft: 10, fontWeight: 600 }} />\n            )}\n          </div>\n        </div>\n        <div style={{ width: \"70%\" }}>\n          <Progress\n            percent={((data.success_count / data.all_count) * 100).toFixed()}\n            status={data.upgrade_state == 3 && \"exception\"}\n          />\n        </div>\n        <div style={{ paddingLeft: 60 }}>\n          {data.upgrade_state == 3 && (\n            <>\n              <Button\n                loading={retryLoading}\n                style={{ marginLeft: 10 }}\n                type=\"primary\"\n                //disabled={unassignedServices !== 0}\n                onClick={() => {\n                  setRowId(location?.state?.history);\n                  // retryUpgrade();\n                  setVfModalVisibility(true)\n                }}\n              >\n                回滚\n              </Button>\n              <Button\n                loading={retryLoading}\n                style={{ marginLeft: 10 }}\n                type=\"primary\"\n                //disabled={unassignedServices !== 0}\n                onClick={() => {\n                  retryUpgrade();\n                }}\n              >\n                重试\n              </Button>\n            </>\n          )}\n\n          <Button\n            style={{ marginLeft: 10 }}\n            type=\"primary\"\n            //disabled={unassignedServices !== 0}\n            onClick={() => {\n              history.push({\n                pathname: \"/application_management/install-record\",\n                state: {\n                  tabKey: \"upgrade\",\n                },\n              });\n            }}\n          >\n            完成\n          </Button>\n        </div>\n      </div>\n      <ServiceRollbackModal\n        sRModalVisibility={vfModalVisibility}\n        setSRModalVisibility={setVfModalVisibility}\n        initLoading={retryLoading}\n        fixedParams={`?history_id=${rowId}`}\n      />\n    </div>\n  );\n};\n\nexport default Content;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Upgrade/index.js",
    "content": "// 服务的升级和回滚\nimport { useHistory, useLocation } from \"react-router-dom\";\n// import { getTabKeyChangeAction } from \"../../store/actionsCreators\";\nimport { useDispatch } from \"react-redux\";\nimport { Steps } from \"antd\";\nimport { useState } from \"react\";\nimport styles from \"./index.module.less\";\n\nimport { LeftOutlined } from \"@ant-design/icons\";\nimport Content from \"./content/index.js\"\n// 安装页面\nconst Upgrade = () => {\n  // const dispatch = useDispatch();\n  const history = useHistory();\n\n  return (\n    <div>\n      <div\n        style={{\n          height: 50,\n          backgroundColor: \"#fff\",\n          display: \"flex\",\n          paddingLeft: 20,\n          paddingRight: 50,\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <div style={{ fontSize: 16 }}>\n          <LeftOutlined\n            style={{ fontSize: 16, marginRight: 20 }}\n            className={styles.backIcon}\n            onClick={() => {\n              history.push({\n                pathname: \"/application_management/install-record\",\n                state: {\n                  tabKey: \"upgrade\",\n                },\n              });\n            }}\n          />\n          服务升级\n        </div>\n        <div />\n      </div>\n      <Content />\n    </div>\n  );\n};\n\nexport default Upgrade;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/Upgrade/index.module.less",
    "content": ".backIcon:hover {\n    color: rgb(46, 124, 238);\n  }\n\n  :global {\n    .ant-anchor-ink::before {\n      background-color: #fff!important;\n    }\n  }"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/card.js",
    "content": "import { OmpToolTip } from \"@/components\";\nimport styles from \"./index.module.less\";\nimport imgObj from \"./img\";\nimport { useState } from \"react\";\n\nconst Card = ({ idx, history, info, tabKey, installOperation }) => {\n  //定义命名\n  let nameObj = {\n    component: {\n      logo: \"app_logo\",\n      name: \"app_name\",\n      version: \"app_version\",\n      description: \"app_description\",\n      instance_number: \"instance_number\",\n      install_url: \"/application_management/app_store/component_installation\",\n    },\n    service: {\n      logo: \"pro_logo\",\n      name: \"pro_name\",\n      version: \"pro_version\",\n      description: \"pro_description\",\n      instance_number: \"instance_number\",\n      install_url: \"/application_management/app_store/application_installation\",\n    },\n  };\n\n  const [isHover, setIsHover] = useState(false);\n\n  return (\n    <div\n      className={styles.cardContainer}\n      style={{\n        transition: \"all .2s ease-in-out\",\n        width: \"calc(97.7% / 4)\",\n        marginLeft: (idx - 1) % 4 !== 0 && \"0.75%\",\n        height: 200,\n        boxSizing: \"border-box\",\n        //border: \"1px solid #000\",\n        marginTop: 10,\n        position: \"relative\",\n        top: 0,\n        backgroundColor: \"#fff\",\n        paddingLeft: 10,\n        paddingRight: 10,\n      }}\n      onMouseEnter={() => {\n        setIsHover(true);\n      }}\n      onMouseLeave={() => {\n        setIsHover(false);\n      }}\n    >\n      <div className={styles.cardContent}>\n        <div style={{ width: 80, paddingTop: 10 }}>\n          {info[nameObj[tabKey].logo] ? (\n            <div\n              style={{\n                width: 50,\n                height: 50,\n                borderRadius: \"50%\",\n                border: \"1px solid #a8d0f8\",\n                display: \"flex\",\n                justifyContent: \"center\",\n                alignItems: \"center\",\n                marginLeft: 10,\n                marginRight: 10,\n                overflow: \"hidden\",\n              }}\n              dangerouslySetInnerHTML={{\n                __html: info[nameObj[tabKey].logo],\n              }}\n            ></div>\n          ) : (\n            <div\n              style={{\n                width: 50,\n                height: 50,\n                borderRadius: \"50%\",\n                border: \"1px solid #a8d0f8\",\n                display: \"flex\",\n                justifyContent: \"center\",\n                alignItems: \"center\",\n                marginLeft: 10,\n                marginRight: 10,\n                overflow: \"hidden\",\n                fontSize: 22,\n                backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n                // backgroundColor:\"#5c8df6\",\n                color: \"#fff\",\n              }}\n            >\n              <div style={{ textAlign: \"center\", position: \"relative\" }}>\n                {info[nameObj[tabKey].name] &&\n                  info[nameObj[tabKey].name][0].toLocaleUpperCase()}\n              </div>\n            </div>\n          )}\n        </div>\n        <div\n          style={{\n            //flex: 1,\n            fontSize: 13,\n            color: \"#a2a2a2\",\n            position: \"relative\",\n            width: \"calc(100% - 80px)\",\n          }}\n          onClick={() => {\n            history?.push({\n              pathname: `/application_management/app_store/app-${tabKey}-detail/${\n                info[nameObj[tabKey].name]\n              }/${info[nameObj[tabKey].version]}`,\n            });\n          }}\n        >\n          <div style={{ fontSize: 14, color: isHover ? \"#247fe6\" : \"#222222\" }}>\n            {info[nameObj[tabKey].name]}\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              fontSize: 12,\n              padding: \"8px 10px 10px 0px\",\n              //fontSize:12\n            }}\n          >\n            <span>最新版本</span>\n            <span>\n              <OmpToolTip maxLength={16}>\n                {info[nameObj[tabKey].version]}\n              </OmpToolTip>\n            </span>\n          </div>\n          <p className={styles.text}>\n            {/* <Tooltip placement=\"top\" title={info[nameObj[tabKey].description]}> */}\n            {info[nameObj[tabKey].description]}\n            {/* </Tooltip> */}\n          </p>\n          <span\n            style={{\n              float: \"right\",\n              position: \"absolute\",\n              bottom: 8,\n              right: 10,\n              fontSize: 12,\n            }}\n          >\n            已安装{info[nameObj[tabKey].instance_number]}个实例\n          </span>\n        </div>\n      </div>\n      <div\n        className={styles.cardBtn}\n        style={{ color: isHover ? \"#247fe6\" : \"rgba(0,0,0,0.65)\" }}\n      >\n        <div\n          style={{ borderRight: \"1px solid #e7e7e7\" }}\n          onClick={() => {\n            history?.push({\n              pathname: `/application_management/app_store/app-${tabKey}-detail/${\n                info[nameObj[tabKey].name]\n              }/${info[nameObj[tabKey].version]}`,\n            });\n          }}\n        >\n          查看\n        </div>\n        <div\n          onClick={() => {\n            if (tabKey == \"service\") {\n              installOperation({ product_name: info.pro_name }, \"服务\");\n            } else {\n              installOperation({ app_name: info.app_name }, \"组件\");\n              // history?.push({\n              //   pathname: `${nameObj[tabKey].install_url}/${\n              //     info[nameObj[tabKey].name]\n              //   }`,\n              // });\n            }\n          }}\n        >\n          安装\n        </div>\n      </div>\n    </div>\n  );\n};\nexport default Card;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/component/RenderComDependence.js",
    "content": "import { Form, Input, Select, Checkbox } from \"antd\";\nimport { useEffect, useState } from \"react\";\n// 组件安装的依赖信息\n\nconst randomNumber = () => {\n  let r = \"\";\n  let str = \"QWERTYUIOPLKJHGFDSAZXCVBNM123456790\";\n  new Array(6).fill(0).map((item) => {\n    let num = parseInt(Math.random() * 26);\n    r += str[num];\n  });\n  return r;\n};\n\nconst RenderComDependence = ({ data, form }) => {\n  if (data.is_base_env) {\n    return (\n      <div\n        key={data.name}\n        style={{\n          marginBottom: 20,\n          display: \"flex\",\n          //justifyContent: \"space-between\",\n          paddingRight: 80,\n        }}\n      >\n        <div\n          style={{\n            flex: 2,\n            paddingLeft: 20,\n          }}\n        >\n          {data.name}\n        </div>\n\n        <div\n          style={{\n            flex: 3,\n          }}\n        >\n          {data.version}\n        </div>\n        <div\n          style={{\n            flex: 15,\n          }}\n        />\n        <div style={{ color: \"#c2c2c2\", flex: 2 }}>\n          <Checkbox defaultChecked disabled style={{ marginRight: 10 }} />\n          安装依赖\n        </div>\n      </div>\n    );\n  } else {\n    // 当is_base_env为false的渲染逻辑\n    return <RenderHasData data={data} form={form} />;\n  }\n};\n\n// 分类渲染\nconst RenderHasData = ({ data, form }) => {\n  if (data.cluster_info?.length == 0 && data.instance_info?.length == 0) {\n    return <RenderClusterDom data={data} form={form} />;\n  } else {\n    let dataSource = data.cluster_info.concat(data.instance_info);\n    //console.log(dataSource);\n    // 因为是两个数据源拼接并且字段不一致在这里合并并统一字段\n    let result = dataSource.map((item) => {\n      if (item.ip) {\n        return {\n          id: `single|${JSON.stringify(item)}`,\n          name: item.service_instance_name,\n        };\n      } else {\n        return {\n          id: `cluster|${JSON.stringify(item)}`,\n          name: item.cluster_name,\n        };\n      }\n    });\n    return <RenderInstanceDom instanceData={result} data={data} form={form} />;\n  }\n};\n\n// 当判断为集群时的渲染\nconst RenderClusterDom = ({ data, form }) => {\n  // 将当前数据在deployModeObj初始化\n  // let obj = R.clone(deployModeObj);\n\n  // console.log(form.getFieldValue(`${data.name}|deploy_mode`));\n\n  return (\n    <Form form={form} name=\"basic\" layout=\"vertical\">\n      <div\n        key={data.name}\n        style={{\n          display: \"flex\",\n          //justifyContent: \"space-between\",\n          paddingRight: 80,\n          alignItems: \"center\",\n        }}\n      >\n        <div\n          style={{\n            flex: 2,\n            paddingLeft: 20,\n          }}\n        >\n          {data.name}\n        </div>\n\n        <div\n          style={{\n            flex: 3,\n          }}\n        >\n          {data.version}\n        </div>\n\n        <ClusterComponent form={form} data={data} />\n        <div\n          style={{\n            flex: 3,\n          }}\n        />\n        <div style={{ fontSize: 14, color: \"#c2c2c2\", flex: 2 }}>\n          {/* <Checkbox defaultChecked disabled style={{ marginRight: 10 }} />\n            安装依赖 */}\n        </div>\n      </div>\n    </Form>\n  );\n};\n\n// 单实例没有实例名称。其他有(单独封装为了复用)\nconst ClusterComponent = ({ data, form }) => {\n  let deploy =\n    data.deploy_mode && data.deploy_mode[0]\n      ? JSON.stringify(data.deploy_mode[0])\n      : \"\";\n  const [deploy_mode, setDeploy_mode] = useState(deploy);\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${data.name}|deploy_mode`]: JSON.stringify(deploy),\n      [`${data.name}|modeName`]: `${data.name}-${randomNumber()}`,\n    });\n  }, []);\n\n  return (\n    <>\n      <div\n        style={{\n          flex: 6,\n        }}\n      >\n        <Form.Item\n          label={<div style={{ fontSize: 12 }}>集群模式</div>}\n          name={`${data.name}|deploy_mode`}\n        >\n          <Select\n            style={{ width: 240 }}\n            onSelect={(e) => {\n              setDeploy_mode(e);\n            }}\n          >\n            {data.ser_deploy_mode?.map((item) => (\n              <Select.Option value={JSON.stringify(item)} key={item.key}>\n                {item.name}\n              </Select.Option>\n            ))}\n          </Select>\n        </Form.Item>\n      </div>\n      {deploy_mode !== '{\"key\":\"single\",\"name\":\"单实例\"}' ? (\n        <div\n          style={{\n            flex: 6,\n          }}\n        >\n          <Form.Item\n            label={<div style={{ fontSize: 12 }}>集群名称</div>}\n            name={`${data.name}|modeName`}\n            rules={[{ required: true, message: \"请输入集群名称\" }]}\n          >\n            <Input style={{ width: 240 }} />\n          </Form.Item>\n        </div>\n      ) : (\n        <div\n          style={{\n            flex: 6,\n          }}\n        />\n      )}\n    </>\n  );\n};\n\n// 判断为实例的渲染\nconst RenderInstanceDom = ({ instanceData, data, form }) => {\n  const [isMultiplexing, setIsMultiplexing] = useState(\"checked\");\n\n  useEffect(() => {\n    form.setFieldsValue({\n      [`${data.name}|isMultiplexing`]: \"checked\",\n      [`${data.name}|instance`]: instanceData[0].id,\n    });\n  }, []);\n\n  return (\n    <Form form={form} name=\"basic\" layout=\"vertical\">\n      <div\n        key={data.name}\n        style={{\n          display: \"flex\",\n          //justifyContent: \"space-between\",\n          paddingRight: 80,\n          alignItems: \"center\",\n        }}\n      >\n        <div\n          style={{\n            flex: 2,\n            paddingLeft: 20,\n          }}\n        >\n          {data.name}\n        </div>\n\n        <div\n          style={{\n            flex: 3,\n          }}\n        >\n          {data.version}\n        </div>\n        {isMultiplexing == \"checked\" ? (\n          <>\n            <div\n              style={{\n                flex: 6,\n              }}\n            >\n              <Form.Item\n                label={<div style={{ fontSize: 12 }}>选择实例</div>}\n                name={`${data.name}|instance`}\n              >\n                <Select style={{ width: 240 }}>\n                  {instanceData.map((item) => (\n                    <Select.Option value={item.id} key={item.id}>\n                      {item.name}\n                    </Select.Option>\n                  ))}\n                  {/* <Select.Option value={123} key={123}>\n                  {123}\n                </Select.Option> */}\n                </Select>\n              </Form.Item>\n            </div>\n            <div\n              style={{\n                flex: 6,\n              }}\n            />\n          </>\n        ) : (\n          <ClusterComponent form={form} data={data} />\n        )}\n\n        <div\n          style={{\n            flex: 3,\n          }}\n        >\n          存在实例,是否复用？\n        </div>\n        <div style={{ fontSize: 14, flex: 2 }}>\n          <Form.Item\n            valuePropName=\"checked\"\n            name={`${data.name}|isMultiplexing`}\n            style={{ marginBottom: 0 }}\n          >\n            <Checkbox\n              onChange={(e) => {\n                e.target.checked\n                  ? setIsMultiplexing(\"checked\")\n                  : setIsMultiplexing(\"unChecked\");\n              }}\n              style={{ marginRight: 10 }}\n            >\n              复用\n            </Checkbox>\n          </Form.Item>\n        </div>\n      </div>\n    </Form>\n  );\n};\n\nexport default RenderComDependence;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/detail.js",
    "content": "import img from \"@/config/logo/logo.svg\";\nimport styles from \"./index.module.less\";\nimport { LeftOutlined } from \"@ant-design/icons\";\nimport { Button, message, Select, Spin, Table } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { handleResponse } from \"@/utils/utils\";\nimport imgObj from \"./img\";\nimport moment from \"moment\";\nimport { getTabKeyChangeAction } from \"../store/actionsCreators\";\nimport { useDispatch } from \"react-redux\";\nimport { getStep1ChangeAction } from \"./Installation/store/actionsCreators\";\nimport { getUniqueKeyChangeAction } from \"../store/actionsCreators\";\n\nconst AppStoreDetail = () => {\n  const dispatch = useDispatch();\n  const history = useHistory();\n  const location = useLocation();\n  let arr = location.pathname.split(\"/\");\n  let name = arr[arr.length - 2];\n  let verson = arr[arr.length - 1];\n  // true 是组件， false是服务\n  let keyTab = location.pathname.includes(\"component\");\n\n  //定义命名\n  let nameObj = keyTab\n    ? {\n        logo: \"app_logo\",\n        name: \"app_name\",\n        version: \"app_version\",\n        description: \"app_description\",\n        instance_number: \"instance_number\",\n        package_md5: \"app_package_md5\",\n        type: \"app_labels\",\n        user: \"app_operation_user\",\n        dependence: \"app_dependence\",\n        instances_info: \"app_instances_info\",\n        install_url: \"/application_management/app_store/component_installation\",\n      }\n    : {\n        logo: \"pro_logo\",\n        name: \"pro_name\",\n        version: \"pro_version\",\n        description: \"pro_description\",\n        instance_number: \"instance_number\",\n        package_md5: \"pro_package_md5\",\n        type: \"pro_labels\",\n        user: \"pro_operation_user\",\n        dependence: \"pro_dependence\",\n        pro_services: \"pro_services\",\n        instances_info: \"pro_instances_info\",\n        install_url:\n          \"/application_management/app_store/application_installation\",\n      };\n\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState({});\n\n  const [versionValue, setVersionValue] = useState(\"\");\n\n  // 安装操作的loading\n  const [installLoading, setInstallLoading] = useState(false);\n\n  // 定义全部实例信息\n  const [allInstancesInfo, setAllInstancesInfo] = useState([]);\n\n  // 是否查看全部版本\n  const [isAll, setIsAll] = useState(false);\n\n  function fetchData() {\n    setLoading(true);\n    fetchGet(\n      keyTab\n        ? apiRequest.appStore.ProductDetail\n        : apiRequest.appStore.ApplicationDetail,\n      {\n        params: {\n          [keyTab ? \"app_name\" : \"pro_name\"]: name,\n        },\n      }\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setAllInstancesInfo(() => {\n            return res.data.versions\n              .map((item) => {\n                return item[nameObj.instances_info];\n              })\n              .flat();\n          });\n          setVersionValue(verson);\n          let y = (res.data.versions = res.data.versions.map((item) => {\n            // arr 为全部数据中version重复数据\n            let arr = [];\n            res.data.versions\n              .filter((i) => i[nameObj.version] == item[nameObj.version])\n              .map((v) => {\n                arr = [...arr, ...v[nameObj.instances_info]];\n              });\n            return {\n              ...item,\n              [nameObj.instances_info]: arr,\n            };\n          }));\n\n          setDataSource(() => {\n            let obj = {};\n            res.data.versions.map((item) => {\n              obj[item[nameObj.version]] = item;\n            });\n            return {\n              ...res.data,\n              versionObj: obj,\n            };\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  let currentVersionDataSource = dataSource.versionObj\n    ? dataSource.versionObj[versionValue]\n    : {};\n\n  const install = () => {\n    setInstallLoading(true);\n    if (keyTab) {\n      fetchPost(apiRequest.appStore.createComponentInstallInfo, {\n        body: {\n          high_availability: true,\n          install_component: [\n            { name: dataSource[nameObj.name], version: versionValue },\n          ],\n        },\n      })\n        .then((res) => {\n          //console.log(operateObj[operateAciton])\n          handleResponse(res, (res) => {\n            if (res.data && res.data.data) {\n              dispatch(getStep1ChangeAction(res.data.data));\n              dispatch(getUniqueKeyChangeAction(res.data.unique_key));\n            }\n            history.push(\"/application_management/app_store/installation\");\n          });\n        })\n        .catch((e) => console.log(e))\n        .finally(() => {\n          setInstallLoading(false);\n        });\n    } else {\n      fetchGet(apiRequest.appStore.queryBatchInstallationServiceList, {\n        params: {\n          product_name: dataSource[nameObj.name],\n        },\n      })\n        .then((res) => {\n          handleResponse(res, (res) => {\n            if (res.data && res.data.data) {\n              if (res.data.data.length == 1 && res.data.data[0].is_continue) {\n                dispatch(getUniqueKeyChangeAction(res.data.unique_key));\n                fetchPost(apiRequest.appStore.createInstallInfo, {\n                  body: {\n                    high_availability: true,\n                    install_product: [\n                      {\n                        name: dataSource[nameObj.name],\n                        version: versionValue,\n                      },\n                    ],\n                    unique_key: res.data.unique_key,\n                  },\n                })\n                  .then((res) => {\n                    //console.log(operateObj[operateAciton])\n                    handleResponse(res, (res) => {\n                      if (res.data && res.data.data) {\n                        dispatch(getStep1ChangeAction(res.data.data));\n                      }\n                      history.push(\n                        \"/application_management/app_store/installation\"\n                      );\n                    });\n                  })\n                  .catch((e) => console.log(e))\n                  .finally(() => {\n                    setInstallLoading(false);\n                  });\n              } else {\n                message.warning(\"该应用已经存在，不可重复安装\");\n                setInstallLoading(false);\n              }\n              // console.log(res.data.data);\n              // setBIserviceList(res.data.data);\n            }\n          });\n        })\n        .catch((e) => console.log(e))\n        .finally(() => {});\n      // console.log(\"服务\");\n    }\n  };\n\n  let tableData = isAll\n    ? allInstancesInfo\n    : currentVersionDataSource[nameObj.instances_info];\n\n  useEffect(() => {\n    fetchData();\n  }, []);\n\n  return (\n    <div className={styles.detailContainer}>\n      <Spin spinning={loading}>\n        <div className={styles.detailHeader}>\n          <div>\n            <LeftOutlined\n              style={{ fontSize: 16 }}\n              className={styles.backIcon}\n              onClick={() => {\n                keyTab\n                  ? dispatch(getTabKeyChangeAction(\"component\"))\n                  : dispatch(getTabKeyChangeAction(\"service\"));\n                history?.push({\n                  pathname: `/application_management/app_store`,\n                });\n              }}\n            />{\" \"}\n            <span style={{ paddingLeft: 20, fontSize: 16, color: \"#4c4c4c\" }}>\n              {dataSource[nameObj.name]}\n            </span>\n          </div>\n          <div style={{ marginRight: 30 }}>\n            {/* <Button\n              style={{ marginRight: 20 }}\n              onClick={() => {\n                history?.push({\n                  pathname: `${nameObj.install_url}/${\n                    dataSource[nameObj.name]\n                  }`,\n                });\n              }}\n            >\n              安装\n            </Button> */}\n            版本:{\" \"}\n            <Select\n              style={{ width: 160, marginLeft: 10 }}\n              value={versionValue}\n              onChange={(e) => {\n                setIsAll(false);\n                setVersionValue(e);\n              }}\n            >\n              {dataSource.versionObj &&\n                Object.keys(dataSource?.versionObj).map((item) => {\n                  return (\n                    <Select.Option key={item} value={item}>\n                      {item}\n                    </Select.Option>\n                  );\n                })}\n            </Select>\n          </div>\n        </div>\n        <div className={styles.detailTitle}>\n          <div\n            style={{\n              width: 80,\n              height: 80,\n              borderRadius: \"50%\",\n              border: \"1px solid #eaeaea\",\n              display: \"flex\",\n              justifyContent: \"center\",\n              alignItems: \"center\",\n              overflow: \"hidden\",\n              //padding: 10,\n            }}\n          >\n            {currentVersionDataSource[nameObj.logo] ? (\n              <div\n                style={{\n                  width: 80,\n                  height: 80,\n                  borderRadius: \"50%\",\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  alignItems: \"center\",\n                  backgroundColor: \"#fff\",\n                }}\n                dangerouslySetInnerHTML={{\n                  __html: currentVersionDataSource[nameObj.logo],\n                }}\n              ></div>\n            ) : (\n              <div\n                style={{\n                  width: 80,\n                  height: 80,\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  alignItems: \"center\",\n                  fontSize: 30,\n                  backgroundImage:\n                    \"linear-gradient(to right, #4f85f6, #669aee)\",\n                  // backgroundColor:\"#5c8df6\",\n                  color: \"#fff\",\n                }}\n              >\n                <div style={{ textAlign: \"center\", position: \"relative\" }}>\n                  {dataSource[nameObj.name] &&\n                    dataSource[nameObj.name][0].toLocaleUpperCase()}\n                </div>\n              </div>\n            )}\n          </div>\n          <div className={styles.detailTitleDescribe}>\n            <div className={styles.detailTitleDescribeText}>\n              {currentVersionDataSource[nameObj.description]}\n            </div>\n            <Button\n              loading={installLoading}\n              onClick={() => {\n                install();\n                // history?.push({\n                //   pathname: `${nameObj.install_url}/${\n                //     dataSource[nameObj.name]\n                //   }`,\n                // });\n              }}\n              // block\n              type=\"primary\"\n              size=\"small\"\n              style={{\n                position: \"absolute\",\n                bottom: 0,\n                paddingLeft: 20,\n                paddingRight: 20,\n              }}\n            >\n              安装\n            </Button>\n          </div>\n        </div>\n        <div className={styles.detailContent}>\n          <div className={styles.detailContentItem}>\n            <div className={styles.detailContentItemLabel}>类别:</div>\n            <div>{currentVersionDataSource[nameObj.type]?.join(\",\")}</div>\n          </div>\n          <div className={styles.detailContentItem}>\n            <div className={styles.detailContentItemLabel}>发布时间:</div>\n            <div>\n              {moment(currentVersionDataSource?.created).format(\n                \"YYYY-MM-DD HH:mm:ss\"\n              )}\n            </div>\n          </div>\n          <div className={styles.detailContentItem}>\n            <div className={styles.detailContentItemLabel}>MD5:</div>\n            <div>{currentVersionDataSource[nameObj.package_md5]}</div>\n          </div>\n          <div className={styles.detailContentItem}>\n            <div className={styles.detailContentItemLabel}>发布人:</div>\n            <div>{currentVersionDataSource[nameObj.user]}</div>\n          </div>\n        </div>\n        <div className={styles.detailDependence}>\n          <div>依赖信息</div>\n          {currentVersionDataSource[nameObj.dependence] ? (\n            <div className={styles.detailDependenceTable}>\n              <Table\n                size=\"middle\"\n                columns={[\n                  {\n                    title: \"名称\",\n                    key: \"name\",\n                    dataIndex: \"name\",\n                    align: \"center\",\n                  },\n                  {\n                    title: \"版本\",\n                    key: \"version\",\n                    dataIndex: \"version\",\n                    align: \"center\",\n                  },\n                ]}\n                pagination={false}\n                dataSource={JSON.parse(\n                  currentVersionDataSource[nameObj.dependence]\n                )}\n              />\n            </div>\n          ) : (\n            <p style={{ paddingTop: 10 }}>无</p>\n          )}\n        </div>\n        {!keyTab && (\n          <div className={styles.detailDependence}>\n            <div>包含服务</div>\n            {currentVersionDataSource.pro_services ? (\n              <div\n                className={styles.detailDependenceTable}\n                //style={{ width: 800 }}\n              >\n                <Table\n                  size=\"middle\"\n                  columns={[\n                    {\n                      title: \"名称\",\n                      key: \"name\",\n                      dataIndex: \"name\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"版本\",\n                      key: \"version\",\n                      dataIndex: \"version\",\n                      align: \"center\",\n                      render: (text) => {\n                        return text || \"-\";\n                      },\n                    },\n                    {\n                      title: \"MD5\",\n                      key: \"md5\",\n                      dataIndex: \"md5\",\n                      align: \"center\",\n                      render: (text) => {\n                        return text || \"-\";\n                      },\n                    },\n                    {\n                      title: \"发布时间\",\n                      key: \"created\",\n                      dataIndex: \"created\",\n                      align: \"center\",\n                      render: (text) => {\n                        return text || \"-\";\n                      },\n                    },\n                    {\n                      title: \"安装\",\n                      key: \"c\",\n                      dataIndex: \"c\",\n                      align: \"center\",\n                      render: (text, record) => {\n                        return (\n                          <a\n                            onClick={() => {\n                              setLoading(true);\n                              fetchPost(\n                                apiRequest.appStore.createComponentInstallInfo,\n                                {\n                                  body: {\n                                    high_availability: false,\n                                    install_component: [{\n                                      name:record.name,\n                                      version:record.version\n                                    }],\n                                  },\n                                }\n                              )\n                                .then((res) => {\n                                  //console.log(operateObj[operateAciton])\n                                  handleResponse(res, (res) => {\n                                    if (res.data && res.data.data) {\n                                      dispatch(\n                                        getStep1ChangeAction(res.data.data)\n                                      );\n                                      dispatch(\n                                        getUniqueKeyChangeAction(\n                                          res.data.unique_key\n                                        )\n                                      );\n                                    }\n                                    history.push(\n                                      \"/application_management/app_store/installation\"\n                                    );\n                                  });\n                                })\n                                .catch((e) => console.log(e))\n                                .finally(() => {\n                                  setLoading(false);\n                                });\n                            }}\n                          >\n                            点击安装\n                          </a>\n                        );\n                      },\n                    },\n                  ]}\n                  pagination={false}\n                  dataSource={currentVersionDataSource.pro_services}\n                />\n              </div>\n            ) : (\n              <p style={{ paddingTop: 10 }}>无</p>\n            )}\n          </div>\n        )}\n        {keyTab ? (\n          <div className={styles.detailDependence}>\n            <div>\n              实例信息\n              <span style={{ paddingLeft: 20, fontSize: 14, color: \"#1f8aee\" }}>\n                {isAll ? (\n                  <span\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => {\n                      setIsAll(false);\n                    }}\n                  >\n                    查看当前版本\n                  </span>\n                ) : (\n                  <span\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => {\n                      setIsAll(true);\n                    }}\n                  >\n                    查看全部版本\n                  </span>\n                )}\n              </span>\n            </div>\n            {tableData && tableData.length == 0 ? (\n              <p style={{ paddingTop: 10 }}>无</p>\n            ) : (\n              <div\n                className={styles.detailDependenceTable}\n                //style={{ width: 800 }}\n              >\n                <Table\n                  size=\"middle\"\n                  columns={[\n                    {\n                      title: \"实例名称\",\n                      key: \"instance_name\",\n                      dataIndex: \"instance_name\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"主机IP\",\n                      key: \"host_ip\",\n                      dataIndex: \"host_ip\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"端口\",\n                      key: \"service_port\",\n                      dataIndex: \"service_port\",\n                      align: \"center\",\n                      render: (text) => {\n                        if (!text) {\n                          return \"-\";\n                        }\n                        return text.map((i) => i.default).join(\", \");\n                      },\n                    },\n                    {\n                      title: \"版本\",\n                      key: \"app_version\",\n                      dataIndex: \"app_version\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"模式\",\n                      key: \"mode\",\n                      dataIndex: \"mode\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"安装时间\",\n                      key: \"created\",\n                      dataIndex: \"created\",\n                      align: \"center\",\n                      render: (text) => {\n                        return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n                      },\n                    },\n                  ]}\n                  //pagination={false}\n                  dataSource={tableData}\n                />\n              </div>\n            )}\n          </div>\n        ) : (\n          <div className={styles.detailDependence}>\n            <div>\n              实例信息\n              <span style={{ paddingLeft: 20, fontSize: 14, color: \"#1f8aee\" }}>\n                {isAll ? (\n                  <span\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => {\n                      setIsAll(false);\n                    }}\n                  >\n                    查看当前版本\n                  </span>\n                ) : (\n                  <span\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => {\n                      setIsAll(true);\n                    }}\n                  >\n                    查看全部版本\n                  </span>\n                )}\n              </span>\n            </div>\n            {tableData && tableData.length == 0 ? (\n              <p style={{ paddingTop: 10 }}>无</p>\n            ) : (\n              <div\n                className={styles.detailDependenceTable}\n                //style={{ width: \"100%\" }}\n              >\n                <Table\n                  size=\"middle\"\n                  columns={[\n                    {\n                      title: \"实例名称\",\n                      key: \"instance_name\",\n                      dataIndex: \"instance_name\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"版本\",\n                      key: \"version\",\n                      dataIndex: \"version\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"服务名称\",\n                      key: \"app_name\",\n                      dataIndex: \"app_name\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"服务版本\",\n                      key: \"app_version\",\n                      dataIndex: \"app_version\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"主机IP\",\n                      key: \"host_ip\",\n                      dataIndex: \"host_ip\",\n                      align: \"center\",\n                    },\n                    {\n                      title: \"端口\",\n                      key: \"service_port\",\n                      dataIndex: \"service_port\",\n                      align: \"center\",\n                      render: (text) => {\n                        if (!text) {\n                          return \"-\";\n                        }\n                        return text.map((i) => i.default).join(\",\");\n                      },\n                    },\n                    {\n                      title: \"安装时间\",\n                      key: \"created\",\n                      dataIndex: \"created\",\n                      align: \"center\",\n                      render: (text) => {\n                        return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n                      },\n                    },\n                  ]}\n                  //pagination={false}\n                  dataSource={tableData}\n                />\n              </div>\n            )}\n          </div>\n        )}\n      </Spin>\n    </div>\n  );\n};\n\nexport default AppStoreDetail;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/img.js",
    "content": "const componentImgStr =  `<?xml version=\"1.0\" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg t=\"1634633143436\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2388\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"200\" height=\"200\"><defs><style type=\"text/css\"></style></defs><path d=\"M882.521 282.988L527.534 78.127c-8.98-5.219-20.146-5.219-29.127 0L143.42 282.988c-8.98 5.219-14.563 14.806-14.563 25.244v409.842c0 10.437 5.583 20.025 14.563 25.244L498.407 948.3c4.491 2.548 9.588 3.884 14.563 3.884s10.073-1.334 14.563-3.884L882.52 743.318c8.98-5.219 14.563-14.806 14.563-25.244V308.232c0-10.437-5.583-20.025-14.563-25.244zM838.83 701.326L512.971 889.438 187.112 701.326V325.101l325.859-188.112L838.83 325.101v376.225z\" p-id=\"2389\"></path><path d=\"M270.124 383.476c-8.01 13.957-3.277 31.797 10.681 39.807l202.676 116.994v231.439c0 16.142 12.986 29.127 29.127 29.127s29.127-12.986 29.127-29.127V540.641l203.404-117.479c13.957-8.01 18.69-25.851 10.681-39.807s-25.851-18.69-39.807-10.681L512.973 489.91l-203.04-117.236c-13.957-8.01-31.676-3.155-39.807 10.801z\" p-id=\"2390\"></path></svg>`\nconst serviceImgStr = `<?xml version=\"1.0\" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg t=\"1634633273202\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"6097\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"200\" height=\"200\"><defs><style type=\"text/css\"></style></defs><path d=\"M501.9 879.5L135.8 709.4c-19.5-9.1-19.5-35.2 0-44.3L501.9 495c7.4-3.4 16.1-3.4 23.5 0l362.9 170.1c19.4 9.1 19.4 35.1 0 44.2L525.4 879.5c-7.4 3.5-16.1 3.5-23.5 0z\" fill=\"#A9D8FF\" opacity=\".5\" p-id=\"6098\"></path><path d=\"M501.9 679.2L135.8 509.1c-19.5-9.1-19.5-35.2 0-44.3l366.1-170.1c7.4-3.4 16.1-3.4 23.5 0l362.9 170.1c19.4 9.1 19.4 35.1 0 44.2L525.4 679.2c-7.4 3.4-16.1 3.4-23.5 0z\" fill=\"#A9D8FF\" p-id=\"6099\"></path><path d=\"M501.9 528.9L135.8 358.8c-19.5-9.1-19.5-35.2 0-44.3l366.1-170.1c7.4-3.4 16.1-3.4 23.5 0l362.9 170.1c19.4 9.1 19.4 35.1 0 44.2L525.4 528.9c-7.4 3.5-16.1 3.5-23.5 0z\" fill=\"#298DF7\" p-id=\"6100\"></path></svg>`\n\nexport default {\n    component:componentImgStr,\n    service:serviceImgStr\n}"
  },
  {
    "path": "omp_web/src/pages/AppStore/config/index.module.less",
    "content": ".cardContainer:hover {\n  //top: -2px !important;\n  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);\n  color: black !important;\n}\n\n.cardContainer {\n  cursor: pointer;\n  .cardContent {\n    height: 76%;\n    display: flex;\n    padding-top: 10px;\n    .text {\n      font-size: 12px;\n      display: -webkit-box;\n      -webkit-line-clamp: 2;\n      -webkit-box-orient: vertical;\n      //height: r(210);\n      //font-size: r(42);\n      // white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      width: 100%;\n      margin: 0;\n    }\n  }\n  .cardBtn {\n    display: flex;\n    //justify-content: space-around;\n    border-top: solid 1px #e7e7e7;\n    align-items: center;\n    height: 24%;\n    // /color: #818181;\n    font-size: 13px;\n    & > div {\n      width: 50%;\n      text-align: center;\n    }\n    // & > div:hover {\n    //   color: rgb(46, 124, 238);\n    // }\n  }\n}\n.detailContainer {\n  background-color: #fff;\n  padding-top: 10px;\n  padding-left: 3px;\n  padding-right: 3px;\n  .detailHeader {\n    overflow: hidden;\n    background-color: #f5f6f5;\n    height: 40px;\n    line-height: 40px;\n    padding-left: 20px;\n    display: flex;\n    justify-content: space-between;\n  }\n\n  .detailTitle {\n    height: 120px;\n    display: flex;\n    padding-left: 20px;\n    padding-top: 0px;\n    padding-right: 200px;\n    // background-color: aliceblue;\n    align-items: center;\n    .detailTitleDescribe {\n      height: 80px;\n      padding-left: 60px;\n      //word-wrap: break-word;\n      width: calc(100% - 120px);\n      color: #4f4f4f;\n      position: relative;\n      .detailTitleDescribeText {\n        display: -webkit-box;\n        -webkit-line-clamp: 2;\n        -webkit-box-orient: vertical;\n        //height: r(210);\n        //font-size: r(42);\n        // white-space: nowrap;\n        text-overflow: ellipsis;\n        overflow: hidden;\n        // /width: 100%;\n      }\n    }\n  }\n  .detailContent {\n    color: #4f4f4f;\n    padding-left: 30px;\n    .detailContentItem {\n      display: flex;\n      padding-top: 15px;\n      .detailContentItemLabel {\n        width: 120px;\n      }\n    }\n  }\n  .detailDependence {\n    padding-left: 30px;\n    margin-top: 40px;\n    font-size: 16px;\n    //background-color: red;\n    .detailDependenceTable {\n      //width: 100%;\n      margin-right: 20px;\n      border: 1px solid #d6d6d6;\n      margin-top: 20px;\n    }\n  }\n}\n.backIcon:hover {\n  color: rgb(46, 124, 238);\n}"
  },
  {
    "path": "omp_web/src/pages/AppStore/index.js",
    "content": "import { Input, Button, Pagination, Empty, Spin, Dropdown, Menu } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styles from \"./index.module.less\";\nimport {\n  SearchOutlined,\n  DownloadOutlined,\n  DownOutlined,\n  SendOutlined,\n  ScanOutlined,\n  ArrowUpOutlined,\n  SyncOutlined,\n  DeleteOutlined,\n  ZoomInOutlined,\n} from \"@ant-design/icons\";\nimport Card from \"./config/card.js\";\nimport { useSelector, useDispatch } from \"react-redux\";\nimport { useHistory } from \"react-router-dom\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse, downloadFile } from \"@/utils/utils\";\nimport ReleaseModal from \"./config/ReleaseModal.js\";\nimport ScanServerModal from \"./config/ScanServerModal\";\nimport DeleteServerModal from \"./config/DeleteServerModal\";\n// 批量安装弹框组件\nimport BatchInstallationModal from \"./config/BatchInstallationModal\";\nimport ServiceUpgradeModal from \"./config/ServiceUpgradeModal\";\nimport ServiceRollbackModal from \"./config/ServiceRollbackModal\";\nimport {\n  getTabKeyChangeAction,\n  getUniqueKeyChangeAction,\n} from \"./store/actionsCreators\";\nimport GetServiceModal from \"./config/GetServiceModal\";\n\nconst AppStore = () => {\n  // appStoreTabKey\n  const appStoreTabKey = useSelector((state) => state.appStore.appStoreTabKey);\n\n  const dispatch = useDispatch();\n  // 视口高度\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  const history = useHistory();\n  const [tabKey, setTabKey] = useState(appStoreTabKey);\n  const [searchKey, setSearchKey] = useState(\"全部\");\n  const [searchData, setSearchData] = useState([]);\n\n  const [searchName, setSearchName] = useState(\"\");\n\n  const [total, setTotal] = useState(0);\n\n  const [timeUnix, setTimeUnix] = useState(\"\");\n\n  const [loading, setLoading] = useState(false);\n  const [dataSource, setDataSource] = useState([]);\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: viewHeight > 955 ? 12 : 8,\n    total: 0,\n    searchParams: {},\n  });\n\n  // 发布操作\n  const [releaseModalVisibility, setReleaseModalVisibility] = useState(false);\n\n  // 扫描服务端\n  const [scanServerModalVisibility, setScanServerModalVisibility] =\n    useState(false);\n\n  // 服务升级操作弹框\n  const [sUModalVisibility, setSUModalVisibility] = useState(false);\n\n  // 服务回退操作弹框\n  const [sRModalVisibility, setSRModalVisibility] = useState(false);\n\n  // 批量安装弹框\n  const [bIModalVisibility, setBIModalVisibility] = useState(false);\n\n  // 删除应用商店\n  const [deleteServerVisibility, setDeleteServerVisibility] = useState(false);\n\n  // 批量安装的应用服务列表\n  const [bIserviceList, setBIserviceList] = useState([]);\n\n  // 服务纳管弹框\n  const [serviceGetModalVisibility, setServiceGetModalVisibility] =\n    useState(false);\n\n  const [serviceGetData, setServiceGetData] = useState([]);\n  const [initData, setInitData] = useState([]);\n\n  // 批量安装标题文案\n  const installTitle = useRef(\"批量\");\n\n  function fetchData(pageParams = { current: 1, pageSize: 8 }, searchParams) {\n    setLoading(true);\n    fetchGet(\n      searchParams.tabKey == \"component\"\n        ? apiRequest.appStore.queryComponents\n        : apiRequest.appStore.queryServices,\n      {\n        params: {\n          page: pageParams.current,\n          size: pageParams.pageSize,\n          ...searchParams,\n          tabKey: null,\n        },\n      }\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          // 获得真正的总数，要查询条件都为空时\n          let obj = { ...searchParams };\n          delete obj.tabKey;\n          let arr = Object.values(obj).filter((i) => i);\n          if (arr.length == 0) {\n            setTotal(res.data.count);\n          }\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchSearchlist();\n        //fetchIPlist();\n      });\n  }\n\n  // 获取批量安装应用服务列表\n  const queryBatchInstallationServiceList = (queryData) => {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.queryBatchInstallationServiceList, {\n      params: queryData,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            setBIserviceList(\n              res.data.data.map((item) => ({ ...item, id: item.name }))\n            );\n            dispatch(getUniqueKeyChangeAction(res.data.unique_key));\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 获取安装基础组件列表\n  const queryInstallComponent = (queryData) => {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.ProductDetail, {\n      params: queryData,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          if (res.data) {\n            let serverlist = {};\n            serverlist.name = res.data.app_name;\n            serverlist.is_continue = true;\n            serverlist.version = res.data.versions.map((item) => {\n              return item.app_version;\n            });\n            setBIserviceList([serverlist]);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const fetchSearchlist = () => {\n    //setSearchLoading(true);\n    fetchGet(apiRequest.appStore.queryLabels, {\n      params: {\n        label_type: tabKey == \"component\" ? 0 : 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setSearchData(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        //setSearchLoading(false);\n      });\n  };\n\n  // 获取可纳管服务列表\n  const queryAllAppList = () => {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.queryAppList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const resArr = res.data;\n          for (let i = 0; i < resArr.length; i++) {\n            const element = resArr[i];\n            if (element.hasOwnProperty(\"child\")) {\n              element.children = element.child[element.version[0]];\n            }\n          }\n          setServiceGetData(resArr);\n          setInitData(resArr);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData(\n      { current: 1, pageSize: pagination.pageSize },\n      {\n        ...pagination.searchParams,\n        tabKey: tabKey,\n        type: searchKey == \"全部\" ? null : searchKey,\n      }\n    );\n\n    return () => {\n      dispatch(getTabKeyChangeAction(tabKey));\n    };\n  }, [tabKey, searchKey]);\n\n  const refresh = () => {\n    fetchData(\n      { current: 1, pageSize: pagination.pageSize },\n      {\n        ...pagination.searchParams,\n        tabKey: tabKey,\n        type: searchKey == \"全部\" ? null : searchKey,\n      }\n    );\n  };\n\n  return (\n    <div>\n      <div className={styles.header}>\n        <div className={styles.headerTabRow}>\n          <div\n            className={styles.headerTab}\n            onClick={(e) => {\n              setPagination({\n                current: 1,\n                pageSize: viewHeight > 955 ? 12 : 8,\n                total: 0,\n                searchParams: {},\n              });\n              setSearchName(\"\");\n              setSearchKey(\"全部\");\n              if (e.target.innerHTML == \"应用服务\") {\n                setTabKey(\"service\");\n              } else if (e.target.innerHTML == \"基础组件\") {\n                setTabKey(\"component\");\n              }\n            }}\n          >\n            <div\n              style={\n                tabKey == \"component\" ? { color: \"rgb(46, 124, 238)\" } : {}\n              }\n            >\n              基础组件\n            </div>\n            <div>|</div>\n            <div\n              style={tabKey == \"service\" ? { color: \"rgb(46, 124, 238)\" } : {}}\n            >\n              应用服务\n            </div>\n          </div>\n          <div className={styles.headerBtn}>\n            <Input\n              placeholder=\"请输入应用名称\"\n              suffix={\n                !searchName && <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n              }\n              style={{ marginRight: 10, width: 200 }}\n              value={searchName}\n              allowClear\n              onChange={(e) => {\n                setSearchName(e.target.value);\n                if (!e.target.value) {\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      [tabKey == \"component\" ? \"app_name\" : \"pro_name\"]: null,\n                    }\n                  );\n                }\n              }}\n              onBlur={() => {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    [tabKey == \"component\" ? \"app_name\" : \"pro_name\"]:\n                      searchName,\n                  }\n                );\n              }}\n              onPressEnter={() => {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    [tabKey == \"component\" ? \"app_name\" : \"pro_name\"]:\n                      searchName,\n                  }\n                );\n              }}\n            />\n            <Button\n              style={{ marginRight: 10 }}\n              type=\"primary\"\n              onClick={() => {\n                installTitle.current = \"批量\";\n                queryBatchInstallationServiceList();\n                setBIModalVisibility(true);\n              }}\n            >\n              批量安装\n            </Button>\n\n            <Dropdown\n              overlay={\n                <Menu\n                  style={{\n                    width: \"calc(100% + 40px)\",\n                    position: \"relative\",\n                    left: -20,\n                  }}\n                >\n                  <Menu.Item\n                    key=\"publishing\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => {\n                      setTimeUnix(new Date().getTime());\n                      setReleaseModalVisibility(true);\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <SendOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 6,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>上传发布服务</div>\n                    </div>\n                  </Menu.Item>\n                  <Menu.Item\n                    key=\"scanServer\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => {\n                      setScanServerModalVisibility(true);\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <ScanOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 4,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>扫描发布服务</div>\n                    </div>\n                  </Menu.Item>\n                  <Menu.Item\n                    key=\"deleteServer\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => setDeleteServerVisibility(true)}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <DeleteOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 4,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>删除</div>\n                    </div>\n                  </Menu.Item>\n\n                  <div\n                    style={{\n                      height: 1,\n                      backgroundColor: \"#e3e3e3\",\n                      margin: \"6px 10px\",\n                    }}\n                  ></div>\n                  <Menu.Item\n                    key=\"upgrade\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => {\n                      setSUModalVisibility(true);\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <ArrowUpOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 4,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>服务升级</div>\n                    </div>\n                  </Menu.Item>\n                  <Menu.Item\n                    key=\"rollback\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => {\n                      setSRModalVisibility(true);\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <SyncOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 4,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>服务回滚</div>\n                    </div>\n                  </Menu.Item>\n                  <div\n                    style={{\n                      height: 1,\n                      backgroundColor: \"#e3e3e3\",\n                      margin: \"6px 6px\",\n                    }}\n                  ></div>\n                  <Menu.Item\n                    key=\"getService\"\n                    style={{ display: \"flex\" }}\n                    onClick={() => {\n                      queryAllAppList();\n                      setServiceGetModalVisibility(true);\n                    }}\n                  >\n                    <div\n                      style={{\n                        display: \"flex\",\n                        alignItems: \"center\",\n                        padding: \"5px 0 5px 5px\",\n                      }}\n                    >\n                      <div\n                        style={{\n                          width: 22,\n                          height: 22,\n                          backgroundColor: \"#2e7cee\",\n                          borderRadius: \"50%\",\n                        }}\n                      >\n                        <ZoomInOutlined\n                          style={{\n                            color: \"#fff\",\n                            position: \"relative\",\n                            left: 4,\n                          }}\n                        />\n                      </div>\n                      <div style={{ paddingLeft: 20 }}>服务纳管</div>\n                    </div>\n                  </Menu.Item>\n                </Menu>\n              }\n              placement=\"bottomRight\"\n            >\n              <Button\n                style={{ marginRight: 10, paddingRight: 10, paddingLeft: 15 }}\n              >\n                更多\n                <DownOutlined style={{ position: \"relative\", top: 1 }} />\n              </Button>\n            </Dropdown>\n          </div>\n        </div>\n\n        <hr className={styles.headerHr} />\n        <div className={styles.headerSearch}>\n          <div\n            className={styles.headerSearchCondition}\n            onClick={(e) => {\n              // 在把含有&符号的字符串存进数据库后，再读出来的时候，发现&都变成了&amp;\n              let str = e.target.innerHTML.replace(\n                new RegExp(\"&amp;\", \"g\"),\n                \"&\"\n              );\n              if (searchData?.indexOf(str) !== -1 || str == \"全部\") {\n                setSearchKey(str);\n              }\n            }}\n          >\n            <p\n              style={searchKey == \"全部\" ? { color: \"rgb(46, 124, 238)\" } : {}}\n            >\n              全部\n            </p>\n            {searchData.map((item) => {\n              return (\n                <p\n                  style={\n                    searchKey == item ? { color: \"rgb(46, 124, 238)\" } : {}\n                  }\n                  key={item}\n                >\n                  {item}\n                </p>\n              );\n            })}\n          </div>\n          <div className={styles.headerSearchInfo}>\n            <Button\n              style={{ marginRight: 15, fontSize: 13 }}\n              icon={<DownloadOutlined />}\n              onClick={() => {\n                downloadFile(apiRequest.appStore.applicationTemplate);\n              }}\n            >\n              <span style={{ color: \"#818181\" }}>下载发布说明</span>\n            </Button>\n            共收录 {total} 个{tabKey == \"component\" ? \"基础组件\" : \"应用服务\"}\n          </div>\n        </div>\n      </div>\n      <Spin spinning={loading}>\n        <div style={{ display: \"flex\", flexWrap: \"wrap\" }}>\n          {dataSource.length == 0 ? (\n            <Empty\n              style={{\n                width: \"100%\",\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                height: viewHeight > 955 ? 500 : 300,\n                flexDirection: \"column\",\n              }}\n              description={\n                tabKey == \"component\" ? \"商店暂无基础组件\" : \"商店暂无应用服务\"\n              }\n            />\n          ) : (\n            <>\n              {dataSource.map((item, idx) => {\n                return (\n                  <Card\n                    history={history}\n                    key={idx}\n                    idx={idx + 1}\n                    info={item}\n                    tabKey={tabKey}\n                    installOperation={(queryData, type) => {\n                      if (type == \"服务\") {\n                        installTitle.current = type;\n                        queryBatchInstallationServiceList(queryData);\n                      } else {\n                        installTitle.current = type;\n                        // 组件安装组件列表\n                        queryInstallComponent(queryData);\n                      }\n\n                      setBIModalVisibility(true);\n                    }}\n                  />\n                );\n              })}\n            </>\n          )}\n        </div>\n      </Spin>\n      {dataSource.length !== 0 && (\n        <div\n          style={{\n            display: \"flex\",\n            justifyContent: \"center\",\n            position: \"relative\",\n            top: 25,\n          }}\n        >\n          <Pagination\n            onChange={(e) => {\n              fetchData(\n                { ...pagination, current: e },\n                {\n                  ...pagination.searchParams,\n                }\n              );\n            }}\n            current={pagination.current}\n            pageSize={pagination.pageSize}\n            total={pagination.total}\n          />\n        </div>\n      )}\n      <ReleaseModal\n        timeUnix={timeUnix}\n        releaseModalVisibility={releaseModalVisibility}\n        setReleaseModalVisibility={setReleaseModalVisibility}\n        refresh={refresh}\n      />\n      <ScanServerModal\n        scanServerModalVisibility={scanServerModalVisibility}\n        setScanServerModalVisibility={setScanServerModalVisibility}\n        refresh={refresh}\n      />\n      <BatchInstallationModal\n        bIModalVisibility={bIModalVisibility}\n        setBIModalVisibility={setBIModalVisibility}\n        dataSource={bIserviceList}\n        installTitle={installTitle.current}\n        initLoading={loading}\n      />\n      <ServiceUpgradeModal\n        sUModalVisibility={sUModalVisibility}\n        setSUModalVisibility={setSUModalVisibility}\n        dataSource={bIserviceList}\n        initLoading={loading}\n      />\n      <ServiceRollbackModal\n        sRModalVisibility={sRModalVisibility}\n        setSRModalVisibility={setSRModalVisibility}\n        dataSource={bIserviceList}\n        initLoading={loading}\n      />\n      <DeleteServerModal\n        deleteServerVisibility={deleteServerVisibility}\n        setDeleteServerVisibility={setDeleteServerVisibility}\n        tabKey={tabKey}\n        refresh={refresh}\n      />\n      <GetServiceModal\n        modalVisibility={serviceGetModalVisibility}\n        setModalVisibility={setServiceGetModalVisibility}\n        initData={initData}\n        dataSource={serviceGetData}\n        setDataSource={setServiceGetData}\n        initLoading={loading}\n      />\n    </div>\n  );\n};\n\nexport default AppStore;\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/index.module.less",
    "content": ".header {\n  background-color: #fff;\n  padding-bottom: 10px;\n  //display: flex;\n  //padding:20px;\n  .headerTabRow {\n    display: flex;\n    //align-items: center;\n    justify-content: space-between;\n    .headerTab {\n      display: flex;\n      color: #4f4f4f;\n      font-size: 14px;\n      margin-top: 20px;\n      & > div:nth-child(1) {\n        padding-left: 20px;\n        //margin-top: 20px;\n        cursor: pointer;\n      }\n      & > div:nth-child(2) {\n        padding-left: 10px;\n        padding-right: 10px;\n        //margin-top: 20px;\n        color: #b9b9b9;\n      }\n      & > div:nth-child(3) {\n       // margin-top: 20px;\n        cursor: pointer;\n      }\n    }\n    .headerBtn {\n        display: flex;\n        padding-top: 10px;\n        padding-bottom: 5px;\n        position: relative;\n        top: 3px;\n        padding-right:20px\n    }\n  }\n\n  .headerHr {\n    border-top: solid 1px #e7e7e7;\n    border-left: solid 1px #e7e7e7;\n  }\n\n  .headerSearch {\n    padding-left: 20px;\n    display: flex;\n    justify-content: space-between;\n    font-size: 12px;\n    .headerSearchCondition {\n        position: relative;\n        top:5px;\n        display: flex;\n        & > p {\n            padding-right: 20px;\n            cursor: pointer;\n        }\n    }\n    .headerSearchInfo {\n        color: #818181;\n        padding-right: 10px;\n    }\n  }\n}"
  },
  {
    "path": "omp_web/src/pages/AppStore/store/actionsCreators.js",
    "content": "import * as actionTypes from \"./constants\";\n\nexport const getTabKeyChangeAction = (value) => ({\n  type: actionTypes.CHANGE_APPSTORETABKEY,\n  payload: {\n    appStoreTabKey: value,\n  },\n});\n\nexport const getUniqueKeyChangeAction = (value) => ({\n  type: actionTypes.SETUNIQUE_KEY,\n  payload: {\n    uniqueKey: value,\n  },\n});\n"
  },
  {
    "path": "omp_web/src/pages/AppStore/store/constants.js",
    "content": "export const CHANGE_APPSTORETABKEY = \"CHANGE_APPSTORETABKEY\";\n\nexport const SETUNIQUE_KEY = \"SETUNIQUE_KEY\";"
  },
  {
    "path": "omp_web/src/pages/AppStore/store/index.js",
    "content": "import reducer from \"./reduer\";\n\n export {\n    reducer\n };"
  },
  {
    "path": "omp_web/src/pages/AppStore/store/reduer.js",
    "content": "import * as actionTypes from \"./constants\";\n\nconst defaultState = {\n  appStoreTabKey: \"component\",\n  uniqueKey: \"\",\n};\n\nfunction reducer(state = defaultState, action) {\n  switch (action.type) {\n    case actionTypes.CHANGE_APPSTORETABKEY:\n      return { ...state, appStoreTabKey: action.payload.appStoreTabKey };\n    case actionTypes.SETUNIQUE_KEY:\n      return { ...state, uniqueKey: action.payload.uniqueKey };\n    default:\n      return state;\n  }\n}\n\nexport default reducer;\n"
  },
  {
    "path": "omp_web/src/pages/BackupRecords/config/columns.js",
    "content": "import { renderDisc } from \"@/utils/utils\";\nimport { Tooltip } from \"antd\";\nimport moment from \"moment\";\n\nconst renderResult = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"critical\", 7, -1)}失败</span>;\n    case 1:\n      return <span>{renderDisc(\"normal\", 7, -1)}成功</span>;\n    case 2:\n      return <span>{renderDisc(\"warning\", 7, -1)}执行中</span>;\n  }\n};\n\nconst getColumnsConfig = (setRow, setDeleteOneModal) => {\n  // 推送邮件相关数据\n\n  return [\n    {\n      title: \"任务名称\",\n      key: \"backup_name\",\n      dataIndex: \"backup_name\",\n      align: \"center\",\n      width: 240,\n      ellipsis: true,\n      fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text || \"-\"} placement=\"topLeft\">\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"状态\",\n      key: \"result\",\n      dataIndex: \"result\",\n      align: \"center\",\n      width: 100,\n      render: (text) => {\n        return renderResult(text);\n      },\n    },\n    {\n      title: \"备份实例\",\n      key: \"content\",\n      dataIndex: \"content\",\n      align: \"center\",\n      width: 140,\n      ellipsis: true,\n    },\n    {\n      title: \"备份文件\",\n      key: \"file_name\",\n      dataIndex: \"file_name\",\n      align: \"center\",\n      width: 180,\n      ellipsis: true,\n      render: (text, record) => {\n        if (record?.file_deleted) {\n          return \"-\";\n        }\n        return (\n          <Tooltip title={text || \"-\"} placement=\"topLeft\">\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"文件大小\",\n      key: \"file_size\",\n      dataIndex: \"file_size\",\n      align: \"center\",\n      width: 80,\n      ellipsis: true,\n      // render: (text) => {\n      //   return <span>{text ? `${text} M` : \"-\"}</span>;\n      // },\n    },\n    {\n      title: \"备份路径\",\n      key: \"retain_path\",\n      dataIndex: \"retain_path\",\n      width: 160,\n      align: \"center\",\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text || \"-\"} placement=\"topLeft\">\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"远程路径\",\n      key: \"remote_path\",\n      dataIndex: \"remote_path\",\n      width: 160,\n      align: \"center\",\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text || \"-\"} placement=\"topLeft\">\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"过期时间\",\n      key: \"expire_time\",\n      dataIndex: \"expire_time\",\n      width: 180,\n      align: \"center\",\n      ellipsis: true,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        }\n        return \"-\";\n      },\n    },\n    {\n      title: \"信息\",\n      key: \"message\",\n      dataIndex: \"message\",\n      align: \"center\",\n      width: 180,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text || \"-\"} placement=\"topLeft\">\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: (text, record, index) => {\n        return (\n          <div\n            onClick={() => {\n              setRow(record);\n            }}\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              {record.result === 2 ? (\n                <>\n                  {/* <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>下载</span> */}\n                  <span\n                    style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}\n                  >\n                    删除\n                  </span>\n                </>\n              ) : (\n                <>\n                  {/* <a\n                    onClick={() => {\n                      if (record.file_name || record.result === 1) {\n                        let a = document.createElement(\"a\");\n                        a.href = `/download-backup/${record.file_name}`;\n                        document.body.appendChild(a);\n                        a.click();\n                        document.body.removeChild(a);\n                      } else {\n                        message.warning(\"该任务文件不支持下载\");\n                      }\n                    }}\n                  >\n                    下载\n                  </a> */}\n                  <a\n                    style={{ marginLeft: 10 }}\n                    onClick={() => setDeleteOneModal(true)}\n                  >\n                    删除\n                  </a>\n                </>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/BackupRecords/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpMessageModal } from \"@/components\";\nimport { Button, message } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport getColumnsConfig from \"./config/columns\";\nimport {\n  ExclamationCircleOutlined,\n} from \"@ant-design/icons\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst BackupRecords = () => {\n  const location = useLocation();\n\n  const history = useHistory();\n\n  const [loading, setLoading] = useState(false);\n  const [deleteLoading, setDeleteLoading] = useState(false);\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 定义row存数据\n  const [row, setRow] = useState({});\n\n  // 删除文件\n  const [deleteModal, setDeleteModal] = useState(false);\n  const [deleteOneModal, setDeleteOneModal] = useState(false);\n\n  // 列表查询\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.dataBackup.queryBackupHistory, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n      });\n  }\n\n  // 删除\n  const deleteBackup = (deleteType = null) => {\n    setDeleteLoading(true);\n    fetchPost(apiRequest.dataBackup.queryBackupHistory, {\n      body: {\n        ids: deleteType === \"only\" ? [row.id] : checkedList.map((e) => e.id),\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"删除文件成功\");\n            setCheckedList([]);\n            setDeleteModal(false);\n            setDeleteOneModal(false);\n            fetchData({\n              current: pagination.current,\n              pageSize: pagination.pageSize,\n            });\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => setDeleteLoading(false));\n  };\n\n  useEffect(() => {\n    fetchData({ current: pagination.current, pageSize: pagination.pageSize });\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        {/* <Button\n          style={{ marginRight: 15 }}\n          type=\"primary\"\n          disabled={checkedList.length == 0}\n          onClick={() => {\n            checkedList.map((item, idx) => {\n              setTimeout(() => {\n                if (item.file_name || item.result === 1) {\n                  // if (item.file_size == 0) {\n                  //   message.warning(\"要下载的文件不存在\");\n                  //   setDownLoadModal(false);\n                  //   queryListData(pagination);\n                  //   return;\n                  // }\n                  let a = document.createElement(\"a\");\n                  a.setAttribute(\"id\", `${idx}-downA`);\n                  document.body.appendChild(a);\n                  let dom = document.getElementById(`${idx}-downA`);\n                  dom.href = `/download-backup/${item.file_name}`;\n                  dom.click();\n                  setTimeout(() => {\n                    document.body.removeChild(dom);\n                  }, 1000);\n                } else {\n                  message.warning(\"该任务文件不支持下载\");\n                }\n              }, idx * 500);\n              //}\n            });\n          }}\n        >\n          下载\n        </Button> */}\n        <Button\n          style={{ marginRight: 15 }}\n          disabled={checkedList.length == 0}\n          type=\"primary\"\n          onClick={() => {\n            setDeleteModal(true);\n          }}\n        >\n          删除\n        </Button>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              setCheckedList([]);\n              fetchData({\n                current: pagination.current,\n                pageSize: pagination.pageSize,\n              });\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(setRow, setDeleteOneModal)}\n          notSelectable={(record) => ({\n            // 执行中不能选中\n            disabled: record.result === 2,\n          })}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  justifyContent: \"space-between\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n\n      <OmpMessageModal\n        visibleHandle={[deleteModal, setDeleteModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={deleteLoading}\n        onFinish={() => {\n          deleteBackup();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定{\" \"}\n          <span style={{ fontWeight: 500 }}>删除 {checkedList.length} </span> 条{\" \"}\n          <span style={{ fontWeight: 500 }}>备份记录</span> 吗？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[deleteOneModal, setDeleteOneModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={deleteLoading}\n        onFinish={() => {\n          deleteBackup(\"only\");\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定 <span style={{ fontWeight: 500 }}>删除</span> 当前{\" \"}\n          <span style={{ fontWeight: 500 }}>备份记录</span> 吗？\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default BackupRecords;\n"
  },
  {
    "path": "omp_web/src/pages/BackupRecords/index.module.less",
    "content": ".machineManagement {\n    display: flex;\n  }\n\n  .subMenu {\n    width: 160px;\n  }\n\n  .warningSearch {\n    display: flex;\n    margin-top: 10px;\n    margin-bottom: 10px;\n\n    & > div:nth-child(1) {\n      margin-right: 10px;\n    }\n\n    & > div:last-child {\n      margin-left: auto;\n    }\n  }\n\n  .antdTableExpandedRow {\n    margin: 0;\n    padding: 0;\n    height: 20px;\n    display: flex;\n    span {\n      margin-left: 60px;\n    }\n  }\n\n  .redType {\n    background-color: #ff4d4f;\n    color: #fff;\n    border-color:#ff4d4f;\n  }\n\n  .formItem {\n    margin-bottom: 15px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    & > span:nth-child(1) {\n      display: inline-block;\n      width: 100px;\n      font-size: 14px;\n      //font-weight: 500;\n      color: #333;\n      text-align: right;\n    }\n\n    & > input:nth-child(2) {\n      width: 240px;\n    }\n  }\n  .machineTable {\n    //cursor: url('../../public/conf/logo.svg'),default;\n    cursor: pointer;\n  }\n.omp_spin_wrapper{\n  height: calc(100%);\n}\n:global {\n  .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover {\n    background-color:#e6f1f6;\n    color:#2e7cee\n  }\n  //悬停样式会覆盖disable样式，在这里把disable权限提高\n  .ant-dropdown-menu-item-disabled {\n    background-color: #fff!important;\n    color:rgba(0, 0, 0, 0.25)!important\n  }\n  // .ant-drawer .ant-drawer-content {\n  //   height: calc(100% - 80px);\n  // }\n}"
  },
  {
    "path": "omp_web/src/pages/BackupStrategy/CustomModal.js",
    "content": "import { Button, Modal, Input, Table, Tooltip, Form, Spin } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport {\n  CopyOutlined,\n  SearchOutlined,\n  PlusSquareOutlined,\n  FormOutlined,\n} from \"@ant-design/icons\";\n\nexport const AddCustomModal = ({\n  customModalType,\n  addCustom,\n  loading,\n  modalForm,\n  addModalVisibility,\n  setAddModalVisibility,\n  updateCustomInfo,\n  setUpdateCustomData,\n}) => {\n  return (\n    <Modal\n      width={580}\n      onCancel={() => {\n        setAddModalVisibility(false);\n        setUpdateCustomData({});\n        modalForm.setFieldsValue({ field_k: \"\", field_v: \"\", notes: \"\" });\n      }}\n      visible={addModalVisibility}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            {customModalType === \"add\" ? (\n              <PlusSquareOutlined />\n            ) : (\n              <FormOutlined />\n            )}\n          </span>\n          <span>\n            {customModalType === \"add\" ? \"添加自定义参数\" : \"编辑自定义参数\"}\n          </span>\n        </span>\n      }\n      zIndex={1004}\n      footer={null}\n      destroyOnClose\n    >\n      <Spin spinning={loading}>\n        <Form\n          name=\"custom\"\n          labelCol={{ span: 4 }}\n          wrapperCol={{ span: 18 }}\n          onFinish={(data) => {\n            if (customModalType === \"add\") {\n              addCustom(data);\n            } else {\n              updateCustomInfo(data);\n            }\n          }}\n          form={modalForm}\n          initialValues={{\n            field_k: \"\",\n            field_v: \"\",\n            notes: \"\",\n          }}\n        >\n          <Form.Item\n            label=\"参数名称\"\n            name=\"field_k\"\n            key=\"field_k\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入参数名称\",\n              },\n            ]}\n          >\n            <Input\n              placeholder={\"请输入参数名称\"}\n              disabled={customModalType === \"update\"}\n              maxLength={64}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"参数值\"\n            name=\"field_v\"\n            key=\"field_v\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入参数值\",\n              },\n            ]}\n          >\n            <Input.TextArea\n              rows={4}\n              style={{ width: 480 }}\n              placeholder={\"请输入参数值\"}\n              maxLength={256}\n            />\n          </Form.Item>\n\n          <Form.Item label=\"备注\" name=\"notes\" key=\"notes\">\n            <Input placeholder={\"请输入备注\"} maxLength={32} />\n          </Form.Item>\n\n          <Form.Item\n            wrapperCol={{ span: 24 }}\n            style={{ textAlign: \"center\", position: \"relative\", top: 10 }}\n          >\n            <Button\n              style={{ marginRight: 16 }}\n              onClick={() => {\n                setAddModalVisibility(false);\n                setUpdateCustomData({});\n                modalForm.setFieldsValue({\n                  field_k: \"\",\n                  field_v: \"\",\n                  notes: \"\",\n                });\n              }}\n            >\n              取消\n            </Button>\n            <Button type=\"primary\" htmlType=\"submit\">\n              确定\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </Modal>\n  );\n};\n\nexport const CustomModal = ({\n  modalVisibility,\n  setModalVisibility,\n  modalLoading,\n  customData,\n  setCustomData,\n  initData,\n  setCustomModalType,\n  setAddModalVisibility,\n  deleteCustomInfo,\n  modalForm,\n  setRow,\n}) => {\n  const [searchName, setSearchName] = useState(\"\");\n\n  const columns = [\n    {\n      title: \"序号\",\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      ellipsis: true,\n      width: 40,\n      render: (text, record) => {\n        return text;\n      },\n    },\n    {\n      title: \"名称\",\n      key: \"field_k\",\n      dataIndex: \"field_k\",\n      align: \"center\",\n      ellipsis: true,\n      width: 80,\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"值\",\n      key: \"field_v\",\n      dataIndex: \"field_v\",\n      align: \"center\",\n      ellipsis: true,\n      width: 150,\n    },\n    {\n      title: \"备注\",\n      key: \"notes\",\n      dataIndex: \"notes\",\n      align: \"center\",\n      ellipsis: true,\n      width: 100,\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"操作\",\n      width: 60,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      render: (text, record) => {\n        return (\n          <div\n            onClick={() => setRow(record)}\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              <a\n                onClick={() => {\n                  setCustomModalType(\"update\");\n                  setAddModalVisibility(true);\n                  modalForm.setFieldsValue({\n                    field_k: record.field_k,\n                    field_v: record.field_v,\n                    notes: record.notes,\n                  });\n                }}\n              >\n                编辑\n              </a>\n\n              <a\n                style={{ marginLeft: 10 }}\n                onClick={() => deleteCustomInfo(record.id)}\n              >\n                删除\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  useEffect(() => {}, []);\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <CopyOutlined />\n          </span>\n          <span>自定义参数</span>\n        </span>\n      }\n      width={860}\n      onCancel={() => {\n        setModalVisibility(false);\n      }}\n      visible={modalVisibility}\n      footer={null}\n      //width={1000}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n        marginTop: -10,\n      }}\n      destroyOnClose\n      zIndex={1002}\n    >\n      <Spin spinning={modalLoading}>\n        <div\n          style={{\n            display: \"flex\",\n            marginBottom: 10,\n          }}\n        >\n          <div style={{ flex: 1 }}>\n            <Input\n              placeholder=\"请输入名称\"\n              suffix={<SearchOutlined style={{ color: \"#b6b6b6\" }} />}\n              style={{\n                width: 220,\n              }}\n              value={searchName}\n              onChange={(e) => {\n                setSearchName(e.target.value);\n                if (e.target.value === \"\") {\n                  setCustomData(initData);\n                }\n              }}\n              onPressEnter={() => {\n                setCustomData(\n                  initData.filter((i) => i.field_k.includes(searchName))\n                );\n              }}\n            />\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              flex: 1,\n              textAlign: \"right\",\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                width: \"100%\",\n                justifyContent: \"flex-end\",\n              }}\n            >\n              <Button\n                type=\"primary\"\n                onClick={() => {\n                  setCustomModalType(\"add\");\n                  setAddModalVisibility(true);\n                }}\n              >\n                添加参数\n              </Button>\n            </div>\n          </div>\n        </div>\n        <div>\n          <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n            <Table\n              size=\"small\"\n              scroll={{ y: 270 }}\n              columns={columns}\n              dataSource={customData}\n              rowKey={(record) => {\n                return record.id;\n              }}\n              pagination={false}\n            />\n          </div>\n          <div\n            style={{ display: \"flex\", justifyContent: \"center\", marginTop: 20 }}\n          >\n            <Button onClick={() => setModalVisibility(false)}>返回</Button>\n          </div>\n        </div>\n      </Spin>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "omp_web/src/pages/BackupStrategy/StrategyModal.js",
    "content": "import {\n  Button,\n  Modal,\n  Input,\n  Tooltip,\n  Form,\n  TimePicker,\n  Spin,\n  Switch,\n  Select,\n} from \"antd\";\nimport {\n  PlusSquareOutlined,\n  FormOutlined,\n  InfoCircleOutlined,\n} from \"@ant-design/icons\";\n\nexport const AddStrategyModal = ({\n  strategyModalType,\n  addStrategy,\n  updateStrategy,\n  loading,\n  modalForm,\n  addModalVisibility,\n  setAddModalVisibility,\n  canBackupIns,\n  initData,\n  strategyFormInit,\n  keyArr,\n  setKeyArr,\n  weekData,\n  frequency,\n  setFrequency,\n}) => {\n  return (\n    <Modal\n      style={{ marginTop: 10 }}\n      width={660}\n      onCancel={() => {\n        setAddModalVisibility(false);\n        setKeyArr([]);\n        modalForm.setFieldsValue(strategyFormInit);\n      }}\n      visible={addModalVisibility}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            {strategyModalType === \"add\" ? (\n              <PlusSquareOutlined />\n            ) : (\n              <FormOutlined />\n            )}\n          </span>\n          <span>\n            {strategyModalType === \"add\" ? \"添加备份策略\" : \"编辑备份策略\"}\n          </span>\n        </span>\n      }\n      zIndex={1004}\n      footer={null}\n      destroyOnClose\n    >\n      <Spin spinning={loading}>\n        <Form\n          name=\"strategy\"\n          labelCol={{ span: 4 }}\n          wrapperCol={{ span: 18 }}\n          onFinish={(data) => {\n            if (strategyModalType === \"add\") {\n              addStrategy(data);\n            } else {\n              updateStrategy(data);\n            }\n          }}\n          form={modalForm}\n          initialValues={strategyFormInit}\n        >\n          <Form.Item\n            label=\"备份实例\"\n            name=\"backup_instances\"\n            key=\"backup_instances\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择备份实例\",\n              },\n            ]}\n          >\n            <Select\n              mode=\"multiple\"\n              placeholder=\"选择实例\"\n              allowClear\n              maxTagCount=\"responsive\"\n            >\n              {canBackupIns?.map((e) => {\n                return (\n                  <Select.Option key={e} value={e}>\n                    {e}\n                  </Select.Option>\n                );\n              })}\n            </Select>\n          </Form.Item>\n\n          <Form.Item\n            label=\"备份路径\"\n            key=\"retain_path\"\n            name=\"retain_path\"\n            rules={[\n              { required: true, message: \"请输入备份路径\" },\n              {\n                validator: (rule, value, callback) => {\n                  if (value) {\n                    if (value.match(/^[ ]*$/)) {\n                      return Promise.reject(\"请输入备份路径\");\n                    }\n                    return Promise.resolve(\"success\");\n                  } else {\n                    return Promise.resolve(\"success\");\n                  }\n                },\n              },\n            ]}\n          >\n            <Input placeholder=\"请输入备份路径\" />\n          </Form.Item>\n\n          <Form.Item\n            label=\"备份保留\"\n            name=\"retain_day\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择保留策略\",\n              },\n            ]}\n          >\n            <Select\n              style={{\n                width: 100,\n              }}\n            >\n              <Select.Option key={-1} value={-1}>\n                永久保留\n              </Select.Option>\n              <Select.Option key={1} value={1}>\n                1天\n              </Select.Option>\n              <Select.Option key={7} value={7}>\n                7天\n              </Select.Option>\n              <Select.Option key={30} value={30}>\n                30天\n              </Select.Option>\n            </Select>\n          </Form.Item>\n\n          <Form.Item label=\"定时备份\">\n            <Input.Group compact>\n              <Form.Item name=\"is_on\" noStyle valuePropName=\"checked\">\n                <Switch style={{ borderRadius: \"10px\" }} />\n              </Form.Item>\n              <Form.Item noStyle>\n                <Tooltip\n                  placement=\"top\"\n                  title={\"开启定时后，将自动执行定时任务\"}\n                >\n                  <InfoCircleOutlined\n                    name=\"icon\"\n                    style={{\n                      color: \"rgba(0,0,0,.45)\",\n                      position: \"relative\",\n                      top: 4,\n                      left: 15,\n                      fontSize: 15,\n                    }}\n                  />\n                </Tooltip>\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n\n          <Form.Item\n            label=\"定时策略\"\n            style={{\n              height: 32,\n            }}\n          >\n            <Input.Group compact>\n              <Form.Item name={[\"strategy\", \"frequency\"]} noStyle>\n                <Select\n                  style={{\n                    width: 100,\n                  }}\n                  onChange={(e) => {\n                    setFrequency(e);\n                  }}\n                >\n                  <Select.Option value=\"day\" key=\"day\">\n                    每天\n                  </Select.Option>\n                  <Select.Option value=\"week\" key=\"week\">\n                    每周\n                  </Select.Option>\n                  <Select.Option value=\"month\" key=\"month\">\n                    每月\n                  </Select.Option>\n                </Select>\n              </Form.Item>\n\n              {frequency == \"week\" && (\n                <Form.Item\n                  name={[\"strategy\", \"week\"]}\n                  style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n                >\n                  <Select\n                    style={{\n                      width: 120,\n                    }}\n                  >\n                    {weekData.map((item, idx) => {\n                      return (\n                        <Select.Option value={`${idx}`} key={item}>\n                          {item}\n                        </Select.Option>\n                      );\n                    })}\n                  </Select>\n                </Form.Item>\n              )}\n\n              {frequency == \"month\" && (\n                <Form.Item\n                  name={[\"strategy\", \"month\"]}\n                  style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n                >\n                  <Select\n                    style={{\n                      width: 120,\n                    }}\n                  >\n                    {new Array(28).fill(0).map((item, idx) => {\n                      return (\n                        <Select.Option key={`${idx + 1}`} value={`${idx + 1}`}>\n                          {idx + 1}日\n                        </Select.Option>\n                      );\n                    })}\n                  </Select>\n                </Form.Item>\n              )}\n\n              <Form.Item\n                name={[\"strategy\", \"time\"]}\n                style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n              >\n                <TimePicker\n                  //defaultValue={moment(\"00:00\", \"HH:mm\")}\n                  format={\"HH:mm\"}\n                  allowClear={false}\n                />\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n\n          <Form.Item\n            label=\"自定义参数\"\n            name=\"backup_custom\"\n            key=\"backup_custom\"\n          >\n            <Select\n              mode=\"multiple\"\n              placeholder=\"选择自定义参数\"\n              allowClear\n              maxTagCount=\"responsive\"\n              onChange={(e) => {\n                setKeyArr(\n                  e.map((i) => {\n                    return i.label[0].props.children[1];\n                  })\n                );\n              }}\n              labelInValue\n            >\n              {initData?.map((e) => {\n                return (\n                  <Select.Option\n                    key={e.id}\n                    value={e.id}\n                    disabled={\n                      keyArr.includes(e.field_k) &&\n                      !modalForm\n                        .getFieldValue(\"backup_custom\")\n                        ?.map((i) => i.key)\n                        .includes(e.id)\n                    }\n                  >\n                    <span\n                      style={{\n                        color: \"#096dd9\",\n                        fontWeight: 600,\n                        marginRight: 10,\n                      }}\n                    >\n                      [{e.field_k}]\n                    </span>\n                    {e.field_v}\n                  </Select.Option>\n                );\n              })}\n            </Select>\n          </Form.Item>\n\n          <Form.Item\n            wrapperCol={{ span: 24 }}\n            style={{ textAlign: \"center\", position: \"relative\", top: 10 }}\n          >\n            <Button\n              style={{ marginRight: 16 }}\n              onClick={() => {\n                setAddModalVisibility(false);\n                setKeyArr([]);\n                modalForm.setFieldsValue(strategyFormInit);\n              }}\n            >\n              取消\n            </Button>\n            <Button type=\"primary\" htmlType=\"submit\">\n              确定\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "omp_web/src/pages/BackupStrategy/config/columns.js",
    "content": "import { renderDisc } from \"@/utils/utils\";\nimport { Tooltip } from \"antd\";\nimport moment from \"moment\";\n\nconst getColumnsConfig = (\n  setStrategyRow,\n  setDeleteStrategyModal,\n  setStrategyModalType,\n  setStrategyModalVisibility,\n  setExecuteVisible,\n  strategyForm,\n  queryCustom,\n  setKeyArr,\n  weekData,\n  setFrequency\n) => {\n  return [\n    {\n      title: \"序号\",\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      width: 40,\n      fixed: \"left\",\n    },\n    {\n      title: \"备份实例\",\n      key: \"backup_instances\",\n      dataIndex: \"backup_instances\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text.join(\",\")}>\n            <span>{text.join(\",\")}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"是否生效\",\n      key: \"is_on\",\n      dataIndex: \"is_on\",\n      align: \"center\",\n      width: 60,\n      ellipsis: true,\n      render: (text) => {\n        if (text) {\n          return <span>{renderDisc(\"normal\", 7, -1)}是</span>;\n        } else {\n          return <span>{renderDisc(\"critical\", 7, -1)}否</span>;\n        }\n      },\n    },\n    {\n      title: \"定时策略\",\n      key: \"crontab_detail\",\n      dataIndex: \"crontab_detail\",\n      align: \"center\",\n      width: 150,\n      ellipsis: true,\n      render: (text) => {\n        if (text.day_of_month !== \"*\") {\n          return (\n            <span>\n              每月 {text.day_of_month} 日 {text.hour}:{text.minute}\n            </span>\n          );\n        } else if (text.day_of_week !== \"*\") {\n          return (\n            <span>\n              每周 {weekData[text.day_of_week]} {text.hour}:{text.minute}\n            </span>\n          );\n        } else {\n          return (\n            <span>\n              每天 {text.hour}:{text.minute}\n            </span>\n          );\n        }\n      },\n    },\n    {\n      title: \"保留路径\",\n      key: \"retain_path\",\n      dataIndex: \"retain_path\",\n      align: \"center\",\n      width: 150,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text || \"-\"}>\n            <span>{text || \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"保留时间\",\n      key: \"retain_day\",\n      dataIndex: \"retain_day\",\n      align: \"center\",\n      width: 100,\n      ellipsis: true,\n      render: (text) => {\n        if (text === -1) return <span>永久保留</span>;\n        return <span>{text} 天</span>;\n      },\n    },\n\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: (text, record, index) => {\n        return (\n          <div\n            style={{ margin: \"auto\" }}\n            onClick={() => setStrategyRow(record)}\n          >\n            <a onClick={() => setExecuteVisible(true)}>执行</a>\n            <a\n              style={{ marginLeft: 10 }}\n              onClick={() => {\n                queryCustom();\n                setStrategyModalType(\"update\");\n                setStrategyModalVisibility(true);\n                const customInfo = record.backup_custom.map((i) => {\n                  return {\n                    key: i.id,\n                    value: i.id,\n                    label: [\n                      <span\n                        style={{\n                          color: \"#096dd9\",\n                          fontWeight: 600,\n                          marginRight: 10,\n                        }}\n                      >\n                        [{i.field_k}]\n                      </span>,\n                      i.field_v,\n                    ],\n                  };\n                });\n                setKeyArr(\n                  customInfo.map((i) => {\n                    return i.label[0].props.children[1];\n                  })\n                );\n                const frType =\n                  record.crontab_detail.day_of_month !== \"*\"\n                    ? \"month\"\n                    : record.crontab_detail.day_of_week !== \"*\"\n                    ? \"week\"\n                    : \"day\";\n                const stRes = {\n                  frequency: frType,\n                  time: moment(\n                    `${record.crontab_detail.hour}:${record.crontab_detail.minute}`,\n                    \"HH:mm\"\n                  ),\n                };\n                setFrequency(frType);\n                if (frType === \"month\")\n                  stRes[\"month\"] = record.crontab_detail.day_of_month;\n                if (frType === \"week\")\n                  stRes[\"week\"] = record.crontab_detail.day_of_week;\n                strategyForm.setFieldsValue({\n                  backup_instances: record.backup_instances,\n                  backup_custom: customInfo,\n                  retain_path: record.retain_path,\n                  retain_day: record.retain_day,\n                  is_on: record.is_on,\n                  strategy: stRes,\n                });\n              }}\n            >\n              编辑\n            </a>\n\n            <a\n              style={{ marginLeft: 10 }}\n              onClick={() => setDeleteStrategyModal(true)}\n            >\n              删除\n            </a>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/BackupStrategy/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpMessageModal } from \"@/components\";\nimport { Form, Button, message } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchDelete, fetchPost, fetchPut } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport styles from \"./index.module.less\";\nimport { ExclamationCircleOutlined } from \"@ant-design/icons\";\nimport moment from \"moment\";\nimport getColumnsConfig from \"./config/columns\";\nimport { AddCustomModal, CustomModal } from \"./CustomModal.js\";\nimport { AddStrategyModal } from \"./StrategyModal\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst BackupStrategy = () => {\n  const location = useLocation();\n\n  const history = useHistory();\n\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState([]);\n\n  // 自定义参数\n  const [customModalVisibility, setCustomModalVisibility] = useState(false);\n  const [customLoading, setCustomLoading] = useState(false);\n  const [customData, setCustomData] = useState(false);\n  const [initData, setinitData] = useState([]);\n\n  // 增/改自定义参数共用\n  const [customModalType, setCustomModalType] = useState(\"add\");\n  const [addModalVisibility, setAddModalVisibility] = useState(false);\n  const [addLoading, setAddLoading] = useState(false);\n\n  // 自定义参数表单数据\n  const [row, setRow] = useState({});\n  const [customModalForm] = Form.useForm();\n  const [strategyRow, setStrategyRow] = useState({});\n  const [updateCustomVisibility, setUpdateCustomVisibility] = useState(false);\n  const [updateCustomData, setUpdateCustomData] = useState({});\n  const [updateRepeatName, setUpdateRepeatName] = useState(\"\");\n  const [deleteCustomVisibility, setDeleteCustomVisibility] = useState(false);\n  const [deleteRepeatName, setDeleteRepeatName] = useState(\"\");\n\n  // 增/改备份策略共用\n  const [strategyModalType, setStrategyModalType] = useState(\"add\");\n  const [strategyModalVisibility, setStrategyModalVisibility] = useState(false);\n  const [strategyLoading, setStrategyLoading] = useState(false);\n  const [keyArr, setKeyArr] = useState([]);\n\n  // 备份策略表单\n  const [strategyForm] = Form.useForm();\n\n  // 备份组件全量数据\n  const [canBackupIns, setCanBackupIns] = useState([]);\n\n  // 删除策略\n  const [deleteStrategyModal, setDeleteStrategyModal] = useState(false);\n  // 执行策略\n  const [executeVisible, setExecuteVisible] = useState(false);\n  const [frequency, setFrequency] = useState(\"day\");\n\n  // 星期汉字映射\n  let weekData = [\n    \"星期一\",\n    \"星期二\",\n    \"星期三\",\n    \"星期四\",\n    \"星期五\",\n    \"星期六\",\n    \"星期日\",\n  ];\n\n  // 策略表单初始值\n  const strategyFormInit = {\n    retain_path: \"/data/omp/data/backup/\",\n    retain_day: 7,\n    is_on: false,\n    strategy: {\n      frequency: \"day\",\n      time: moment(\"00:00\", \"HH:mm\"),\n      week: \"0\",\n      month: \"1\",\n    },\n    backup_instances: [],\n    backup_custom: [],\n  };\n\n  // 数据备份策略列表查询\n  const fetchData = () => {\n    setLoading(true);\n    fetchGet(apiRequest.dataBackup.strategySetting)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1,\n              };\n            })\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 自定义参数查询\n  const queryCustom = () => {\n    setCustomLoading(true);\n    fetchGet(apiRequest.dataBackup.backupCustom)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const resData = res.data.map((item, idx) => {\n            return {\n              ...item,\n              _idx: idx + 1,\n            };\n          });\n          setCustomData(resData);\n          setinitData(resData);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setCustomLoading(false);\n      });\n  };\n\n  // 添加自定义参数\n  const addCustom = (data) => {\n    setAddLoading(true);\n    fetchPost(apiRequest.dataBackup.backupCustom, {\n      body: data,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"添加自定义参数成功\");\n            customModalForm.setFieldsValue({\n              field_k: \"\",\n              field_v: \"\",\n              notes: \"\",\n            });\n            queryCustom();\n            setAddModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setAddLoading(false);\n      });\n  };\n\n  // 修改自定义参数 - 提示存在实例使用\n  const updateCustomInfo = (data) => {\n    setAddLoading(true);\n    fetchGet(apiRequest.dataBackup.backupRepeatCustom, {\n      params: {\n        id: row.id,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const repeatName = res.data[0].name;\n          if (repeatName) {\n            setUpdateRepeatName(repeatName);\n            setUpdateCustomData(data);\n            setUpdateCustomVisibility(true);\n          } else {\n            updateCustom(data);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setAddLoading(false);\n      });\n  };\n\n  // 修改自定义参数\n  const updateCustom = (data) => {\n    setAddLoading(true);\n    fetchPut(`${apiRequest.dataBackup.backupCustom}${row.id}/`, {\n      body: data,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"修改自定义参数成功\");\n            customModalForm.setFieldsValue({\n              field_k: \"\",\n              field_v: \"\",\n              notes: \"\",\n            });\n            queryCustom();\n            fetchData();\n            setUpdateCustomVisibility(false);\n            setAddModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setAddLoading(false);\n      });\n  };\n\n  // 删除自定义参数\n  const deleteCustomInfo = (id) => {\n    setAddLoading(true);\n    fetchGet(apiRequest.dataBackup.backupRepeatCustom, {\n      params: {\n        id: id,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const repeatName = res.data[0].name;\n          setDeleteRepeatName(repeatName);\n          setDeleteCustomVisibility(true);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setAddLoading(false);\n      });\n  };\n\n  // 删除自定义参数\n  const deleteCustom = () => {\n    setAddLoading(true);\n    fetchDelete(`${apiRequest.dataBackup.backupCustom}${row.id}/`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"删除成功\");\n            queryCustom();\n            fetchData();\n            setDeleteCustomVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setAddLoading(false);\n      });\n  };\n\n  // 查询可备份实例\n  const queryCanBackup = () => {\n    setStrategyLoading(true);\n    fetchGet(apiRequest.dataBackup.queryCanBackup)\n      .then((res) => {\n        handleResponse(res, () => {\n          setCanBackupIns(res.data.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 构建添加/修改备份策略的请求体\n  const makeRequestBody = (data) => {\n    const timeInfo = data.strategy.time.format(\"HH:mm\");\n    return {\n      backup_instances: data.backup_instances,\n      retain_path: data.retain_path,\n      retain_day: data.retain_day,\n      is_on: data.is_on,\n      backup_custom:\n        data.backup_custom?.map((e) => {\n          return {\n            id: e.key,\n            field_k: e.label[0].props.children[1],\n            field_v: e.label[1],\n          };\n        }) || [],\n      crontab_detail: {\n        month_of_year: \"*\",\n        day_of_month: data.strategy.month || \"*\",\n        day_of_week: data.strategy.week || \"*\",\n        hour: timeInfo.split(\":\")[0] || \"*\",\n        minute: timeInfo.split(\":\")[1] || \"*\",\n      },\n    };\n  };\n\n  // 添加备份策略\n  const addStrategy = (data) => {\n    setStrategyLoading(true);\n    fetchPost(apiRequest.dataBackup.strategySetting, {\n      body: makeRequestBody(data),\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"添加备份策略成功\");\n            strategyForm.setFieldsValue(strategyFormInit);\n            fetchData();\n            setStrategyModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 编辑备份策略\n  const updateStrategy = (data) => {\n    setStrategyLoading(true);\n    fetchPut(`${apiRequest.dataBackup.strategySetting}${strategyRow.id}/`, {\n      body: makeRequestBody(data),\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"修改备份策略成功\");\n            strategyForm.setFieldsValue(strategyFormInit);\n            fetchData();\n            setStrategyModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 删除备份策略\n  const deleteStrategy = () => {\n    setLoading(true);\n    fetchDelete(`${apiRequest.dataBackup.strategySetting}${strategyRow.id}/`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"删除成功\");\n            fetchData();\n            setDeleteStrategyModal(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 执行备份策略\n  const executeStrategy = () => {\n    setLoading(true);\n    fetchPost(apiRequest.dataBackup.strategySetting, {\n      body: {\n        id: strategyRow.id,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"任务下发成功\");\n            setExecuteVisible(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n    queryCanBackup();\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          style={{ marginRight: 10 }}\n          type=\"primary\"\n          onClick={() => {\n            queryCustom();\n            setStrategyModalType(\"add\");\n            setStrategyModalVisibility(true);\n          }}\n        >\n          添加策略\n        </Button>\n        <Button\n          style={{ marginRight: 10 }}\n          type=\"primary\"\n          onClick={() => {\n            setCustomModalVisibility(true);\n            queryCustom();\n          }}\n        >\n          自定义参数\n        </Button>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button style={{ marginLeft: 10 }} onClick={() => fetchData()}>\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          columns={getColumnsConfig(\n            setStrategyRow,\n            setDeleteStrategyModal,\n            setStrategyModalType,\n            setStrategyModalVisibility,\n            setExecuteVisible,\n            strategyForm,\n            queryCustom,\n            setKeyArr,\n            weekData,\n            setFrequency\n          )}\n          dataSource={dataSource}\n          pagination={{\n            pageSize: 10,\n          }}\n          rowKey={(record) => record.id}\n          noScroll={true}\n        />\n      </div>\n\n      <CustomModal\n        modalVisibility={customModalVisibility}\n        setModalVisibility={setCustomModalVisibility}\n        modalLoading={customLoading}\n        customData={customData}\n        setCustomData={setCustomData}\n        initData={initData}\n        setCustomModalType={setCustomModalType}\n        setAddModalVisibility={setAddModalVisibility}\n        deleteCustomInfo={deleteCustomInfo}\n        modalForm={customModalForm}\n        setRow={setRow}\n      />\n      <AddCustomModal\n        customModalType={customModalType}\n        addCustom={addCustom}\n        loading={addLoading}\n        modalForm={customModalForm}\n        addModalVisibility={addModalVisibility}\n        setAddModalVisibility={setAddModalVisibility}\n        updateCustomInfo={updateCustomInfo}\n        setUpdateCustomData={setUpdateCustomData}\n      />\n      <AddStrategyModal\n        strategyModalType={strategyModalType}\n        addStrategy={addStrategy}\n        updateStrategy={updateStrategy}\n        loading={strategyLoading}\n        modalForm={strategyForm}\n        addModalVisibility={strategyModalVisibility}\n        setAddModalVisibility={setStrategyModalVisibility}\n        canBackupIns={canBackupIns}\n        initData={initData}\n        strategyFormInit={strategyFormInit}\n        keyArr={keyArr}\n        setKeyArr={setKeyArr}\n        weekData={weekData}\n        frequency={frequency}\n        setFrequency={setFrequency}\n      />\n\n      <OmpMessageModal\n        visibleHandle={[updateCustomVisibility, setUpdateCustomVisibility]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={addLoading}\n        onFinish={() => {\n          updateCustom(updateCustomData);\n        }}\n        zIndex={1004}\n      >\n        <div style={{ padding: \"20px\" }}>\n          该参数{\" \"}\n          <span style={{ fontWeight: 500, color: \"red\" }}>\n            {updateRepeatName}\n          </span>{\" \"}\n          实例正在使用\n          <br />\n          确认<span style={{ fontWeight: 500 }}>修改</span>该参数吗？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[deleteCustomVisibility, setDeleteCustomVisibility]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={addLoading}\n        onFinish={() => deleteCustom()}\n        zIndex={1004}\n      >\n        <div style={{ padding: \"20px\" }}>\n          {deleteRepeatName && (\n            <>\n              该参数{\" \"}\n              <span style={{ fontWeight: 500, color: \"red\" }}>\n                {deleteRepeatName}\n              </span>{\" \"}\n              实例正在使用\n              <br />\n            </>\n          )}\n          确认<span style={{ fontWeight: 500 }}>删除</span>该参数吗？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[deleteStrategyModal, setDeleteStrategyModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          deleteStrategy();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定 <span style={{ fontWeight: 500 }}>删除</span> 该策略吗？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[executeVisible, setExecuteVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          executeStrategy();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确认对实例{\" \"}\n          <span style={{ fontWeight: 500, color: \"red\" }}>\n            {strategyRow.backup_instances?.join(\",\")}\n          </span>{\" \"}\n          执行备份策略吗？\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default BackupStrategy;\n"
  },
  {
    "path": "omp_web/src/pages/BackupStrategy/index.module.less",
    "content": ".header {\n  padding: 10px;\n  padding-left: 20px;\n  background-color: #f7f7f7;\n}\n\n.content {\n  padding-left: 40px;\n  padding-top: 30px;\n  display: flex;\n  align-items: center;\n  .label {\n    padding-right: 30px;\n  }\n}\n\n.tips {\n  margin-top: 30px;\n  padding-left: 40px;\n  font-size: 13px;\n}\n\n.saveButtonWrapper {\n  height: 50px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.saveButton {\n  margin-left: 50%;\n  transform: translateX(-50%);\n}\n"
  },
  {
    "path": "omp_web/src/pages/DeploymentPlan/config/columns.js",
    "content": "import moment from \"moment\";\nimport { nonEmptyProcessing } from \"@/utils/utils\";\n\nconst getColumnsConfig = (history) => {\n  return [\n    {\n      title: \"编号\",\n      width: 140,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      render: nonEmptyProcessing,\n      fixed: \"left\",\n    },\n    {\n      title: \"模板名称\",\n      width: 280,\n      key: \"plan_name\",\n      dataIndex: \"plan_name\",\n      align: \"center\",\n    },\n    {\n      title: \"主机数量\",\n      key: \"host_num\",\n      width: 150,\n      dataIndex: \"host_num\",\n      align: \"center\",\n    },\n    {\n      title: \"产品数量\",\n      key: \"product_num\",\n      width: 150,\n      dataIndex: \"product_num\",\n      align: \"center\",\n    },\n    {\n      title: \"服务数量\",\n      key: \"service_num\",\n      width: 150,\n      dataIndex: \"service_num\",\n      align: \"center\",\n    },\n    {\n      title: \"创建时间\",\n      key: \"created\",\n      dataIndex: \"created\",\n      align: \"center\",\n      width: 280,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"创建用户\",\n      key: \"create_user\",\n      dataIndex: \"create_user\",\n      align: \"center\",\n      width: 200,\n      render: (text) => {\n        return \"Admin\";\n      },\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <a\n              onClick={() => {\n                history.push({\n                  pathname: \"/application_management/app_store/installation\",\n                  state: {\n                    uniqueKey: record.operation_uuid,\n                    step: 3,\n                  },\n                });\n              }}\n            >\n              安装记录\n            </a>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/DeploymentPlan/config/models.js",
    "content": "import React, { useEffect } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport { Button, message, Tooltip, Modal, Steps, Upload, Table } from \"antd\";\nimport {\n  SyncOutlined,\n  ImportOutlined,\n  DownloadOutlined,\n  CloudUploadOutlined,\n  CheckCircleFilled,\n  CloseCircleFilled,\n} from \"@ant-design/icons\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { useState, useRef } from \"react\";\nimport XLSX from \"xlsx\";\n\nconst getHeaderRow = (sheet) => {\n  const headers = [];\n  const range = XLSX.utils.decode_range(sheet[\"!ref\"]);\n  let C;\n  const R = range.s.r;\n  for (C = range.s.c; C <= range.e.c; ++C) {\n    const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];\n    let hdr = \"UNKNOWN \" + C;\n    if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);\n    headers.push(hdr);\n  }\n  return headers;\n};\n\nclass UploadExcelComponent extends React.Component {\n  state = {\n    loading: false,\n    excelData: {\n      header: null,\n      results: null,\n    },\n  };\n  draggerProps = () => {\n    let _this = this;\n    return {\n      name: \"file\",\n      multiple: false,\n      accept: \".xlsx\",\n      maxCount: 1,\n      onRemove() {\n        _this.props.onRemove();\n        return true;\n      },\n      onChange(info) {\n        const { status } = info.file;\n        if (status === \"done\") {\n          //console.log(info.file);\n          message.success(`${info.file.name} 文件解析成功`);\n        } else if (status === \"error\") {\n          message.error(\n            `${info.file.name} 文件解析失败, 请确保文件内容格式符合规范后重新上传`\n          );\n        }\n      },\n      beforeUpload(file, fileList) {\n        //console.log(file);\n        // bmf.md5(file,(err,md5)=>{\n        //   console.log(err,md5,\"=====?---\")\n        // })\n        // 校验文件大小\n        const fileSize = file.size / 1024 / 1024; //单位是M\n        //console.log(fileSize);\n        if (Math.ceil(fileSize) > 10) {\n          message.error(\"仅支持传入10M以内文件\");\n          return Upload.LIST_IGNORE;\n        }\n        if (!/\\.(xlsx)$/.test(file.name)) {\n          message.error(\"仅支持传入.xlsx文件\");\n          return Upload.LIST_IGNORE;\n        }\n      },\n      customRequest(e) {\n        _this.readerData(e.file).then(\n          (msg) => {\n            //console.log(e);\n            e.onSuccess();\n          },\n          () => {\n            e.onError();\n          }\n        );\n      },\n    };\n  };\n  readerData = (rawFile) => {\n    // bmf.md5(rawFile,(err,md5)=>{\n    //   console.log(err,md5,\"=====?\")\n    // })\n    return new Promise((resolve, reject) => {\n      const reader = new FileReader();\n\n      reader.onload = (e) => {\n        try {\n          const data = e.target.result;\n          const workbook = XLSX.read(data, { type: \"array\" });\n          // 主机数据\n          const firstSheetName = workbook.SheetNames[0];\n          const worksheet = workbook.Sheets[firstSheetName];\n          const header = getHeaderRow(worksheet);\n          const results = XLSX.utils.sheet_to_json(worksheet);\n\n          // 服务数据\n          const sencondSheetName = workbook.SheetNames[1];\n          const serviceSheet = workbook.Sheets[sencondSheetName];\n          const serviceHeader = getHeaderRow(serviceSheet);\n          const serviceResults = XLSX.utils.sheet_to_json(serviceSheet);\n\n          this.generateData(\n            { header, results },\n            { serviceHeader, serviceResults }\n          );\n          resolve();\n        } catch (error) {\n          reject();\n        }\n      };\n      reader.readAsArrayBuffer(rawFile);\n    });\n  };\n  generateData = ({ header, results }, { serviceHeader, serviceResults }) => {\n    this.setState({\n      excelData: { header, results },\n      excelServiceData: { serviceHeader, serviceResults },\n    });\n    this.props.uploadSuccess &&\n      this.props.uploadSuccess(\n        this.state.excelData,\n        this.state.excelServiceData\n      );\n  };\n  render() {\n    return (\n      <div>\n        <Upload.Dragger {...this.draggerProps()}>\n          <p className=\"ant-upload-drag-icon\">\n            <CloudUploadOutlined />\n          </p>\n          <p style={{ textAlign: \"center\", color: \"#575757\" }}>\n            点击或将文件拖拽到这里上传\n          </p>\n          <p\n            style={{\n              textAlign: \"center\",\n              color: \"#8e8e8e\",\n              fontSize: 13,\n              paddingTop: 10,\n            }}\n          >\n            支持扩展名: .xlsx\n          </p>\n        </Upload.Dragger>\n      </div>\n    );\n  }\n}\n\n/* 导入执行计划 */\nexport const ImportPlanModal = ({ importPlan, setImportPlan }) => {\n  const history = useHistory();\n  const [dataSource, setDataSource] = useState([]);\n  const [columns, setColumns] = useState([]);\n\n  const [serviceDataSource, setServiceDataSource] = useState([]);\n  const [serviceColumns, setServiceColumns] = useState([]);\n\n  const [tableCorrectData, setTableCorrectData] = useState([]);\n  const [tableErrorData, setTableErrorData] = useState([]);\n  const [tableColumns, setTableColumns] = useState([]);\n\n  const [serviceTableCorrectData, setServiceTableCorrectData] = useState([]);\n  const [serviceTableErrorData, setServiceTableErrorData] = useState([]);\n  const [serviceTableColumns, setServiceTableColumns] = useState([]);\n\n  // 涉及数量信息\n  const [numInfo, setNumInfo] = useState({});\n\n  const [stepNum, setStepNum] = useState(0);\n\n  const [loading, setLoading] = useState(false);\n\n  // 导入部署步骤状态\n  const [hostStep, setHostStep] = useState(null);\n  const [serviceStep, setServiceStep] = useState(null);\n  const [importStep, setImportStep] = useState(null);\n\n  // 导入部署模板状态\n  const [importResult, setImportResult] = useState(null);\n\n  // 主机和服务的正确数据\n  let hostCorrectData = null;\n  let serviceCorrectData = null;\n\n  // 轮训控制器\n  const hostAgentTimer = useRef(null);\n\n  // 失败的columns\n  const errorColumns = [\n    {\n      title: \"行数\",\n      key: \"row\",\n      dataIndex: \"row\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 60,\n      ellipsis: true,\n      fixed: \"left\",\n    },\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 120,\n      ellipsis: true,\n      //fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n      //fixed: \"left\"\n    },\n    {\n      title: \"端口\",\n      key: \"port\",\n      dataIndex: \"port\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 80,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      //ellipsis: true,\n    },\n    {\n      title: \"数据分区\",\n      key: \"data_folder\",\n      dataIndex: \"data_folder\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 180,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"用户名\",\n      key: \"username\",\n      dataIndex: \"username\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"运行用户\",\n      key: \"run_user\",\n      dataIndex: \"run_user\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"失败原因\",\n      key: \"validate_error\",\n      dataIndex: \"validate_error\",\n      fixed: \"right\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 240,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n  ];\n\n  // 服务失败的columns\n  const serviceErrorColumns = [\n    {\n      title: \"行数\",\n      key: \"row\",\n      dataIndex: \"row\",\n      align: \"center\",\n      width: 60,\n      ellipsis: true,\n      fixed: \"left\",\n      render: (text) => {\n        if (text < 1) return \"-\";\n        return text;\n      },\n    },\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 120,\n      ellipsis: true,\n      //fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"服务名称\",\n      key: \"service_name\",\n      dataIndex: \"service_name\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 140,\n      ellipsis: true,\n      //fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    ,\n    {\n      title: \"运行内存\",\n      key: \"memory\",\n      dataIndex: \"memory\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 80,\n      ellipsis: true,\n      //fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"失败原因\",\n      key: \"validate_error\",\n      dataIndex: \"validate_error\",\n      fixed: \"right\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 260,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n  ];\n\n  // 校验主机数据\n  const fetchBatchValidate = () => {\n    setLoading(true);\n    if (dataSource.length == 0) {\n      message.warning(\"节点信息中无有效数据，请补充后重新上传\");\n      setHostStep(false);\n      setImportResult(false);\n      return;\n    }\n    let queryBody = dataSource.map((item) => {\n      let result = {};\n      for (const key in item) {\n        switch (key) {\n          case \"IP[必填]\":\n            result.ip = item[key];\n            break;\n          case \"实例名[必填]\":\n            result.instance_name = item[key];\n            break;\n          case \"密码[必填]\":\n            result.password = item[key];\n            break;\n          case \"操作系统[必填]\":\n            result.operate_system = item[key];\n            break;\n          case \"数据分区[必填]\":\n            result.data_folder = item[key];\n            break;\n          case \"用户名[必填]\":\n            result.username = item[key];\n            break;\n          case \"端口[必填]\":\n            result.port = item[key];\n            break;\n          case \"运行用户\":\n            result.run_user = item[key];\n            break;\n          case \"时间同步服务器\":\n            result.use_ntpd = true;\n            result.ntpd_server = item[key];\n            break;\n          default:\n            break;\n        }\n      }\n      if (!result.use_ntpd) {\n        result.use_ntpd = false;\n      }\n      return {\n        ...result,\n        row: item.key,\n      };\n    });\n    // 校验数据\n    fetchPost(apiRequest.machineManagement.batchValidate, {\n      body: {\n        host_list: queryBody,\n      },\n    })\n      .then((res) => {\n        res = res.data;\n        if (res.code == 0) {\n          if (res.data && res.data.error?.length > 0) {\n            setTableErrorData(\n              res.data.error?.map((item, idx) => {\n                return {\n                  key: idx,\n                  ...item,\n                };\n              })\n            );\n            setTableColumns(errorColumns);\n            setHostStep(false);\n            setImportResult(false);\n          } else {\n            hostCorrectData = res.data.correct?.map((item, idx) => {\n              return {\n                key: idx,\n                ...item,\n              };\n            });\n            setHostStep(true);\n            serviceDataValidate();\n          }\n        } else {\n          message.warning(res.message);\n          setHostStep(false);\n          setImportResult(false);\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 校验服务数据\n  const serviceDataValidate = () => {\n    setLoading(true);\n    if (serviceDataSource.length == 0) {\n      message.warning(\"服务分布中无有效数据，请补充后重新上传\");\n      setServiceStep(false);\n      setImportResult(false);\n      return;\n    }\n    // 获取主机实例名数组;\n    let instanceNameArr = dataSource.map((item) => {\n      let instanceName = \"\";\n      for (const key in item) {\n        switch (key) {\n          case \"实例名[必填]\":\n            instanceName = item[key];\n            break;\n          default:\n            break;\n        }\n      }\n      return instanceName;\n    });\n    // 获取服务数据\n    let serviceArr = serviceDataSource.map((item) => {\n      let result = {};\n      for (const key in item) {\n        switch (key) {\n          case \"主机实例名[必填]\":\n            result.instance_name = item[key];\n            break;\n          case \"服务名[必填]\":\n            result.service_name = item[key];\n            break;\n          case \"运行内存\":\n            result.memory = item[key];\n            break;\n          case \"虚拟IP\":\n            result.vip = item[key];\n            break;\n          case \"角色\":\n            result.role = item[key];\n            break;\n          case \"模式\":\n            result.mode = item[key];\n            break;\n          default:\n            break;\n        }\n      }\n      return {\n        ...result,\n        row: item.key,\n      };\n    });\n    // 校验服务分布信息\n    fetchPost(apiRequest.deloymentPlan.serviceValidate, {\n      body: {\n        instance_name_ls: instanceNameArr,\n        service_data_ls: serviceArr,\n      },\n    })\n      .then((res) => {\n        res = res.data;\n        if (res.code == 0) {\n          if (res.data && res.data.error?.length > 0) {\n            setServiceTableErrorData(\n              res.data.error?.map((item, idx) => {\n                return {\n                  key: idx,\n                  ...item,\n                };\n              })\n            );\n            setServiceTableColumns(serviceErrorColumns);\n            setServiceStep(false);\n            setImportResult(false);\n          } else {\n            serviceCorrectData = res.data.correct?.map((item, idx) => {\n              return {\n                key: idx,\n                ...item,\n              };\n            });\n            setServiceStep(true);\n            startDeployment();\n          }\n        } else {\n          message.warning(res.message);\n          setServiceStep(false);\n          setImportResult(false);\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 导入服务数据\n  const serviceImport = () => {\n    setLoading(true);\n    let instanceArr = hostCorrectData.map((item) => {\n      return {\n        instance_name: item.instance_name,\n        run_user: item.run_user,\n      };\n    });\n    let serviceArr = serviceCorrectData.map((item) => {\n      delete item.key;\n      return {\n        ...item,\n      };\n    });\n    fetchPost(apiRequest.deloymentPlan.serviceImport, {\n      body: {\n        instance_info_ls: instanceArr,\n        service_data_ls: serviceArr,\n      },\n    })\n      .then((res) => {\n        res = res.data;\n        if (res.code === 0) {\n          setNumInfo(res.data);\n          setImportStep(true);\n          setImportResult(true);\n          // 开始安装\n          startInstall(res.data.operation_uuid);\n        } else {\n          message.warning(\n            <>\n              {res.message}\n              <a\n                style={{ marginLeft: 8, color: \"#3790ff\" }}\n                onClick={() => {\n                  message.destroy();\n                }}\n              >\n                [关闭]\n              </a>\n            </>,\n            0\n          );\n          setImportStep(false);\n          setImportResult(false);\n        }\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 开始部署\n  const startDeployment = () => {\n    setLoading(true);\n    // 批量导入主机数据\n    let hostArr = hostCorrectData.map((item) => {\n      delete item.key;\n      return {\n        ...item,\n      };\n    });\n    // 纳管主机\n    fetchPost(apiRequest.machineManagement.batchImport, {\n      body: {\n        host_list: hostArr,\n      },\n    })\n      .then((res) => {\n        res = res.data;\n        if (res.code === 0) {\n          serviceImport();\n        } else {\n          message.warning(res.message);\n          setImportStep(false);\n          setImportResult(false);\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 执行安装任务\n  const retryInstall = (operation_uuid) => {\n    // 跳转安装页面\n    fetchPost(apiRequest.appStore.retryInstall, {\n      body: {\n        unique_key: operation_uuid,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            // 跳转安装页面\n            history.push({\n              pathname: \"/application_management/app_store/installation\",\n              state: {\n                uniqueKey: operation_uuid,\n                step: 3,\n              },\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 查询主机 agent 状态\n  const queryHostAgent = (operation_uuid) => {\n    let ipArr = hostCorrectData.map((item) => {\n      return item.ip;\n    });\n    fetchPost(apiRequest.machineManagement.hostsAgentStatus, {\n      body: {\n        ip_list: ipArr,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code === 0 && res.data) {\n            // 调用安装\n            retryInstall(operation_uuid);\n            // 清除定时器\n            clearInterval(hostAgentTimer.current);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 开始安装\n  const startInstall = (operation_uuid) => {\n    hostAgentTimer.current = setInterval(() => {\n      queryHostAgent(operation_uuid);\n    }, 1000);\n  };\n\n  // 点击导入按钮\n  const clickImport = () => {\n    setHostStep(null);\n    setServiceStep(null);\n    setImportStep(null);\n    setImportResult(null);\n    setTableCorrectData([]);\n    setTableErrorData([]);\n    setServiceTableCorrectData([]);\n    setServiceTableErrorData([]);\n    setStepNum(1);\n    fetchBatchValidate();\n  };\n\n  useEffect(() => {\n    return () => {\n      // 页面销毁时清除延时器\n      clearInterval(hostAgentTimer.current);\n    };\n  }, []);\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <ImportOutlined />\n          </span>\n          <span>导入部署模板</span>\n        </span>\n      }\n      visible={importPlan}\n      footer={null}\n      width={800}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      onCancel={() => {\n        setImportPlan(false);\n      }}\n      afterClose={() => {\n        setDataSource([]);\n        setTableCorrectData([]);\n        setTableErrorData([]);\n        setTableColumns([]);\n        setStepNum(0);\n        setColumns([]);\n\n        setServiceDataSource([]);\n        setServiceColumns([]);\n        setServiceTableCorrectData([]);\n        setServiceTableErrorData([]);\n        setServiceTableColumns([]);\n      }}\n      destroyOnClose\n    >\n      <Steps size=\"small\" current={stepNum}>\n        <Steps.Step title=\"上传文件\" />\n        <Steps.Step title=\"导入结果\" />\n      </Steps>\n      <div style={{ paddingLeft: 10, paddingTop: 30 }}>\n        <div\n          style={{\n            visibility: stepNum == 0 ? \"visible\" : \"hidden\",\n            height: stepNum == 0 ? null : 0,\n            overflow: \"hidden\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            <div style={{ flex: 1, fontWeight: 500 }}>下载模版: </div>\n            <div style={{ flex: 10, paddingLeft: 20 }}>\n              <Button\n                icon={<DownloadOutlined />}\n                size=\"middle\"\n                style={{ fontSize: 13 }}\n                onClick={() => {\n                  let a = document.createElement(\"a\");\n                  a.href = apiRequest.deloymentPlan.deploymentTemplate;\n                  document.body.appendChild(a);\n                  a.click();\n                  document.body.removeChild(a);\n                }}\n              >\n                点击下载\n              </Button>\n            </div>\n          </div>\n          <div style={{ display: \"flex\", marginTop: 30 }}>\n            <div style={{ flex: 1, fontWeight: 500 }}>上传文件: </div>\n            <div style={{ flex: 10, paddingLeft: 20 }}>\n              {importPlan && (\n                <UploadExcelComponent\n                  onRemove={() => {\n                    setDataSource([]);\n                    setColumns([]);\n                    setTableCorrectData([]);\n                    setTableErrorData([]);\n                    setTableColumns([]);\n\n                    setServiceDataSource([]);\n                    setServiceColumns([]);\n                    setServiceTableCorrectData([]);\n                    setServiceTableErrorData([]);\n                    setServiceTableColumns([]);\n                  }}\n                  uploadSuccess={(\n                    { results, header },\n                    { serviceHeader, serviceResults }\n                  ) => {\n                    // 处理主机数据\n                    let dataS = results\n                      .filter((item) => {\n                        if (item[\"字段名称(请勿编辑)\"]?.includes(\"请勿编辑\")) {\n                          return false;\n                        }\n                        if (!item[\"实例名[必填]\"]) {\n                          return false;\n                        }\n                        return true;\n                      })\n                      .map((item, idx) => {\n                        return { ...item, key: item.__rowNum__ + 1 };\n                      });\n                    let column = header.filter((item) => {\n                      if (\n                        item?.includes(\"请勿编辑\") ||\n                        item?.includes(\"UNKNOWN\")\n                      ) {\n                        return false;\n                      }\n                      return true;\n                    });\n                    setDataSource(dataS);\n                    setColumns(column);\n\n                    // 处理服务数据\n                    let dataService = serviceResults\n                      .filter((item) => {\n                        if (item[\"字段名称(请勿编辑)\"]?.includes(\"请勿编辑\")) {\n                          return false;\n                        }\n                        if (!item[\"主机实例名[必填]\"]) {\n                          return false;\n                        }\n                        return true;\n                      })\n                      .map((item) => {\n                        return { ...item, key: item.__rowNum__ + 1 };\n                      });\n                    setServiceDataSource(dataService);\n                    setServiceColumns(serviceHeader);\n                  }}\n                />\n              )}\n\n              <div\n                style={{\n                  display: \"inline-block\",\n                  marginLeft: \"50%\",\n                  transform: \"translateX(-50%)\",\n                  marginTop: 40,\n                }}\n              >\n                <Button\n                  loading={loading}\n                  onClick={() => clickImport()}\n                  type=\"primary\"\n                  disabled={columns.length == 0}\n                >\n                  开始导入\n                </Button>\n              </div>\n            </div>\n          </div>\n        </div>\n        {stepNum == 1 && (\n          <>\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                paddingBottom: 10,\n                flexDirection: \"column\",\n              }}\n            >\n              <p\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontSize: 20,\n                }}\n              >\n                {hostStep === null && (\n                  <>\n                    <SyncOutlined\n                      spin\n                      style={{\n                        marginRight: 16,\n                      }}\n                    />\n                    正在校验主机数据 ...\n                  </>\n                )}\n                {hostStep === true && (\n                  <>\n                    <CheckCircleFilled\n                      style={{\n                        color: \"#52c41a\",\n                        fontSize: 30,\n                        marginRight: 10,\n                      }}\n                    />\n                    主机数据校验通过\n                  </>\n                )}\n                {hostStep === false && (\n                  <>\n                    <CloseCircleFilled\n                      style={{\n                        color: \"#f73136\",\n                        fontSize: 30,\n                        marginRight: 10,\n                      }}\n                    />\n                    主机数据校验未通过\n                  </>\n                )}\n              </p>\n\n              {hostStep && (\n                <p\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    fontSize: 20,\n                  }}\n                >\n                  {serviceStep === null && (\n                    <>\n                      <SyncOutlined\n                        spin\n                        style={{\n                          marginRight: 16,\n                        }}\n                      />\n                      正在校验服务数据 ...\n                    </>\n                  )}\n                  {serviceStep === true && (\n                    <>\n                      <CheckCircleFilled\n                        style={{\n                          color: \"#52c41a\",\n                          fontSize: 30,\n                          marginRight: 10,\n                        }}\n                      />\n                      服务数据校验通过\n                    </>\n                  )}\n                  {serviceStep === false && (\n                    <>\n                      <CloseCircleFilled\n                        style={{\n                          color: \"#f73136\",\n                          fontSize: 30,\n                          marginRight: 10,\n                        }}\n                      />\n                      服务数据校验未通过\n                    </>\n                  )}\n                </p>\n              )}\n\n              {serviceStep && (\n                <p\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    fontSize: 20,\n                  }}\n                >\n                  {importStep === null && (\n                    <>\n                      <SyncOutlined\n                        spin\n                        style={{\n                          marginRight: 16,\n                        }}\n                      />\n                      正在导入模板 ...\n                    </>\n                  )}\n                  {importStep === true && (\n                    <>\n                      <CheckCircleFilled\n                        style={{\n                          color: \"#52c41a\",\n                          fontSize: 30,\n                          marginRight: 10,\n                        }}\n                      />\n                      部署模板导入成功\n                    </>\n                  )}\n                  {importStep === false && (\n                    <>\n                      <CloseCircleFilled\n                        style={{\n                          color: \"#f73136\",\n                          fontSize: 30,\n                          marginRight: 10,\n                        }}\n                      />\n                      部署模板导入失败\n                    </>\n                  )}\n                </p>\n              )}\n\n              {tableErrorData.length > 0 && (\n                <Table\n                  bordered\n                  scroll={{ x: 700 }}\n                  columns={tableColumns}\n                  dataSource={\n                    tableErrorData.length > 0\n                      ? tableErrorData\n                      : tableCorrectData\n                  }\n                  pagination={{\n                    pageSize: 5,\n                  }}\n                />\n              )}\n\n              {serviceTableErrorData.length > 0 && (\n                <Table\n                  bordered\n                  scroll={{ x: 700 }}\n                  columns={serviceTableColumns}\n                  dataSource={\n                    serviceTableErrorData.length > 0\n                      ? serviceTableErrorData\n                      : serviceTableCorrectData\n                  }\n                  pagination={{\n                    pageSize: 5,\n                  }}\n                />\n              )}\n\n              {importStep && (\n                <>\n                  <p style={{ textAlign: \"center\" }}>\n                    成功创建 {numInfo.host_num} 台主机\n                  </p>\n                  <p style={{ textAlign: \"center\" }}>\n                    本次共导入 {numInfo.product_num} 个产品，\n                    {numInfo.service_num} 个服务\n                  </p>\n                </>\n              )}\n            </div>\n\n            {importResult && (\n              <div\n                style={{\n                  display: \"inline-block\",\n                  marginLeft: \"50%\",\n                  transform: \"translateX(-50%)\",\n                  marginTop: 40,\n                }}\n              >\n                <p\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    fontSize: 16,\n                  }}\n                >\n                  <SyncOutlined\n                    spin\n                    style={{\n                      marginRight: 16,\n                    }}\n                  />\n                  即将进入安装，请稍后 ...\n                </p>\n              </div>\n            )}\n\n            {importResult === false && (\n              <div\n                style={{\n                  display: \"inline-block\",\n                  marginLeft: \"50%\",\n                  transform: \"translateX(-50%)\",\n                  marginTop: 40,\n                }}\n              >\n                <Button\n                  style={{ marginRight: 16 }}\n                  onClick={() => {\n                    setStepNum(0);\n                  }}\n                >\n                  重新上传\n                </Button>\n              </div>\n            )}\n          </>\n        )}\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "omp_web/src/pages/DeploymentPlan/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpMessageModal } from \"@/components\";\nimport { Button } from \"antd\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\n//import updata from \"@/store_global/globalStore\";\nimport { useDispatch } from \"react-redux\";\nimport { ExclamationCircleOutlined } from \"@ant-design/icons\";\nimport getColumnsConfig from \"./config/columns\";\nimport { ImportPlanModal } from \"./config/models\";\nimport { useHistory } from \"react-router-dom\";\n\nconst DeploymentPlan = () => {\n  const dispatch = useDispatch();\n  const history = useHistory();\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [userListSource, setUserListSource] = useState([]);\n  const [searchValue, setSearchValue] = useState(\"\");\n  const [selectValue, setSelectValue] = useState();\n\n  const [labelControl, setLabelControl] = useState(\"plan_name\");\n  const [instanceSelectValue, setInstanceSelectValue] = useState(\"\");\n\n  const [operable, setOperable] = useState(false);\n\n  // 导入弹框\n  const [importPlan, setImportPlan] = useState(false);\n  // 不可用弹框\n  const [disableModal, setDisableModal] = useState(false);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n  const [showModal, setShowModal] = useState(false);\n\n  const msgRef = useRef(null);\n\n  // 获取导入模板按钮是否可操作\n  function getOpreable() {\n    fetchGet(apiRequest.deloymentPlan.deploymentOperable)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code === 0 && res.data === true) {\n            setOperable(true);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.deloymentPlan.deploymentList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n        // 获取按钮是否可操作\n        getOpreable();\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  //console.log(checkedList)\n  // 防止在校验进入死循环\n  const flag = useRef(null);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          onClick={() => {\n            if (operable) setImportPlan(true);\n            else setDisableModal(true);\n          }}\n        >\n          导入模板\n        </Button>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(history)}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n\n      <ImportPlanModal\n        importPlan={importPlan}\n        setImportPlan={setImportPlan}\n      ></ImportPlanModal>\n\n      <OmpMessageModal\n        visibleHandle={[disableModal, setDisableModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        noFooter={true}\n      >\n        <div style={{ padding: \"20px\" }}>\n          已安装服务，快速部署仅在未安装任何服务状态下可用\n          <div\n            style={{\n              textAlign: \"center\",\n              position: \"relative\",\n              bottom: -26,\n            }}\n          >\n            <Button\n              type=\"primary\"\n              onClick={() => {\n                setDisableModal(false);\n              }}\n            >\n              确定\n            </Button>\n          </div>\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default DeploymentPlan;\n"
  },
  {
    "path": "omp_web/src/pages/EmailSettings/index.js",
    "content": "import { OmpContentWrapper } from \"@/components\";\nimport { Spin, Form, Input, Button, InputNumber, message } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport styles from \"./index.module.less\";\nimport { MailOutlined } from \"@ant-design/icons\";\n\nconst EmailSettings = () => {\n  const [loading, setLoading] = useState(false);\n  const [dataSource, setDataSource] = useState([]);\n  const [form] = Form.useForm();\n\n  function fetchData() {\n    setLoading(true);\n    fetchGet(apiRequest.emailSetting.querySetting)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          form.setFieldsValue({\n            address: res.data.host,\n            port: res.data.port,\n            email: res.data.username,\n            token: res.data.password,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  const multipleUpdate = (data) => {\n    setLoading(true);\n    fetchPost(apiRequest.emailSetting.updateSetting, {\n      body: {\n        host: data.address,\n        port: data.port,\n        username: data.email,\n        password: data.token,\n      },\n    })\n      .then((res) => {\n        console.log(res);\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"更新邮件全局设置成功\");\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        fetchData();\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div className={styles.header}>\n        <MailOutlined style={{ paddingRight: 5 }} />\n        全局设置\n      </div>\n      <Spin spinning={loading}>\n        <Form\n          name=\"email-setting\"\n          labelCol={{ span: 3 }}\n          wrapperCol={{ span: 6 }}\n          style={{ paddingTop: 40 }}\n          onFinish={multipleUpdate}\n          form={form}\n        >\n          <Form.Item\n            label=\"邮件服务器\"\n            name=\"address\"\n            rules={[{ required: true, message: \"请输入邮件服务器地址\" }]}\n          >\n            <Input\n              placeholder=\"例如: 192.168.10.10\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"端口号\"\n            name=\"port\"\n            rules={[{ required: true, message: \"请输入端口号\" }]}\n          >\n            <InputNumber\n              placeholder=\"例如: 465\"\n              min={1}\n              max={65535}\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"发件人邮箱\"\n            name=\"email\"\n            rules={[\n              {\n                type: \"email\",\n                message: \"请输入正确格式的邮箱\",\n              },\n              {\n                required: true,\n                message: \"请输入发件人邮箱\",\n              },\n            ]}\n          >\n            <Input\n              placeholder=\"例如: emailname@163.com\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"发件人令牌\"\n            name=\"token\"\n            rules={[{ required: true, message: \"请输入发件人令牌\" }]}\n          >\n            <Input.Password\n              placeholder=\"请输入Token\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item className={styles.saveButtonWrapper}>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              className={styles.saveButton}\n            >\n              保存\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </OmpContentWrapper>\n  );\n};\n\nexport default EmailSettings;\n"
  },
  {
    "path": "omp_web/src/pages/EmailSettings/index.module.less",
    "content": ".header {\n  padding: 10px;\n  padding-left: 20px;\n  background-color: #f7f7f7;\n}\n\n.saveButtonWrapper {\n  height: 100px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.saveButton {\n  margin-left: 50%;\n  transform: translateX(-50%);\n}\n"
  },
  {
    "path": "omp_web/src/pages/ExceptionList/config/columns.js",
    "content": "import { colorConfig } from \"@/utils/utils\";\nimport { Tooltip, Badge } from \"antd\";\nimport moment from \"moment\";\n\nconst getColumnsConfig = (\n  queryRequest,\n  setShowIframe,\n  //updateAlertRead,\n  history,\n  initfilter\n) => {\n  return [\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      fixed: \"left\",\n      // sorter: (a, b) => a.instance_name - b.instance_name,\n      // sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <Badge dot={record.is_read === 0} offset={[5, 2]}>\n              <span style={{ fontSize: 12 }}>\n                {record.instance_name ? record.instance_name : \"-\"}\n              </span>\n            </Badge>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      //width: 180,\n      ellipsis: true,\n      sorter: (a, b) => a.ip - b.ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        return (\n          <a\n            onClick={() => {\n              text &&\n                history.push({\n                  pathname: \"/resource-management/machine-management\",\n                  state: {\n                    ip: text,\n                  },\n                });\n            }}\n          >\n            {text}\n          </a>\n        );\n      },\n    },\n    {\n      title: \"级别\",\n      key: \"severity\",\n      dataIndex: \"severity\",\n      align: \"center\",\n      width: 120,\n      // sorter: (a, b) => a.severity - b.severity,\n      // sortDirections: [\"descend\", \"ascend\"],\n      //ellipsis: true,\n      //width:120,\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"critical\",\n          text: \"严重\",\n        },\n        {\n          value: \"warning\",\n          text: \"警告\",\n        },\n      ],\n      render: (text) => {\n        switch (text) {\n          case \"critical\":\n            return <span style={{ color: colorConfig[text] }}>严重</span>;\n          case \"warning\":\n            return <span style={{ color: colorConfig[text] }}>警告</span>;\n          case \"info\":\n            return <span style={{ color: colorConfig[text] }}>警告</span>;\n          default:\n            return \"-\";\n        }\n      },\n    },\n    {\n      title: \"告警类型\",\n      key: \"type\",\n      dataIndex: \"type\",\n      // usefilter: true,\n      // queryRequest: queryRequest,\n      // initfilter: initfilter,\n      // filterMenuList: [\n      //   {\n      //     value: \"service\",\n      //     text: \"服务\",\n      //   },\n      //   {\n      //     value: \"host\",\n      //     text: \"主机\",\n      //   },\n      // ],\n      align: \"center\",\n      //ellipsis: true,\n      width: 150,\n      render: (text) => {\n        if (text == \"host\") {\n          return \"主机\";\n        } else if (text == \"service\") {\n          return \"服务\";\n        } else if (text == \"component\") {\n          return \"组件\";\n        } else if (text == \"database\") {\n          return \"数据库\";\n        }\n      },\n    },\n    {\n      title: \"告警描述\",\n      key: \"description\",\n      dataIndex: \"description\",\n      align: \"center\",\n      width: 420,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"告警时间\",\n      //width:180,\n      key: \"date\",\n      dataIndex: \"date\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.date - b.date,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        return str;\n      },\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      fixed: \"right\",\n      align: \"center\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              {record.monitor_url ? (\n                <a\n                  onClick={() => {\n                    //record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_url,\n                      record: {\n                        ...record,\n                        ip: record.ip,\n                      },\n                      isLog: false,\n                    });\n                  }}\n                >\n                  监控\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n              )}\n\n              {record.type == \"host\" ? (\n                <a\n                  onClick={() =>\n                    history.push({\n                      pathname: \"/status-patrol/patrol-inspection-record\",\n                    })\n                  }\n                  style={{ marginLeft: \"10px\" }}\n                >\n                  分析\n                </a>\n              ) : record.log_url ? (\n                <a\n                  onClick={() => {\n                    //record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.log_url,\n                      record: {\n                        ...record,\n                        ip: record.ip,\n                      },\n                      isLog: true,\n                    });\n                  }}\n                  style={{ marginLeft: \"10px\" }}\n                >\n                  日志\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  日志\n                </span>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/ExceptionList/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpSelect,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, Select, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport getColumnsConfig from \"./config/columns\";\nimport { SearchOutlined } from \"@ant-design/icons\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst ExceptionList = () => {\n  const history = useHistory();\n  const location = useLocation();\n\n  const initIp = location.state?.ip;\n  const initInstanceName = location.state?.instance_name;\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [ipListSource, setIpListSource] = useState([]);\n\n  const [selectValue, setSelectValue] = useState(initIp);\n\n  const [instanceSelectValue, setInstanceSelectValue] =\n    useState(initInstanceName);\n\n  const [searchParams, setSearchParams] = useState({});\n\n  // 筛选label\n  const [labelControl, setLabelControl] = useState(\n    initIp ? \"ip\" : \"instance_name\"\n  );\n\n  const [showIframe, setShowIframe] = useState({});\n\n  function fetchData(searchParams = {}) {\n    setLoading(true);\n    fetchGet(apiRequest.ExceptionList.exceptionList, {\n      params: {\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setSearchParams(searchParams);\n          setDataSource(\n            res.data.map((item, idx) => ({\n              ...item,\n              key: idx + item.ip,\n            }))\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchIPlist();\n      });\n  }\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData({\n      ip: location.state?.ip,\n      type: location.state?.type,\n      instance_name: location.state?.instance_name,\n    });\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n        <div />\n        <div style={{ display: \"flex\" }}>\n          <div style={{ display: \"flex\", marginLeft: \"10px\" }}>\n            <Input.Group compact style={{ display: \"flex\" }}>\n              <Select\n                value={labelControl}\n                style={{ width: 100 }}\n                onChange={(e) => {\n                  setLabelControl(e);\n                  fetchData({\n                    ...searchParams,\n                    ip: null,\n                    instance_name: null,\n                  });\n                  setInstanceSelectValue();\n                  setSelectValue();\n                }}\n              >\n                <Select.Option value=\"ip\"> IP地址</Select.Option>\n                <Select.Option value=\"instance_name\">实例名称</Select.Option>\n              </Select>\n              {labelControl === \"ip\" && (\n                <OmpSelect\n                  searchLoading={searchLoading}\n                  selectValue={selectValue}\n                  listSource={ipListSource}\n                  setSelectValue={setSelectValue}\n                  fetchData={(value) => {\n                    fetchData({ ...searchParams, ip: value });\n                  }}\n                />\n              )}\n              {labelControl === \"instance_name\" && (\n                <Input\n                  placeholder=\"输入实例名称\"\n                  style={{ width: 200 }}\n                  allowClear\n                  value={instanceSelectValue}\n                  onChange={(e) => {\n                    setInstanceSelectValue(e.target.value);\n                    if (!e.target.value) {\n                      fetchData({\n                        ...searchParams,\n                        instance_name: null,\n                      });\n                    }\n                  }}\n                  onBlur={() => {\n                    if (instanceSelectValue) {\n                      fetchData({\n                        ...searchParams,\n                        instance_name: instanceSelectValue,\n                      });\n                    }\n                  }}\n                  onPressEnter={() => {\n                    fetchData({\n                      ...searchParams,\n                      instance_name: instanceSelectValue,\n                    });\n                  }}\n                  suffix={\n                    !instanceSelectValue && (\n                      <SearchOutlined\n                        style={{ fontSize: 12, color: \"#b6b6b6\" }}\n                      />\n                    )\n                  }\n                />\n              )}\n            </Input.Group>\n\n            <Button\n              style={{ marginLeft: 10 }}\n              onClick={() => {\n                fetchData({ ...searchParams });\n              }}\n            >\n              刷新\n            </Button>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //scroll={{ x: 1400 }}\n          onChange={(e, filters, sorter) => {\n            if (sorter.columnKey) {\n              let sort = sorter.order == \"descend\" ? 0 : 1;\n              setTimeout(() => {\n                fetchData({\n                  ...searchParams,\n                  ordering: sorter.column ? sorter.columnKey : null,\n                  asc: sorter.column ? sort : null,\n                });\n              }, 200);\n            }\n          }}\n          columns={getColumnsConfig(\n            (params) => {\n              fetchData({ ...searchParams, ...params });\n            },\n            setShowIframe,\n            history,\n            location.state?.type\n          )}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  //justifyContent: \"space-between\",\n                  flexDirection: \"row-reverse\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {dataSource?.length}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n          }}\n          //rowKey={(record) => record.ip}\n          //checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n    </OmpContentWrapper>\n  );\n};\n\nexport default ExceptionList;\n"
  },
  {
    "path": "omp_web/src/pages/HomePage/index.js",
    "content": "import { apiRequest } from \"@/config/requestApi\";\nimport { fetchGet } from \"@/utils/request\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { Spin } from \"antd\";\nimport React, { useEffect, useState } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport styles from \"./index.module.less\";\nimport OmpStateBlock from \"@/components/OmpStateBlock\";\nimport { OmpProgress } from \"@/components\";\n//import { context } from \"@/Root\";\nimport ExceptionList from \"./warningList\";\nimport { OmpContentWrapper } from \"@/components\";\n\nfunction calcPercentage(normal = 0, total = 1) {\n  const percent = ((normal / total) * 100).toFixed(0);\n  return isNaN(Number(percent)) ? 100 : Number(percent);\n}\n\nconst Homepage = () => {\n  const history = useHistory();\n\n  const [isLoading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState({});\n\n  // data数据源，key聚合数据的唯一值\n  const dataAggregation = (data, key) => {\n    let arr = [];\n    data?.map((i, d) => {\n      let isExistenceArr = arr.filter((e) => e[key] == i[key]);\n      // console.log(isExistenceArr);\n      if (isExistenceArr.length == 0) {\n        arr.push({\n          [key]: i[key],\n          severity: i.severity,\n          info: [\n            {\n              ...i,\n            },\n          ],\n        });\n      } else {\n        let m = data[d];\n        // console.log(m);\n        let idx = arr.indexOf(isExistenceArr[0]);\n        arr[idx] = {\n          [key]: i[key],\n          severity: i.severity,\n          info: [\n            ...arr[idx].info,\n            {\n              ...m,\n            },\n          ],\n        };\n      }\n    });\n    console.log(arr);\n    return arr;\n  };\n\n  const queryData = () => {\n    setLoading(true);\n    fetchGet(apiRequest.homepage.instrumentPanel)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          setDataSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryData();\n  }, []);\n  console.log();\n  return (\n    <OmpContentWrapper\n      wrapperStyle={{\n        width: \"100%\",\n        height: \"calc(100% - 40px)\",\n        backgroundColor: \"#edf0f3\",\n        padding: 0,\n      }}\n    >\n      <div className={styles.homepageWrapper}>\n        {/* <OmpProgress /> */}\n        <Spin spinning={isLoading}>\n          <div\n            className={styles.pageBlock}\n            style={{\n              borderRadius: 4,\n              //border:\"1px solid  #DCDEE5\",\n              marginBottom: \"15px\",\n              backgroundColor: \"white\",\n            }}\n          >\n            <div className={styles.blockTitle}>状态概览</div>\n            <div\n              style={{\n                display: \"flex\",\n                flexFlow: \"row wrap\",\n                justifyContent: \"space-around\",\n                //border: \"1px solid  #DCDEE5\"\n              }}\n              className={styles.blockContent}\n            >\n              {/* 应用服务 */}\n              <div className={styles.blockOverviewItem}>\n                <OmpProgress\n                  percent={calcPercentage(\n                    dataSource.service?.service_info_all_count -\n                      dataSource.service?.service_info_exc_count -\n                      dataSource.service?.service_info_no_monitor_count,\n                    dataSource.service?.service_info_all_count\n                  )}\n                  trafficWay={[\n                    {\n                      name: \"异常\",\n                      value: dataSource.service?.service_info_exc_count,\n                    },\n                    {\n                      name: \"未监控\",\n                      value: dataSource.service?.service_info_no_monitor_count,\n                    },\n                    {\n                      name: \"正常\",\n                      value:\n                        dataSource.service?.service_info_all_count -\n                        dataSource.service?.service_info_exc_count -\n                        dataSource.service?.service_info_no_monitor_count,\n                    },\n                  ]}\n                />\n                <div className={styles.progressInfo}>\n                  <div>应用服务状态</div>\n                  <div\n                    onClick={() =>\n                      dataSource.service?.service_info_all_count &&\n                      history.push({\n                        pathname: \"/application_management/service_management\",\n                        state: {\n                          app_type: \"1\",\n                        },\n                      })\n                    }\n                    style={\n                      dataSource.service?.service_info_all_count\n                        ? { cursor: \"pointer\", marginBottom: 2 }\n                        : { marginBottom: 2 }\n                    }\n                  >\n                    服务总数：\n                    <span\n                      style={\n                        dataSource.service?.service_info_all_count\n                          ? { color: \"#1890ff\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.service?.service_info_all_count}个\n                    </span>\n                  </div>\n                  {/* <div style={{ marginBottom: 2 }}>\n                    未监控数：\n                    <span>\n                      {dataSource.service?.service_info_no_monitor_count}个\n                    </span>\n                  </div> */}\n                  <div\n                    style={\n                      dataSource.service?.service_info_exc_count > 0\n                        ? { cursor: \"pointer\", paddingTop: 10 }\n                        : { paddingTop: 10 }\n                    }\n                    onClick={() =>\n                      dataSource.service?.service_info_exc_count &&\n                      history.push({\n                        pathname: \"/application-monitoring/exception-list\",\n                        state: {\n                          type: \"service\",\n                        },\n                      })\n                    }\n                  >\n                    异常服务：\n                    <span\n                      style={\n                        dataSource.service?.service_info_exc_count > 0\n                          ? { color: \"#cf1322\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.service?.service_info_exc_count}个\n                    </span>\n                  </div>\n                </div>\n              </div>\n\n              {/* 基础组件 */}\n              <div className={styles.blockOverviewItem}>\n                <OmpProgress\n                  percent={calcPercentage(\n                    dataSource.component?.component_info_all_count -\n                      dataSource.component?.component_info_exc_count -\n                      dataSource.component?.component_info_no_monitor_count,\n                    dataSource.component?.component_info_all_count\n                  )}\n                  trafficWay={[\n                    {\n                      name: \"异常\",\n                      value: dataSource.component?.component_info_exc_count,\n                    },\n                    {\n                      name: \"未监控\",\n                      value:\n                        dataSource.component?.component_info_no_monitor_count,\n                    },\n                    {\n                      name: \"正常\",\n                      value:\n                        dataSource.component?.component_info_all_count -\n                        dataSource.component?.component_info_exc_count -\n                        dataSource.component?.component_info_no_monitor_count,\n                    },\n                  ]}\n                />\n                <div className={styles.progressInfo}>\n                  <div>基础组件状态</div>\n                  <div\n                    onClick={() =>\n                      dataSource.component?.component_info_all_count &&\n                      history.push({\n                        pathname: \"/application_management/service_management\",\n                        state: {\n                          app_type: \"0\",\n                        },\n                      })\n                    }\n                    style={\n                      dataSource.component?.component_info_all_count\n                        ? { cursor: \"pointer\", marginBottom: 2 }\n                        : { marginBottom: 2 }\n                    }\n                  >\n                    组件实例：\n                    <span\n                      style={\n                        dataSource.component?.component_info_all_count\n                          ? { color: \"#1890ff\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.component?.component_info_all_count}个\n                    </span>\n                  </div>\n                  {/* <div style={{ marginBottom: 2 }}>\n                    未监控数：\n                    <span>\n                      {dataSource.component?.component_info_no_monitor_count}个\n                    </span>\n                  </div> */}\n                  <div\n                    style={\n                      dataSource.component?.component_info_exc_count > 0\n                        ? { cursor: \"pointer\", paddingTop: 10 }\n                        : { paddingTop: 10 }\n                    }\n                    onClick={() =>\n                      dataSource.component?.component_info_exc_count &&\n                      history.push({\n                        pathname: \"/application-monitoring/exception-list\",\n                        state: {\n                          type: \"component\",\n                        },\n                      })\n                    }\n                  >\n                    异常组件：\n                    <span\n                      style={\n                        dataSource.component?.component_info_exc_count > 0\n                          ? { color: \"#cf1322\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.component?.component_info_exc_count}个\n                    </span>\n                  </div>\n                </div>\n              </div>\n\n              {/* 数据库 */}\n              <div className={styles.blockOverviewItem}>\n                <OmpProgress\n                  percent={calcPercentage(\n                    dataSource.database?.database_info_all_count -\n                      dataSource.database?.database_info_exc_count -\n                      dataSource.database?.database_info_no_monitor_count,\n                    dataSource.database?.database_info_all_count\n                  )}\n                  trafficWay={[\n                    {\n                      name: \"异常\",\n                      value: dataSource.database?.database_info_exc_count,\n                    },\n                    {\n                      name: \"未监控\",\n                      value:\n                        dataSource.database?.database_info_no_monitor_count,\n                    },\n                    {\n                      name: \"正常\",\n                      value:\n                        dataSource.database?.database_info_all_count -\n                        dataSource.database?.database_info_exc_count -\n                        dataSource.database?.database_info_no_monitor_count,\n                    },\n                  ]}\n                />\n                <div className={styles.progressInfo}>\n                  <div>数据库状态</div>\n                  <div\n                    onClick={() =>\n                      dataSource.database?.database_info_all_count &&\n                      history.push({\n                        pathname: \"/application_management/service_management\",\n                        state: {\n                          label_name: \"数据库\",\n                        },\n                      })\n                    }\n                    style={\n                      dataSource.database?.database_info_all_count\n                        ? { cursor: \"pointer\", marginBottom: 2 }\n                        : { marginBottom: 2 }\n                    }\n                  >\n                    数据库实例：\n                    <span\n                      style={\n                        dataSource.database?.database_info_all_count > 0\n                          ? { color: \"#1890ff\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.database?.database_info_all_count}个\n                    </span>\n                  </div>\n                  {/* <div style={{ marginBottom: 2 }}>\n                    未监控数：\n                    <span>\n                      {dataSource.database?.database_info_no_monitor_count}个\n                    </span>\n                  </div> */}\n                  <div\n                    style={\n                      dataSource.database?.database_info_exc_count > 0\n                        ? { cursor: \"pointer\", paddingTop: 10 }\n                        : { paddingTop: 10 }\n                    }\n                    onClick={() =>\n                      dataSource.database?.database_info_exc_count &&\n                      history.push({\n                        pathname: \"/application-monitoring/exception-list\",\n                        state: {\n                          type: \"database\",\n                        },\n                      })\n                    }\n                  >\n                    异常实例：\n                    <span\n                      style={\n                        dataSource.database?.database_info_exc_count > 0\n                          ? { color: \"#cf1322\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.database?.database_info_exc_count}个\n                    </span>\n                  </div>\n                </div>\n              </div>\n              {/* 主机状态 */}\n              <div className={styles.blockOverviewItem}>\n                <OmpProgress\n                  percent={calcPercentage(\n                    dataSource.host?.host_info_all_count -\n                      dataSource.host?.host_info_exc_count -\n                      dataSource.host?.host_info_no_monitor_count,\n                    dataSource.host?.host_info_all_count\n                  )}\n                  trafficWay={[\n                    {\n                      name: \"异常\",\n                      value: dataSource.host?.host_info_exc_count,\n                    },\n                    {\n                      name: \"未监控\",\n                      value: dataSource.host?.host_info_no_monitor_count,\n                    },\n                    {\n                      name: \"正常\",\n                      value:\n                        dataSource.host?.host_info_all_count -\n                        dataSource.host?.host_info_exc_count -\n                        dataSource.host?.host_info_no_monitor_count,\n                    },\n                  ]}\n                />\n                <div className={styles.progressInfo}>\n                  <div>主机状态</div>\n                  <div\n                    onClick={() =>\n                      dataSource.host?.host_info_all_count &&\n                      history.push({\n                        pathname: \"/resource-management/machine-management\",\n                      })\n                    }\n                    style={\n                      dataSource.host?.host_info_all_count\n                        ? { cursor: \"pointer\" }\n                        : {}\n                    }\n                  >\n                    主机总数：\n                    <span\n                      style={\n                        dataSource.host?.host_info_all_count\n                          ? { color: \"#1890ff\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.host?.host_info_all_count}个\n                    </span>\n                  </div>\n                  <div\n                    style={\n                      dataSource.host?.host_info_exc_count > 0\n                        ? { cursor: \"pointer\" }\n                        : {}\n                    }\n                    onClick={() =>\n                      dataSource.host?.host_info_exc_count &&\n                      history.push({\n                        pathname: \"/application-monitoring/exception-list\",\n                        state: {\n                          type: \"host\",\n                        },\n                      })\n                    }\n                  >\n                    异常主机：\n                    <span\n                      style={\n                        dataSource.host?.host_info_exc_count > 0\n                          ? { color: \"#cf1322\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.host?.host_info_exc_count}个\n                    </span>\n                  </div>\n                </div>\n              </div>\n              {/* 三方组件 */}\n              {/* <div className={styles.blockOverviewItem}>\n                <OmpProgress\n                  percent={calcPercentage(\n                    dataSource.third?.third_info_all_count -\n                      dataSource.third?.third_info_exc_count -\n                      dataSource.third?.third_info_no_monitor_count,\n                    dataSource.third?.third_info_all_count\n                  )}\n                  trafficWay={[\n                    {\n                      name: \"异常\",\n                      value: dataSource.third?.third_info_exc_count,\n                    },\n                    {\n                      name: \"未监控\",\n                      value: dataSource.third?.third_info_no_monitor_count,\n                    },\n                    {\n                      name: \"正常\",\n                      value:\n                        dataSource.third?.third_info_all_count -\n                        dataSource.third?.third_info_exc_count -\n                        dataSource.third?.third_info_no_monitor_count,\n                    },\n                  ]}\n                />\n                <div className={styles.progressInfo}>\n                  <div style={{ marginBottom: 8 }}>三方组件状态</div>\n                  <div\n                    onClick={() =>\n                      dataSource.third?.third_info_all_count &&\n                      history.push({\n                        pathname: \"/application_management/service_management\",\n                      })\n                    }\n                    style={\n                      dataSource.third?.third_info_all_count\n                        ? { cursor: \"pointer\", marginBottom: 2 }\n                        : { marginBottom: 2 }\n                    }\n                  >\n                    组件实例：\n                    <span\n                      style={\n                        dataSource.third?.third_info_all_count\n                          ? { color: \"#1890ff\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.third?.third_info_all_count}个\n                    </span>\n                  </div>\n                  <div style={{ marginBottom: 2 }}>\n                    未监控数：\n                    <span>\n                      {dataSource.third?.third_info_no_monitor_count}个\n                    </span>\n                  </div>\n                  <div\n                    style={\n                      dataSource.third?.third_info_exc_count > 0\n                        ? { cursor: \"pointer\" }\n                        : {}\n                    }\n                    onClick={() =>\n                      dataSource.third?.third_info_exc_count &&\n                      history.push({\n                        pathname: \"/application-monitoring/exception-list\",\n                      })\n                    }\n                  >\n                    异常实例：\n                    <span\n                      style={\n                        dataSource.third?.third_info_exc_count > 0\n                          ? { color: \"#cf1322\" }\n                          : {}\n                      }\n                    >\n                      {dataSource.third?.third_info_exc_count}个\n                    </span>\n                  </div>\n                </div>\n              </div> */}\n            </div>\n          </div>\n\n          <div\n            style={{\n              marginBottom: 10,\n              backgroundColor: \"#fff\",\n              paddingBottom: 10,\n            }}\n          >\n            <p\n              style={{\n                padding: 10,\n                paddingBottom: 0,\n                paddingTop: 10,\n                margin: 0,\n                fontWeight: 500,\n              }}\n            >\n              异常清单\n            </p>\n            <ExceptionList />\n          </div>\n\n          <div className={styles.pageBlock}>\n            <OmpStateBlock\n              // key={\"serviceData\"}\n              // tag={\"all\"}\n              link={(data) => {\n                console.log(data);\n                history.push({\n                  pathname: \"/application_management/service_management\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                    app_type: \"1\",\n                  },\n                });\n              }}\n              criticalLink={(data) => {\n                history.push({\n                  pathname: \"/application-monitoring/exception-list\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                    type: \"service\",\n                  },\n                });\n              }}\n              title={\"应用服务状态\"}\n              // hasNotMonitored\n              data={dataAggregation(\n                dataSource.service?.service_info_list,\n                \"instance_name\"\n              )}\n            />\n          </div>\n\n          <div className={styles.pageBlock}>\n            <OmpStateBlock\n              // key={\"serviceData\"}\n              // tag={\"all\"}\n              link={(data) => {\n                history.push({\n                  pathname: \"/application_management/service_management\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                    app_type: \"0\",\n                  },\n                });\n              }}\n              criticalLink={(data) => {\n                history.push({\n                  pathname: \"/application-monitoring/exception-list\",\n                  state: {\n                    instance_name: data?.info[0]?.instance_name,\n                    type: \"component\",\n                  },\n                });\n              }}\n              // hasNotMonitored\n              title={\"基础组件状态\"}\n              data={dataAggregation(\n                dataSource.component?.component_info_list,\n                \"instance_name\"\n              )}\n            />\n          </div>\n\n          <div className={styles.pageBlock}>\n            <OmpStateBlock\n              // key={\"serviceData\"}\n              // tag={\"all\"}\n              link={(data) => {\n                history.push({\n                  pathname: \"/application_management/service_management\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                    label_name: \"数据库\",\n                  },\n                });\n              }}\n              criticalLink={(data) => {\n                history.push({\n                  pathname: \"/application-monitoring/exception-list\",\n                  state: {\n                    instance_name: data?.info[0]?.instance_name,\n                    type: \"database\",\n                  },\n                });\n              }}\n              // hasNotMonitored\n              title={\"数据库状态\"}\n              data={dataAggregation(\n                dataSource.database?.database_info_list,\n                \"instance_name\"\n              )}\n            />\n          </div>\n          <div className={styles.pageBlock}>\n            <OmpStateBlock\n              // key={\"serviceData\"}\n              // tag={\"all\"}\n              title={\"主机运行状态\"}\n              link={(data) => {\n                history.push({\n                  pathname: \"/resource-management/machine-management\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                  },\n                });\n              }}\n              criticalLink={(data) => {\n                history.push({\n                  pathname: \"/application-monitoring/exception-list\",\n                  state: {\n                    ip: data?.info[0]?.ip,\n                    type: \"host\",\n                  },\n                });\n              }}\n              data={dataAggregation(dataSource.host?.host_info_list, \"ip\")}\n            />\n          </div>\n          {/* <div className={styles.pageBlock}>\n            <OmpStateBlock\n              // key={\"serviceData\"}\n              // tag={\"all\"}\n              link={(data) => {\n                history.push({\n                  pathname: \"/application_management/service_management\",\n                  state: {\n                    ip: data.ip,\n                  },\n                });\n              }}\n              criticalLink={(data) => {\n                history.push({\n                  pathname: \"/application-monitoring/exception-list\",\n                  state: {\n                    ip: data.ip,\n                    type: \"service\",\n                  },\n                });\n              }}\n              hasNotMonitored\n              title={\"三方组件状态\"}\n              data={dataSource.third?.third_info_list}\n            />\n          </div> */}\n        </Spin>\n      </div>\n    </OmpContentWrapper>\n  );\n};\n\nexport default React.memo(Homepage);\n"
  },
  {
    "path": "omp_web/src/pages/HomePage/index.module.less",
    "content": ".homepageWrapper {\n    //padding: 15px;\n    //padding-top:0px;\n    .pageBlock {\n      //border: 1px solid  #DCDEE5;\n      //background-color: aqua;\n      .blockTitle {\n        font-weight: 500;\n        //color: #333;\n        //border:1px solid  #DCDEE5;\n        padding: 10px 0 0px 10px;\n      }\n    }\n  }\n  \n  .blockContent {\n    //border: 1px solid  #DCDEE5;\n    background-color: #fff;\n    border-radius: 5px;\n    padding: 10px;\n    //margin-bottom: 15px;\n  }\n  \n  .blockOverviewItem {\n    flex: 1;\n    display: flex;\n    justify-content: center;\n    flex-flow: row nowrap;\n    padding-bottom: 10px;\n    padding-top: 5px;\n    border-radius: 5px;\n  \n    & > div:nth-child(1) {\n      margin-right: 15px;\n      align-self: center;\n      & > div:nth-child(1) {\n        width: 80px !important;\n        height: 80px !important;\n        font-size: 16px !important;\n      }\n    }\n  \n    .progressInfo {\n      color: #333;\n      & > div:nth-child(1) {\n        font-size: 13px;\n        font-weight: 500;\n        margin-bottom: 15px;\n      }\n      & > div:nth-child(2) {\n        margin-bottom: 10px;\n      }\n    }\n  }\n  \n  .checkboxGroup {\n    display: flex;\n    justify-content: flex-end;\n    margin-bottom: 10px;\n    // div {\n    //   display: flex;\n    // }\n  }\n  \n  .blockItemWrapper {\n    display: flex;\n    flex-flow: row wrap;\n  }\n  \n  .stateButton {\n    width: 180px;\n    margin-right: 10px;\n    margin-bottom: 10px;\n  \n    & > div {\n      max-width: 145px;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n  }\n  \n  .popContent {\n    & > span {\n      margin-right: 10px;\n    }\n  \n    .ip {\n      display: inline-flex;\n      // todo 这个地方最大宽度不确定，此处为ip地址最大长度，但会导致ip地址较短时页面空一大块\n      min-width: 120px;\n    }\n  }"
  },
  {
    "path": "omp_web/src/pages/HomePage/warningList.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpDrawer } from \"@/components\";\nimport { Tooltip, Badge } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit, colorConfig } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport moment from \"moment\";\nimport { useHistory } from \"react-router-dom\";\n\nconst ExceptionList = () => {\n  const history = useHistory();\n  const [loading, setLoading] = useState(false);\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [searchParams, setSearchParams] = useState({});\n\n  const [showIframe, setShowIframe] = useState({});\n\n  const [pageSize, setPageSize] = useState(5);\n\n  function fetchData(searchParams = {}, noLoading) {\n    !noLoading && setLoading(true);\n    fetchGet(apiRequest.ExceptionList.exceptionList, {\n      params: {\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setSearchParams(searchParams);\n          setDataSource(\n            res.data.map((item, idx) => ({\n              ...item,\n              key: idx + item.ip,\n            }))\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(null, true);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n          //fontSize:12\n        }}\n      >\n        <OmpTable\n          size=\"small\"\n          loading={loading}\n          //scroll={{ x: 1400 }}\n          onChange={(e, filters, sorter) => {\n            setPageSize(e.pageSize);\n            if (sorter.columnKey) {\n              let sort = sorter.order == \"descend\" ? 0 : 1;\n              setTimeout(() => {\n                fetchData({\n                  ...searchParams,\n                  ordering: sorter.column ? sorter.columnKey : null,\n                  asc: sorter.column ? sort : null,\n                });\n              }, 200);\n            }\n          }}\n          columns={getColumnsConfig(\n            (params) => {\n              fetchData({ ...searchParams, ...params });\n            },\n            setShowIframe,\n            history\n          )}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"5\", \"10\", \"20\", \"50\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  //justifyContent: \"space-between\",\n                  flexDirection: \"row-reverse\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p\n                  style={{\n                    color: \"rgb(152, 157, 171)\",\n                    position: \"relative\",\n                    top: -4,\n                  }}\n                >\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {dataSource?.length}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            pageSize: pageSize,\n          }}\n          //rowKey={(record) => record.ip}\n          //checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n    </OmpContentWrapper>\n  );\n};\n\nconst getColumnsConfig = (\n  queryRequest,\n  setShowIframe,\n  //updateAlertRead,\n  history\n) => {\n  return [\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      fixed: \"left\",\n      sorter: (a, b) => a.instance_name - b.instance_name,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <Badge dot={record.is_read === 0} offset={[5, 2]}>\n              <span style={{ fontSize: 12 }}>\n                {record.instance_name ? record.instance_name : \"-\"}\n              </span>\n            </Badge>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      ellipsis: true,\n      sorter: (a, b) => a.ip - b.ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        return (\n          <a\n            onClick={() => {\n              text &&\n                history.push({\n                  pathname: \"/resource-management/machine-management\",\n                  state: {\n                    ip: text,\n                  },\n                });\n            }}\n          >\n            {text}\n          </a>\n        );\n      },\n    },\n    {\n      title: \"级别\",\n      key: \"severity\",\n      dataIndex: \"severity\",\n      align: \"center\",\n      width: 120,\n      // sorter: (a, b) => a.severity - b.severity,\n      // sortDirections: [\"descend\", \"ascend\"],\n      //ellipsis: true,\n      //width:120,\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"critical\",\n          text: \"严重\",\n        },\n        {\n          value: \"warning\",\n          text: \"警告\",\n        },\n      ],\n      render: (text) => {\n        switch (text) {\n          case \"critical\":\n            return <span style={{ color: colorConfig[text] }}>严重</span>;\n          case \"warning\":\n            return <span style={{ color: colorConfig[text] }}>警告</span>;\n          case \"info\":\n            return <span style={{ color: colorConfig[text] }}>警告</span>;\n          default:\n            return \"-\";\n        }\n      },\n    },\n    {\n      title: \"告警类型\",\n      key: \"type\",\n      dataIndex: \"type\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      // filterMenuList: [\n      //   {\n      //     value: \"service\",\n      //     text: \"服务\",\n      //   },\n      //   {\n      //     value: \"host\",\n      //     text: \"主机\",\n      //   },\n      // ],\n      align: \"center\",\n      //ellipsis: true,\n      width: 150,\n      render: (text) => {\n        if (text == \"host\") {\n          return \"主机\";\n        } else if (text == \"service\") {\n          return \"服务\";\n        } else if (text == \"component\") {\n          return \"组件\";\n        } else if (text == \"database\") {\n          return \"数据库\";\n        }\n      },\n    },\n    {\n      title: \"告警描述\",\n      key: \"description\",\n      dataIndex: \"description\",\n      align: \"center\",\n      width: 420,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"告警时间\",\n      //width:180,\n      key: \"date\",\n      dataIndex: \"date\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.date - b.date,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        return str;\n      },\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      fixed: \"right\",\n      align: \"center\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              {record.monitor_url ? (\n                <a\n                  onClick={() => {\n                    //record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_url,\n                      record: {\n                        ...record,\n                        ip: record.ip,\n                      },\n                      isLog: false,\n                    });\n                  }}\n                >\n                  监控\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n              )}\n\n              {record.type == \"host\" ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() =>\n                    history.push({\n                      pathname: \"/status-patrol/patrol-inspection-record\",\n                    })\n                  }\n                >\n                  分析\n                </a>\n              ) : record.log_url ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() => {\n                    //record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.log_url,\n                      record: {\n                        ...record,\n                        ip: record.ip,\n                      },\n                      isLog: true,\n                    });\n                  }}\n                >\n                  日志\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  日志\n                </span>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default ExceptionList;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/config/ServiceUpgradeModal.js",
    "content": "import { Button, Modal, Select, Switch } from \"antd\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { CopyOutlined } from \"@ant-design/icons\";\n//import BMF from \"browser-md5-file\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { OmpTable } from \"@/components\";\nimport { useHistory } from \"react-router-dom\";\nimport { useSelector, useDispatch } from \"react-redux\";\n\nconst ServiceUpgradeModal = ({\n  vfModalVisibility,\n  setVfModalVisibility,\n  dataSource,\n  installTitle,\n  initLoading,\n}) => {\n  const uniqueKey = useSelector((state) => state.appStore.uniqueKey);\n\n  const reduxDispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const history = useHistory();\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n  // console.log(checkedList)\n  //应用服务选择的版本号\n  const versionInfo = useRef({});\n\n  const columns = [\n    {\n      title: \"名称\",\n      key: \"name\",\n      dataIndex: \"name\",\n      align: \"center\",\n      ellipsis: true,\n      width: 80,\n      render: (text, record) => {\n        return text;\n      },\n    },\n    {\n      title: \"升级版本\",\n      key: \"version\",\n      dataIndex: \"version\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        return (\n          <Select\n            bordered={false}\n            defaultValue={text[0]}\n            style={{ width: 120 }}\n            onSelect={(v) => {\n              versionInfo.current[record.name] = v;\n            }}\n          >\n            {text.map((item) => {\n              return (\n                <Select.Option value={item} key={`${item}-${record.name}`}>\n                  {item}\n                </Select.Option>\n              );\n            })}\n          </Select>\n        );\n      },\n    },\n  ];\n\n  // 高可用是否开启\n  const [highAvailabilityCheck, setHighAvailabilityCheck] = useState(false);\n\n  // 批量安装/服务安装选择确认请求\n  const createInstallInfo = (install_product) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createInstallInfo, {\n      body: {\n        high_availability: highAvailabilityCheck,\n        install_product: install_product,\n        unique_key: uniqueKey,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            //reduxDispatch(getStep1ChangeAction(res.data.data));\n          }\n          history.push(\"/application_management/app_store/installation\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 组件安装\n  const createComponentInstallInfo = (install_product) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.createComponentInstallInfo, {\n      body: {\n        high_availability: highAvailabilityCheck,\n        install_component: install_product,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res.data && res.data.data) {\n            // reduxDispatch(getStep1ChangeAction(res.data.data));\n            // reduxDispatch(getUniqueKeyChangeAction(res.data.unique_key));\n          }\n          history.push(\"/application_management/app_store/installation\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    // 选中全部\n    setCheckedList(dataSource.filter((item) => item.is_continue));\n  }, [dataSource]);\n  // console.log(checkedList)\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <CopyOutlined />\n          </span>\n          <span>\n            {installTitle == \"服务\"\n              ? \"服务安装-选择版本\"\n              : installTitle == \"组件\"\n              ? \"组件安装-选择版本\"\n              : \"批量安装-选择应用服务\"}\n          </span>\n        </span>\n      }\n      afterClose={() => {\n        setCheckedList([]);\n        setHighAvailabilityCheck(false);\n      }}\n      onCancel={() => {\n        setVfModalVisibility(false);\n      }}\n      visible={vfModalVisibility}\n      footer={null}\n      //width={1000}\n      loading={loading}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      destroyOnClose\n    >\n      <div>\n        <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n          <OmpTable\n            size=\"small\"\n            scroll={{ y: 270 }}\n            loading={loading || initLoading}\n            //scroll={{ x: 1900 }}\n            columns={columns}\n            dataSource={dataSource}\n            rowKey={(record) => {\n              return record.name;\n            }}\n            checkedState={[checkedList, setCheckedList]}\n            pagination={false}\n            notSelectable={(record) => ({\n              // is_continue的不能选中\n              disabled: !record.is_continue,\n            })}\n            rowSelection={{\n              selectedRowKeys: checkedList?.map((item) => item?.name),\n            }}\n          />\n        </div>\n        <div\n          style={{\n            display: \"flex\",\n            marginTop: 20,\n            justifyContent: \"space-between\",\n            padding: \"0px 20px\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            高可用\n            <Switch\n              style={{ marginLeft: 10 }}\n              checked={highAvailabilityCheck}\n              onChange={(e) => {\n                setHighAvailabilityCheck(e);\n              }}\n            />\n          </div>\n          <div style={{ display: \"flex\" }}>\n            <div style={{ marginRight: 15 }}>\n              已选择 {checkedList.length} 个\n            </div>\n            <div>共 {dataSource.length} 个</div>\n          </div>\n        </div>\n        <div\n          style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n        >\n          <div\n            style={{\n              width: 170,\n              display: \"flex\",\n              justifyContent: \"space-between\",\n            }}\n          >\n            <Button onClick={() => setVfModalVisibility(false)}>取消</Button>\n            <Button\n              type=\"primary\"\n              style={{ marginLeft: 16 }}\n              loading={loading || initLoading}\n              disabled={checkedList.length == 0}\n              onClick={() => {\n                if (installTitle == \"组件\") {\n                  let install_product = checkedList.map((item) => {\n                    return {\n                      name: item.name,\n                      version:\n                        versionInfo.current[item.name] || item.version[0],\n                    };\n                  });\n                  createComponentInstallInfo(install_product);\n                } else {\n                  let install_product = checkedList.map((item) => {\n                    return {\n                      name: item.name,\n                      version:\n                        versionInfo.current[item.name] || item.version[0],\n                    };\n                  });\n                  createInstallInfo(install_product);\n                  // history.push(\"/application_management/app_store/installation\")\n                }\n              }}\n            >\n              确认选择\n            </Button>\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default ServiceUpgradeModal;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/index.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { useState, useEffect } from \"react\";\nimport { useHistory } from \"react-router-dom\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { Button } from \"antd\";\nimport {\n  handleResponse,\n  _idxInit,\n  nonEmptyProcessing,\n  renderDisc,\n} from \"@/utils/utils\";\nimport moment from \"moment\";\nimport ServiceRollbackModal from \"../AppStore/config/ServiceRollbackModal\";\n\nconst renderStatus = (record) => {\n  let text = record.state;\n  if (text.includes(\"SUCCESS\")) {\n    return (\n      <span>\n        {renderDisc(\"normal\", 7, -1)}\n        {record.state_display}\n      </span>\n    );\n  }\n  if (text.includes(\"FAIL\")) {\n    return (\n      <span>\n        {renderDisc(\"critical\", 7, -1)}\n        {record.state_display}\n      </span>\n    );\n  }\n  if (text.includes(\"WAIT\") || text.includes(\"ING\")) {\n    return (\n      <span>\n        {renderDisc(\"warning\", 7, -1)}\n        {record.state_display}\n      </span>\n    );\n  }\n  return \"-\";\n};\n\nconst typeMap = {\n  MainInstallHistory: \"安装\",\n  RollbackHistory: \"回滚\",\n  UpgradeHistory: \"升级\",\n};\n\nconst notProhibit = {\n  cursor: \"not-allowed\",\n  color: \"#bbbbbb\",\n};\n\nconst InstallationRecord = () => {\n  const history = useHistory();\n\n  const [loading, setLoading] = useState(false);\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [vfModalVisibility, setVfModalVisibility] = useState(false);\n\n  const [rowId, setRowId] = useState(\"\");\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"类型\",\n      width: 80,\n      key: \"module\",\n      dataIndex: \"module\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      usefilter: true,\n      queryRequest: (params) => {\n        fetchData(\n          { current: 1, pageSize: pagination.pageSize },\n          pagination.ordering,\n          { ...pagination.searchParams, ...params }\n        );\n      },\n      // initfilter: initfilterAppType,\n      filterMenuList: Object.keys(typeMap).map((k) => {\n        return {\n          value: k,\n          text: typeMap[k],\n        };\n      }),\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record, idx) => {\n        //history.push()\n        return typeMap[text];\n      },\n    },\n    {\n      title: \"执行用户\",\n      key: \"operator\",\n      width: 100,\n      dataIndex: \"operator\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"状态\",\n      key: \"install_status\",\n      dataIndex: \"install_status\",\n      width: 100,\n      //sorter: (a, b) => a.is_superuser - b.is_superuser,\n      //sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        return renderStatus(record);\n      },\n    },\n    {\n      title: \"服务数量\",\n      key: \"count\",\n      dataIndex: \"count\",\n      width: 60,\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"开始时间\",\n      key: \"created\",\n      dataIndex: \"created\",\n      align: \"center\",\n      width: 120,\n      sorter: (a, b) => a.created - b.created,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"结束时间\",\n      key: \"end_time\",\n      dataIndex: \"end_time\",\n      align: \"center\",\n      width: 120,\n      render: (text, record) => {\n        if (record.install_status == 1) {\n          return \"-\";\n        }\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"用时\",\n      key: \"duration\",\n      dataIndex: \"duration\",\n      align: \"center\",\n      width: 120,\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"操作\",\n      key: \"1\",\n      width: 58,\n      dataIndex: \"1\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        switch (record.module) {\n          case \"MainInstallHistory\":\n            return (\n              <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n                <div\n                  onClick={() => {\n                    history.push({\n                      pathname:\n                        \"/application_management/app_store/installation\",\n                      state: {\n                        uniqueKey: record.module_id,\n                        step: 3,\n                      },\n                    });\n                  }}\n                  style={{ display: \"flex\", justifyContent: \"space-around\" }}\n                >\n                  <a>查看</a>\n                </div>\n              </div>\n            );\n            break;\n          case \"RollbackHistory\":\n            return (\n              <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n                <div\n                  onClick={() => {\n                    history.push({\n                      pathname:\n                        \"/application_management/app_store/service_rollback\",\n                      state: {\n                        history: record.module_id,\n                      },\n                    });\n                  }}\n                >\n                  <a>查看</a>\n                </div>\n              </div>\n            );\n            break;\n          case \"UpgradeHistory\":\n            return (\n              <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n                <div style={{ margin: \"auto\" }}>\n                  <a\n                    onClick={() => {\n                      history.push({\n                        pathname:\n                          \"/application_management/app_store/service_upgrade\",\n                        state: {\n                          history: record.module_id,\n                        },\n                      });\n                    }}\n                  >\n                    查看\n                  </a>\n                  <a\n                    style={\n                      record.can_rollback\n                        ? { marginLeft: 10 }\n                        : {\n                            marginLeft: 10,\n                            ...notProhibit,\n                          }\n                    }\n                    onClick={() => {\n                      if (record.can_rollback) {\n                        setRowId(record.module_id);\n                        setVfModalVisibility(true);\n                      }\n                    }}\n                  >\n                    回滚\n                  </a>\n                </div>\n              </div>\n            );\n            break;\n          default:\n            return \"-\";\n            break;\n        }\n      },\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    ordering,\n    searchParams\n  ) {\n    console.log(searchParams);\n    setLoading(true);\n    fetchGet(apiRequest.installHistoryPage.queryAllList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        search: searchParams?.module,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ paddingBottom: 0 }}>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              // console.log(pagination, \"hosts/hosts/?page=1&size=10\");\n              fetchData(\n                {\n                  current: pagination.current,\n                  pageSize: pagination.pageSize,\n                },\n                pagination.ordering,\n                pagination.searchParams\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, ordering, pagination.searchParams);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n      <ServiceRollbackModal\n        sRModalVisibility={vfModalVisibility}\n        setSRModalVisibility={setVfModalVisibility}\n        initLoading={loading}\n        fixedParams={`?history_id=${rowId}`}\n      />\n    </OmpContentWrapper>\n  );\n};\n\nexport default InstallationRecord;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/indexOld.js",
    "content": "import { OmpContentWrapper, OmpContentNav } from \"@/components\";\nimport { useState } from \"react\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport Installation from \"./tabs/installation\"\nimport Upgrade from \"./tabs/upgrade\"\nimport Rollback from \"./tabs/rollback\"\nconst InstallationRecord = () => {\n  const history = useHistory();\n\n  const tabKey = useLocation().state?.tabKey\n  \n  const [currentList, setCurrentList] = useState(tabKey || \"installation\");\n\n  const contentNavData = [\n    {\n      name: \"installation\",\n      text: \"安装记录\",\n      handler: () => {\n        if (currentList !== \"installation\") {\n          setCurrentList(\"installation\");\n        }\n      },\n    },\n    {\n      name: \"upgrade\",\n      text: \"升级记录\",\n      handler: () => {\n        if (currentList !== \"upgrade\") {\n          setCurrentList(\"upgrade\");\n        }\n      },\n    },\n    {\n      name: \"backoff\",\n      text: \"回滚记录\",\n      handler: () => {\n        if (currentList !== \"backoff\") {\n          setCurrentList(\"backoff\");\n        }\n      },\n    },\n  ];\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ padding:0}}>\n      <OmpContentNav data={contentNavData} currentFocus={currentList} />\n      {currentList == \"installation\" && <Installation history={history} /> }\n      {currentList == \"upgrade\" && <Upgrade history={history} /> }\n      {currentList == \"backoff\" && <Rollback history={history} />}\n    </OmpContentWrapper>\n  );\n};\n\nexport default InstallationRecord;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/tabs/installation.js",
    "content": "import {\n  OmpTable,\n  OmpContentWrapper\n} from \"@/components\";\nimport { Button } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  nonEmptyProcessing,\n  renderDisc,\n} from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\n//import updata from \"@/store_global/globalStore\";\nimport moment from \"moment\";\n\nconst renderStatus = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"warning\", 7, -1)}等待安装</span>;\n    case 1:\n      return <span>{renderDisc(\"warning\", 7, -1)}正在安装</span>;\n    case 2:\n      return <span>{renderDisc(\"normal\", 7, -1)}成功</span>;\n    case 3:\n      return <span>{renderDisc(\"critical\", 7, -1)}失败</span>;\n    case 4:\n      return <span>{renderDisc(\"notMonitored\", 7, -1)}正在注册</span>;\n    default:\n      return \"-\";\n  }\n};\n\nconst Installation = ({ history }) => {\n  const [loading, setLoading] = useState(false);\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"编号\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record, idx) => {\n        //history.push()\n        return idx + 1 + (pagination.current - 1) * pagination.pageSize;\n      },\n    },\n    {\n      title: \"执行用户\",\n      key: \"operator\",\n      width: 100,\n      dataIndex: \"operator\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"状态\",\n      key: \"install_status\",\n      dataIndex: \"install_status\",\n      width: 100,\n      //sorter: (a, b) => a.is_superuser - b.is_superuser,\n      //sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"开始时间\",\n      key: \"created\",\n      dataIndex: \"created\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"结束时间\",\n      key: \"modified\",\n      dataIndex: \"modified\",\n      align: \"center\",\n      width: 120,\n      render: (text, record) => {\n        if (record.install_status == 1) {\n          return \"-\";\n        }\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"操作\",\n      key: \"1\",\n      width: 58,\n      dataIndex: \"1\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div\n            onClick={() => {\n              history.push({\n                pathname: \"/application_management/app_store/installation\",\n                state: {\n                  uniqueKey: record.operation_uuid,\n                  step: 3,\n                },\n              });\n            }}\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n          >\n            <a>查看</a>\n          </div>\n        );\n      },\n    },\n  ];\n\n  //auth/users\n  function fetchData(pageParams = { current: 1, pageSize: 10 }) {\n    setLoading(true);\n    fetchGet(apiRequest.installHistoryPage.queryInstallHistoryList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ paddingBottom:0}}>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              // console.log(pagination, \"hosts/hosts/?page=1&size=10\");\n              fetchData({\n                current: pagination.current,\n                pageSize: pagination.pageSize,\n              });\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            setTimeout(() => {\n              fetchData(e);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n    </OmpContentWrapper>\n  );\n};\n\nexport default Installation;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/tabs/rollback.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { Button } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  nonEmptyProcessing,\n  renderDisc,\n} from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport moment from \"moment\";\n\nconst renderStatus = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"warning\", 7, -1)}等待回滚</span>;\n    case 1:\n      return <span>{renderDisc(\"warning\", 7, -1)}正在回滚</span>;\n    case 2:\n      return <span>{renderDisc(\"normal\", 7, -1)}回滚成功</span>;\n    case 3:\n      return <span>{renderDisc(\"critical\", 7, -1)}回滚失败</span>;\n    case 4:\n      return <span>{renderDisc(\"notMonitored\", 7, -1)}正在回滚</span>;\n    default:\n      return \"-\";\n  }\n};\n\nconst Rollback = ({ history }) => {\n  const [loading, setLoading] = useState(false);\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const [vfModalVisibility, setVfModalVisibility] = useState(false);\n\n  const [serviceList, setServiceList] = useState([]);\n\n  const columns = [\n    {\n      title: \"编号\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record, idx) => {\n        //history.push()\n        return idx + 1 + (pagination.current - 1) * pagination.pageSize;\n      },\n    },\n    {\n      title: \"执行用户\",\n      key: \"operator\",\n      width: 100,\n      dataIndex: \"operator\",\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"服务数量\",\n      key: \"service_count\",\n      width: 60,\n      dataIndex: \"service_count\",\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"状态\",\n      key: \"rollback_state\",\n      dataIndex: \"rollback_state\",\n      width: 100,\n      align: \"center\",\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"回滚时间\",\n      key: \"created\",\n      dataIndex: \"created\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"操作\",\n      key: \"1\",\n      width: 50,\n      dataIndex: \"1\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div>\n              <a\n                onClick={() => {\n                  history.push({\n                    pathname:\n                      \"/application_management/app_store/service_rollback\",\n                    state: {\n                      history: record.id,\n                    },\n                  });\n                }}\n              >\n                查看\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  //auth/users\n  function fetchData(pageParams = { current: 1, pageSize: 10 }) {\n    setLoading(true);\n    fetchGet(apiRequest.installHistoryPage.queryRollbackHistoryList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ paddingBottom: 0 }}>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              // console.log(pagination, \"hosts/hosts/?page=1&size=10\");\n              fetchData({\n                current: pagination.current,\n                pageSize: pagination.pageSize,\n              });\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            setTimeout(() => {\n              fetchData(e);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n      {/* <ServiceUpgradeModal\n        vfModalVisibility={vfModalVisibility}\n        setVfModalVisibility={setVfModalVisibility}\n        dataSource={serviceList}\n        // installTitle={installTitle.current}\n        initLoading={loading}\n      /> */}\n    </OmpContentWrapper>\n  );\n};\n\nexport default Rollback;\n"
  },
  {
    "path": "omp_web/src/pages/InstallationRecord/tabs/upgrade.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { Button } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  nonEmptyProcessing,\n  renderDisc,\n} from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport moment from \"moment\";\nimport ServiceRollbackModal from \"../../AppStore/config/ServiceRollbackModal\";\nconst renderStatus = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"warning\", 7, -1)}等待升级</span>;\n    case 1:\n      return <span>{renderDisc(\"warning\", 7, -1)}正在升级</span>;\n    case 2:\n      return <span>{renderDisc(\"normal\", 7, -1)}升级成功</span>;\n    case 3:\n      return <span>{renderDisc(\"critical\", 7, -1)}升级失败</span>;\n    case 4:\n      return <span>{renderDisc(\"notMonitored\", 7, -1)}正在升级</span>;\n    default:\n      return \"-\";\n  }\n};\n\nconst notProhibit = {\n  cursor: \"not-allowed\",\n  color: \"#bbbbbb\",\n};\n\nconst Upgrade = ({ history }) => {\n  const [loading, setLoading] = useState(false);\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [rowId, setRowId] = useState(\"\");\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const [vfModalVisibility, setVfModalVisibility] = useState(false);\n\n  const [serviceList, setServiceList] = useState([]);\n\n  const columns = [\n    {\n      title: \"编号\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record, idx) => {\n        //history.push()\n        return idx + 1 + (pagination.current - 1) * pagination.pageSize;\n      },\n    },\n    {\n      title: \"执行用户\",\n      key: \"operator\",\n      width: 100,\n      dataIndex: \"operator\",\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"服务数量\",\n      key: \"service_count\",\n      width: 60,\n      dataIndex: \"service_count\",\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"状态\",\n      key: \"upgrade_state\",\n      dataIndex: \"upgrade_state\",\n      width: 100,\n      align: \"center\",\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"升级时间\",\n      key: \"created\",\n      dataIndex: \"created\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    {\n      title: \"操作\",\n      key: \"1\",\n      width: 50,\n      dataIndex: \"1\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              <a\n                onClick={() => {\n                  history.push({\n                    pathname:\n                      \"/application_management/app_store/service_upgrade\",\n                    state: {\n                      history: record.id,\n                    },\n                  });\n                }}\n              >\n                查看\n              </a>\n              <a\n                style={\n                  record.can_rollback\n                    ? { marginLeft: 10 }\n                    : {\n                        marginLeft: 10,\n                        ...notProhibit,\n                      }\n                }\n                onClick={() => {\n                  if (record.can_rollback) {\n                    setRowId(record.id);\n                    setVfModalVisibility(true);\n                  }\n                }}\n              >\n                回滚\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  //auth/users\n  function fetchData(pageParams = { current: 1, pageSize: 10 }) {\n    setLoading(true);\n    fetchGet(apiRequest.installHistoryPage.queryUpgradeHistoryList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res);\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ paddingBottom:0 }}>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              // console.log(pagination, \"hosts/hosts/?page=1&size=10\");\n              fetchData({\n                current: pagination.current,\n                pageSize: pagination.pageSize,\n              });\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            setTimeout(() => {\n              fetchData(e);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n      <ServiceRollbackModal\n        sRModalVisibility={vfModalVisibility}\n        setSRModalVisibility={setVfModalVisibility}\n        initLoading={loading}\n        fixedParams={`?history_id=${rowId}`}\n      />\n    </OmpContentWrapper>\n  );\n};\n\nexport default Upgrade;\n"
  },
  {
    "path": "omp_web/src/pages/Login/index.js",
    "content": "import { Input, Checkbox, Button, Form } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport styles from \"./index.module.less\";\nimport img from \"@/config/logo/logo.svg\";\nimport {\n  LockOutlined,\n  UserOutlined,\n  CloseCircleFilled,\n  SafetyCertificateOutlined,\n} from \"@ant-design/icons\";\nimport { OmpContentWrapper } from \"@/components\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { logout, encrypt } from \"@/utils/utils\";\nimport { withRouter } from \"react-router\";\n\nconst Login = withRouter(({ history }) => {\n  const [msgShow, setMsgShow] = useState(false);\n  const [codeError, setCodeError] = useState(false);\n  const [isAutoLogin, setIsAutoLogin] = useState(false);\n  const [loading, setLoading] = useState(false);\n  const [image, setImage] = useState(`/api/users/captcha/?${Math.random()}`);\n  const [form] = Form.useForm();\n  const onCheckboxChange = (e) => {\n    setIsAutoLogin(e.target.checked);\n  };\n\n  function login(data) {\n    setLoading(true);\n    fetchPost(apiRequest.auth.login, {\n      body: {\n        username: encrypt(data.username),\n        password: encrypt(data.password),\n        remember: isAutoLogin,\n        code: data.code,\n      },\n    })\n      .then((res) => {\n        if (res.data && res.data.code == 1) {\n          setMsgShow(true);\n          setCodeError(res.data.message === \"code error\");\n        } else if (res.data.code == 0) {\n          history.replace({\n            pathname: \"/homepage\",\n            state: {\n              data: {},\n            },\n          });\n          //console.log(data)\n          localStorage.setItem(\"username\", data.username);\n        }\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally(() => setLoading(false));\n  }\n\n  useEffect(() => {\n    logout(\"loginPage\");\n  }, []);\n\n  return (\n    <OmpContentWrapper\n      wrapperStyle={{ width: \"100%\", height: \"calc(100% - 40px)\" }}\n    >\n      <div className={styles.loginWrapper}>\n        <div className={styles.loginContent}>\n          <header className={styles.loginTitle}>\n            <img className={styles.loginLogo} src={img} />\n            <span className={styles.loginOMP}>\n              {/* OMP<span className={styles.loginOpenText}>运维管理平台</span> */}\n              运维工具包\n            </span>\n          </header>\n          <p className={styles.loginInputTitle}>用户名密码登录</p>\n          <div\n            style={{\n              position: \"relative\",\n              top: -20,\n              backgroundColor: \"#fbe3e2\",\n              padding: \"10px\",\n              height: \"40px\",\n              color: \"#86292e\",\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              cursor: \"pointer\",\n            }}\n            className={\n              msgShow ? styles.loginMessageShow : styles.loginMessageHide\n            }\n            onClick={() => setMsgShow(false)}\n          >\n            {codeError ? \"验证码错误\" : \"用户名或密码错误\"}\n            <CloseCircleFilled\n              style={{ color: \"#fff\", fontSize: 20, marginLeft: \"auto\" }}\n            />\n          </div>\n          <main\n            className={styles.loginInputWrapper}\n            style={{ position: \"relative\", top: msgShow ? 0 : -24 }}\n          >\n            <Form\n              form={form}\n              onFinish={(e) => {\n                login(e);\n              }}\n            >\n              <Form.Item\n                label=\"\"\n                name=\"username\"\n                key=\"username\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入用户名\",\n                  },\n                ]}\n              >\n                <Input\n                  prefix={\n                    <UserOutlined\n                      style={{ color: \"#b8b8b8\", paddingRight: 10 }}\n                    />\n                  }\n                  style={{ paddingLeft: 10, width: 360, height: 40 }}\n                  placeholder=\"用户名\"\n                />\n              </Form.Item>\n              <Form.Item\n                label=\"\"\n                name=\"password\"\n                key=\"password\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入密码\",\n                  },\n                ]}\n              >\n                <Input.Password\n                  prefix={\n                    <LockOutlined\n                      style={{ color: \"#b8b8b8\", paddingRight: 10 }}\n                    />\n                  }\n                  style={{\n                    paddingLeft: 10,\n                    width: 360,\n                    height: 40,\n                    marginTop: 10,\n                  }}\n                  placeholder=\"密码\"\n                />\n              </Form.Item>\n              <Form.Item\n                label=\"\"\n                name=\"code\"\n                key=\"code\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入验证码\",\n                  },\n                ]}\n              >\n                <Input\n                  prefix={\n                    <SafetyCertificateOutlined\n                      style={{ color: \"#b8b8b8\", paddingRight: 10 }}\n                    />\n                  }\n                  suffix={\n                    <img\n                      src={image}\n                      style={{\n                        width: 120,\n                        height: 38,\n                        borderLeft: \"1px solid #d9d9d9\",\n                        marginRight: -8,\n                        paddingLeft: 6,\n                        cursor: \"pointer\",\n                      }}\n                      alt=\"无法正常显示\"\n                      onClick={() => {\n                        setImage(`/api/users/captcha/?${Math.random()}`);\n                      }}\n                    />\n                  }\n                  style={{\n                    paddingLeft: 10,\n                    width: 360,\n                    height: 40,\n                    marginTop: 10,\n                  }}\n                  placeholder=\"验证码\"\n                />\n              </Form.Item>\n              <div className={styles.loginAuto}>\n                <Checkbox checked={isAutoLogin} onChange={onCheckboxChange}>\n                  <span style={{ color: \"#3a3542\" }}>7天自动登录</span>\n                </Checkbox>\n              </div>\n              <Form.Item>\n                <Button\n                  style={{\n                    width: 360,\n                    height: 40,\n                    fontSize: 16,\n                    marginTop: 24,\n                  }}\n                  type=\"primary\"\n                  htmlType=\"submit\"\n                  loading={loading}\n                >\n                  {loading ? \"登录中\" : \"登录\"}\n                </Button>{\" \"}\n              </Form.Item>\n            </Form>\n          </main>\n          {/* <p className={styles.loginFooter}>一站式运维管理平台</p> */}\n        </div>\n      </div>\n    </OmpContentWrapper>\n  );\n});\n\nexport default Login;\n"
  },
  {
    "path": "omp_web/src/pages/Login/index.module.less",
    "content": ".loginWrapper {\n  width: 100%;\n  height: 100%;\n  display: flex;\n  justify-content: center;\n  padding-top: 10%;\n  .loginContent {\n    .loginTitle {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      margin-bottom: 50px;\n      .loginLogo {\n        width: 56px;\n        height: 56px;\n      }\n      .loginOMP {\n        font-size: 25px;\n        padding-left: 10px;\n        font-weight: 600;\n        color: #3a3542;\n        .loginOpenText {\n          font-size: 16px;\n          color: #b4acac;\n          padding-left: 10px;\n          font-weight: 500;\n        }\n      }\n    }\n    .loginInputTitle {\n      color: #99a4a9;\n      font-size: 18px;\n      // animation-duration: 1s;\n      // animation-fill-mode: both;\n      // animation-name: fadeRoute;\n    }\n    .loginMessageShow {\n      opacity: 1;\n      transition: all .2s ease-in;\n    }\n\n    .loginMessageHide{\n      opacity: 0;\n      transition: all .2s ease-in;\n      //animation: hide-item 2s ease-in forwards;\n    }\n\n    // @keyframes hide-item {\n    //   0% {opacity: 1;color:red}\n    //   50% {opacity: .5;color:blue}\n    //   100% {opacity: 0;color:green}\n    // }\n\n    .loginMessage{\n      background-color: #99a4a9;\n    }\n    .loginInputWrapper {\n      transition: all .2s ease-in;\n      // div {\n      //   padding-bottom: 20px;\n      // }\n      .loginAuto {\n        text-align: right;\n        margin-top: 24px;\n      }\n    }\n  }\n  .loginFooter {\n    color: rgba(0,0,0,.6);\n    text-align: center;\n    margin-top: 60px;\n  }\n}"
  },
  {
    "path": "omp_web/src/pages/LoginLog/index.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { Button, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit, nonEmptyProcessing } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { SearchOutlined } from \"@ant-design/icons\";\n\nconst LoginLog = () => {\n  const [loading, setLoading] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [selectValue, setSelectValue] = useState();\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"序号\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n      fixed: \"left\",\n    },\n    {\n      title: \"用户名\",\n      key: \"username\",\n      width: 100,\n      dataIndex: \"username\",\n      sorter: (a, b) => a.username - b.username,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      width: 100,\n      sorter: (a, b) => a.ip - b.ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"角色\",\n      key: \"role\",\n      dataIndex: \"role\",\n      width: 100,\n      sorter: (a, b) => a.role - b.role,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    // {\n    //   title: \"用户状态\",\n    //   key: \"is_active\",\n    //   dataIndex: \"is_active\",\n    //   align: \"center\",\n    //   width: 100,\n    //   render: (text) => {\n    //     if (text) {\n    //       return \"正常\";\n    //     } else {\n    //       return \"停用\";\n    //     }\n    //   },\n    // },\n    {\n      title: \"登录时间\",\n      key: \"login_time\",\n      dataIndex: \"login_time\",\n      align: \"center\",\n      width: 100,\n      sorter: (a, b) => a.login_time - b.login_time,\n      sortDirections: [\"descend\", \"ascend\"],\n      // render: (text) => {\n      //   if (text) {\n      //     return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n      //   } else {\n      //     return \"-\";\n      //   }\n      // },\n      render: nonEmptyProcessing,\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.operationRecord.queryLoginLog, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 60, display: \"flex\", alignItems: \"center\" }}>\n            用户名:\n          </span>\n          <Input\n            placeholder=\"请输入用户名\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    username: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { username: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n    </OmpContentWrapper>\n  );\n};\n\nexport default LoginLog;\n"
  },
  {
    "path": "omp_web/src/pages/MachineManagement/config/columns.js",
    "content": "import {\n  nonEmptyProcessing,\n  renderDisc,\n  RenderStatusForResult,\n} from \"@/utils/utils\";\nimport { OmpToolTip } from \"@/components\";\nimport { DesktopOutlined } from \"@ant-design/icons\";\nimport { Dropdown, Menu, Drawer, Tooltip, Spin, Timeline } from \"antd\";\nimport moment from \"moment\";\nimport styles from \"../index.module.less\";\nimport { useSelector } from \"react-redux\";\nimport { useRef } from \"react\";\n\nconst colorConfig = {\n  normal: null,\n  warning: \"#ffbf00\",\n  critical: \"#f04134\",\n};\n\nexport const DetailHost = ({\n  isShowDrawer,\n  setIsShowDrawer,\n  loading,\n  data,\n  baseEnv,\n}) => {\n  // 视口宽度\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  // 组件图片字符串\n  const componentImgStr = `<?xml version=\"1.0\" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg t=\"1634633143436\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2388\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"200\" height=\"200\"><defs><style type=\"text/css\"></style></defs><path d=\"M882.521 282.988L527.534 78.127c-8.98-5.219-20.146-5.219-29.127 0L143.42 282.988c-8.98 5.219-14.563 14.806-14.563 25.244v409.842c0 10.437 5.583 20.025 14.563 25.244L498.407 948.3c4.491 2.548 9.588 3.884 14.563 3.884s10.073-1.334 14.563-3.884L882.52 743.318c8.98-5.219 14.563-14.806 14.563-25.244V308.232c0-10.437-5.583-20.025-14.563-25.244zM838.83 701.326L512.971 889.438 187.112 701.326V325.101l325.859-188.112L838.83 325.101v376.225z\" p-id=\"2389\"></path><path d=\"M270.124 383.476c-8.01 13.957-3.277 31.797 10.681 39.807l202.676 116.994v231.439c0 16.142 12.986 29.127 29.127 29.127s29.127-12.986 29.127-29.127V540.641l203.404-117.479c13.957-8.01 18.69-25.851 10.681-39.807s-25.851-18.69-39.807-10.681L512.973 489.91l-203.04-117.236c-13.957-8.01-31.676-3.155-39.807 10.801z\" p-id=\"2390\"></path></svg>`;\n\n  const wrapperRef = useRef(null);\n  return (\n    <Drawer\n      title={\n        <div style={{ display: \"flex\" }}>\n          <DesktopOutlined style={{ position: \"relative\", top: 3, left: -5 }} />\n          主机详细信息面板\n          <span style={{ paddingLeft: 30, fontWeight: 400, fontSize: 15 }}>\n            IP: {isShowDrawer.record.ip}\n          </span>\n        </div>\n      }\n      headerStyle={{\n        padding: \"19px 24px\",\n      }}\n      placement=\"right\"\n      closable={true}\n      width={`calc(100% - 200px)`}\n      style={{\n        height: \"calc(100%)\",\n        // paddingTop: \"60px\",\n      }}\n      onClose={() => {\n        setIsShowDrawer({\n          ...isShowDrawer,\n          isOpen: false,\n        });\n      }}\n      visible={isShowDrawer.isOpen}\n      bodyStyle={{\n        padding: 10,\n        //paddingLeft:10,\n        backgroundColor: \"#e7e9f0\", //\"#f4f6f8\"\n        height: \"calc(100%)\",\n      }}\n      destroyOnClose={true}\n    >\n      <div\n        style={{ height: \"calc(100% - 14px)\", width: \"100%\", display: \"flex\" }}\n      >\n        <div\n          style={{\n            height: \"100%\",\n            width: \"100%\",\n            //border: \"solid 1px rgb(220,220,220)\",\n            borderRadius: \"5px\",\n            backgroundColor: \"#fff\",\n            flex: 4,\n            padding: 20,\n          }}\n        >\n          <div style={{ paddingBottom: 35, fontSize: 15, fontWeight: 500 }}>\n            基本信息\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              //paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>实例名称</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.instance_name}</div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>HOSTNAME</div>\n            <div style={{ flex: 1 }}>\n              {nonEmptyProcessing(isShowDrawer.record.host_name)}\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>IP地址</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.ip}</div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>SSH端口</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.port}</div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>用户名</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.username}</div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>系统</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.operate_system}</div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>CPU</div>\n            <div style={{ flex: 1 }}>\n              {nonEmptyProcessing(isShowDrawer.record.cpu)} c\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>内存</div>\n            <div style={{ flex: 1 }}>\n              {nonEmptyProcessing(isShowDrawer.record.memory)} G\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>硬盘</div>\n            <div style={{ flex: 1 }}>\n              {isShowDrawer.record.disk\n                ? Object.keys(isShowDrawer.record.disk).map((item) => (\n                    <div\n                      key={item}\n                      style={{\n                        display: \"flex\",\n                        justifyContent: \"space-between\",\n                      }}\n                    >\n                      <span style={{ width: \"65%\" }}>\n                        <OmpToolTip maxLength={16}>{item}</OmpToolTip>\n                      </span>\n                      <span style={{ width: \"35%\" }}>\n                        {isShowDrawer.record.disk[item]} G\n                      </span>\n                    </div>\n                  ))\n                : \"-\"}\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>创建时间</div>\n            <div style={{ flex: 1 }}>\n              {moment(isShowDrawer.record.created).format(\n                \"YYYY-MM-DD HH:mm:ss\"\n              )}\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>维护模式</div>\n            <div style={{ flex: 1 }}>\n              {isShowDrawer.record.is_maintenance ? \"是\" : \"否\"}\n            </div>\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>主机初始化</div>\n            <div style={{ flex: 1 }}>\n              {renderInitStatue(isShowDrawer.record.init_status)}\n            </div>\n          </div>\n        </div>\n        <div\n          style={{\n            height: \"100%\",\n            width: \"100%\",\n            flex: 7,\n            marginLeft: 20,\n            display: \"flex\",\n            flexWrap: \"wrap\",\n          }}\n        >\n          <div\n            style={{\n              height: \"100%\",\n              width: \"49%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              height: 200,\n              padding: 20,\n            }}\n          >\n            <div style={{ paddingBottom: 35, fontSize: 15, fontWeight: 500 }}>\n              Agent状态\n            </div>\n            <div style={{ display: \"flex\", paddingTop: 15, paddingBottom: 15 }}>\n              <div style={{ flex: 1 }}>主机Agent</div>\n              <div style={{ flex: 1 }}>\n                {renderStatus(isShowDrawer.record.host_agent)}\n              </div>\n            </div>\n            <div style={{ display: \"flex\", paddingTop: 15, paddingBottom: 15 }}>\n              <div style={{ flex: 1 }}>监控Agent</div>\n              <div style={{ flex: 1 }}>\n                {renderStatus(isShowDrawer.record.monitor_agent)}\n              </div>\n            </div>\n          </div>\n          <div\n            style={{\n              height: \"100%\",\n              width: \"48%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              marginLeft: \"2%\",\n              height: 200,\n              padding: 20,\n            }}\n          >\n            <Spin spinning={loading} wrapperClassName={styles.omp_spin_wrapper}>\n              <div style={{ paddingBottom: 35, fontSize: 15, fontWeight: 500 }}>\n                部署组件信息\n              </div>\n              <div\n                style={{ display: \"flex\", paddingTop: 15, paddingBottom: 15 }}\n              >\n                <div style={{ flex: 1 }}>部署组件</div>\n                <div style={{ flex: 1 }}>\n                  {isShowDrawer.record.service_num} 个\n                </div>\n              </div>\n              <div\n                style={{ display: \"flex\", paddingTop: 15, paddingBottom: 15 }}\n              >\n                <div style={{ flex: 1 }}>基础环境</div>\n                <div\n                  style={{\n                    flex: 1,\n                    display: \"flex\",\n                    marginLeft: -20,\n                  }}\n                >\n                  {baseEnv.length > 0 ? (\n                    baseEnv.map((item) => {\n                      return (\n                        <Tooltip\n                          title={`${item.service__app_name} ${item.service__app_version}`}\n                        >\n                          <div\n                            style={{\n                              width: 32,\n                              height: 32,\n                              borderRadius: \"50%\",\n                              border: \"1px solid #a8d0f8\",\n                              display: \"flex\",\n                              justifyContent: \"center\",\n                              alignItems: \"center\",\n                              marginLeft: 4,\n                              marginRight: 4,\n                              overflow: \"hidden\",\n                            }}\n                            dangerouslySetInnerHTML={{\n                              __html: item.service__app_logo || componentImgStr,\n                            }}\n                            key={item.service__app_name}\n                          ></div>\n                        </Tooltip>\n                      );\n                    })\n                  ) : (\n                    <div style={{ marginLeft: 10 }}>无</div>\n                  )}\n                </div>\n              </div>\n            </Spin>\n          </div>\n\n          <div\n            ref={wrapperRef}\n            style={{\n              height: \"calc(100% - 220px)\",\n              marginTop: 20,\n              width: \"99%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              //height:200\n              padding: 20,\n              //overflow:\"hidden\"\n            }}\n          >\n            <div style={{ paddingBottom: 20, fontSize: 15, fontWeight: 500 }}>\n              历史记录\n            </div>\n            <Spin spinning={loading} wrapperClassName={styles.omp_spin_wrapper}>\n              <Timeline\n                style={{\n                  overflowY: \"scroll\",\n                  paddingTop: 10,\n                  height: wrapperRef.current\n                    ? wrapperRef.current?.offsetHeight - 100\n                    : 100,\n                }}\n              >\n                {data.map((item) => {\n                  return (\n                    <Timeline.Item key={item.id}>\n                      <p style={{ color: \"#595959\" }}>\n                        <RenderStatusForResult result={item?.result} />[\n                        {item.username}] {item.description}\n                      </p>\n                      <p style={{ color: \"#595959\" }}>\n                        {moment(item.created).format(\"YYYY-MM-DD HH:mm:ss\")}\n                      </p>\n                    </Timeline.Item>\n                  );\n                })}\n              </Timeline>\n            </Spin>\n          </div>\n        </div>\n      </div>\n    </Drawer>\n  );\n};\n\n//操作\nconst renderMenu = (\n  setUpdateMoadlVisible,\n  setCloseMaintainModal,\n  setOpenMaintainModal,\n  record\n) => {\n  return (\n    <Menu>\n      <Menu.Item key=\"changge\" onClick={() => setUpdateMoadlVisible(true)}>\n        <span style={{ fontSize: 12 }}>修改主机信息</span>\n      </Menu.Item>\n      {record.is_maintenance ? (\n        <Menu.Item key=\"close\" onClick={() => setCloseMaintainModal(true)}>\n          <span style={{ fontSize: 12 }}>关闭维护模式</span>\n        </Menu.Item>\n      ) : (\n        <Menu.Item key=\"open\" onClick={() => setOpenMaintainModal(true)}>\n          <span style={{ fontSize: 12 }}>开启维护模式</span>\n        </Menu.Item>\n      )}\n    </Menu>\n  );\n};\n\nconst renderStatus = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"normal\", 7, -1)}正常</span>;\n    case 1:\n      return <span>{renderDisc(\"warning\", 7, -1)}重启中</span>;\n    case 2:\n      return <span>{renderDisc(\"critical\", 7, -1)}启动失败</span>;\n    case 3:\n      return <span>{renderDisc(\"warning\", 7, -1)}部署中</span>;\n    case 4:\n      return <span>{renderDisc(\"critical\", 7, -1)}部署失败</span>;\n    default:\n      return \"-\";\n  }\n};\n\nconst renderInitStatue = (text) => {\n  switch (text) {\n    case 0:\n      return <span>{renderDisc(\"normal\", 7, -1)}成功</span>;\n    case 1:\n      return <span>{renderDisc(\"notMonitored\", 7, -1)}未执行</span>;\n    case 2:\n      return <span>{renderDisc(\"warning\", 7, -1)}执行中</span>;\n    case 3:\n      return <span>{renderDisc(\"critical\", 7, -1)}失败</span>;\n  }\n};\n\nconst getColumnsConfig = (\n  setIsShowDrawer,\n  setRow,\n  setUpdateMoadlVisible,\n  fetchHostDetail,\n  setCloseMaintainModal,\n  setOpenMaintainModal,\n  setShowIframe,\n  history\n) => {\n  return [\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      sorter: (a, b) => a.ip - b.ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      //width: 140,\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        if (str == \"-\") {\n          return \"-\";\n        } else {\n          return (\n            <a\n              onClick={() => {\n                fetchHostDetail(record.id);\n                setIsShowDrawer({\n                  isOpen: true,\n                  record: record,\n                });\n              }}\n            >\n              {str}\n            </a>\n          );\n        }\n      },\n      //ellipsis: true,\n      fixed: \"left\",\n    },\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"CPU使用率\",\n      key: \"cpu_usage\",\n      dataIndex: \"cpu_usage\",\n      align: \"center\",\n      sorter: (a, b) => a.cpu_usage - b.cpu_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{ color: colorConfig[record.cpu_status], fontWeight: 500 }}\n          >\n            {str}%\n          </span>\n        );\n      },\n    },\n    {\n      title: \"内存使用率\",\n      key: \"mem_usage\",\n      dataIndex: \"mem_usage\",\n      sorter: (a, b) => a.mem_usage - b.mem_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{ color: colorConfig[record.mem_status], fontWeight: 500 }}\n          >\n            {str}%\n          </span>\n        );\n      },\n    },\n    {\n      title: \"根分区使用率\",\n      key: \"root_disk_usage\",\n      //width:120,\n      dataIndex: \"root_disk_usage\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.root_disk_usage - b.root_disk_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{\n              color: colorConfig[record.root_disk_status],\n              fontWeight: 500,\n            }}\n          >\n            {str}%\n          </span>\n        );\n      },\n      // width:120\n    },\n    {\n      title: \"数据分区使用率\",\n      width: 130,\n      key: \"data_disk_usage\",\n      dataIndex: \"data_disk_usage\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.data_disk_usage - b.data_disk_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{\n              color: colorConfig[record.data_disk_status],\n              fontWeight: 500,\n            }}\n          >\n            {str}%\n          </span>\n        );\n      },\n    },\n    {\n      title: \"维护模式\",\n      key: \"is_maintenance\",\n      dataIndex: \"is_maintenance\",\n      align: \"center\",\n      //ellipsis: true,\n      render: (text) => {\n        if (nonEmptyProcessing(text) == \"-\") return \"-\";\n        return text ? \"开\" : \"关\";\n      },\n    },\n    // {\n    //   title: \"主机初始化\",\n    //   key: \"init_status\",\n    //   dataIndex: \"init_status\",\n    //   align: \"center\",\n    //   //ellipsis: true,\n    //   render: (text) => {\n    //     return renderInitStatue(text);\n    //   },\n    // },\n    {\n      title: \"主机Agent\",\n      key: \"host_agent\",\n      dataIndex: \"host_agent\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.host_agent - b.host_agent,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"监控Agent\",\n      key: \"monitor_agent\",\n      dataIndex: \"monitor_agent\",\n      sorter: (a, b) => a.monitor_agent - b.monitor_agent,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      //ellipsis: true,\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"服务总数\",\n      key: \"service_num\",\n      dataIndex: \"service_num\",\n      align: \"center\",\n      // ellipsis: true,\n      sorter: (a, b) => a.service_num - b.service_num,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        if (text && text !== 0 && text !== \"-\") {\n          return (\n            <a\n              onClick={() => {\n                text &&\n                  history.push({\n                    pathname: \"/application_management/service_management\",\n                    state: {\n                      ip: record.ip,\n                    },\n                  });\n              }}\n            >\n              {text}个\n            </a>\n          );\n        } else {\n          if ((!text || text == \"-\") && text !== 0) {\n            return \"-\";\n          }\n          return `${text}个`;\n        }\n      },\n    },\n    {\n      title: \"告警总数\",\n      key: \"alert_num\",\n      dataIndex: \"alert_num\",\n      align: \"center\",\n      //ellipsis: true,\n      sorter: (a, b) => a.alert_num - b.alert_num,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        if (text && text !== 0 && text !== \"-\") {\n          return (\n            <a\n              onClick={() => {\n                text &&\n                  history.push({\n                    pathname: \"/application-monitoring/alarm-log\",\n                    state: {\n                      ip: record.ip,\n                    },\n                  });\n              }}\n            >\n              {text}次\n            </a>\n          );\n        } else {\n          if ((!text || text == \"-\") && text !== 0) {\n            return \"-\";\n          }\n          return `${text}次`;\n        }\n      },\n    },\n    {\n      title: \"操作\",\n      //width: 100,\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        if (record?.host_agent == 3 || record?.monitor_agent == 3) {\n          return (\n            <div\n              onClick={() => {\n                setRow(record);\n              }}\n              style={{ display: \"flex\", justifyContent: \"space-around\" }}\n            >\n              <div style={{ margin: \"auto\" }}>\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  更多\n                </span>\n              </div>\n            </div>\n          );\n        }\n        return (\n          <div\n            onClick={() => {\n              setRow(record);\n            }}\n            style={{ display: \"flex\" }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              {record.monitor_url ? (\n                <a\n                  onClick={() => {\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_url,\n                      record: record,\n                      isLog: false,\n                    });\n                  }}\n                >\n                  监控\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n              )}\n\n              <Dropdown\n                arrow\n                placement=\"bottomCenter\"\n                overlay={renderMenu(\n                  setUpdateMoadlVisible,\n                  setCloseMaintainModal,\n                  setOpenMaintainModal,\n                  record\n                )}\n              >\n                <a style={{ marginLeft: 10 }}>更多</a>\n              </Dropdown>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/MachineManagement/config/modals.js",
    "content": "import React from \"react\";\nimport { OmpModal } from \"@/components\";\nimport {\n  Button,\n  Input,\n  Select,\n  Form,\n  message,\n  InputNumber,\n  Row,\n  Col,\n  Tooltip,\n  Modal,\n  Steps,\n  Upload,\n  Switch,\n} from \"antd\";\nimport {\n  PlusSquareOutlined,\n  FormOutlined,\n  InfoCircleOutlined,\n  ImportOutlined,\n  DownloadOutlined,\n  CloudUploadOutlined,\n  CheckCircleFilled,\n  CloseCircleFilled,\n} from \"@ant-design/icons\";\nimport {\n  MessageTip,\n  isChineseChar,\n  isNumberChar,\n  isValidIpChar,\n  isExpression,\n  isLetterChar,\n  isSpace,\n  handleResponse,\n} from \"@/utils/utils\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { useState, useRef } from \"react\";\nimport star from \"./asterisk.svg\";\nimport XLSX from \"xlsx\";\nimport { OmpTable } from \"@/components\";\n// import BMF from 'browser-md5-file';\n\n// let bmf = new BMF()\n// const [modalForm] = Form.useForm();\n\nexport const AddMachineModal = ({\n  loading,\n  visibleHandle,\n  onFinish,\n  createHost,\n  msgInfo,\n  setLoading,\n}) => {\n  const [modalForm] = Form.useForm();\n  const [modalLoading, setmodalLoading] = useState(false);\n  const [isOpen, setIsOpen] = useState(true);\n\n  const timer = useRef(null);\n  const timer2 = useRef(null);\n  return (\n    <OmpModal\n      loading={modalLoading ? modalLoading : loading}\n      setLoading={setLoading}\n      visibleHandle={visibleHandle}\n      okBtnText={modalLoading ? \"校验中\" : loading ? \"创建中\" : null}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <PlusSquareOutlined />\n          </span>\n          <span>创建主机信息</span>\n        </span>\n      }\n      form={modalForm}\n      onFinish={(data) => {\n        createHost(data);\n        //onFinish(\"post\", data);\n      }}\n      initialValues={{\n        data_folder: \"/data\",\n        port: 22,\n        operate_system: \"CentOS\",\n        username: \"root\",\n        use_ntpd: true,\n      }}\n    >\n      <MessageTip\n        msg={msgInfo.msg}\n        setMsgShow={msgInfo.setMsgShow}\n        msgShow={msgInfo.msgShow}\n      />\n      <div\n        style={{\n          transition: \"all .2s ease-in\",\n          position: \"relative\",\n          top: msgInfo.msgShow ? 0 : -24,\n        }}\n      >\n        <Form.Item\n          label=\"实例名称\"\n          name=\"instance_name\"\n          key=\"instance_name\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入实例名称\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (!value) {\n                  return Promise.resolve(\"success\");\n                }\n                // 校验开头\n                let startChar = value.slice(0, 1);\n                if (\n                  isNumberChar(startChar) ||\n                  isLetterChar(startChar) ||\n                  startChar == \"-\"\n                ) {\n                  if (!isExpression(value)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(`实例名称不支持中文`);\n                    } else {\n                      if (value.length > 16) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        if (isSpace(value)) {\n                          return Promise.reject(\"实例名称不支持空格\");\n                        }\n                        return new Promise((resolve, rej) => {\n                          if (timer.current) {\n                            clearTimeout(timer.current);\n                          }\n                          timer.current = setTimeout(() => {\n                            setmodalLoading(true);\n                            fetchPost(apiRequest.machineManagement.checkHost, {\n                              body: {\n                                instance_name: value,\n                              },\n                            })\n                              .then((res) => {\n                                if (res && res.data) {\n                                  if (res.data.data) {\n                                    resolve(\"success\");\n                                  } else {\n                                    rej(`实例名称已存在`);\n                                  }\n                                }\n                              })\n                              .catch((e) => console.log(e))\n                              .finally(() => {\n                                setmodalLoading(false);\n                              });\n                          }, 400);\n                        });\n                      }\n                    }\n                  } else {\n                    return Promise.reject(\"实例名称不支持表情\");\n                  }\n                } else {\n                  return Promise.reject(`实例名称开头只支持字母、数字或\"-\"`);\n                }\n              },\n            },\n          ]}\n        >\n          <Input maxLength={16} placeholder={\"请输入实例名称\"} />\n        </Form.Item>\n\n        <Form.Item\n          label=\"系统平台\"\n          name=\"operate_system\"\n          key=\"operate_system\"\n          rules={[\n            {\n              required: true,\n              message: \"请选择系统平台\",\n            },\n          ]}\n        >\n          <Select placeholder={\"请选择系统平台\"}>\n            <Select.Option value=\"CentOS\">CentOS</Select.Option>\n            <Select.Option value=\"RedHat\">RedHat</Select.Option>\n          </Select>\n        </Form.Item>\n\n        <Form.Item\n          label=\"数据分区\"\n          name=\"data_folder\"\n          key=\"data_folder\"\n          extra={\n            <span style={{ fontSize: 10 }}>\n              应用组件默认安装到数据分区,请确保具有足够空间\n            </span>\n          }\n          rules={[\n            {\n              required: true,\n              message: \"请输入数据分区\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                var reg = /[^a-zA-Z0-9\\_\\-\\/]/g;\n                if (!value) {\n                  return Promise.resolve(\"success\");\n                } else {\n                  if (value.startsWith(\"/\")) {\n                    if (!isChineseChar(value)) {\n                      if (!reg.test(value)) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.reject(\n                          `数据分区只支持字母、数字、\"/\"、\"-\"和\"_\"`\n                        );\n                      }\n                    } else {\n                      return Promise.reject(\n                        `数据分区只支持字母、数字、\"/\"、\"-\"和\"_\"`\n                      );\n                    }\n                  } else {\n                    return Promise.reject(`数据分区开头必须为\"/\"`);\n                  }\n                }\n              },\n            },\n          ]}\n        >\n          <Input maxLength={255} placeholder={\"请输入数据分区\"} />\n        </Form.Item>\n\n        <Form.Item\n          name=\"ip\"\n          key=\"ip\"\n          label={\n            <span>\n              <img\n                src={star}\n                style={{ position: \"relative\", top: -2, left: -3 }}\n              />\n              IP地址\n            </span>\n          }\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            // {\n            //   required: true,\n            //   message: \"请输入IP地址或端口号\",\n            // },\n            {\n              validator: (rule, v, callback) => {\n                let value = modalForm.getFieldValue(\"IPtext\");\n                let portValue = modalForm.getFieldValue(\"port\");\n                if (!value) {\n                  return Promise.reject(\"请输入IP地址或端口号\");\n                }\n                if (!portValue) {\n                  return Promise.reject(\"请输入IP地址或端口号\");\n                }\n                if (isValidIpChar(value)) {\n                  return new Promise((resolve, rej) => {\n                    if (timer2.current) {\n                      clearTimeout(timer2.current);\n                    }\n                    timer2.current = setTimeout(() => {\n                      setmodalLoading(true);\n                      fetchPost(apiRequest.machineManagement.checkHost, {\n                        body: {\n                          ip: value,\n                        },\n                      })\n                        .then((res) => {\n                          if (res && res.data) {\n                            if (res.data.data) {\n                              resolve(\"success\");\n                            } else {\n                              rej(`ip地址已存在`);\n                            }\n                          }\n                        })\n                        .catch((e) => console.log(e))\n                        .finally(() => {\n                          setmodalLoading(false);\n                        });\n                    }, 600);\n                  });\n                } else {\n                  return Promise.reject(\"请输入正确格式的IP地址\");\n                }\n              },\n            },\n          ]}\n        >\n          <Row gutter={8}>\n            <Col span={16}>\n              <Form.Item\n                name=\"IPtext\"\n                key=\"IPtext\"\n                noStyle\n                seforminstanceinvalidator=\"true\"\n              >\n                <Input placeholder={\"例如: 192.168.10.10\"} />\n              </Form.Item>\n            </Col>\n            <span style={{ display: \"flex\", alignItems: \"center\" }}>:</span>\n            <Col span={4}>\n              <Form.Item name=\"port\" key=\"port\" noStyle>\n                <InputNumber\n                  onChange={() => {\n                    modalForm.validateFields([\"ip\"]);\n                  }}\n                  style={{ width: 82 }}\n                  min={1}\n                  max={65535}\n                />\n              </Form.Item>\n            </Col>\n          </Row>\n        </Form.Item>\n\n        <Form.Item\n          label=\"用户名\"\n          name=\"username\"\n          key=\"username\"\n          extra={\n            <span style={{ fontSize: 10 }}>\n              使用\n              <strong\n                style={{\n                  fontWeight: 700,\n                  color: \"#595959\",\n                  margin: \"0 1px 0 1px\",\n                }}\n              >\n                普通用户\n              </strong>\n              纳管主机时，为确保正常安装服务，请\n              <a\n                style={{\n                  fontWeight: 700,\n                  margin: \"0 1px 0 1px\",\n                }}\n                onClick={() => {\n                  let a = document.createElement(\"a\");\n                  a.href = apiRequest.machineManagement.downInitScript;\n                  document.body.appendChild(a);\n                  a.click();\n                  document.body.removeChild(a);\n                }}\n              >\n                下载\n              </a>\n              主机初始化脚本，并手动执行\n            </span>\n          }\n          rules={[\n            {\n              required: true,\n              message: \"请输入用户名\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                var reg = /[^a-zA-Z0-9\\_\\-]/g;\n                var startReg = /[^a-zA-Z0-9\\_]/g;\n                if (value) {\n                  let startChar = value.slice(0, 1);\n                  if (!startReg.test(startChar)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(`用户名只支持数字、字母、\"-\"或\"_\"`);\n                    } else {\n                      if (!reg.test(value)) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.reject(\n                          `用户名只支持数字、字母、\"-\"或\"_\"`\n                        );\n                      }\n                    }\n                  } else {\n                    return Promise.reject(`用户名开头只支持数字、字母、或\"_\"`);\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input\n            // suffix={\n            //   <Tooltip title=\"请使用root或具有sudo免密码权限的用户\">\n            //     <InfoCircleOutlined style={{ color: \"rgba(0,0,0,.45)\" }} />\n            //   </Tooltip>\n            // }\n            maxLength={16}\n            placeholder={\"请输入用户名\"}\n          />\n        </Form.Item>\n\n        <Form.Item\n          label=\"密码\"\n          name=\"password\"\n          key=\"password\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入密码\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (value) {\n                  if (!isExpression(value)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(\"密码不支持中文\");\n                    } else {\n                      if (value.length < 8) {\n                        return Promise.reject(\"密码长度为8到64位\");\n                      } else {\n                        if (isSpace(value)) {\n                          return Promise.reject(\"密码不支持空格\");\n                        }\n                        return Promise.resolve(\"success\");\n                      }\n                    }\n                  } else {\n                    return Promise.reject(`密码不支持输入表情`);\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password maxLength={64} placeholder={\"请输入密码\"} />\n        </Form.Item>\n        <Form.Item\n          label=\"安装时间同步服务\"\n          name=\"use_ntpd\"\n          key=\"use_ntpd\"\n          valuePropName=\"checked\"\n          extra={\n            <span style={{ fontSize: 10 }}>\n              开启后，将对纳管主机安装ntpdate服务\n            </span>\n          }\n        >\n          <Switch\n            style={{ borderRadius: \"10px\" }}\n            onChange={(e) => setIsOpen(e)}\n          />\n        </Form.Item>\n        {isOpen && (\n          <Form.Item\n            label=\"时间同步服务器\"\n            name=\"ntpd_server\"\n            key=\"ntpd_server\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入时间同步服务器\",\n              },\n              {\n                validator: (rule, value, callback) => {\n                  if (value) {\n                    if (isValidIpChar(value)) {\n                      return Promise.resolve(\"success\");\n                    } else {\n                      return Promise.reject(\"请输入正确格式的IP地址\");\n                    }\n                  } else {\n                    return Promise.resolve(\"success\");\n                  }\n                },\n              },\n            ]}\n          >\n            <Input placeholder={\"例如: 192.168.10.10\"} />\n          </Form.Item>\n        )}\n      </div>\n    </OmpModal>\n  );\n};\n\nexport const UpDateMachineModal = ({\n  loading,\n  visibleHandle,\n  onFinish,\n  createHost,\n  msgInfo,\n  row,\n  setLoading,\n}) => {\n  const [modalForm] = Form.useForm();\n  // console.log(row)\n  const [modalLoading, setmodalLoading] = useState(false);\n  const timer = useRef(null);\n  const timer2 = useRef(null);\n  return (\n    <OmpModal\n      loading={modalLoading ? modalLoading : loading}\n      setLoading={setLoading}\n      okBtnText={modalLoading ? \"校验中\" : loading ? \"修改中\" : null}\n      visibleHandle={visibleHandle}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <FormOutlined />\n          </span>\n          <span>修改主机信息</span>\n        </span>\n      }\n      form={modalForm}\n      onFinish={(data) => {\n        createHost(data);\n        //onFinish(\"post\", data);\n      }}\n      initialValues={{\n        instance_name: row.instance_name,\n        IPtext: row.ip,\n        data_folder: row.data_folder,\n        port: row.port,\n        operate_system: row.operate_system,\n        username: row.username,\n        ip: row.ip,\n        password: row.password && window.atob(row.password),\n      }}\n    >\n      <MessageTip\n        msg={msgInfo.msg}\n        setMsgShow={msgInfo.setMsgShow}\n        msgShow={msgInfo.msgShow}\n      />\n      <div\n        style={{\n          transition: \"all .2s ease-in\",\n          position: \"relative\",\n          top: msgInfo.msgShow ? 0 : -24,\n        }}\n      >\n        <Form.Item\n          label=\"实例名称\"\n          name=\"instance_name\"\n          key=\"instance_name\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入实例名称\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (!value) {\n                  return Promise.resolve(\"success\");\n                }\n                // 校验开头\n                let startChar = value.slice(0, 1);\n                if (\n                  isNumberChar(startChar) ||\n                  isLetterChar(startChar) ||\n                  startChar == \"-\"\n                ) {\n                  if (!isExpression(value)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(`实例名称不支持中文`);\n                    } else {\n                      if (value.length > 16) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        if (isSpace(value)) {\n                          return Promise.reject(\"实例名称不支持空格\");\n                        }\n                        return new Promise((resolve, rej) => {\n                          if (timer.current) {\n                            clearTimeout(timer.current);\n                          }\n                          timer.current = setTimeout(() => {\n                            setmodalLoading(true);\n                            fetchPost(apiRequest.machineManagement.checkHost, {\n                              body: {\n                                instance_name: value,\n                                id: row.id,\n                              },\n                            })\n                              .then((res) => {\n                                if (res && res.data) {\n                                  if (res.data.data) {\n                                    resolve(\"success\");\n                                  } else {\n                                    rej(res.data.message);\n                                  }\n                                }\n                              })\n                              .catch((e) => console.log(e))\n                              .finally(() => {\n                                setmodalLoading(false);\n                              });\n                          }, 400);\n                        });\n                      }\n                    }\n                  } else {\n                    return Promise.reject(\"实例名称不支持表情\");\n                  }\n                } else {\n                  return Promise.reject(`实例名称开头只支持字母、数字或\"-\"`);\n                }\n              },\n            },\n          ]}\n        >\n          <Input maxLength={16} placeholder={\"请输入实例名称\"} />\n        </Form.Item>\n\n        <Form.Item\n          label=\"系统平台\"\n          name=\"operate_system\"\n          key=\"operate_system\"\n          rules={[\n            {\n              required: true,\n              message: \"请选择系统平台\",\n            },\n          ]}\n        >\n          <Select placeholder={\"请选择系统平台\"}>\n            <Select.Option value=\"CentOS\">CentOS</Select.Option>\n            <Select.Option value=\"RedHat\">RedHat</Select.Option>\n          </Select>\n        </Form.Item>\n\n        <Form.Item\n          label=\"数据分区\"\n          name=\"data_folder\"\n          key=\"data_folder\"\n          extra={\n            <span style={{ fontSize: 10 }}>\n              应用组件默认安装到数据分区,请确保具有足够空间\n            </span>\n          }\n          rules={[\n            {\n              required: true,\n              message: \"请输入数据分区\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                var reg = /[^a-zA-Z0-9\\_\\-\\/]/g;\n                if (!value) {\n                  return Promise.resolve(\"success\");\n                } else {\n                  if (value.startsWith(\"/\")) {\n                    if (!isChineseChar(value)) {\n                      if (!reg.test(value)) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.reject(\n                          `数据分区只支持字母、数字、\"/\"、\"-\"和\"_\"`\n                        );\n                      }\n                    } else {\n                      return Promise.reject(\n                        `数据分区只支持字母、数字、\"/\"、\"-\"和\"_\"`\n                      );\n                    }\n                  } else {\n                    return Promise.reject(`数据分区开头必须为\"/\"`);\n                  }\n                }\n              },\n            },\n          ]}\n        >\n          <Input maxLength={255} placeholder={\"请输入数据分区\"} />\n        </Form.Item>\n\n        <Form.Item\n          name=\"ip\"\n          key=\"ip\"\n          label={\n            <span>\n              <img\n                src={star}\n                style={{ position: \"relative\", top: -2, left: -3 }}\n              />\n              IP地址\n            </span>\n          }\n          rules={[\n            {\n              validator: (rule, v, callback) => {\n                let value = modalForm.getFieldValue(\"IPtext\");\n                let portValue = modalForm.getFieldValue(\"port\");\n                if (!value) {\n                  return Promise.reject(\"请输入IP地址或端口号\");\n                }\n                if (!portValue) {\n                  return Promise.reject(\"请输入IP地址或端口号\");\n                }\n                if (isValidIpChar(value)) {\n                  return new Promise((resolve, rej) => {\n                    setmodalLoading(true);\n                    fetchPost(apiRequest.machineManagement.checkHost, {\n                      body: {\n                        ip: value,\n                        id: row.id,\n                      },\n                    })\n                      .then((res) => {\n                        if (res && res.data) {\n                          if (res.data.data) {\n                            resolve(\"success\");\n                          } else {\n                            rej(res.data.message);\n                          }\n                        }\n                      })\n                      .catch((e) => console.log(e))\n                      .finally(() => {\n                        setmodalLoading(false);\n                      });\n                  });\n                } else {\n                  return Promise.reject(\"请输入正确格式的IP地址\");\n                }\n              },\n            },\n          ]}\n        >\n          <Row gutter={8}>\n            <Col span={16}>\n              <Form.Item name=\"IPtext\" key=\"IPtext\" noStyle>\n                <Input disabled placeholder={\"例如: 192.168.10.10\"} />\n              </Form.Item>\n            </Col>\n            <span style={{ display: \"flex\", alignItems: \"center\" }}>:</span>\n            <Col span={4}>\n              <Form.Item name=\"port\" key=\"port\" noStyle>\n                <InputNumber\n                  style={{ width: 82 }}\n                  min={1}\n                  max={65535}\n                  onChange={() => {\n                    modalForm.validateFields([\"ip\"]);\n                  }}\n                />\n              </Form.Item>\n            </Col>\n          </Row>\n        </Form.Item>\n\n        <Form.Item\n          label=\"用户名\"\n          name=\"username\"\n          key=\"username\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入用户名\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                var reg = /[^a-zA-Z0-9\\_\\-]/g;\n                var startReg = /[^a-zA-Z0-9\\_]/g;\n                if (value) {\n                  let startChar = value.slice(0, 1);\n                  if (!startReg.test(startChar)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(`用户名只支持数字、字母、\"-\"或\"_\"`);\n                    } else {\n                      if (!reg.test(value)) {\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.reject(\n                          `用户名只支持数字、字母、\"-\"或\"_\"`\n                        );\n                      }\n                    }\n                  } else {\n                    return Promise.reject(`用户名开头只支持数字、字母、或\"_\"`);\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input\n            maxLength={16}\n            placeholder={\"请输入用户名\"}\n            suffix={\n              <Tooltip title=\"请使用root或具有sudo免密码权限的用户\">\n                <InfoCircleOutlined style={{ color: \"rgba(0,0,0,.45)\" }} />\n              </Tooltip>\n            }\n          />\n        </Form.Item>\n\n        <Form.Item\n          label=\"密码\"\n          name=\"password\"\n          key=\"password\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入密码\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (value) {\n                  if (!isExpression(value)) {\n                    if (isChineseChar(value)) {\n                      return Promise.reject(\"密码不支持中文\");\n                    } else {\n                      if (value.length < 8) {\n                        return Promise.reject(\"密码长度为8到64位\");\n                      } else {\n                        if (isSpace(value)) {\n                          return Promise.reject(\"密码不支持空格\");\n                        }\n                        return Promise.resolve(\"success\");\n                      }\n                    }\n                  } else {\n                    return Promise.reject(`密码不支持输入表情`);\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password maxLength={64} placeholder={\"请输入密码\"} />\n        </Form.Item>\n      </div>\n    </OmpModal>\n  );\n};\n\nconst getHeaderRow = (sheet) => {\n  const headers = [];\n  const range = XLSX.utils.decode_range(sheet[\"!ref\"]);\n  let C;\n  const R = range.s.r;\n  for (C = range.s.c; C <= range.e.c; ++C) {\n    const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];\n    let hdr = \"UNKNOWN \" + C;\n    if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);\n    headers.push(hdr);\n  }\n  return headers;\n};\n\nclass UploadExcelComponent extends React.Component {\n  state = {\n    loading: false,\n    excelData: {\n      header: null,\n      results: null,\n    },\n  };\n  draggerProps = () => {\n    let _this = this;\n    return {\n      name: \"file\",\n      multiple: false,\n      accept: \".xlsx\",\n      maxCount: 1,\n      onRemove() {\n        _this.props.onRemove();\n        return true;\n      },\n      onChange(info) {\n        const { status } = info.file;\n        if (status === \"done\") {\n          //console.log(info.file);\n          message.success(`${info.file.name} 文件解析成功`);\n        } else if (status === \"error\") {\n          message.error(\n            `${info.file.name} 文件解析失败, 请确保文件内容格式符合规范后重新上传`\n          );\n        }\n      },\n      beforeUpload(file, fileList) {\n        //console.log(file);\n        // bmf.md5(file,(err,md5)=>{\n        //   console.log(err,md5,\"=====?---\")\n        // })\n        // 校验文件大小\n        const fileSize = file.size / 1024 / 1024; //单位是M\n        //console.log(fileSize);\n        if (Math.ceil(fileSize) > 10) {\n          message.error(\"仅支持传入10M以内文件\");\n          return Upload.LIST_IGNORE;\n        }\n        if (!/\\.(xlsx)$/.test(file.name)) {\n          message.error(\"仅支持传入.xlsx文件\");\n          return Upload.LIST_IGNORE;\n        }\n      },\n      customRequest(e) {\n        _this.readerData(e.file).then(\n          (msg) => {\n            //console.log(e);\n            e.onSuccess();\n          },\n          () => {\n            e.onError();\n          }\n        );\n      },\n    };\n  };\n  readerData = (rawFile) => {\n    // bmf.md5(rawFile,(err,md5)=>{\n    //   console.log(err,md5,\"=====?\")\n    // })\n    return new Promise((resolve, reject) => {\n      const reader = new FileReader();\n\n      reader.onload = (e) => {\n        try {\n          const data = e.target.result;\n          const workbook = XLSX.read(data, { type: \"array\" });\n          const firstSheetName = workbook.SheetNames[0];\n          const worksheet = workbook.Sheets[firstSheetName];\n          const header = getHeaderRow(worksheet);\n          const results = XLSX.utils.sheet_to_json(worksheet);\n          //console.log(header, results, \"====\");\n          this.generateData({ header, results });\n          resolve();\n        } catch (error) {\n          reject();\n        }\n      };\n      reader.readAsArrayBuffer(rawFile);\n    });\n  };\n  generateData = ({ header, results }) => {\n    this.setState({\n      excelData: { header, results },\n    });\n    this.props.uploadSuccess && this.props.uploadSuccess(this.state.excelData);\n  };\n  render() {\n    return (\n      <div>\n        <Upload.Dragger {...this.draggerProps()}>\n          <p className=\"ant-upload-drag-icon\">\n            <CloudUploadOutlined />\n          </p>\n          <p style={{ textAlign: \"center\", color: \"#575757\" }}>\n            点击或将文件拖拽到这里上传\n          </p>\n          <p\n            style={{\n              textAlign: \"center\",\n              color: \"#8e8e8e\",\n              fontSize: 13,\n              paddingTop: 10,\n            }}\n          >\n            支持扩展名: .xlsx\n          </p>\n        </Upload.Dragger>\n      </div>\n    );\n  }\n}\n\n/* 批量导入主机 */\nexport const BatchImportMachineModal = ({\n  batchImport,\n  setBatchImport,\n  refreshData,\n}) => {\n  const [dataSource, setDataSource] = useState([]);\n  const [columns, setColumns] = useState([]);\n\n  // 校验后的表格的colums和dataSource也是不确定的\n  // 因为不单是在表格展示中需要区分校验成功与否，在这里定义多个数据源用以区分是否成功\n  const [tableCorrectData, setTableCorrectData] = useState([]);\n  const [tableErrorData, setTableErrorData] = useState([]);\n  const [tableColumns, setTableColumns] = useState([]);\n\n  const [stepNum, setStepNum] = useState(0);\n\n  const [loading, setLoading] = useState(false);\n\n  // 失败的columns\n  const errorColumns = [\n    {\n      title: \"行数\",\n      key: \"row\",\n      dataIndex: \"row\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 60,\n      ellipsis: true,\n      fixed: \"left\",\n    },\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 140,\n      ellipsis: true,\n      //fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 140,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n      //fixed: \"left\"\n    },\n    {\n      title: \"端口\",\n      key: \"port\",\n      dataIndex: \"port\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 80,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      //ellipsis: true,\n    },\n    {\n      title: \"数据分区\",\n      key: \"data_folder\",\n      dataIndex: \"data_folder\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 180,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"是否安装时间同步\",\n      key: \"use_ntpd\",\n      dataIndex: \"use_ntpd\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <span>{text === false ? \"否\" : text === true ? \"是\" : \"-\"}</span>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"时间同步服务器\",\n      key: \"ntpd_server\",\n      dataIndex: \"ntpd_server\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"失败原因\",\n      key: \"validate_error\",\n      dataIndex: \"validate_error\",\n      fixed: \"right\",\n      // sorter: (a, b) => a.ip - b.ip,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 240,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n  ];\n\n  // 成功的columns\n  const correctColumns = [\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      //render: nonEmptyProcessing,\n      width: 140,\n      ellipsis: true,\n      fixed: \"left\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      align: \"center\",\n      width: 140,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"端口\",\n      key: \"port\",\n      dataIndex: \"port\",\n      align: \"center\",\n      width: 80,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"数据分区\",\n      key: \"data_folder\",\n      dataIndex: \"data_folder\",\n      align: \"center\",\n      width: 180,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"用户名\",\n      key: \"username\",\n      dataIndex: \"username\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"密码\",\n      key: \"password\",\n      dataIndex: \"password\",\n      align: \"center\",\n      width: 130,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"是否安装时间同步\",\n      key: \"use_ntpd\",\n      dataIndex: \"use_ntpd\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <span>{text === false ? \"否\" : text === true ? \"是\" : \"-\"}</span>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"时间同步服务器\",\n      key: \"ntpd_server\",\n      dataIndex: \"ntpd_server\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n    {\n      title: \"系统\",\n      key: \"operate_system\",\n      dataIndex: \"operate_system\",\n      align: \"center\",\n      width: 120,\n      fixed: \"right\",\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n      ellipsis: true,\n    },\n  ];\n\n  // 校验数据接口\n  const fetchBatchValidate = () => {\n    if (dataSource.length == 0) {\n      message.warning(\n        \"解析结果中无有效数据，请确保文件内容格式符合规范后重新上传\"\n      );\n      return;\n    }\n    setLoading(true);\n    setTableCorrectData([]);\n    setTableErrorData([]);\n    let queryBody = dataSource.map((item) => {\n      let result = {};\n      for (const key in item) {\n        switch (key) {\n          case \"IP[必填]\":\n            result.ip = item[key];\n            break;\n          case \"实例名[必填]\":\n            result.instance_name = item[key];\n            break;\n          case \"密码[必填]\":\n            result.password = item[key];\n            break;\n          case \"操作系统[必填]\":\n            result.operate_system = item[key];\n            break;\n          case \"数据分区[必填]\":\n            result.data_folder = item[key];\n            break;\n          case \"用户名[必填]\":\n            result.username = item[key];\n            break;\n          case \"端口[必填]\":\n            result.port = item[key];\n            break;\n          case \"时间同步服务器\":\n            result.use_ntpd = true;\n            result.ntpd_server = item[key];\n            break;\n          default:\n            break;\n        }\n      }\n      if (!result.use_ntpd) {\n        result.use_ntpd = false;\n      }\n      return {\n        ...result,\n        row: item.key,\n      };\n    });\n    // console.log(queryBody)\n    // 校验数据\n    fetchPost(apiRequest.machineManagement.batchValidate, {\n      body: {\n        host_list: queryBody,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            if (res.data && res.data.error?.length > 0) {\n              setTableErrorData(\n                res.data.error?.map((item, idx) => {\n                  return {\n                    key: idx,\n                    ...item,\n                  };\n                })\n              );\n              setTableColumns(errorColumns);\n            } else {\n              setTableCorrectData(\n                res.data.correct?.map((item, idx) => {\n                  return {\n                    key: idx,\n                    ...item,\n                  };\n                })\n              );\n              setTableColumns(correctColumns);\n            }\n            setStepNum(1);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 主机创建操作\n  const fetchBatchImport = () => {\n    let queryBody = tableCorrectData.map((item) => {\n      delete item.key;\n      return {\n        ...item,\n      };\n    });\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.batchImport, {\n      body: {\n        host_list: queryBody,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            setStepNum(2);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  return (\n    <Modal\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            <ImportOutlined />\n          </span>\n          <span>批量创建主机</span>\n        </span>\n      }\n      visible={batchImport}\n      footer={null}\n      width={800}\n      loading={loading}\n      // onFinish={(data) => {\n      //   createHost(data);\n      //   //onFinish(\"post\", data);\n      // }}\n      bodyStyle={{\n        paddingLeft: 30,\n        paddingRight: 30,\n      }}\n      onCancel={() => {\n        setBatchImport(false);\n      }}\n      afterClose={() => {\n        setDataSource([]);\n        setTableCorrectData([]);\n        setTableErrorData([]);\n        setTableColumns([]);\n        setStepNum(0);\n        setColumns([]);\n        refreshData();\n      }}\n      destroyOnClose\n    >\n      <Steps size=\"small\" current={stepNum}>\n        <Steps.Step title=\"上传文件\" />\n        <Steps.Step title=\"数据校验\" />\n        <Steps.Step title=\"创建结果\" />\n      </Steps>\n      <div style={{ paddingLeft: 30, paddingTop: 30 }}>\n        <div\n          style={{\n            visibility: stepNum == 0 ? \"visible\" : \"hidden\",\n            height: stepNum == 0 ? null : 0,\n            overflow: \"hidden\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            <div style={{ flex: 1, fontWeight: 500 }}>下载模版: </div>\n            <div style={{ flex: 10, paddingLeft: 20 }}>\n              <Button\n                icon={<DownloadOutlined />}\n                size=\"middle\"\n                style={{ fontSize: 13 }}\n                onClick={() => {\n                  let a = document.createElement(\"a\");\n                  a.href = apiRequest.machineManagement.downTemplate;\n                  document.body.appendChild(a);\n                  a.click();\n                  document.body.removeChild(a);\n                }}\n              >\n                点击下载\n              </Button>\n            </div>\n          </div>\n          <div style={{ display: \"flex\", marginTop: 30 }}>\n            <div style={{ flex: 1, fontWeight: 500 }}>上传文件: </div>\n            <div style={{ flex: 10, paddingLeft: 20 }}>\n              {batchImport && (\n                <UploadExcelComponent\n                  onRemove={() => {\n                    setDataSource([]);\n                    setColumns([]);\n                    setTableCorrectData([]);\n                    setTableErrorData([]);\n                    setTableColumns([]);\n                  }}\n                  uploadSuccess={({ results, header }) => {\n                    //console.log(results, header);\n                    let dataS = results\n                      .filter((item) => {\n                        if (item[\"字段名称(请勿编辑)\"]?.includes(\"请勿编辑\")) {\n                          return false;\n                        }\n                        if (!item[\"实例名[必填]\"]) {\n                          return false;\n                        }\n                        return true;\n                      })\n                      .map((item, idx) => {\n                        return { ...item, key: item.__rowNum__ + 1 };\n                      });\n                    let column = header.filter((item) => {\n                      if (\n                        item?.includes(\"请勿编辑\") ||\n                        item?.includes(\"UNKNOWN\")\n                      ) {\n                        return false;\n                      }\n                      return true;\n                    });\n\n                    setDataSource(dataS);\n                    setColumns(column);\n                  }}\n                />\n              )}\n\n              <div\n                style={{\n                  display: \"inline-block\",\n                  marginLeft: \"50%\",\n                  transform: \"translateX(-50%)\",\n                  marginTop: 40,\n                }}\n              >\n                <Button\n                  loading={loading}\n                  onClick={() => fetchBatchValidate()}\n                  type=\"primary\"\n                  disabled={columns.length == 0}\n                >\n                  校验\n                </Button>\n              </div>\n            </div>\n          </div>\n        </div>\n        {stepNum == 1 && (\n          <>\n            {tableErrorData.length > 0 ? (\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                  paddingBottom: 10,\n                  flexDirection: \"column\",\n                }}\n              >\n                <p\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    fontSize: 20,\n                    color: \"#f73136\",\n                  }}\n                >\n                  <CloseCircleFilled\n                    style={{ color: \"#f73136\", fontSize: 30, marginRight: 10 }}\n                  />\n                  数据校验失败 !\n                </p>\n                <p style={{ fontSize: 13 }}>请核对并修改信息后，再重新提交</p>\n              </div>\n            ) : (\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                  paddingBottom: 10,\n                }}\n              >\n                <p\n                  style={{\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    fontSize: 20,\n                  }}\n                >\n                  <CheckCircleFilled\n                    style={{ color: \"#52c41a\", fontSize: 30, marginRight: 10 }}\n                  />\n                  数据校验成功 !\n                </p>\n              </div>\n            )}\n\n            <OmpTable\n              bordered\n              scroll={{ x: 700 }}\n              columns={tableColumns}\n              dataSource={\n                tableErrorData.length > 0 ? tableErrorData : tableCorrectData\n              }\n              pagination={{\n                pageSize: 5,\n              }}\n            />\n            <div\n              style={{\n                display: \"inline-block\",\n                marginLeft: \"50%\",\n                transform: \"translateX(-50%)\",\n                marginTop: 40,\n              }}\n            >\n              <Button\n                style={{ marginRight: 16 }}\n                onClick={() => {\n                  setStepNum(0);\n                }}\n              >\n                上一步\n              </Button>\n              <Button\n                loading={loading}\n                type=\"primary\"\n                htmlType=\"submit\"\n                onClick={() => {\n                  fetchBatchImport();\n                }}\n                disabled={tableErrorData.length > 0}\n              >\n                创建\n              </Button>\n            </div>\n          </>\n        )}\n        {stepNum == 2 && (\n          <>\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                paddingBottom: 20,\n                paddingTop: 30,\n              }}\n            >\n              <p\n                style={{ display: \"flex\", alignItems: \"center\", fontSize: 20 }}\n              >\n                <CheckCircleFilled\n                  style={{ color: \"#52c41a\", fontSize: 30, marginRight: 10 }}\n                />\n                主机创建完成 !\n              </p>\n            </div>\n            <p style={{ textAlign: \"center\" }}>\n              本次共创建 {tableCorrectData.length} 台主机\n            </p>\n            <div\n              style={{\n                display: \"inline-block\",\n                marginLeft: \"50%\",\n                transform: \"translateX(-50%)\",\n                marginTop: 40,\n              }}\n            >\n              <Button\n                loading={loading}\n                type=\"primary\"\n                htmlType=\"submit\"\n                onClick={() => {\n                  setBatchImport(false);\n                }}\n              >\n                完成\n              </Button>\n            </div>\n          </>\n        )}\n      </div>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "omp_web/src/pages/MachineManagement/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpMessageModal,\n  OmpSelect,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, message, Menu, Dropdown } from \"antd\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { handleResponse, _idxInit, refreshTime } from \"@/utils/utils\";\nimport { fetchGet, fetchPost, fetchPatch } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport {\n  AddMachineModal,\n  UpDateMachineModal,\n  BatchImportMachineModal,\n} from \"./config/modals\";\nimport { useDispatch } from \"react-redux\";\nimport getColumnsConfig, { DetailHost } from \"./config/columns\";\nimport { DownOutlined, ExclamationCircleOutlined } from \"@ant-design/icons\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst MachineManagement = () => {\n  const location = useLocation();\n\n  const history = useHistory();\n\n  const dispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //添加弹框的控制state\n  const [addModalVisible, setAddMoadlVisible] = useState(false);\n  //修改弹框的控制state\n  const [updateMoadlVisible, setUpdateMoadlVisible] = useState(false);\n\n  // 批量导入弹框\n  const [batchImport, setBatchImport] = useState(false);\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [ipListSource, setIpListSource] = useState([]);\n  const [selectValue, setSelectValue] = useState(location.state?.ip);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const [isShowDrawer, setIsShowDrawer] = useState({\n    isOpen: false,\n    src: \"\",\n    record: {},\n  });\n\n  const [msgShow, setMsgShow] = useState(false);\n\n  const msgRef = useRef(null);\n\n  // 定义row存数据\n  const [row, setRow] = useState({});\n\n  // 主机详情历史数据\n  const [historyData, setHistoryData] = useState([]);\n\n  // 主机详情基础组件信息\n  const [baseEnvData, setBaseEnvData] = useState([]);\n\n  // 主机详情loading\n  const [historyLoading, setHistoryLoading] = useState([]);\n\n  // 重启主机agent\n  const [restartHostAgentModal, setRestartHostAgentModal] = useState(false);\n\n  // 重启监控agent\n  const [restartMonterAgentModal, setRestartMonterAgentModal] = useState(false);\n\n  // 重装主机agent\n  const [reInstallHostAgentModal, setReInstallHostAgentModal] = useState(false);\n\n  // 重装监控agent\n  const [reInstallMonterAgentModal, setReInstallMonterAgentModal] =\n    useState(false);\n\n  // 开启维护\n  const [openMaintainModal, setOpenMaintainModal] = useState(false);\n  // 关闭维护\n  const [closeMaintainModal, setCloseMaintainModal] = useState(false);\n\n  // 开启维护（单次）\n  const [openMaintainOneModal, setOpenMaintainOneModal] = useState(false);\n  // 关闭维护（单次）\n  const [closeMaintainOneModal, setCloseMaintainOneModal] = useState(false);\n\n  // 初始化主机\n  const [ininHostModal, setIninHostModal] = useState(false);\n\n  // 删除主机\n  const [deleteHostModal, setDeleteHostModal] = useState(false);\n\n  const [showIframe, setShowIframe] = useState({});\n\n  // 列表查询\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.machineManagement.hosts, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          console.log(res.data.results);\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchIPlist();\n      });\n  }\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  const createHost = (data) => {\n    setLoading(true);\n    data.ip = data.IPtext;\n    delete data.IPtext;\n    data.port = `${data.port}`;\n    delete data.icon;\n    fetchPost(apiRequest.machineManagement.hosts, {\n      body: {\n        ...data,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"添加主机成功\");\n            fetchData(\n              { current: pagination.current, pageSize: pagination.pageSize },\n              { ip: selectValue },\n              pagination.ordering\n            );\n            setAddMoadlVisible(false);\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const upDateHost = (data) => {\n    setLoading(true);\n    data.ip = data.IPtext;\n    delete data.IPtext;\n    data.port = `${data.port}`;\n    fetchPatch(`${apiRequest.machineManagement.hosts}${row.id}/`, {\n      body: {\n        ...data,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            // msgRef.current = res.data.message\n            // setMsgShow(true)\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"更新主机信息成功\");\n            fetchData(\n              { current: pagination.current, pageSize: pagination.pageSize },\n              { ip: selectValue },\n              pagination.ordering\n            );\n            setUpdateMoadlVisible(false);\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // const fetchHistoryData = (id) => {\n  //   setHistoryLoading(true);\n  //   fetchGet(apiRequest.machineManagement.operateLog, {\n  //     params: {\n  //       host_id: id,\n  //     },\n  //   })\n  //     .then((res) => {\n  //       handleResponse(res, (res) => {\n  //         setHistoryData(res.data);\n  //       });\n  //     })\n  //     .catch((e) => console.log(e))\n  //     .finally(() => {\n  //       setHistoryLoading(false);\n  //     });\n  // };\n\n  // 获取主机详情\n  const fetchHostDetail = (id) => {\n    setHistoryLoading(true);\n    fetchGet(`${apiRequest.machineManagement.hostDetail}${id}`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const { deployment_information, history } = res.data;\n          setHistoryData(history);\n          setBaseEnvData(deployment_information);\n          console.log(deployment_information);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setHistoryLoading(false);\n      });\n  };\n\n  // 重启监控agent\n  const fetchRestartMonitorAgent = () => {\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.restartMonitorAgent, {\n      body: {\n        host_ids: checkedList.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"重启监控Agent任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setRestartMonterAgentModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 重启主机agent\n  const fetchRestartHostAgent = () => {\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.restartHostAgent, {\n      body: {\n        host_ids: checkedList.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"重启主机Agent任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setRestartHostAgentModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 重装主机agent\n  const fetchInstallHostAgent = () => {\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.reInstallHostAgent, {\n      body: {\n        host_ids: checkedList.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"重装主机Agent任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setReInstallHostAgentModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 重装监控agent\n  const fetchInstallMonitorAgent = () => {\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.reInstallMonitorAgent, {\n      body: {\n        host_ids: checkedList.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"重装监控Agent任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setReInstallMonterAgentModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 初始化主机\n  const fetchInitHostAgent = () => {\n    setLoading(true);\n    let hostIdArr = [];\n    hostIdArr = checkedList\n      .filter((item) => {\n        return item.init_status === 1 || item.init_status === 3;\n      })\n      .map((item) => item.id);\n    if (hostIdArr.length === 0) {\n      setLoading(false);\n      setIninHostModal(false);\n      setCheckedList([]);\n      message.success(\"所选主机已经初始化完成\");\n      return;\n    }\n    fetchPost(apiRequest.machineManagement.hostInit, {\n      body: {\n        host_ids: hostIdArr,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"初始化主机任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setIninHostModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 主机进入｜退出维护模式\n  const fetchMaintainChange = (e, checkedList) => {\n    let host_arr = [];\n    if (e) {\n      host_arr = checkedList.filter((item) => {\n        return !item.is_maintenance;\n      });\n    } else {\n      host_arr = checkedList.filter((item) => {\n        return item.is_maintenance;\n      });\n    }\n    if (host_arr.length == 0) {\n      setLoading(false);\n      setOpenMaintainOneModal(false);\n      setCloseMaintainOneModal(false);\n      setOpenMaintainModal(false);\n      setCloseMaintainModal(false);\n      setCheckedList([]);\n      if (e) {\n        message.success(\"主机开启维护模式成功\");\n      } else {\n        message.success(\"主机关闭维护模式成功\");\n      }\n      return;\n    }\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.hostsMaintain, {\n      body: {\n        is_maintenance: e,\n        host_ids: host_arr.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        if (res.data.code == 0) {\n          if (e) {\n            message.success(\"主机开启维护模式成功\");\n          } else {\n            message.success(\"主机关闭维护模式成功\");\n          }\n        }\n        if (res.data.code == 1) {\n          message.warning(res.data.message);\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setOpenMaintainOneModal(false);\n        setCloseMaintainOneModal(false);\n        setOpenMaintainModal(false);\n        setCloseMaintainModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  // 主机删除\n  const deleteHost = () => {\n    setLoading(true);\n    fetchPost(apiRequest.machineManagement.deleteHost, {\n      body: {\n        host_ids: checkedList.map((item) => item.id),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"删除主机任务已下发\");\n          }\n          if (res.code == 1) {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setDeleteHostModal(false);\n        setCheckedList([]);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ip: selectValue },\n          pagination.ordering\n        );\n      });\n  };\n\n  useEffect(() => {\n    fetchData(\n      { current: pagination.current, pageSize: pagination.pageSize },\n      { ip: location.state?.ip }\n    );\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          onClick={() => {\n            setAddMoadlVisible(true);\n          }}\n        >\n          添加\n        </Button>\n\n        <Button\n          type=\"primary\"\n          style={{ marginLeft: 10 }}\n          onClick={() => {\n            setBatchImport(true);\n          }}\n        >\n          导入\n        </Button>\n\n        <Dropdown\n          //placement=\"bottomLeft\"\n          overlay={\n            <Menu>\n              <Menu.Item\n                key=\"openMaintain\"\n                style={{ textAlign: \"center\" }}\n                onClick={() => setOpenMaintainModal(true)}\n                disabled={checkedList.map((item) => item.id).length == 0}\n              >\n                开启维护模式\n              </Menu.Item>\n              <Menu.Item\n                key=\"closeMaintain\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setCloseMaintainModal(true);\n                }}\n              >\n                关闭维护模式\n              </Menu.Item>\n              <Menu.Item\n                key=\"reStartHost\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setRestartHostAgentModal(true);\n                }}\n              >\n                重启主机Agent\n              </Menu.Item>\n              <Menu.Item\n                key=\"reStartMonitor\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setRestartMonterAgentModal(true);\n                }}\n              >\n                重启监控Agent\n              </Menu.Item>\n              <Menu.Item\n                key=\"reInstallHost\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setReInstallHostAgentModal(true);\n                }}\n              >\n                重装主机Agent\n              </Menu.Item>\n              <Menu.Item\n                key=\"reInstallMonitor\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setReInstallMonterAgentModal(true);\n                }}\n              >\n                重装监控Agent\n              </Menu.Item>\n              <Menu.Item\n                key=\"initHost\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.map((item) => item.id).length == 0}\n                onClick={() => {\n                  setDeleteHostModal(true);\n                }}\n              >\n                删除主机\n              </Menu.Item>\n              {/* <Menu.Item\n                key=\"initHost\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.map((item) => item.id).length == 0}\n                onClick={() => {\n                  setIninHostModal(true);\n                }}\n              >\n                初始化主机\n              </Menu.Item> */}\n            </Menu>\n          }\n          placement=\"bottomCenter\"\n        >\n          <Button style={{ marginLeft: 10, paddingRight: 10, paddingLeft: 15 }}>\n            更多\n            <DownOutlined />\n          </Button>\n        </Dropdown>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 60, display: \"flex\", alignItems: \"center\" }}>\n            IP地址:\n          </span>\n          <OmpSelect\n            searchLoading={searchLoading}\n            selectValue={selectValue}\n            listSource={ipListSource}\n            setSelectValue={setSelectValue}\n            // onFocus={()=>{\n            //   location.state = {}\n            // }}\n            fetchData={(value) => {\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                { ip: value },\n                pagination.ordering\n              );\n            }}\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              //location.state = {}\n              dispatch(refreshTime());\n              setCheckedList([]);\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { ip: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //scroll={{ x: 1900 }}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(\n            setIsShowDrawer,\n            setRow,\n            setUpdateMoadlVisible,\n            fetchHostDetail,\n            setCloseMaintainOneModal,\n            setOpenMaintainOneModal,\n            setShowIframe,\n            history\n          )}\n          // notSelectable={(record) => ({\n          //   // 部署中的不能选中\n          //   disabled: record?.host_agent == 3 || record?.monitor_agent == 3,\n          // })}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  justifyContent: \"space-between\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      {addModalVisible && (\n        <AddMachineModal\n          setLoading={setLoading}\n          loading={loading}\n          visibleHandle={[addModalVisible, setAddMoadlVisible]}\n          createHost={createHost}\n          msgInfo={{\n            msgShow: msgShow,\n            setMsgShow: setMsgShow,\n            msg: msgRef.current,\n          }}\n        />\n      )}\n      {updateMoadlVisible && (\n        <UpDateMachineModal\n          loading={loading}\n          setLoading={setLoading}\n          visibleHandle={[updateMoadlVisible, setUpdateMoadlVisible]}\n          createHost={upDateHost}\n          row={row}\n          msgInfo={{\n            msgShow: msgShow,\n            setMsgShow: setMsgShow,\n            msg: msgRef.current,\n          }}\n        />\n      )}\n      <DetailHost\n        isShowDrawer={isShowDrawer}\n        setIsShowDrawer={setIsShowDrawer}\n        loading={historyLoading}\n        data={historyData}\n        baseEnv={baseEnvData}\n      />\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n\n      <OmpMessageModal\n        visibleHandle={[restartHostAgentModal, setRestartHostAgentModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchRestartHostAgent();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要重启{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>主机Agent</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[restartMonterAgentModal, setRestartMonterAgentModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchRestartMonitorAgent();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要重启{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>监控Agent</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[reInstallHostAgentModal, setReInstallHostAgentModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchInstallHostAgent();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要重装{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>主机Agent</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[\n          reInstallMonterAgentModal,\n          setReInstallMonterAgentModal,\n        ]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchInstallMonitorAgent();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要重装{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>监控Agent</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[openMaintainModal, setOpenMaintainModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchMaintainChange(true, checkedList);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span>{\" \"}\n          主机下发 <span style={{ fontWeight: 500 }}>开启维护模式</span> 操作？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[closeMaintainModal, setCloseMaintainModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchMaintainChange(false, checkedList);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span>{\" \"}\n          主机下发 <span style={{ fontWeight: 500 }}>关闭维护模式</span> 操作？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[openMaintainOneModal, setOpenMaintainOneModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchMaintainChange(true, [row]);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>当前</span> 主机下发{\" \"}\n          <span style={{ fontWeight: 500 }}>开启维护模式</span> 操作？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[closeMaintainOneModal, setCloseMaintainOneModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchMaintainChange(false, [row]);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>当前</span> 主机下发{\" \"}\n          <span style={{ fontWeight: 500 }}>关闭维护模式</span> 操作？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[ininHostModal, setIninHostModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          fetchInitHostAgent();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>执行初始化</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[deleteHostModal, setDeleteHostModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          deleteHost();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对{\" \"}\n          <span style={{ fontWeight: 500 }}>{checkedList.length}台</span> 主机{\" \"}\n          <span style={{ fontWeight: 500 }}>执行删除命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <BatchImportMachineModal\n        batchImport={batchImport}\n        setBatchImport={setBatchImport}\n        refreshData={() => {\n          fetchData(\n            { current: pagination.current, pageSize: pagination.pageSize },\n            { ip: selectValue },\n            pagination.ordering\n          );\n        }}\n      ></BatchImportMachineModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default MachineManagement;\n"
  },
  {
    "path": "omp_web/src/pages/MachineManagement/index.module.less",
    "content": ".machineManagement {\n    display: flex;\n  }\n  \n  .subMenu {\n    width: 160px;\n  }\n  \n  .warningSearch {\n    display: flex;\n    margin-top: 10px;\n    margin-bottom: 10px;\n  \n    & > div:nth-child(1) {\n      margin-right: 10px;\n    }\n  \n    & > div:last-child {\n      margin-left: auto;\n    }\n  }\n  \n  .antdTableExpandedRow {\n    margin: 0;\n    padding: 0;\n    height: 20px;\n    display: flex;\n    span {\n      margin-left: 60px;\n    }\n  }\n  \n  .redType {\n    background-color: #ff4d4f;\n    color: #fff;\n    border-color:#ff4d4f;\n  }\n  \n  .formItem {\n    margin-bottom: 15px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  \n    & > span:nth-child(1) {\n      display: inline-block;\n      width: 100px;\n      font-size: 14px;\n      //font-weight: 500;\n      color: #333;\n      text-align: right;\n    }\n  \n    & > input:nth-child(2) {\n      width: 240px;\n    }\n  }\n  .machineTable {\n    //cursor: url('../../public/conf/logo.svg'),default;\n    cursor: pointer;\n  }\n.omp_spin_wrapper{\n  height: calc(100%);\n}\n:global {\n  .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover {\n    background-color:#e6f1f6;\n    color:#2e7cee\n  }\n  //悬停样式会覆盖disable样式，在这里把disable权限提高\n  .ant-dropdown-menu-item-disabled {\n    background-color: #fff!important;\n    color:rgba(0, 0, 0, 0.25)!important\n  } \n  // .ant-drawer .ant-drawer-content {\n  //   height: calc(100% - 80px);\n  // }\n}"
  },
  {
    "path": "omp_web/src/pages/MonitoringSettings/index.js",
    "content": "import { OmpContentWrapper } from \"@/components\";\nimport { Button, Input, Form, message, Spin, Switch } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchPost, fetchPatch } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { SettingOutlined, MailOutlined } from \"@ant-design/icons\";\n//import updata from \"@/store_global/globalStore\";\nimport styles from \"./index.module.less\";\n\nconst MonitoringSettings = () => {\n  const [loading, setLoading] = useState(false);\n  const [pushLoading, setPushLoading] = useState(false);\n\n  //数据\n  const [dataSource, setDataSource] = useState([]);\n\n  const [form] = Form.useForm();\n  const [pushForm] = Form.useForm();\n\n  const [pushIsOpen, setPushIsOpen] = useState(false);\n\n  //auth/users\n  function fetchData() {\n    setLoading(true);\n    fetchGet(apiRequest.MonitoringSettings.monitorurl)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          // console.log(res.data);\n          res.data.map((item) => {\n            switch (item.name) {\n              case \"prometheus\":\n                form.setFieldsValue({\n                  prometheus: item.monitor_url,\n                });\n                return;\n              case \"alertmanager\":\n                form.setFieldsValue({\n                  alertmanager: item.monitor_url,\n                });\n                return;\n              case \"grafana\":\n                form.setFieldsValue({\n                  grafana: item.monitor_url,\n                });\n                return;\n              default:\n                return;\n            }\n          });\n          let dir = {};\n          res.data.map((item) => {\n            dir[item.name] = item.id;\n          });\n          setDataSource(dir);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  // 查询推送数据\n  const fetchPushDate = () => {\n    setPushLoading(true);\n    fetchGet(apiRequest.MonitoringSettings.queryPushConfig)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res && res.data) {\n            console.log(res);\n            const { used, server_url } = res.data.email;\n            pushForm.setFieldsValue({\n              pushIsOpen: used,\n              email: server_url,\n            });\n            setPushIsOpen(used);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setPushLoading(false);\n      });\n  };\n\n  const multipleUpdate = (data) => {\n    //  console.log(data)\n    const arr = Object.keys(data).map((key) => {\n      return {\n        id: dataSource[key],\n        monitor_url: data[key],\n      };\n    });\n    setLoading(true);\n    fetchPatch(apiRequest.MonitoringSettings.multiple_update, {\n      body: {\n        data: arr,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"更新监控平台配置成功\");\n            fetchData();\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n    fetchPushDate();\n  }, []);\n\n  // 定义监控地址校验函数\n  const validateMonitorAddress = (rule, value, callback, title) => {\n    if (value) {\n      var reg = /[^a-zA-Z0-9\\-\\_\\.\\~\\!\\*\\'\\(\\)\\;\\:\\@\\&\\=\\+\\$\\,\\/\\?\\#\\[\\]]/g;\n      if (!reg.test(value)) {\n        return Promise.resolve(\"success\");\n      } else {\n        return Promise.reject(`${title}地址存在非法字符`);\n      }\n    } else {\n      return Promise.resolve(\"success\");\n    }\n  };\n\n  // 改变告警邮件推送\n  const changePush = (data) => {\n    setPushLoading(true);\n    fetchPost(apiRequest.MonitoringSettings.updatePushConfig, {\n      body: {\n        way_name: \"email\",\n        env_id: 1,\n        server_url: pushForm.getFieldValue(\"email\"),\n        used: data.pushIsOpen,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"更新监控推送设置成功\");\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setPushLoading(false);\n        fetchPushDate();\n      });\n  };\n\n  return (\n    <OmpContentWrapper>\n      <div className={styles.header}>\n        <SettingOutlined style={{ paddingRight: 5 }} />\n        监控平台配置\n      </div>\n      <Spin spinning={loading}>\n        <Form\n          name=\"setting\"\n          labelCol={{ span: 3 }}\n          wrapperCol={{ span: 6 }}\n          style={{ paddingTop: 40 }}\n          onFinish={multipleUpdate}\n          form={form}\n        >\n          <Form.Item\n            label=\"Prometheus地址\"\n            name=\"prometheus\"\n            rules={[\n              { required: true, message: \"请输入 Prometheus 地址\" },\n              {\n                validator: (rule, value, callback) => {\n                  return validateMonitorAddress(\n                    rule,\n                    value,\n                    callback,\n                    \"Prometheus\"\n                  );\n                },\n              },\n            ]}\n          >\n            <Input\n              addonBefore=\"Http://\"\n              placeholder=\"请输入 Prometheus 地址\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"Grafana地址\"\n            name=\"grafana\"\n            rules={[\n              { required: true, message: \"请输入 Grafana 地址\" },\n              {\n                validator: (rule, value, callback) => {\n                  return validateMonitorAddress(\n                    rule,\n                    value,\n                    callback,\n                    \"Grafana\"\n                  );\n                },\n              },\n            ]}\n          >\n            <Input\n              addonBefore=\"Http://\"\n              placeholder=\"请输入 Grafana 地址\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"Alert Manager地址\"\n            name=\"alertmanager\"\n            rules={[\n              { required: true, message: \"请输入 Alert Manager 地址\" },\n              {\n                validator: (rule, value, callback) => {\n                  return validateMonitorAddress(\n                    rule,\n                    value,\n                    callback,\n                    \"Alert Manager\"\n                  );\n                },\n              },\n            ]}\n          >\n            <Input\n              addonBefore=\"Http://\"\n              placeholder=\"请输入 Alert Manager 地址\"\n              style={{\n                width: 360,\n              }}\n            />\n          </Form.Item>\n\n          <Form.Item className={styles.saveButtonWrapper}>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              className={styles.saveButton}\n            >\n              保存\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n\n      {/* 告警邮件 */}\n      <div className={styles.header}>\n        <MailOutlined style={{ paddingRight: 5 }} />\n        告警邮件\n      </div>\n      <Spin spinning={pushLoading}>\n        <Form\n          name=\"pushSetting\"\n          labelCol={{ span: 3 }}\n          wrapperCol={{ span: 6 }}\n          style={{ paddingTop: 40 }}\n          onFinish={changePush}\n          form={pushForm}\n          initialValues={{\n            pushIsOpen: false,\n          }}\n        >\n          <Form.Item label=\"邮件推送\" name=\"pushIsOpen\" valuePropName=\"checked\">\n            <Switch\n              onChange={(e) => setPushIsOpen(e)}\n              style={{ borderRadius: \"10px\" }}\n            />\n          </Form.Item>\n          {pushIsOpen && (\n            <Form.Item\n              label=\"接收人\"\n              name=\"email\"\n              rules={[\n                {\n                  type: \"email\",\n                  message: \"请输入正确格式的邮箱\",\n                },\n                {\n                  required: true,\n                  message: \"请输入接收人邮箱\",\n                },\n              ]}\n            >\n              <Input\n                placeholder=\"例如: emailname@163.com\"\n                style={{\n                  width: 360,\n                }}\n              />\n            </Form.Item>\n          )}\n          <Form.Item className={styles.saveButtonWrapper}>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              className={styles.saveButton}\n            >\n              保存\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </OmpContentWrapper>\n  );\n};\n\nexport default MonitoringSettings;\n"
  },
  {
    "path": "omp_web/src/pages/MonitoringSettings/index.module.less",
    "content": ".wrapper {\n  padding-left: 20px;\n}\n.header {\n  padding: 10px;\n  padding-left: 20px;\n  background-color: #f7f7f7;\n}\n\n.saveButtonWrapper {\n  height: 100px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.saveButton {\n  margin-left: 50%;\n  transform: translateX(-50%);\n}\n"
  },
  {
    "path": "omp_web/src/pages/PatrolInspectionRecord/config/columns.js",
    "content": "import { renderDisc, downloadFile, handleResponse } from \"@/utils/utils\";\nimport { message } from \"antd\";\nimport moment from \"moment\";\nimport { apiRequest } from \"src/config/requestApi\";\nimport { fetchGet } from \"@/utils/request\";\n\nconst getColumnsConfig = (queryRequest, history, pushData) => {\n  // 推送邮件相关数据\n  const { pushForm, setPushLoading, setPushAnalysisModal, setPushInfo } =\n    pushData;\n\n  const fetchDetailData = (id) => {\n    fetchGet(`${apiRequest.inspection.reportDetail}/${id}/`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          downloadFile(`/download-inspection/${res.data.file_name}`);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 查询推送数据\n  const fetchPushDate = (record) => {\n    setPushLoading(true);\n    fetchGet(apiRequest.inspection.queryPushConfig)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res && res.data) {\n            const { to_users } = res.data;\n            pushForm.setFieldsValue({\n              email: to_users,\n            });\n            setPushInfo({\n              id: record.id,\n              module: record.inspection_type,\n              to_users: to_users,\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setPushLoading(false);\n      });\n  };\n\n  // 点击推送\n  const clickPush = (record) => {\n    setPushAnalysisModal(true);\n    fetchPushDate(record);\n    // pushForm.setFieldsValue({\n    //   id:\n    // })\n  };\n\n  return [\n    {\n      title: \"序列\",\n      width: 40,\n      key: \"idx\",\n      dataIndex: \"idx\",\n      align: \"center\",\n      fixed: \"left\",\n    },\n    {\n      title: \"报告名称\",\n      width: 120,\n      key: \"inspection_name\",\n      dataIndex: \"inspection_name\",\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record, index) => {\n        if (record.inspection_status == 2) {\n          return (\n            <a\n              style={{\n                fontSize: 12,\n              }}\n              onClick={() => {\n                history?.push({\n                  pathname: `/status-patrol/patrol-inspection-record/status-patrol-detail/${record.id}`,\n                });\n              }}\n            >\n              {text}\n            </a>\n          );\n        }\n        return text;\n      },\n    },\n    {\n      title: \"报告类型\",\n      width: 80,\n      key: \"inspection_type\",\n      align: \"center\",\n      dataIndex: \"inspection_type\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"service\",\n          text: \"组件巡检\",\n        },\n        {\n          value: \"host\",\n          text: \"主机巡检\",\n        },\n        {\n          value: \"deep\",\n          text: \"深度分析\",\n        },\n      ],\n      render: (text, record, index) => {\n        if (text == \"service\") {\n          return \"组件巡检\";\n        }\n        if (text == \"host\") {\n          return \"主机巡检\";\n        }\n        if (text == \"deep\") {\n          return \"深度分析\";\n        }\n      },\n    },\n    {\n      title: \"巡检结果\",\n      width: 150,\n      key: \"inspection_status\",\n      dataIndex: \"inspection_status\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"1\",\n          text: \"进行中\",\n        },\n        {\n          value: \"2\",\n          text: \"成功\",\n        },\n        {\n          value: \"3\",\n          text: \"失败\",\n        },\n      ],\n      align: \"center\",\n      render: (text, record, index) => {\n        if (!text && text !== 0) {\n          return \"-\";\n        } else if (text === 2) {\n          return <div>{renderDisc(\"normal\", 7, -1)}成功</div>;\n        } else if (text === 1) {\n          return <div>{renderDisc(\"normal\", 7, -1)}进行中</div>;\n        } else if (text === 3) {\n          return <div>{renderDisc(\"critical\", 7, -1)}失败</div>;\n        } else {\n          return text;\n        }\n      },\n    },\n    {\n      title: \"执行方式\",\n      align: \"center\",\n      dataIndex: \"execute_type\",\n      key: \"execute_type\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"man\",\n          text: \"手动\",\n        },\n        {\n          value: \"auto\",\n          text: \"定时\",\n        },\n      ],\n      render: (text) => {\n        if (text == \"man\") {\n          return \"手动执行\";\n        } else if (text == \"auto\") {\n          return \"定时执行\";\n        } else {\n          return \"-\";\n        }\n      },\n      width: 80,\n    },\n    {\n      title: \"巡检时间\",\n      width: 200,\n      key: \"start_time\",\n      dataIndex: \"start_time\",\n      ellipsis: true,\n      sorter: (a, b) =>\n        moment(a.start_time).valueOf() - moment(b.start_time).valueOf(),\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text) => {\n        if (!text) return \"-\";\n        return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n      },\n    },\n    {\n      title: \"巡检用时\",\n      key: \"duration\",\n      dataIndex: \"duration\",\n      render: (text) => {\n        if (text && text !== \"-\") {\n          let timer = moment.duration(text, \"seconds\");\n\n          let hours = timer.hours();\n          let hoursResult = hours ? `${hours}小时` : \"\";\n\n          let minutes = timer.minutes();\n          let minutesResult = minutes % 60 ? `${minutes % 60}分钟` : \"\";\n\n          let seconds = timer.seconds();\n          let secondsResult = seconds % 60 ? `${seconds % 60}秒` : \"\";\n\n          return `${hoursResult} ${minutesResult} ${secondsResult}`;\n        } else {\n          return \"-\";\n        }\n      },\n      align: \"center\",\n      width: 60,\n    },\n    {\n      title: \"推送结果\",\n      key: \"send_email_result\",\n      dataIndex: \"send_email_result\",\n      align: \"center\",\n      width: 80,\n      render: (text, record) => {\n        switch (text) {\n          case 1:\n            return <div>{renderDisc(\"normal\", 7, -1)}成功</div>;\n          case 2:\n            return <div>{renderDisc(\"warning\", 7, -1)}推送中</div>;\n          case 0:\n            return <div>{renderDisc(\"critical\", 7, -1)}失败</div>;\n          case 3:\n            return <div>{renderDisc(\"warning\", 7, -1)}未推送 </div>;\n          default:\n            return \"-\";\n        }\n      },\n    },\n    {\n      title: \"操作\",\n      width: 60,\n      key: \"\",\n      dataIndex: \"\",\n      fixed: \"right\",\n      align: \"center\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              {record.inspection_status == 2 ? (\n                <>\n                  <a\n                    onClick={() => {\n                      message.success(\n                        `正在下载巡检报告，双击文件夹中index.html查看报告`\n                      );\n                      fetchDetailData(record.id);\n                    }}\n                  >\n                    导出\n                  </a>\n                  <a\n                    style={{ marginLeft: 10 }}\n                    onClick={() => clickPush(record)}\n                  >\n                    推送\n                  </a>\n                </>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25\" }}>导出</span>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/PatrolInspectionRecord/config/detail.js",
    "content": "import \"./index.css\";\nimport {\n  columnsConfig,\n  formatTableRenderData,\n  host_port_connectivity_columns,\n  kafka_offsets_columns,\n  kafka_partition_columns,\n  kafka_topic_size_columns,\n  handleResponse,\n  downloadFile,\n} from \"@/utils/utils\";\nimport { Card, Collapse, message, Table, Drawer } from \"antd\";\nimport * as R from \"ramda\";\nimport { useEffect, useState } from \"react\";\n//import data from \"./data.json\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nconst { Panel } = Collapse;\n\nconst reportColumnConfig = [\n  { ...columnsConfig.report_service_name, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_host_ip, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_service_port, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_service_status, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_cpu_usage, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_mem_usage, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_run_time, className: \"_bigfontSize\" },\n  { ...columnsConfig.report_log_level, className: \"_bigfontSize\" },\n  // { ...columnsConfig.report_cluster_name, className: styles._bigfontSize },\n];\n\nexport default function PatrolInspectionDetail() {\n  const title = \"巡检报告\";\n\n  const location = useLocation();\n  const history = useHistory();\n\n  // /const data = localStorage.getItem(\"recordDetailData\");\n\n  let arr = location.pathname.split(\"/\");\n  const id = arr[arr.length - 1];\n  const [drawerVisible, setDrawerVisible] = useState(false);\n  const [drawerText, setDrawerText] = useState(\"\");\n\n  const [expandKey, setExpandKey] = useState([]);\n\n  const [data, setData] = useState({});\n\n  const [loading, setLoading] = useState(false);\n\n  // 是否为主机巡检\n  const [isHost, setIsHost] = useState(false);\n\n  const fetchDetailData = (id) => {\n    setLoading(true);\n    fetchGet(`${apiRequest.inspection.reportDetail}/${id}/`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setData(res.data);\n\n          // 通过文件名判断是否为主机巡检\n          if (res.data.file_name.indexOf(\"host\") === 0) {\n            setIsHost(true);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchDetailData(id);\n  }, []);\n\n  if (!data) {\n    return <div>数据暂无</div>;\n  }\n\n  return (\n    <div id=\"reportContent\" className={\"reportContent\"}>\n      <div className={\"reportTitle\"}>\n        <div\n          style={{\n            width: \"100%\",\n            display: \"flex\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n          }}\n        >\n          {title}\n        </div>\n        <div>\n          <div\n            className={\"goBackElement\"}\n            id={\"invisible\"}\n            style={{ paddingRight: 10 }}\n            onClick={() =>\n              history.push(\"/status-patrol/patrol-inspection-record\")\n            }\n          >\n            返回\n          </div>\n          <div\n            id={\"invisible\"}\n            onClick={() => {\n              message.success(\n                `正在下载巡检报告，双击文件夹中index.html查看报告`\n              );\n              // download();\n              downloadFile(`/download-inspection/${data.file_name}`);\n            }}\n          >\n            导出\n          </div>\n        </div>\n      </div>\n      <div>\n        <Collapse\n          bordered={false}\n          defaultActiveKey={[\n            \"overview\",\n            \"risk\",\n            \"map\",\n            \"host\",\n            \"database\",\n            \"component\",\n            \"service\",\n          ]}\n          style={{ marginTop: 10 }}\n          //   expandIcon={({ isActive }) => (\n          //     <Icon type=\"caret-right\" rotate={isActive ? 90 : 0} />\n          //   )}\n        >\n          <Panel header=\"概述信息\" key=\"overview\" className={\"panelItem\"}>\n            <div className={\"overviewItemWrapper\"}>\n              <OverviewItem data={data.summary?.task_info} type={\"task_info\"} />\n              <OverviewItem data={data.summary?.time_info} type={\"time_info\"} />\n              <OverviewItem\n                data={data.summary?.scan_info}\n                type={\"scan_info\"}\n                isHost={isHost}\n              />\n              <OverviewItem\n                data={data.summary?.scan_result}\n                type={\"scan_result\"}\n              />\n            </div>\n          </Panel>\n\n          {/* risks存在，且内部数据任一不为空才显示风险指标一栏 */}\n          {data.risks &&\n            (!R.isEmpty(data.risks.host_list) ||\n              !R.isEmpty(data.risks.service_list)) && (\n              <Panel header=\"风险指标\" key=\"risk\" className={\"panelItem\"}>\n                {data.risks.host_list.length > 0 && (\n                  <Table\n                    style={{ marginTop: 20 }}\n                    bordered={true}\n                    size={\"small\"}\n                    pagination={false}\n                    rowKey={(record, index) => record.id}\n                    columns={[\n                      {\n                        ...columnsConfig.report_idx,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_host_ip,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_system,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_risk_level,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_risk_describe,\n                        className: \"_bigfontSize\",\n                        render: (text) => {\n                          return (\n                            <span\n                              style={{ cursor: \"pointer\" }}\n                              onClick={() => {\n                                console.log(text);\n                                setDrawerText(text);\n                                setDrawerVisible(true);\n                              }}\n                            >\n                              {text}\n                            </span>\n                          );\n                        },\n                      },\n                      {\n                        ...columnsConfig.report_resolve_info,\n                        className: \"_bigfontSize\",\n                      },\n                    ]}\n                    title={() => \"主机指标\"}\n                    dataSource={data.risks.host_list}\n                  />\n                )}\n                {data.risks.service_list.length > 0 && (\n                  <Table\n                    bordered={true}\n                    style={{ marginTop: 20 }}\n                    size={\"small\"}\n                    pagination={false}\n                    rowKey={(record, index) => record.id}\n                    columns={[\n                      {\n                        ...columnsConfig.report_idx,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_service_name,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_host_ip,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_service_port,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_risk_level,\n                        className: \"_bigfontSize\",\n                      },\n                      {\n                        ...columnsConfig.report_risk_describe,\n                        className: \"_bigfontSize\",\n                        render: (text) => {\n                          return (\n                            <span\n                              style={{ cursor: \"pointer\" }}\n                              onClick={() => {\n                                console.log(text);\n                                setDrawerText(text);\n                                setDrawerVisible(true);\n                              }}\n                            >\n                              {text}\n                            </span>\n                          );\n                        },\n                      },\n                      {\n                        ...columnsConfig.report_resolve_info,\n                        className: \"_bigfontSize\",\n                      },\n                    ]}\n                    title={() => \"服务指标\"}\n                    dataSource={data.risks.service_list}\n                  />\n                )}\n              </Panel>\n            )}\n\n          {!R.either(R.isNil, R.isEmpty)(data?.service_topology) && (\n            <Panel header=\"服务平面图\" key=\"map\" className={\"panelItem\"}>\n              <div\n                style={{ display: \"flex\", flexFlow: \"row wrap\", margin: 10 }}\n              >\n                {R.addIndex(R.map)((item, index) => {\n                  return (\n                    <PlanChart\n                      key={index}\n                      title={item?.host_ip}\n                      list={item?.service_list}\n                      data={data}\n                    />\n                  );\n                }, data?.service_topology)}\n              </div>\n            </Panel>\n          )}\n\n          {!R.either(R.isNil, R.isEmpty)(data.detail_dict?.host) && (\n            <Panel header=\"主机列表\" key=\"host\" className={\"panelItem\"}>\n              <Table\n                //rowClassName={()=>{return styles.didingyi;}}\n                bordered={true}\n                size={\"small\"}\n                style={{ marginTop: 20 }}\n                scroll={{ x: 1100 }}\n                pagination={false}\n                rowKey={(record, index) => record.id}\n                // defaultExpandAllRows\n                columns={[\n                  {\n                    ...columnsConfig.report_idx,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_host_ip,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_release_version,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_host_massage,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_cpu_usage,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_mem_usage,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_disk_usage_root,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_disk_usage_data,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_run_time,\n                    className: \"_bigfontSize\",\n                  },\n                  {\n                    ...columnsConfig.report_sys_load,\n                    className: \"_bigfontSize\",\n                  },\n                ]}\n                //expandedRowKeys={expandKey}\n                expandedRowRender={(...arg) => {\n                  arg[0].basic = arg[0].basic.filter(\n                    (item) => item.name !== \"cluster_ip\"\n                  );\n                  return RenderExpandedContent(\n                    ...arg,\n                    drawerVisible,\n                    setDrawerVisible,\n                    drawerText,\n                    setDrawerText\n                  );\n                }}\n                // onExpand={(expanded, record) => {\n                //   //console.log([...expandKey, record.id]);\n                //   setExpandKey([...expandKey, record.id]);\n                //   //console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);\n                // }}\n                dataSource={data.detail_dict.host}\n              />\n            </Panel>\n          )}\n\n          {!R.either(R.isNil, R.isEmpty)(data.detail_dict?.database) && (\n            <Panel header=\"数据库列表\" key=\"database\" className={\"panelItem\"}>\n              <Table\n                size={\"small\"}\n                bordered={true}\n                style={{ marginTop: 20 }}\n                pagination={false}\n                rowKey={(record, index) => record.id}\n                // defaultExpandAllRows\n                columns={reportColumnConfig}\n                expandedRowRender={(...arg) => {\n                  arg[0].basic = arg[0].basic.filter(\n                    (item) => item.name !== \"cluster_ip\"\n                  );\n                  RenderExpandedContent(\n                    ...arg,\n                    drawerVisible,\n                    setDrawerVisible,\n                    drawerText,\n                    setDrawerText\n                  );\n                }}\n                dataSource={data.detail_dict.database}\n              />\n            </Panel>\n          )}\n\n          {!R.either(R.isNil, R.isEmpty)(data.detail_dict?.component) && (\n            <Panel header=\"组件列表\" key=\"component\" className={\"panelItem\"}>\n              <Table\n                size={\"small\"}\n                bordered={true}\n                style={{ marginTop: 20 }}\n                pagination={false}\n                rowKey={(record, index) => record.id}\n                // defaultExpandAllRows\n                columns={reportColumnConfig}\n                expandedRowRender={(...arg) => {\n                  arg[0].basic = arg[0].basic.filter(\n                    (item) => item.name !== \"cluster_ip\"\n                  );\n                  return RenderExpandedContent(\n                    ...arg,\n                    drawerVisible,\n                    setDrawerVisible,\n                    drawerText,\n                    setDrawerText\n                  );\n                }}\n                dataSource={data.detail_dict.component}\n              />\n            </Panel>\n          )}\n\n          {!R.either(R.isNil, R.isEmpty)(data.detail_dict?.service) && (\n            <Panel header=\"服务列表\" key=\"service\" className={\"panelItem\"}>\n              <Table\n                size={\"small\"}\n                bordered={true}\n                style={{ marginTop: 20 }}\n                pagination={false}\n                rowKey={(record, index) => record.id}\n                // defaultExpandAllRows\n                columns={reportColumnConfig}\n                expandedRowRender={(...arg) => {\n                  arg[0].basic = arg[0].basic.filter(\n                    (item) => item.name !== \"cluster_ip\"\n                  );\n                  return RenderExpandedContent(\n                    ...arg,\n                    drawerVisible,\n                    setDrawerVisible,\n                    drawerText,\n                    setDrawerText\n                  );\n                }}\n                dataSource={data.detail_dict.service}\n              />\n            </Panel>\n          )}\n        </Collapse>\n      </div>\n      <Drawer\n        title=\"进程日志\"\n        placement=\"right\"\n        closable={false}\n        onClose={() => setDrawerVisible(false)}\n        visible={drawerVisible}\n        width={720}\n        destroyOnClose\n      >\n        {drawerText}\n      </Drawer>\n    </div>\n  );\n}\n\nfunction formatTime(text = 0) {\n  let duration = text;\n  const second = Math.round(Number(text)),\n    days = Math.floor(second / 86400),\n    hours = Math.floor((second % 86400) / 3600),\n    minutes = Math.floor(((second % 86400) % 3600) / 60),\n    seconds = Math.floor(((second % 86400) % 3600) % 60);\n\n  if (days > 0) {\n    duration = days + \"天\" + hours + \"小时\" + minutes + \"分\" + seconds + \"秒\";\n  } else if (hours > 0) {\n    duration = hours + \"小时\" + minutes + \"分\" + seconds + \"秒\";\n  } else if (minutes > 0) {\n    duration = minutes + \"分\" + seconds + \"秒\";\n  } else if (seconds > 0) {\n    duration = seconds + \"秒\";\n  }\n\n  return duration;\n}\n\n// 概览信息\nfunction OverviewItem({ data, type, isHost = false }) {\n  switch (type) {\n    case \"task_info\":\n      return (\n        <div className={\"overviewItem\"}>\n          <div>任务信息</div>\n          <div>\n            <div>任务名称：{data?.task_name}</div>\n            <div>操作人员：{data?.operator}</div>\n            <div>任务状态：{data?.task_status === 2 ? \"已完成\" : \"失败\"}</div>\n          </div>\n        </div>\n      );\n    case \"time_info\":\n      return (\n        <div className={\"overviewItem\"}>\n          <div>时间统计</div>\n          <div>\n            <div>开始时间：{data?.start_time}</div>\n            <div>结束时间：{data?.end_time}</div>\n            <div>任务耗时：{formatTime(data?.cost)}</div>\n          </div>\n        </div>\n      );\n    case \"scan_info\":\n      return (\n        <div className={\"overviewItem\"}>\n          <div>扫描统计</div>\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            <div>\n              {data?.host >= 0 && <div>主机个数：{data.host}台</div>}\n              {/* {data?.component >= 0 && <div>组件个数：{data.component}个</div>} */}\n              {data?.service >= 0 && <div>服务个数：{data.service}个</div>}\n            </div>\n          </div>\n        </div>\n      );\n    case \"scan_result\":\n      return (\n        <div className={\"overviewItem\"}>\n          <div>分析结果</div>\n          <div style={{ display: \"flex\", alignItems: \"center\" }}>\n            <div>\n              <div>总指标数：{data?.all_target_num}</div>\n              <div>异常指标：{data?.abnormal_target}</div>\n              {/* <div>健康度：{data.healthy}</div> */}\n            </div>\n          </div>\n        </div>\n      );\n  }\n}\n\n//平面图\nfunction PlanChart({ title, list, data }) {\n  return (\n    <div className={\"planChartWrapper\"}>\n      <div className={\"planChartTitle\"}>\n        <span className={\"planChartTitleCircular\"} />\n        {title}\n      </div>\n      <div className={\"planChartBlockWrapper\"}>\n        {list?.map((item) => {\n          return (\n            <div className={\"stateButton\"} key={item}>\n              <div>{item}</div>\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\n// Table渲染的子项\n// 注：此处需要针对特殊属性渲染额外效果，故将已在table渲染过的属性单独拿出来\nfunction RenderExpandedContent(\n  {\n    basic,\n    host_ip,\n    service_status,\n    run_time,\n    log_level,\n    mem_usage,\n    cpu_usage,\n    service_name,\n    service_port,\n    cluster_name,\n    release_version,\n    host_massage,\n    disk_usage_root,\n    disk_usage_data,\n    sys_load,\n    ...specialProps\n  },\n  ...arg\n) {\n  const { topic_partition, kafka_offsets, topic_size } = specialProps;\n\n  let [drawerVisible, setDrawerVisible, drawerText, setDrawerText] =\n    arg.slice(-4);\n\n  const formattedData = Object.entries(specialProps).filter((item) =>\n    Array.isArray(item[1])\n  );\n\n  /* eslint-disable */\n  let deal_host_memory_top_columns = [\n    {\n      title: \"TOP\",\n      dataIndex: \"TOP\",\n      //ellipsis: true,\n      width: 50,\n      className: \"_bigfontSize\",\n    },\n    {\n      title: \"PID\",\n      dataIndex: \"PID\",\n      //ellipsis: true,\n      align: \"center\",\n      width: 100,\n      className: \"_bigfontSize\",\n    },\n    {\n      title: \"使用率\",\n      dataIndex: \"P_RATE\",\n      //ellipsis: true,\n      align: \"center\",\n      width: 100,\n      className: \"_bigfontSize\",\n    },\n    {\n      title: \"进程\",\n      dataIndex: \"P_CMD\",\n      ellipsis: true,\n      className: \"_bigfontSize\",\n      render: (text) => {\n        return (\n          <span\n            style={{ cursor: \"pointer\" }}\n            onClick={() => {\n              setDrawerText(text);\n              setDrawerVisible(true);\n            }}\n          >\n            {text}\n          </span>\n        );\n      },\n    },\n  ];\n  /* eslint-disable */\n  const contentMap = {\n    // 主机列表\n    port_connectivity: {\n      columns: host_port_connectivity_columns,\n      dataSource: specialProps.port_connectivity,\n      title: \"端口连通性\",\n    },\n    memory_top: {\n      columns: deal_host_memory_top_columns,\n      dataSource: specialProps.memory_top,\n      title: \"内存使用率Top10进程\",\n    },\n    cpu_top: {\n      columns: deal_host_memory_top_columns,\n      dataSource: specialProps.cpu_top,\n      title: \"cpu使用率Top10进程\",\n    },\n    kernel_parameters: {\n      columns: [],\n      dataSource: specialProps.kernel_parameters,\n      title: \"内核参数\",\n    },\n    //  服务列表\n    topic_partition: {\n      columns: kafka_partition_columns,\n      dataSource: specialProps.topic_partition,\n      title: \"分区信息\",\n    },\n    kafka_offsets: {\n      columns: kafka_offsets_columns,\n      dataSource: specialProps.kafka_offsets,\n      title: \"消费位移信息\",\n    },\n    topic_size: {\n      columns: kafka_topic_size_columns,\n      dataSource: specialProps.topic_size,\n      title: \"Topic消息大小\",\n    },\n  };\n\n  return (\n    <div className={\"expandedRowWrapper\"}>\n      {Array.isArray(basic) && <BasicCard basic={basic} />}\n      {formattedData.length > 0 && (\n        <Collapse\n          defaultActiveKey={R.keys(specialProps)}\n          style={{ marginTop: 10 }}\n          //   expandIcon={({ isActive }) => (\n          //     <Icon type=\"caret-right\" rotate={isActive ? 90 : 0} />\n          //   )}\n        >\n          {formattedData.map((item, idx) => {\n            // 根据当前渲染项，找到对应的content配置数据\n            const currentContent = contentMap[item[0]];\n\n            // 只取目前已经配置了的数据\n            if (!R.isNil(currentContent)) {\n              return (\n                <Panel header={currentContent.title} key={item[0]}>\n                  {currentContent.columns.length > 0 ? (\n                    <Table\n                      bordered={true}\n                      rowKey={(record, index) => record.id}\n                      size={\"small\"}\n                      columns={currentContent.columns}\n                      dataSource={currentContent.dataSource}\n                      pagination={false}\n                    />\n                  ) : (\n                    <div className={\"basicCardWrapper\"}>\n                      {currentContent.dataSource.map((item, idx) => {\n                        return (\n                          <div key={idx} className={\"basicCardItem\"}>\n                            {item}\n                          </div>\n                        );\n                      })}\n                    </div>\n                  )}\n                </Panel>\n              );\n            } else {\n              //   todo 其他数据项\n              console.log(\"未配置的数据项\", item);\n            }\n          })}\n        </Collapse>\n      )}\n      <Drawer\n        title=\"进程日志\"\n        placement=\"right\"\n        closable={false}\n        onClose={() => setDrawerVisible(false)}\n        visible={drawerVisible}\n        width={720}\n        destroyOnClose\n      >\n        {drawerText}\n      </Drawer>\n    </div>\n  );\n}\n\n// 卡片面板\nfunction BasicCard({ basic }) {\n  return (\n    <Card>\n      <div className={\"basicCardWrapper\"}>\n        {basic.map((item, idx) => (\n          <div key={idx} className={\"basicCardItem\"}>\n            <span style={{ color: \"#333\" }}>{item.name_cn}: </span>\n            <span>{formatTableRenderData(JSON.stringify(item.value))}</span>\n          </div>\n        ))}\n      </div>\n    </Card>\n  );\n}\n"
  },
  {
    "path": "omp_web/src/pages/PatrolInspectionRecord/config/index.css",
    "content": ".warningSearch {\n  display: flex;\n  justify-content: flex-end;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.warningSearch > div:nth-child(1) {\n  margin-right: auto;\n}\n.rangePicker {\n  margin-right: 15px;\n}\n.contentLeftMenuWrapper {\n  display: flex;\n}\n.leftMenuListContent {\n  display: flex;\n  flex-direction: column;\n  font-size: 16px;\n}\n.leftMenu {\n  background-color: #fafafa;\n  padding: 15px;\n  margin-top: 10px;\n  margin-right: 10px;\n  height: 82vh;\n  overflow-y: auto;\n}\n\n.trendContentWrapper {\n  width: calc(100% - 0px);\n  padding: 10px;\n}\n.title {\n  margin-bottom: 5px;\n  margin-left: 15px;\n  color: #333;\n  font-size: 16px;\n  font-weight: 500;\n  padding: 10px 0;\n}\n.trendItemContent {\n  background-color: #fff;\n  border-radius: 10px;\n}\n.rangPicker {\n  display: flex;\n  justify-content: flex-end;\n  width: 100%;\n  padding: 10px 60px 10px;\n}\n.header {\n  display: flex;\n  align-items: center;\n  padding: 8px 8px 8px 15px;\n  color: #333;\n  border-bottom: 1px solid #bfbfbf;\n}\n\n.header > div:nth-child(2) {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 35px;\n  margin-left: 5px;\n}\n.pageInfo {\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  padding: 10px;\n}\n.pageInfo > div:nth-child(1) {\n  margin-right: 10px;\n}\n.panelItem {\n  background: #edf0f3;\n  border-radius: 4px;\n  border: 0;\n  overflow: hidden;\n  border-bottom: 0 !important;\n}\n.panelItem > div:nth-child(1) {\n  padding: 8px 15px;\n}\n\n.panelItem > div:nth-child(2) {\n  background-color: #fff !important;\n}\n\n.reportContent {\n  padding: 15px;\n}\n\n.reportTitle {\n  display: flex;\n  justify-content: space-between;\n  color: #1890ff;\n  margin-bottom: 20px;\n  height: 40px;\n  align-items: center;\n  position: relative;\n}\n.reportTitle > div:nth-child(1) {\n  font-size: 22px;\n  font-weight: 500;\n  color: #333;\n}\n\n.reportTitle > div:nth-child(2) {\n  position: absolute;\n  right: 0;\n  display: flex;\n  cursor: pointer;\n}\n\n.reportTitle > div:nth-child(2) {\n  margin-right: 10px;\n}\n.overviewItemWrapper {\n  display: flex;\n  justify-content: space-between;\n  flex-flow: wrap;\n  margin: 10px 0;\n}\n\n.overviewItemWrapper > div:nth-child(even) {\n  margin-right: 0;\n}\n.overviewItem {\n  display: flex;\n  width: 49.5%;\n  color: #333;\n  margin-bottom: 10px;\n}\n.overviewItem > div {\n  border: 1px solid #e8e8e8;\n  padding: 10px;\n}\n\n.overviewItem > div:nth-child(1) {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  border-right: none;\n  width: 200px;\n}\n\n.overviewItem > div:nth-child(2) {\n  width: 100%;\n}\n.planChartWrapper {\n  margin-top: 10px;\n  width: 100%;\n  border: 1px solid #e8e8e8;\n  border-radius: 2px;\n}\n.planChartBlockWrapper {\n  display: flex;\n  flex-flow: row wrap;\n  max-height: 240px;\n  overflow-y: auto;\n  padding-top: 15px;\n  padding: 20px;\n}\n.stateButton {\n  position: relative;\n  top: 0px;\n  border: 1px solid #333;\n  transition: all 0.2s ease-in-out;\n  width: 178px;\n  margin-right: 32px;\n  margin-bottom: 10px;\n  height: 32px;\n}\n.stateButton > div {\n  margin: auto;\n  width: 100%;\n  height: 100%;\n  line-height: 30px;\n  text-align: center;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.planChartTitle {\n  background-color: #fafbfd;\n  font-weight: 500;\n  height: 30px;\n  line-height: 30px;\n  border-bottom: solid 1px #e8e8e8;\n}\n.planChartTitleCircular {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  background-color: #54bba6;\n  border-radius: 50%;\n  margin-right: 10px;\n  margin-left: 20px;\n}\n.topologyWrapper {\n  display: flex;\n  align-items: center;\n  margin-right: 80px;\n  margin-bottom: 80px;\n}\n\n.topologyChildren {\n  display: flex;\n  flex-direction: column;\n}\n.topologyChildren > div:nth-child(1) {\n  position: relative;\n}\n.verticalLine {\n  position: absolute;\n  background-color: #333;\n  left: 0;\n  top: 22px;\n  height: calc(100% - 55px);\n  width: 2px;\n  border-radius: 1px;\n}\n.topologyItem {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 10px;\n  border: 2px solid #333;\n  border-radius: 5px;\n}\n\n.rootItemBox {\n  display: flex;\n  align-items: center;\n  margin-bottom: 10px;\n}\n\n.connectLine {\n  display: inline-block;\n  width: 30px;\n  background-color: #333;\n  height: 2px;\n  border-radius: 1px;\n}\n\n.basicCardWrapper {\n  display: flex;\n  flex-flow: row wrap;\n}\n\n.basicCardItem {\n  padding: 5px;\n  margin-right: 10px;\n  margin-bottom: 10px;\n  width: 300px;\n}\n.buttonContainer {\n  width: 100%;\n}\n.greenType {\n  background-color: #5ba165;\n  color: #fff;\n  border-color: #5ba165;\n}\n.redType {\n  background-color: #ff4d4f;\n  color: #fff;\n  border-color: #ff4d4f;\n}\n.buttonContainer > button {\n  margin-right: 15px;\n}\n._bigfontSize {\n  font-size: 14px;\n}\n"
  },
  {
    "path": "omp_web/src/pages/PatrolInspectionRecord/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpMessageModal } from \"@/components\";\nimport { Button, message, Input, Checkbox, Form } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport getColumnsConfig from \"./config/columns\";\nimport { ExclamationCircleOutlined, SearchOutlined } from \"@ant-design/icons\";\nimport { useHistory } from \"react-router-dom\";\nimport PatrolInspectionDetail from \"@/pages/PatrolInspectionRecord/config/detail\";\nconst PatrolInspectionRecord = () => {\n  const history = useHistory();\n  const [loading, setLoading] = useState(false);\n  const [pushLoading, setPushLoading] = useState(false);\n  const [instanceSelectValue, setInstanceSelectValue] = useState(\"\");\n\n  // 深度分析modal弹框\n  const [deepAnalysisModal, setDeepAnalysisModal] = useState(false);\n  // 主机巡检modal弹框\n  const [hostAnalysisModal, setHostAnalysisModal] = useState(false);\n  // 组件巡检modal弹框\n  const [componenetAnalysisModal, setComponenetAnalysisModal] = useState(false);\n  // 邮件推送modal弹框\n  const [pushAnalysisModal, setPushAnalysisModal] = useState(false);\n\n  const [checkboxGroupData, setcheckboxGroupData] = useState([]);\n\n  // ip列表\n  const [ipListSource, setIpListSource] = useState([]);\n  // service列表\n  const [serviceListSource, setServiceListSource] = useState([]);\n\n  const [dataSource, setDataSource] = useState([]);\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 详情数据\n  const [showDetail, setShowDetail] = useState({\n    isShow: false,\n    data: {},\n  });\n\n  // 推送表单数据\n  const [pushForm] = Form.useForm();\n  // 点击推送按钮数据\n  const [pushInfo, setPushInfo] = useState();\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams = {},\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.inspection.inspectionList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((i, idx) => {\n              return {\n                ...i,\n                idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n                key: i.id,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  const taskDistribution = (type, data) => {\n    setLoading(true);\n    fetchPost(apiRequest.inspection.taskDistribution, {\n      body: {\n        inspection_name: \"mock\",\n        inspection_type: type,\n        inspection_status: \"1\",\n        execute_type: \"man\",\n        inspection_operator: localStorage.getItem(\"username\"),\n        env: 1,\n        ...data,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"任务已下发\");\n            fetchData(\n              { current: pagination.current, pageSize: pagination.pageSize },\n              { inspection_name: instanceSelectValue },\n              pagination.ordering\n            );\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setDeepAnalysisModal(false);\n        setHostAnalysisModal(false);\n        setComponenetAnalysisModal(false);\n      });\n  };\n\n  // 巡检的主机ip列表\n  const fetchIPlist = () => {\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  // 巡检的组件列表\n  const fetchServicelist = () => {\n    fetchGet(apiRequest.inspection.servicesList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setServiceListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  useEffect(() => {\n    fetchData();\n    fetchIPlist();\n    fetchServicelist();\n  }, []);\n\n  const pushEmail = () => {\n    setPushLoading(true);\n    fetchPost(apiRequest.inspection.pushEmail, {\n      body: {\n        ...pushInfo,\n        to_users: pushForm.getFieldValue(\"email\"),\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"推送成功\");\n            setPushAnalysisModal(false);\n            fetchData();\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => setPushLoading(false));\n  };\n\n  if (showDetail.isShow) {\n    return <PatrolInspectionDetail data={showDetail.data} />;\n  }\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          // size=\"middle\"\n          onClick={() => setDeepAnalysisModal(true)}\n        >\n          深度分析\n        </Button>\n\n        <Button\n          type=\"primary\"\n          onClick={() => setHostAnalysisModal(true)}\n          style={{ marginLeft: 10 }}\n        >\n          主机巡检\n        </Button>\n\n        <Button\n          type=\"primary\"\n          onClick={() => setComponenetAnalysisModal(true)}\n          style={{ marginLeft: 10 }}\n        >\n          组件巡检\n        </Button>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 80, display: \"flex\", alignItems: \"center\" }}>\n            报告名称:\n          </span>\n          <Input\n            // size=\"middle\"\n            placeholder=\"输入报告名称\"\n            style={{ width: 200 }}\n            allowClear\n            value={instanceSelectValue}\n            onChange={(e) => {\n              setInstanceSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  { current: 1, pageSize: pagination.pageSize },\n                  {\n                    ...pagination.searchParams,\n                    inspection_name: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              if (instanceSelectValue) {\n                fetchData(\n                  { current: 1, pageSize: pagination.pageSize },\n                  {\n                    ...pagination.searchParams,\n                    inspection_name: instanceSelectValue,\n                  }\n                );\n              }\n            }}\n            onPressEnter={() => {\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                {\n                  ...pagination.searchParams,\n                  inspection_name: instanceSelectValue,\n                }\n              );\n            }}\n            suffix={\n              !instanceSelectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { inspection_name: instanceSelectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //   /scroll={{ x: 1900 }}\n          onChange={(e, filters, sorter) => {\n            console.log(\"ui\");\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(\n            (params) => {\n              // console.log(pagination.searchParams)\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                { ...pagination.searchParams, ...params },\n                pagination.ordering\n              );\n            },\n            history,\n            { pushForm, setPushLoading, setPushAnalysisModal, setPushInfo }\n            //fetchDetailData\n          )}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  flexDirection: \"row-reverse\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n        />\n      </div>\n      <OmpMessageModal\n        visibleHandle={[deepAnalysisModal, setDeepAnalysisModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          taskDistribution(\"deep\");\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要执行 <span style={{ fontWeight: 500 }}>深度分析</span> 操作 ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        afterClose={() => {\n          setcheckboxGroupData([]);\n        }}\n        visibleHandle={[hostAnalysisModal, setHostAnalysisModal]}\n        disabled={checkboxGroupData.length == 0}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            主机巡检\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          taskDistribution(\"host\", {\n            hosts: checkboxGroupData,\n          });\n        }}\n      >\n        <>\n          <div\n            style={{\n              borderBottom: \"1px solid #E9E9E9\",\n              paddingBottom: 10,\n              marginBottom: 10,\n            }}\n          >\n            <Checkbox\n              indeterminate={\n                checkboxGroupData.length !== 0 &&\n                checkboxGroupData.length !== ipListSource.length\n              }\n              // onChange={this.onCheckAllChange}\n              checked={checkboxGroupData.length == ipListSource.length}\n              onChange={(e) => {\n                //console.log(e.target.checked)\n                if (e.target.checked) {\n                  setcheckboxGroupData(ipListSource);\n                } else {\n                  setcheckboxGroupData([]);\n                }\n              }}\n            >\n              全选\n            </Checkbox>\n          </div>\n          <Checkbox.Group\n            style={{ width: \"100%\" }}\n            onChange={(e) => {\n              setcheckboxGroupData(e);\n            }}\n            value={checkboxGroupData}\n          >\n            <div style={{ display: \"flex\", flexWrap: \"wrap\" }}>\n              {ipListSource.map((item) => {\n                return (\n                  <div key={item} style={{ padding: \"0 10px 15px 0\" }}>\n                    <Checkbox key={item} value={item}>\n                      {item}\n                    </Checkbox>\n                  </div>\n                );\n              })}\n            </div>\n          </Checkbox.Group>\n        </>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        afterClose={() => {\n          setcheckboxGroupData([]);\n        }}\n        visibleHandle={[componenetAnalysisModal, setComponenetAnalysisModal]}\n        disabled={checkboxGroupData.length == 0}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            组件巡检\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          taskDistribution(\"service\", {\n            services: checkboxGroupData,\n          });\n        }}\n      >\n        <>\n          <div\n            style={{\n              borderBottom: \"1px solid #E9E9E9\",\n              paddingBottom: 10,\n              marginBottom: 10,\n            }}\n          >\n            <Checkbox\n              indeterminate={\n                checkboxGroupData.length !== 0 &&\n                checkboxGroupData.length !== serviceListSource.length\n              }\n              // onChange={this.onCheckAllChange}\n              checked={checkboxGroupData.length == serviceListSource.length}\n              onChange={(e) => {\n                //console.log(e.target.checked)\n                if (e.target.checked) {\n                  setcheckboxGroupData(\n                    serviceListSource.map((i) => i.service__id)\n                  );\n                } else {\n                  setcheckboxGroupData([]);\n                }\n              }}\n            >\n              全选\n            </Checkbox>\n          </div>\n          <Checkbox.Group\n            style={{ width: \"100%\" }}\n            onChange={(e) => {\n              setcheckboxGroupData(e);\n            }}\n            value={checkboxGroupData}\n          >\n            <div style={{ display: \"flex\", flexWrap: \"wrap\" }}>\n              {serviceListSource.map((item) => {\n                return (\n                  <div\n                    key={item.service__id}\n                    style={{ padding: \"0 10px 15px 0\" }}\n                  >\n                    <Checkbox key={item.service__id} value={item.service__id}>\n                      {item.service__app_name}\n                    </Checkbox>\n                  </div>\n                );\n              })}\n            </div>\n          </Checkbox.Group>\n        </>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[pushAnalysisModal, setPushAnalysisModal]}\n        title={<span>邮件推送</span>}\n        loading={pushLoading}\n        onFinish={() => pushEmail()}\n      >\n        <Form style={{ marginLeft: 40 }} form={pushForm}>\n          <Form.Item\n            name=\"email\"\n            label=\"接收人\"\n            rules={[\n              {\n                type: \"email\",\n                message: \"请输入正确格式的邮箱\",\n              },\n            ]}\n          >\n            <Input\n              placeholder=\"例如: emailname@163.com\"\n              style={{\n                width: 320,\n              }}\n            />\n          </Form.Item>\n          <p\n            style={{\n              marginTop: 30,\n              paddingLeft: 40,\n              fontSize: 13,\n            }}\n          >\n            <ExclamationCircleOutlined style={{ paddingRight: 10 }} />\n            如果需要配置默认的巡检报告接收人，请点击\n            <a\n              onClick={() =>\n                history.push({\n                  pathname: \"/status-patrol/patrol-strategy\",\n                })\n              }\n              style={{ marginLeft: 4 }}\n            >\n              这里\n            </a>\n          </p>\n        </Form>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default PatrolInspectionRecord;\n"
  },
  {
    "path": "omp_web/src/pages/PatrolStrategy/index.js",
    "content": "import { OmpContentWrapper } from \"@/components\";\nimport {\n  Switch,\n  Spin,\n  Form,\n  Input,\n  Button,\n  Select,\n  Tooltip,\n  TimePicker,\n  message,\n} from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchPost, fetchPut } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport styles from \"./index.module.less\";\nimport {\n  SettingOutlined,\n  InfoCircleOutlined,\n  MailOutlined,\n} from \"@ant-design/icons\";\nimport moment from \"moment\";\n\nconst PatrolStrategy = () => {\n  const [loading, setLoading] = useState(false);\n  const [pushLoading, setPushLoading] = useState(false);\n\n  const [form] = Form.useForm();\n  const [pushForm] = Form.useForm();\n\n  const [dataSource, setDataSource] = useState({});\n  const [pushDataSource, setPushDataSource] = useState({});\n\n  const [isOpen, setIsOpen] = useState(false);\n  const [pushIsOpen, setPushIsOpen] = useState(false);\n  const [frequency, setFrequency] = useState(\"day\");\n\n  let weekData = [\n    \"星期一\",\n    \"星期二\",\n    \"星期三\",\n    \"星期四\",\n    \"星期五\",\n    \"星期六\",\n    \"星期日\",\n  ];\n\n  const queryPatrolStrategyData = () => {\n    fetchGet(apiRequest.inspection.queryPatrolStrategy, {\n      params: {\n        job_type: 0,\n      },\n    })\n      .then((res) => {\n        if (res && res.data && res.data.data) {\n          setDataSource(res.data.data);\n          let data = res.data.data;\n          let crontab_detail = data.crontab_detail;\n          form.setFieldsValue({\n            name: {\n              value: data.job_name,\n            },\n            type: {\n              value: data.job_type + \"\",\n            },\n            isOpen: {\n              value: !Boolean(data.is_start_crontab),\n            },\n          });\n\n          if (crontab_detail.day_of_week !== \"*\") {\n            setFrequency(\"week\");\n            form.setFieldsValue({\n              strategy: {\n                frequency: \"week\",\n                time: moment(\n                  `${crontab_detail.hour}:${crontab_detail.minute}`,\n                  \"HH:mm\"\n                ),\n                week: crontab_detail.day_of_week,\n              },\n            });\n          }\n\n          if (crontab_detail.day !== \"*\") {\n            setFrequency(\"month\");\n            form.setFieldsValue({\n              strategy: {\n                frequency: \"month\",\n                time: moment(\n                  `${crontab_detail.hour}:${crontab_detail.minute}`,\n                  \"HH:mm\"\n                ),\n                month: crontab_detail.day,\n              },\n            });\n          }\n\n          if (crontab_detail.day == \"*\" && crontab_detail.day_of_week == \"*\") {\n            setFrequency(\"day\");\n            form.setFieldsValue({\n              strategy: {\n                frequency: \"day\",\n                time: moment(\n                  `${crontab_detail.hour}:${crontab_detail.minute}`,\n                  \"HH:mm\"\n                ),\n              },\n            });\n          }\n\n          setIsOpen(!Boolean(res.data.data.is_start_crontab));\n        }\n      })\n      .catch((e) => {\n        console.log(e);\n      })\n      .finally();\n  };\n\n  // 修改策略的方法，当前无策略时使用post请求，当前有策略时使用put\n  const changeStrategy = (data) => {\n    let queryData = form.getFieldsValue();\n    let timeInfo = form.getFieldValue(\"strategy\");\n    setLoading(true);\n    if (queryData.strategy) timeInfo = queryData.strategy;\n    if (dataSource.job_name) {\n      // 本来有任务，使用更新put\n      fetchPut(apiRequest.inspection.updatePatrolStrategy, {\n        body: {\n          job_type: 0,\n          job_name: queryData.name.value,\n          is_start_crontab: queryData.isOpen.value ? 0 : 1,\n          crontab_detail: {\n            hour: timeInfo.time.format(\"HH:mm\").split(\":\")[0] || \"*\",\n            minute: timeInfo.time.format(\"HH:mm\").split(\":\")[1] || \"*\",\n            month: \"*\",\n            day_of_week: timeInfo.week || \"*\",\n            day: timeInfo.month || \"*\",\n          },\n          env: 1,\n        },\n      })\n        .then((res) => {\n          if (res && res.data) {\n            if (res.data.code == 1) {\n              message.warning(res.data.message);\n            }\n            if (res.data.code == 0) {\n              message.success(\"更新巡检策略成功\");\n            }\n          }\n        })\n        .catch((e) => console.log(e))\n        .finally(() => {\n          setLoading(false);\n          queryPatrolStrategyData();\n        });\n    } else {\n      // 无任务使用post\n      fetchPost(apiRequest.inspection.createPatrolStrategy, {\n        body: {\n          job_type: \"0\",\n          job_name: queryData.name.value,\n          is_start_crontab: queryData.isOpen.value ? 0 : 1,\n          crontab_detail: {\n            hour: timeInfo.time.format(\"HH:mm\").split(\":\")[0] || \"*\",\n            minute: timeInfo.time.format(\"HH:mm\").split(\":\")[1] || \"*\",\n            month: \"*\",\n            day_of_week: timeInfo.week || \"*\",\n            day: timeInfo.month || \"*\",\n          },\n          env: 1,\n        },\n      })\n        .then((res) => {\n          if (res && res.data) {\n            if (res.data.code == 1) {\n              message.warning(res.data.message);\n            }\n            if (res.data.code == 0) {\n              message.success(\"新增巡检策略成功\");\n            }\n          }\n        })\n        .catch((e) => console.log(e))\n        .finally(() => {\n          setLoading(false);\n          queryPatrolStrategyData();\n        });\n    }\n  };\n\n  // 查询推送数据\n  const fetchPushDate = () => {\n    setPushLoading(true);\n    fetchGet(apiRequest.inspection.queryPushConfig)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res && res.data) {\n            const { send_email, to_users } = res.data;\n            pushForm.setFieldsValue({\n              pushIsOpen: send_email,\n              email: to_users,\n            });\n            setPushIsOpen(send_email);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setPushLoading(false);\n      });\n  };\n\n  // 改变推送\n  const changePush = (data) => {\n    setPushLoading(true);\n    fetchPost(apiRequest.inspection.updatePushConfig, {\n      body: {\n        env_id: 1,\n        to_users: pushForm.getFieldValue(\"email\"),\n        send_email: data.pushIsOpen,\n      },\n    })\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"更新巡检报告推送设置成功\");\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setPushLoading(false);\n        fetchPushDate();\n      });\n  };\n\n  useEffect(() => {\n    queryPatrolStrategyData();\n    fetchPushDate();\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div className={styles.header}>\n        <SettingOutlined style={{ paddingRight: 5 }} />\n        巡检设置\n      </div>\n      <Spin spinning={loading}>\n        <Form\n          name=\"setting\"\n          labelCol={{ span: 3 }}\n          wrapperCol={{ span: 8 }}\n          style={{ paddingTop: 40 }}\n          onFinish={changeStrategy}\n          form={form}\n          initialValues={{\n            type: {\n              value: \"0\",\n            },\n            name: {\n              value: \"深度分析\",\n            },\n            isOpen: {\n              value: false,\n            },\n            strategy: {\n              frequency: \"day\",\n              time: moment(\"00:00\", \"HH:mm\"),\n              week: \"0\",\n              month: \"1\",\n            },\n          }}\n        >\n          <Form.Item label=\"任务类型\">\n            <Input.Group compact>\n              <Form.Item\n                name={[\"type\", \"value\"]}\n                noStyle\n                rules={[{ required: true, message: \"请选择任务类型\" }]}\n              >\n                <Select\n                  style={{\n                    width: 200,\n                  }}\n                  placeholder=\"请选择巡检任务类型\"\n                >\n                  <Select.Option value=\"0\" key=\"0\">\n                    深度分析\n                  </Select.Option>\n                  {/* <Select.Option value=\"1\" key=\"1\">\n                    主机巡检\n                  </Select.Option>\n                  <Select.Option value=\"2\" key=\"2\">\n                    组件巡检\n                  </Select.Option> */}\n                </Select>\n              </Form.Item>\n              <Form.Item name={[\"type\", \"icon\"]} noStyle>\n                <Tooltip\n                  placement=\"top\"\n                  title={\"当前版本只支持“深度分析”类型的巡检任务设置\"}\n                >\n                  <InfoCircleOutlined\n                    name=\"icon\"\n                    style={{\n                      color: \"rgba(0,0,0,.45)\",\n                      position: \"relative\",\n                      top: 8,\n                      left: 15,\n                      fontSize: 15,\n                    }}\n                  />\n                </Tooltip>\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n\n          <Form.Item label=\"任务名称\">\n            <Input.Group compact>\n              <Form.Item\n                name={[\"name\", \"value\"]}\n                noStyle\n                rules={[\n                  { required: true, message: \"请输入巡检任务名称\" },\n                  {\n                    validator: (rule, value, callback) => {\n                      if (value) {\n                        if (value.match(/^[ ]*$/)) {\n                          return Promise.reject(\"请输入巡检任务名称\");\n                        }\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <Input\n                  placeholder=\"例如：深度分析\"\n                  style={{\n                    width: 200,\n                  }}\n                  maxLength={16}\n                />\n              </Form.Item>\n              <Form.Item name={[\"name\", \"icon\"]} noStyle>\n                <Tooltip\n                  placement=\"top\"\n                  title={\n                    \"任务名称显示在“报告列表”及“报告内容”中，系统自动补充日期信息\"\n                  }\n                >\n                  <InfoCircleOutlined\n                    name=\"icon\"\n                    style={{\n                      color: \"rgba(0,0,0,.45)\",\n                      position: \"relative\",\n                      top: 8,\n                      left: 15,\n                      fontSize: 15,\n                    }}\n                  />\n                </Tooltip>\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n\n          <Form.Item label=\"定时巡检\">\n            <Input.Group compact>\n              <Form.Item\n                name={[\"isOpen\", \"value\"]}\n                noStyle\n                valuePropName=\"checked\"\n              >\n                <Switch\n                  onChange={(e) => setIsOpen(e)}\n                  style={{ borderRadius: \"10px\" }}\n                />\n              </Form.Item>\n              <Form.Item name={[\"name\", \"icon\"]} noStyle>\n                <Tooltip\n                  placement=\"top\"\n                  title={\"开启定时巡检后，将在设定的时间，自动执行巡检任务\"}\n                >\n                  <InfoCircleOutlined\n                    name=\"icon\"\n                    style={{\n                      color: \"rgba(0,0,0,.45)\",\n                      position: \"relative\",\n                      top: 4,\n                      left: 15,\n                      fontSize: 15,\n                    }}\n                  />\n                </Tooltip>\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n          {isOpen && (\n            <Form.Item label=\"定时策略\">\n              <Input.Group compact>\n                <Form.Item name={[\"strategy\", \"frequency\"]} noStyle>\n                  <Select\n                    style={{\n                      width: 80,\n                    }}\n                    onChange={(e) => {\n                      setFrequency(e);\n                    }}\n                  >\n                    <Select.Option value=\"day\" key=\"day\">\n                      每天\n                    </Select.Option>\n                    <Select.Option value=\"week\" key=\"week\">\n                      每周\n                    </Select.Option>\n                    <Select.Option value=\"month\" key=\"month\">\n                      每月\n                    </Select.Option>\n                  </Select>\n                </Form.Item>\n\n                {frequency == \"week\" && (\n                  <Form.Item\n                    name={[\"strategy\", \"week\"]}\n                    style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n                  >\n                    <Select\n                      style={{\n                        width: 120,\n                      }}\n                    >\n                      {weekData.map((item, idx) => {\n                        return (\n                          <Select.Option value={`${idx}`} key={item}>\n                            {item}\n                          </Select.Option>\n                        );\n                      })}\n                    </Select>\n                  </Form.Item>\n                )}\n\n                {frequency == \"month\" && (\n                  <Form.Item\n                    name={[\"strategy\", \"month\"]}\n                    style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n                  >\n                    <Select\n                      style={{\n                        width: 120,\n                      }}\n                    >\n                      {new Array(28).fill(0).map((item, idx) => {\n                        return (\n                          <Select.Option\n                            key={`${idx + 1}`}\n                            value={`${idx + 1}`}\n                          >\n                            {idx + 1}日\n                          </Select.Option>\n                        );\n                      })}\n                    </Select>\n                  </Form.Item>\n                )}\n\n                <Form.Item\n                  name={[\"strategy\", \"time\"]}\n                  style={{ display: \"inline-block\", marginLeft: \"10px\" }}\n                >\n                  <TimePicker\n                    //defaultValue={moment(\"00:00\", \"HH:mm\")}\n                    format={\"HH:mm\"}\n                    allowClear={false}\n                  />\n                </Form.Item>\n              </Input.Group>\n            </Form.Item>\n          )}\n          <Form.Item className={styles.saveButtonWrapper}>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              className={styles.saveButton}\n            >\n              保存\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n\n      {/* 巡检报告推送设置 */}\n      <div className={styles.header}>\n        <MailOutlined style={{ paddingRight: 5 }} />\n        巡检报告推送设置\n      </div>\n      <Spin spinning={pushLoading}>\n        <Form\n          name=\"pushSetting\"\n          labelCol={{ span: 3 }}\n          wrapperCol={{ span: 8 }}\n          style={{ paddingTop: 40 }}\n          onFinish={changePush}\n          form={pushForm}\n          initialValues={{\n            pushIsOpen: false,\n          }}\n        >\n          <Form.Item label=\"启用\" name=\"pushIsOpen\" valuePropName=\"checked\">\n            <Switch\n              onChange={(e) => setPushIsOpen(e)}\n              style={{ borderRadius: \"10px\" }}\n            />\n          </Form.Item>\n          {pushIsOpen && (\n            <Form.Item\n              label=\"巡检报告接收人\"\n              name=\"email\"\n              rules={[\n                {\n                  type: \"email\",\n                  message: \"请输入正确格式的邮箱\",\n                },\n                {\n                  required: true,\n                  message: \"请输入接收人邮箱\",\n                },\n              ]}\n            >\n              <Input\n                placeholder=\"例如: emailname@163.com\"\n                style={{\n                  width: 360,\n                }}\n              />\n            </Form.Item>\n          )}\n          <Form.Item className={styles.saveButtonWrapper}>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              className={styles.saveButton}\n            >\n              保存\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </OmpContentWrapper>\n  );\n};\n\nexport default PatrolStrategy;\n"
  },
  {
    "path": "omp_web/src/pages/PatrolStrategy/index.module.less",
    "content": ".header {\n  padding: 10px;\n  padding-left: 20px;\n  background-color: #f7f7f7;\n}\n\n.content {\n  padding-left: 40px;\n  padding-top: 30px;\n  display: flex;\n  align-items: center;\n  .label {\n    padding-right: 30px;\n  }\n}\n\n.tips {\n  margin-top: 30px;\n  padding-left: 40px;\n  font-size: 13px;\n}\n\n.saveButtonWrapper {\n  height: 100px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.saveButton {\n  margin-left: 50%;\n  transform: translateX(-50%);\n}\n"
  },
  {
    "path": "omp_web/src/pages/RuleCenter/index.js",
    "content": "import { apiRequest } from \"@/config/requestApi\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { handleResponse } from \"@/utils/utils\";\nimport {\n  Collapse,\n  Select,\n  Spin,\n  InputNumber,\n  message,\n  Tabs,\n  Button,\n  Tooltip,\n} from \"antd\";\nimport * as R from \"ramda\";\nimport { useEffect, useState } from \"react\";\nimport styles from \"./index.module.less\";\nimport {\n  PlusCircleTwoTone,\n  InfoCircleOutlined,\n  MinusCircleTwoTone,\n  CaretRightOutlined,\n} from \"@ant-design/icons\";\n\nconst { Panel } = Collapse;\nconst { Option } = Select;\nconst { TabPane } = Tabs;\n\n// 保存设置按钮\nfunction SaveSettingsButtonGroup({\n  saveHandler = () => ({}),\n  disabled = false,\n  style = {},\n  wrapperStyle = {},\n  title = \"保存\",\n}) {\n  return (\n    <div className={styles.saveButtonWrapper} style={wrapperStyle}>\n      <Button\n        onClick={() => saveHandler()}\n        type={\"primary\"}\n        style={{ marginRight: 15, ...style }}\n        disabled={disabled}\n      >\n        {title}\n      </Button>\n    </div>\n  );\n}\n\n// InfoTip\nfunction InfoTip({ text }) {\n  return (\n    <Tooltip title={text}>\n      <InfoCircleOutlined\n        style={{\n          marginLeft: \"10px\",\n          color: \"#909090\",\n          height: \"100%\",\n          display: \"inline-flex\",\n          alignItems: \"center\",\n        }}\n      />\n    </Tooltip>\n  );\n}\n\nconst defaultData = [\n  {\n    condition: \">=\",\n    level: \"critical\",\n    value: 90,\n  },\n  {\n    condition: \">=\",\n    level: \"warning\",\n    value: 80,\n  },\n];\n\n// 单行指标项\nfunction TargetItem({ data: { name, info, conditionsArr, handler } }) {\n  return (\n    <div className={styles.targetItem}>\n      <div className={styles.itemTitle}>\n        <span>指标项</span>: {name}\n        <InfoTip text={info} />\n      </div>\n      <div className={styles.conditionItemWrapper}>\n        {conditionsArr.map((item, idx) => {\n          return (\n            <div key={idx} className={styles.conditionItem}>\n              阈值：\n              <Select\n                value={item.condition}\n                placeholder={\"请选择\"}\n                style={{ width: 110, marginRight: 10 }}\n                onChange={(item) => {\n                  const foo = R.clone(conditionsArr);\n                  foo[idx] = R.assoc(\"condition\", item, foo[idx]);\n                  handler(foo);\n                }}\n              >\n                <Option value=\">=\">{\">=\"}</Option>\n              </Select>\n              <InputNumber\n                style={{ width: 110, marginRight: 20 }}\n                placeholder={\"例如：80%\"}\n                min={0}\n                max={100}\n                value={item.value}\n                step={1}\n                formatter={(value) => `${value}%`}\n                parser={(value) => value.replace(\"%\", \"\")}\n                onChange={(val) => {\n                  const foo = R.clone(conditionsArr);\n                  foo[idx] = R.assoc(\"value\", val, foo[idx]);\n                  handler(foo);\n                }}\n              />\n              级别：\n              <Select\n                //defaultValue={idx == 0?\"critical\":\"warning\"}\n                value={item.level}\n                placeholder={\"请选择\"}\n                style={{ width: 110, marginRight: 40 }}\n                onChange={(item) => {\n                  const foo = R.clone(conditionsArr);\n                  foo[idx] = R.assoc(\"level\", item, foo[idx]);\n                  handler(foo);\n                }}\n              >\n                {conditionsArr.length === 1 ? (\n                  <>\n                    <Option value=\"critical\">严重</Option>\n                    <Option value=\"warning\">警告</Option>\n                  </>\n                ) : idx === 0 ? (\n                  <Option value=\"critical\">严重</Option>\n                ) : (\n                  <Option value=\"warning\">警告</Option>\n                )}\n              </Select>\n              {conditionsArr.length === 1 && (\n                <PlusCircleTwoTone\n                  onClick={() => {\n                    const foo = R.clone(conditionsArr);\n                    const index_type = foo[0].index_type;\n                    const old_value = parseInt(foo[0].value);\n                    if (foo[0].level === \"critical\") {\n                      foo.push({\n                        condition: \">=\",\n                        level: \"warning\",\n                        value: old_value - 10 > 0 ? old_value - 10 : 0,\n                        index_type,\n                      });\n                    } else {\n                      foo.unshift({\n                        condition: \">=\",\n                        level: \"critical\",\n                        value: old_value + 10 > 100 ? 100 : old_value + 10,\n                        index_type,\n                      });\n                    }\n                    handler(foo);\n                  }}\n                  style={{\n                    fontSize: 18,\n                    marginRight: 20,\n                  }}\n                />\n              )}\n              {idx === 1 && (\n                <MinusCircleTwoTone\n                  onClick={() => {\n                    const foo = R.clone(conditionsArr);\n                    foo.splice(1, 1);\n                    handler(foo);\n                  }}\n                  style={{ fontSize: 18 }}\n                  twoToneColor=\"#f5222d\"\n                />\n              )}\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nfunction RuleCenter() {\n  const [cpuUsed, setCpuUsed] = useState([0, 1]);\n  const [memoryUsed, setMemoryUsed] = useState([0, 1]);\n  const [diskRootUsed, setDiskRootUsed] = useState([0, 1]);\n  const [diskDataUsed, setDiskDataUsed] = useState([0, 1]);\n  const [serviceActive, setServiceActive] = useState([0, 1]);\n  const [serviceCpuUsed, setServiceCpuUsed] = useState([0, 1]);\n  const [serviceMemoryUsed, setServiceMemoryUsed] = useState([0, 1]);\n\n  const [kafkaData, setKafkaData] = useState([\n    {\n      index_type: \"kafka_consumergroup_lag\",\n      condition: \">=\",\n      value: 5000,\n      level: \"critical\",\n    },\n    {\n      index_type: \"kafka_consumergroup_lag\",\n      condition: \">=\",\n      value: 3000,\n      level: \"warning\",\n    },\n  ]);\n\n  const machineTargetsMap = [\n    {\n      name: \"cpu_used\",\n      info: `主机当前“CPU”使用率`,\n      conditionsArr: cpuUsed,\n      handler: (val) => setCpuUsed(val),\n    },\n    {\n      name: \"memory_used\",\n      info: `主机当前“内存”使用率`,\n      conditionsArr: memoryUsed,\n      handler: (val) => setMemoryUsed(val),\n    },\n    {\n      name: \"disk_root_used\",\n      info: `主机当前“根分区”使用率`,\n      conditionsArr: diskRootUsed,\n      handler: (val) => setDiskRootUsed(val),\n    },\n    {\n      name: \"disk_data_used\",\n      info: `主机当前“数据分区”使用率`,\n      conditionsArr: diskDataUsed,\n      handler: (val) => setDiskDataUsed(val),\n    },\n  ];\n\n  const serviceTargetsMap = [\n    {\n      name: \"service_active\",\n      info: `服务当前“是否存活”，验证标准是端口是否可以连通`,\n      conditionsArr: serviceActive,\n      handler: (val) => setServiceActive(val),\n    },\n    {\n      name: \"service_cup_used\",\n      info: `服务当前“CPU”使用率`,\n      conditionsArr: serviceCpuUsed,\n      handler: (val) => setServiceCpuUsed(val),\n    },\n    {\n      name: \"service_memory_used\",\n      info: `服务当前“内存”使用率`,\n      conditionsArr: serviceMemoryUsed,\n      handler: (val) => setServiceMemoryUsed(val),\n    },\n  ];\n\n  const [isMachineLoading, setMachineLoading] = useState(false);\n  const [isServiceLoading, setServiceLoading] = useState(false);\n  const [isCustomizationLoading, setCustomizationLoading] = useState(false);\n\n  function fetchHostDate() {\n    setMachineLoading(true);\n    fetchGet(apiRequest.ruleCenter.hostThreshold, {\n      params: {\n        env_id: 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const {\n            data: { cpu_used, memory_used, disk_root_used, disk_data_used },\n          } = res.dat;\n          setCpuUsed(cpu_used.length > 0 ? cpu_used : defaultData);\n          setMemoryUsed(memory_used.length > 0 ? memory_used : defaultData);\n          setDiskRootUsed(\n            disk_root_used.length > 0 ? disk_root_used : defaultData\n          );\n          setDiskDataUsed(\n            disk_data_used.length > 0 ? disk_data_used : defaultData\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setMachineLoading(false);\n      });\n  }\n\n  function fetchServiceDate() {\n    setServiceLoading(true);\n    fetchGet(apiRequest.ruleCenter.serviceThreshold, {\n      params: {\n        env_id: 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          const {\n            data: { service_active, service_cpu_used, service_memory_used },\n          } = res.data;\n          setServiceActive(\n            service_active.length > 0\n              ? service_active\n              : [\n                  {\n                    index_type: \"service_active\",\n                    condition: \"==\",\n                    value: \"False\",\n                    level: \"critical\",\n                  },\n                ]\n          );\n          setServiceCpuUsed(\n            service_cpu_used.length > 0 ? service_cpu_used : defaultData\n          );\n          setServiceMemoryUsed(\n            service_memory_used.length > 0 ? service_memory_used : defaultData\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setServiceLoading(false);\n      });\n  }\n\n  function fetchCustomDate() {\n    setCustomizationLoading(true);\n    fetchGet(apiRequest.ruleCenter.queryCustomThreshold, {\n      params: {\n        env_id: 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code === 0 && Object.keys(res.data).length !== 0) {\n            setKafkaData(\n              res.data?.kafka?.kafka_consumergroup_lag?.map((item) => {\n                // 把其中的value改成number\n                return { ...item, value: Number(item.value) };\n              })\n            );\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setServiceLoading(false);\n      });\n  }\n\n  function fetchData() {\n    setMachineLoading(true);\n    setServiceLoading(true);\n    setCustomizationLoading(true);\n    Promise.all([\n      fetchGet(apiRequest.ruleCenter.hostThreshold, {\n        params: {\n          env_id: 1,\n        },\n      }),\n      fetchGet(apiRequest.ruleCenter.serviceThreshold, {\n        params: {\n          env_id: 1,\n        },\n      }),\n      fetchGet(apiRequest.ruleCenter.queryCustomThreshold, {\n        params: {\n          env_id: 1,\n        },\n      }),\n    ])\n      .then(([hostResponse, serviceResponse, customThresholdRes]) => {\n        hostResponse = hostResponse.data;\n        serviceResponse = serviceResponse.data;\n        customThresholdRes = customThresholdRes.data;\n        if (hostResponse.code === 3) {\n          message.warn(\"登录已过期，请重新登录\");\n\n          localStorage.clear();\n          window.__history__.replace(\"/login\");\n          return;\n        }\n        const {\n          data: { cpu_used, memory_used, disk_root_used, disk_data_used },\n        } = hostResponse;\n        const {\n          data: { service_active, service_cpu_used, service_memory_used },\n        } = serviceResponse;\n        setCpuUsed(cpu_used.length > 0 ? cpu_used : defaultData);\n        setMemoryUsed(memory_used.length > 0 ? memory_used : defaultData);\n        setDiskRootUsed(\n          disk_root_used.length > 0 ? disk_root_used : defaultData\n        );\n        setDiskDataUsed(\n          disk_data_used.length > 0 ? disk_data_used : defaultData\n        );\n        setServiceActive(\n          service_active.length > 0\n            ? service_active\n            : [\n                {\n                  index_type: \"service_active\",\n                  condition: \"==\",\n                  value: \"False\",\n                  level: \"critical\",\n                },\n              ]\n        );\n        setServiceCpuUsed(\n          service_cpu_used.length > 0 ? service_cpu_used : defaultData\n        );\n        setServiceMemoryUsed(\n          service_memory_used.length > 0 ? service_memory_used : defaultData\n        );\n        if (\n          customThresholdRes.code === 0 &&\n          Object.keys(customThresholdRes.data).length !== 0\n        ) {\n          setKafkaData(\n            customThresholdRes.data?.kafka?.kafka_consumergroup_lag?.map(\n              (item) => {\n                // 把其中的value改成number\n                return { ...item, value: Number(item.value) };\n              }\n            )\n          );\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setMachineLoading(false);\n        setServiceLoading(false);\n        setCustomizationLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData();\n  }, []);\n\n  function isThresholdAccurate(data) {\n    console.log(data);\n    const invalidData = R.filter((item) => {\n      const critical = R.find(R.propEq(\"level\", \"critical\"), item) || {};\n      const warning = R.find(R.propEq(\"level\", \"warning\"), item) || {};\n      return Number(critical.value) <= Number(warning.value);\n    }, data);\n\n    //判断其中同一条指标项的程度不能重复\n    for (let item of Object.values(data)) {\n      if (item.length == 2 && item[0].level == item[1].level) {\n        message.warn(\n          `请检查${item[0].index_type}的阈值触发规则，不能设置相同级别`\n        );\n        return;\n      }\n    }\n\n    //判断级别不能相等\n    const checkDataAgain = (data) => {\n      let arr = Object.values(data).filter((item) => item.length == 2);\n      let result = arr.filter((item) => item[0].level == item[1].level);\n      return result;\n    };\n\n    if (!R.isEmpty(invalidData)) {\n      const type = R.values(invalidData)[0][0].index_type;\n      message.warn(`请检查${type}的阈值触发规则，严重应该大于警告`);\n      return;\n    } else {\n      if (checkDataAgain(data).length !== 0) {\n        const type = checkDataAgain(data)[0][0].index_type;\n        message.warn(`请检查${type}的阈值触发规则，严重应该大于警告`);\n        return;\n      }\n      return true;\n    }\n  }\n\n  const checkKafkaData = (data) => {\n    if (data.length == 1) return;\n    if (data[0].level == data[1].level)\n      return \"请检查kafka_consumergroup_lag的阈值触发规则，不能设置相同级别\";\n    let [criticalItem] = data.filter((item) => item.level === \"critical\");\n    let [warningItem] = data.filter((item) => item.level === \"warning\");\n    if (criticalItem.value <= warningItem.value)\n      return \"请检查kafka_consumergroup_lag的阈值触发规则，严重应该大于警告\";\n  };\n\n  return (\n    <div>\n      <Collapse\n        bordered={false}\n        defaultActiveKey={[\"machine\", \"service\", \"customization\"]}\n        style={{ marginTop: 10 }}\n        expandIcon={({ isActive }) => (\n          <CaretRightOutlined rotate={isActive ? 90 : 0} />\n        )}\n      >\n        <Panel header=\"主机指标\" key=\"machine\" className={styles.panelItem}>\n          <Spin key={\"machine\"} spinning={isMachineLoading}>\n            <div className={styles.targetItemWrapper}>\n              {machineTargetsMap.map((item, idx) => {\n                return (\n                  <TargetItem\n                    handler={(val) => item.handler(val)}\n                    key={`machine-${idx}`}\n                    data={item}\n                  />\n                );\n              })}\n            </div>\n            <SaveSettingsButtonGroup\n              saveHandler={() => {\n                const update_data = {\n                  cpu_used: cpuUsed,\n                  memory_used: memoryUsed,\n                  disk_root_used: diskRootUsed,\n                  disk_data_used: diskDataUsed,\n                };\n                // 如果核验数据未通过，直接退出\n                if (isThresholdAccurate(update_data)) {\n                  setMachineLoading(true);\n                  fetchPost(apiRequest.ruleCenter.hostThreshold, {\n                    body: {\n                      update_data: update_data,\n                      env_id: 1,\n                    },\n                  })\n                    .then((res) => {\n                      handleResponse(res, (res) => {\n                        if (res.code == 0) {\n                          message.success(\"更新主机指标成功\");\n                          fetchHostDate();\n                        }\n                      });\n                    })\n                    .catch((e) => console.log(e))\n                    .finally(() => {\n                      setMachineLoading(false);\n                    });\n                }\n              }}\n            />\n          </Spin>\n        </Panel>\n\n        <Panel header=\"服务指标\" key=\"service\" className={styles.panelItem}>\n          <Spin key={\"service\"} spinning={isServiceLoading}>\n            <div className={styles.targetItemWrapper}>\n              {serviceTargetsMap.map((item, idx) => {\n                if (item.name === \"service_active\") {\n                  //服务状态单独展示\n                  const { name, info, conditionsArr, handler } = item;\n                  return (\n                    <div key={\"service_active\"} className={styles.targetItem}>\n                      <div className={styles.itemTitle}>\n                        <span>指标项</span>: {name}\n                        <InfoTip text={info} />\n                      </div>\n                      <div\n                        style={{ display: \"inline-flex\", flexFlow: \"row wrap\" }}\n                      >\n                        <div key={idx} className={styles.conditionItem}>\n                          服务：\n                          <Select\n                            value={conditionsArr[0].condition}\n                            placeholder={\"请选择\"}\n                            style={{ width: 110, marginRight: 10 }}\n                            onChange={(item) => {\n                              const _clonedArr = R.clone(conditionsArr);\n                              _clonedArr[idx] = R.assoc(\n                                \"condition\",\n                                item,\n                                _clonedArr[idx]\n                              );\n                              handler(_clonedArr);\n                            }}\n                          >\n                            <Option value=\"==\">{\"==\"}</Option>\n                          </Select>\n                          <Select\n                            value={conditionsArr[0].value}\n                            placeholder={\"请选择\"}\n                            style={{ width: 110, marginRight: 20 }}\n                            onChange={(val) => {\n                              const foo = R.clone(conditionsArr);\n                              foo[idx] = R.assoc(\"value\", val, foo[idx]);\n                              handler(foo);\n                            }}\n                          >\n                            <Option value=\"False\">未存活</Option>\n                          </Select>\n                          级别：\n                          <Select\n                            value={conditionsArr[0].level}\n                            placeholder={\"请选择\"}\n                            style={{ width: 110, marginRight: 40 }}\n                            onChange={(item) => {\n                              const foo = R.clone(conditionsArr);\n                              foo[idx] = R.assoc(\"level\", item, foo[idx]);\n                              handler(foo);\n                            }}\n                          >\n                            <Option value=\"critical\">严重</Option>\n                          </Select>\n                        </div>\n                      </div>\n                    </div>\n                  );\n                } else {\n                  return (\n                    <TargetItem\n                      key={item.name}\n                      handler={(val) => item.handler(val)}\n                      data={item}\n                    />\n                  );\n                }\n              })}\n            </div>\n            <SaveSettingsButtonGroup\n              saveHandler={() => {\n                const update_data = {\n                  service_active: serviceActive,\n                  service_cpu_used: serviceCpuUsed,\n                  service_memory_used: serviceMemoryUsed,\n                };\n\n                // 不检查service_active，因为只有一项\n                if (\n                  isThresholdAccurate({\n                    service_cpu_used: serviceCpuUsed,\n                    service_memory_used: serviceMemoryUsed,\n                  })\n                ) {\n                  setServiceLoading(true);\n                  fetchPost(apiRequest.ruleCenter.serviceThreshold, {\n                    body: {\n                      update_data: update_data,\n                      env_id: 1,\n                    },\n                  })\n                    .then((res) => {\n                      handleResponse(res, (res) => {\n                        if (res.code == 0) {\n                          message.success(\"更新服务指标成功\");\n                          fetchServiceDate();\n                        }\n                      });\n                    })\n                    .catch((e) => console.log(e))\n                    .finally(() => {\n                      setServiceLoading(false);\n                    });\n                }\n              }}\n            />\n          </Spin>\n        </Panel>\n        {/* 定制化指标 */}\n\n        <Panel\n          header=\"定制化指标\"\n          key=\"customization\"\n          className={styles.panelItem}\n        >\n          <Spin key={\"customization\"} spinning={isCustomizationLoading}>\n            <div className={styles.targetItemWrapper}>\n              <Tabs tabPosition=\"left\" type=\"card\" style={{ marginTop: 10 }}>\n                <TabPane tab=\"kafka\" key=\"1\">\n                  <div\n                    className={styles.targetItem}\n                    style={{ padding: \"20px 0px 60px 0px\" }}\n                  >\n                    <div className={styles.itemTitle}>\n                      <span>指标项</span>: kafka_consumergroup_lag\n                      <InfoTip text=\"kafka消息队列未消费数据\" />\n                    </div>\n                    <div\n                      className={styles.conditionItemWrapper}\n                      style={{ paddingLeft: 50 }}\n                    >\n                      {kafkaData.map((item, idx) => {\n                        return (\n                          <div key={idx} className={styles.conditionItem}>\n                            阈值：\n                            <Select\n                              value={item.condition}\n                              placeholder={\"请选择\"}\n                              style={{ width: 110, marginRight: 10 }}\n                              onChange={(item) => {\n                                const foo = R.clone(kafkaData);\n                                foo[idx].condition = item;\n                                setKafkaData(foo);\n                              }}\n                            >\n                              <Option value=\">=\">{\">=\"}</Option>\n                            </Select>\n                            <InputNumber\n                              style={{ width: 110, marginRight: 20 }}\n                              placeholder={\"例如：80\"}\n                              min={0}\n                              //max={100}\n                              value={item.value}\n                              step={1}\n                              ///formatter={(value) => `${value}%`}\n                              // parser={(value) => value.replace(\"%\", \"\")}\n                              precision={0}\n                              onChange={(val) => {\n                                const copyData = [...kafkaData];\n                                copyData[idx].value = val;\n                                setKafkaData(copyData);\n                              }}\n                            />\n                            级别：\n                            <Select\n                              //defaultValue={idx == 0?\"critical\":\"warning\"}\n                              value={item.level}\n                              placeholder={\"请选择\"}\n                              style={{ width: 110, marginRight: 40 }}\n                              onChange={(item) => {\n                                const foo = R.clone(kafkaData);\n                                foo[idx].level = item;\n                                setKafkaData(foo);\n                              }}\n                            >\n                              {kafkaData.length === 1 ? (\n                                <>\n                                  <Option value=\"critical\">严重</Option>\n                                  <Option value=\"warning\">警告</Option>\n                                </>\n                              ) : idx === 0 ? (\n                                <Option value=\"critical\">严重</Option>\n                              ) : (\n                                <Option value=\"warning\">警告</Option>\n                              )}\n                            </Select>\n                            {kafkaData.length === 1 && (\n                              <PlusCircleTwoTone\n                                onClick={() => {\n                                  const foo = R.clone(kafkaData);\n                                  const index_type = foo[0].index_type;\n                                  if (foo[0].level == \"critical\") {\n                                    foo.push({\n                                      condition: \">=\",\n                                      level: \"warning\",\n                                      value:\n                                        foo[0].value - 1000 > 0\n                                          ? foo[0].value - 1000\n                                          : 0,\n                                      index_type,\n                                    });\n                                  } else {\n                                    foo.unshift({\n                                      condition: \">=\",\n                                      level: \"critical\",\n                                      value: foo[0].value + 1000,\n                                      index_type,\n                                    });\n                                  }\n                                  setKafkaData(foo);\n                                }}\n                                style={{\n                                  fontSize: 18,\n                                  marginRight: 20,\n                                }}\n                              />\n                            )}\n                            {idx === 1 && (\n                              <MinusCircleTwoTone\n                                onClick={() => {\n                                  const foo = R.clone(kafkaData);\n                                  foo.splice(1, 1);\n                                  setKafkaData(foo);\n                                }}\n                                style={{ fontSize: 18 }}\n                                theme={\"twoTone\"}\n                                twoToneColor=\"#f5222d\"\n                              />\n                            )}\n                          </div>\n                        );\n                      })}\n                      {/* <Button\n                        onClick={() => {\n                          let checkMessage = checkKafkaData(kafkaData);\n                          checkMessage\n                            ? message.warn(checkMessage)\n                            : console.log(\"校验通过\");\n                        }}\n                      >\n                        保存\n                      </Button> */}\n                    </div>\n                  </div>\n                  <SaveSettingsButtonGroup\n                    wrapperStyle={{\n                      position: \"relative\",\n                      left: -45,\n                    }}\n                    saveHandler={() => {\n                      let checkMessage = checkKafkaData(kafkaData);\n                      if (checkMessage) {\n                        message.warn(checkMessage);\n                      } else {\n                        setCustomizationLoading(true);\n\n                        fetchPost(apiRequest.ruleCenter.queryCustomThreshold, {\n                          body: {\n                            //update_data: update_data,\n                            env_id: 1,\n                            service_name: \"kafka\",\n                            index_type: \"kafka_consumergroup_lag\",\n                            index_type_info: [...kafkaData],\n                          },\n                        })\n                          .then((res) => {\n                            handleResponse(res, (res) => {\n                              if (res.code == 0) {\n                                message.success(\"更新定制化指标成功\");\n                                fetchCustomDate();\n                              }\n                            });\n                          })\n                          .catch((e) => console.log(e))\n                          .finally(() => {\n                            setCustomizationLoading(false);\n                          });\n                      }\n                    }}\n                  />\n                </TabPane>\n              </Tabs>\n            </div>\n          </Spin>\n        </Panel>\n      </Collapse>\n    </div>\n  );\n}\n\nexport default RuleCenter;\n"
  },
  {
    "path": "omp_web/src/pages/RuleCenter/index.module.less",
    "content": ".warningSearch {\n  display: flex;\n  margin-top: 10px;\n  margin-bottom: 10px;\n\n  & > div:nth-child(1) {\n    margin-right: 10px;\n  }\n\n  & > div:last-child {\n    margin-left: auto;\n  }\n}\n\n.tabItemWrapper {\n  //background-color: #f5f5f5;\n  .tabTitle {\n    color: #333;\n    height: 35px;\n    margin-top: 15px;\n    display: flex;\n    align-items: center;\n    background-color: #f5f5f5;\n    padding-left: 10px;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  .tabContent {\n    padding: 15px 15px 15px 60px;\n\n    & > div {\n      margin-bottom: 20px;\n    }\n\n    .tabItemTitle {\n      color: #333;\n      font-weight: 500;\n      display: inline-flex;\n      align-items: center;\n    }\n\n    .inlineTabItemTitle {\n      color: #333;\n      font-weight: 500;\n      margin-bottom: 10px;\n      display: inline-flex;\n      align-items: center;\n      justify-content: flex-end;\n      width: 100px;\n    }\n\n    .inlineTabItemSwitch {\n      display: inline-block;\n      position: relative;\n      top: -1px;\n    }\n\n    .patrolSettingWrapper {\n      display: flex;\n      align-items: center;\n\n      & > div:nth-child(1) {\n        align-self: flex-start;\n        margin-top: 5px;\n      }\n\n      .chooseInterval {\n        margin-right: 10px;\n      }\n    }\n  }\n}\n\n.tabItemNotice {\n  font-size: 14px;\n  color: #8c8c8c;\n  margin-top: 5px;\n  margin-bottom: 15px;\n}\n\n.panelItem {\n  background: \"#f5f5f5\";\n  border-radius: 4px;\n  border: 0;\n  overflow: \"hidden\";\n  border-bottom: 0 !important;\n\n  & > div:nth-child(1) {\n    padding: 8px 15px;\n  }\n\n  // 覆盖展开后的content背景色\n  & > div:nth-child(2) {\n    background-color: #fff !important;\n  }\n\n  .targetItemWrapper {\n    display: flex;\n    flex-direction: column;\n\n    padding-top: 15px;\n\n    & > div {\n      margin-bottom: 10px;\n    }\n  }\n}\n\n.targetItem {\n  padding-left: 60px;\n  margin-bottom: 10px;\n  display: flex;\n  min-height: 30px;\n}\n\n.itemTitle {\n  color: #333;\n  width: 250px;\n  min-width: 250px;\n  font-weight: 500;\n  margin-top: -8px;\n}\n\n.conditionItemWrapper {\n  display: flex;\n  flex-flow: row wrap;\n}\n\n.conditionItem {\n  display: flex;\n  align-items: center;\n  height: 32px;\n  margin-bottom: 10px;\n}\n\n.compsItemWrapper {\n  display: flex;\n  flex-flow: row wrap;\n  align-items: center;\n  margin-bottom: 20px;\n\n  & > div {\n    margin-bottom: 0 !important;\n  }\n}\n\n.saveButtonWrapper {\n  height: 100px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n"
  },
  {
    "path": "omp_web/src/pages/RuleExtend/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpModal,\n  OmpMessageModal,\n} from \"@/components\";\nimport {\n  Button,\n  Input,\n  Form,\n  message,\n  Tooltip,\n  InputNumber,\n  Modal,\n  Upload,\n  Table,\n} from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  renderDisc,\n  downloadFile,\n} from \"@/utils/utils\";\nimport { fetchGet, fetchDelete, fetchPut } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport {\n  SearchOutlined,\n  QuestionCircleOutlined,\n  ExclamationCircleOutlined,\n  ImportOutlined,\n  UploadOutlined,\n  FormOutlined,\n  CloseOutlined,\n  DownloadOutlined,\n} from \"@ant-design/icons\";\nimport { useHistory } from \"react-router-dom\";\nimport axios from \"axios\";\nimport star from \"./asterisk.svg\";\n\nconst RuleExtend = () => {\n  const [loading, setLoading] = useState(false);\n  const [modalForm] = Form.useForm();\n\n  const [upDateForm] = Form.useForm();\n\n  const history = useHistory();\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  const [row, setRow] = useState({});\n\n  // 删除操作\n  const [deleteVisible, setDeleteVisible] = useState(false);\n\n  // 行内删除操作\n  const [deleteRowVisible, setDeleteRowVisible] = useState(false);\n\n  // 添加操作控制器\n  const [addVisible, setAddVisible] = useState(false);\n\n  // 修改操作控制器\n  const [upDateVisible, setUpDateVisible] = useState(false);\n\n  // 查询操作控制器\n  const [statusVisible, setStatusVisible] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [selectValue, setSelectValue] = useState();\n\n  // detail数据\n  const [detailList, setDetailList] = useState([]);\n\n  // 选中的绑定主机\n  const [executionData, setExecutionData] = useState([]);\n\n  // 绑定主机控制器\n  const [hostListVisible, setHostListVisible] = useState(false);\n\n  // 是否展示执行绑定主机校验信息\n  const [isShowErrMsg, setIsShowErrMsg] = useState(false);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 主机翻页数据\n  const [hostPagination, setHostPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n  });\n\n  // 主机列表\n  const [hostList, setHostList] = useState([]);\n\n  const renderStatus = (text) => {\n    switch (text) {\n      case 0:\n        return <span>{renderDisc(\"normal\", 7, -1)}正常</span>;\n      case 1:\n        return <span>{renderDisc(\"warning\", 7, -1)}重启中</span>;\n      case 2:\n        return <span>{renderDisc(\"critical\", 7, -1)}启动失败</span>;\n      case 3:\n        return <span>{renderDisc(\"warning\", 7, -1)}部署中</span>;\n      case 4:\n        return <span>{renderDisc(\"critical\", 7, -1)}部署失败</span>;\n      default:\n        return \"-\";\n    }\n  };\n\n  const columns = [\n    {\n      title: \"描述\",\n      // width: 60,\n      key: \"description\",\n      dataIndex: \"description\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 220,\n      fixed: \"left\",\n      ellipsis: true,\n    },\n    {\n      title: (\n        <span>\n          绑定主机\n          <span\n            name=\"tishi\"\n            style={{\n              position: \"relative\",\n              top: 0,\n              left: 2,\n            }}\n          >\n            {\" \"}\n            <Tooltip title=\"脚本绑定主机数量，绑定主机后会在该主机上采集指标\">\n              <QuestionCircleOutlined />\n            </Tooltip>\n          </span>\n        </span>\n      ),\n      key: \"bound_hosts_num\",\n      width: 120,\n      dataIndex: \"bound_hosts_num\",\n      align: \"center\",\n    },\n    {\n      title: \"探测周期(s)\",\n      key: \"scrape_interval\",\n      dataIndex: \"scrape_interval\",\n      width: 120,\n      align: \"center\",\n    },\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n            onClick={() => {\n              console.log(record);\n              setRow(record);\n            }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              <a\n                onClick={() => {\n                  upDateForm.setFieldsValue({\n                    description: record.description,\n                    scrape_interval: record.scrape_interval,\n                    bound_hosts: record.bound_hosts,\n                  });\n                  setExecutionData(\n                    record.bound_hosts.map((item, idx) => ({\n                      id: idx,\n                      ip: item,\n                    }))\n                  );\n                  setUpDateVisible(true);\n                }}\n              >\n                修改\n              </a>\n              <a\n                style={{\n                  marginLeft: 10,\n                }}\n                onClick={() => {\n                  queryExpansionIndex(record.id);\n                  setStatusVisible(true);\n                }}\n              >\n                查询\n              </a>\n              <a\n                style={{\n                  marginLeft: 10,\n                }}\n                onClick={() => {\n                  setDeleteRowVisible(true);\n                }}\n              >\n                删除\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.ruleCenter.queryExtendRuleList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  /* 限制数字输入框只能输入整数 */\n  const limitNumber = (value) => {\n    if (typeof value === \"string\") {\n      return !isNaN(Number(value)) ? value.replace(/^(0+)|[^\\d]/g, \"\") : \"\";\n    } else if (typeof value === \"number\") {\n      return !isNaN(value) ? String(value).replace(/^(0+)|[^\\d]/g, \"\") : \"\";\n    } else {\n      return \"\";\n    }\n  };\n\n  // 添加规则\n  const addExtend = (data) => {\n    setLoading(true);\n    let formData = new FormData();\n    formData.append(\"file\", data.collectionScript.file.originFileObj);\n    formData.append(\"scrape_interval\", data.scrape_interval);\n\n    const config = {\n      headers: {\n        \"Content-Type\": \"multipart/form-data;boundary=\" + new Date().getTime(),\n      },\n    };\n\n    axios\n      .post(apiRequest.ruleCenter.queryExtendRuleList, formData, config)\n      .then(function (response) {\n        console.log(response);\n        if (response && response.data && response.data.code == 1) {\n          message.warning(response.data.message);\n        } else {\n          message.success(`添加操作下发成功`);\n        }\n      })\n      .catch(function (error) {\n        console.log(error);\n      })\n      .finally(() => {\n        setLoading(false);\n        setAddVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  // 删除规则\n  function deleteRule(id) {\n    setLoading(true);\n    fetchDelete(`${apiRequest.ruleCenter.queryExtendRuleList}${id}/`)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(`删除操作下发成功`);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setDeleteRowVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  }\n\n  // 查询扩展指标\n  const queryExpansionIndex = (id) => {\n    setLoading(true);\n    fetchGet(apiRequest.ruleCenter.queryDetail, {\n      params: {\n        id,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDetailList(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 请求主机列表hostPagination, setHostPagination]\n  const queryHostList = (pageParams = { current: 1, pageSize: 10 }) => {\n    setLoading(true);\n    fetchGet(apiRequest.machineManagement.hosts, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          // 设置表格的默认选中\n          // setCheckedList(executionData);\n          setCheckedList(\n            res.data.results.filter((item) => {\n              let result = executionData.filter((i) => i.ip == item.ip);\n              return result.length !== 0;\n            })\n          );\n\n          setHostList(res.data.results);\n          setHostPagination({\n            ...hostPagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 修改接口\n  const upDateRule = (data, id) => {\n    setLoading(true);\n    fetchPut(`${apiRequest.ruleCenter.queryExtendRuleList}${id}/`, {\n      body: data,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(`修改操作下发成功`);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setUpDateVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          onClick={() => {\n            setAddVisible(true);\n          }}\n        >\n          添加\n        </Button>\n        {/* <Button\n          disabled={checkedList.map((item) => item.id).length == 0}\n          onClick={() => {\n            setDeleteVisible(true);\n          }}\n          style={{ marginLeft: 10 }}\n        >\n          删除\n        </Button> */}\n        {/* <Dropdown\n          //placement=\"bottomLeft\"\n          overlay={\n            <Menu>\n              <Menu.Item\n                key=\"openMaintain\"\n                style={{ textAlign: \"center\" }}\n                onClick={() => {}}\n                disabled={checkedList.map((item) => item.id).length == 0}\n              >\n                禁用\n              </Menu.Item>\n              <Menu.Item\n                key=\"closeMaintain\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {}}\n              >\n                启用\n              </Menu.Item>\n            </Menu>\n          }\n          placement=\"bottomCenter\"\n        >\n          <Button style={{ marginLeft: 10, paddingRight: 10, paddingLeft: 15 }}>\n            更多\n            <DownOutlined />\n          </Button>\n        </Dropdown> */}\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 50, display: \"flex\", alignItems: \"center\" }}>\n            描述:\n          </span>\n          <Input\n            placeholder=\"请输入描述\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    description: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  description: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  description: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { search: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          //   rowKey={(record) => record.id}\n          //   checkedState={[checkedList, setCheckedList]}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  justifyContent: \"space-between\",\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n\n      <OmpMessageModal\n        visibleHandle={[deleteRowVisible, setDeleteRowVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          deleteRule(row.id);\n          // statusUpdate([row.id], 1)\n          // deleteQuota(row);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{row.description}</span>{\" \"}\n          指标 <span style={{ fontWeight: 500 }}>下发删除命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      {/* <OmpMessageModal\n        visibleHandle={[deleteVisible, setDeleteVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          // statusUpdate([row.id], 1)\n          // deleteQuota(row);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{checkedList.length}</span>{\" \"}\n          条指标 <span style={{ fontWeight: 500 }}>下发删除命令</span> ？\n        </div>\n      </OmpMessageModal> */}\n      {/* 添加规则 */}\n      <OmpModal\n        loading={loading}\n        width={600}\n        formLabelCol={{ span: 8 }}\n        formWrapperCol={{ span: 18 }}\n        setLoading={setLoading}\n        visibleHandle={[addVisible, setAddVisible]}\n        okBtnText={\"确定\"}\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <ImportOutlined />\n            </span>\n            <span>添加扩展指标</span>\n          </span>\n        }\n        form={modalForm}\n        onFinish={(data) => {\n          console.log(data);\n          addExtend(data);\n        }}\n        initialValues={{\n          scrape_interval: 60,\n        }}\n      >\n        <div\n          style={{\n            transition: \"all .2s ease-in\",\n            position: \"relative\",\n            left: -10,\n          }}\n        >\n          <Form.Item label=\"下载模版\" name=\"down\" key=\"down\">\n            <Button\n              onClick={() => {\n                downloadFile(\"/custom_scripts/template.py\");\n              }}\n              icon={<DownloadOutlined />}\n            >\n              点击下载\n            </Button>\n          </Form.Item>\n          <Form.Item\n            label=\"上传采集脚本\"\n            name=\"collectionScript\"\n            key=\"collectionScript\"\n            rules={[\n              {\n                required: true,\n                message: \"请上传采集脚本\",\n              },\n            ]}\n          >\n            <Upload\n              name=\"file\"\n              maxCount={1}\n              accept=\".py\"\n              beforeUpload={(file, fileList) => {\n                const fileSize = file.size / 1024 / 1024; //单位是mb\n                if (Math.ceil(fileSize) > 2) {\n                  message.error(\"仅支持传入2MB以内文件\");\n                  return Upload.LIST_IGNORE;\n                }\n                // return Upload.LIST_IGNORE;\n              }}\n              customRequest={(e) => {\n                // console.log(e)\n                // 直接调用成功，在整个表单提交的时候去携带文件流\n                e.onSuccess();\n              }}\n            >\n              <Button icon={<UploadOutlined />}>点击上传</Button>\n            </Upload>\n          </Form.Item>\n\n          <Form.Item\n            label=\"探测周期(s)\"\n            name=\"scrape_interval\"\n            key=\"scrape_interval\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入探测周期\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"scrape_interval\">\n              <InputNumber\n                min={1}\n                max={120}\n                formatter={limitNumber}\n                parser={limitNumber}\n              />\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 0,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"指定Prometheus对指标采集间隔时间\">\n                <QuestionCircleOutlined />\n              </Tooltip>\n            </span>\n          </Form.Item>\n        </div>\n      </OmpModal>\n\n      {/* 查询 */}\n      <OmpMessageModal\n        width={900}\n        visibleHandle={[statusVisible, setStatusVisible]}\n        noFooter\n        style={{ position: \"relative\", top: 180 }}\n        title={<span>查看扩展指标状态</span>}\n        loading={loading}\n      >\n        <div style={{ border: \"1px solid #d6d6d6\" }}>\n          <Table\n            scroll={{ x: 800 }}\n            columns={[\n              {\n                title: \"采集端\",\n                key: \"scrape_url\",\n                dataIndex: \"scrape_url\",\n                align: \"center\",\n                width: 280,\n                ellipsis: true,\n                render: (text) => {\n                  if (!text) {\n                    return \"-\";\n                  }\n                  return <Tooltip title={text}>{text}</Tooltip>;\n                },\n              },\n              {\n                title: \"状态\",\n                key: \"status\",\n                dataIndex: \"status\",\n                align: \"center\",\n                width: 120,\n                // fixed: \"right\",\n                render: (text) => {\n                  if (text === \"down\") {\n                    return \"停用\";\n                  }\n                  return \"正常\";\n                },\n              },\n              {\n                title: \"采集用时\",\n                key: \"last_scrape_duration\",\n                dataIndex: \"last_scrape_duration\",\n                align: \"center\",\n                width: 120,\n                render: (text) => {\n                  if (!text) {\n                    return \"-\";\n                  }\n\n                  return `${(text * 1000).toFixed(2)}ms`;\n                },\n                // fixed: \"right\",\n              },\n              {\n                title: \"错误信息\",\n                key: \"last_error\",\n                dataIndex: \"last_error\",\n                align: \"center\",\n                width: 220,\n                ellipsis: true,\n                render: (text) => {\n                  if (!text) {\n                    return \"-\";\n                  }\n                  return <Tooltip title={text}>{text}</Tooltip>;\n                },\n                fixed: \"right\",\n              },\n            ]}\n            dataSource={detailList}\n          />\n        </div>\n      </OmpMessageModal>\n\n      {/* 修改规则 */}\n      <OmpModal\n        loading={loading}\n        width={900}\n        formLabelCol={{ span: 4 }}\n        formWrapperCol={{ span: 16 }}\n        setLoading={setLoading}\n        visibleHandle={[upDateVisible, setUpDateVisible]}\n        okBtnText={\"确定\"}\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <FormOutlined />\n            </span>\n            <span>修改扩展指标</span>\n          </span>\n        }\n        form={upDateForm}\n        beForeOk={() => {\n          if (executionData.length == 0) {\n            // performTasks();\n            setIsShowErrMsg(true);\n          }\n        }}\n        afterClose={() => {\n          setExecutionData([]);\n        }}\n        onFinish={(data) => {\n          console.log(data);\n          if (executionData.length !== 0) {\n            // performTasks();\n            console.log(\"通过\");\n            upDateRule(\n              {\n                description: data.description,\n                scrape_interval: data.scrape_interval,\n                bound_hosts: executionData.map((i) => i.ip),\n              },\n              row.id\n            );\n          }\n        }}\n        initialValues={{\n          scrape_interval: 60,\n        }}\n      >\n        <div\n          style={{\n            transition: \"all .2s ease-in\",\n            position: \"relative\",\n            left: -10,\n          }}\n        >\n          <Form.Item\n            label=\"描述\"\n            name=\"description\"\n            key=\"description\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入描述\",\n              },\n            ]}\n          >\n            <Input style={{ width: 520 }} placeholder={\"请输入描述\"} />\n          </Form.Item>\n\n          <Form.Item\n            label=\"探测周期(s)\"\n            name=\"scrape_interval\"\n            key=\"scrape_interval\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择规则类型\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"scrape_interval\">\n              <InputNumber\n                min={1}\n                max={120}\n                formatter={limitNumber}\n                parser={limitNumber}\n              />\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 0,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"指定Prometheus对指标采集间隔时间\">\n                <QuestionCircleOutlined />\n              </Tooltip>\n            </span>\n          </Form.Item>\n\n          <Form.Item\n            name=\"target_objs\"\n            extra={\n              <div\n                style={{\n                  position: \"relative\",\n                  overflow: \"hidden\",\n                  height: 20,\n                }}\n              >\n                <span\n                  style={{\n                    transition: \"all .2s ease-in\",\n                    color: \"#ff4d4f\",\n                    position: \"absolute\",\n                    top: isShowErrMsg ? 0 : -20,\n                  }}\n                >\n                  请选择绑定主机\n                </span>\n              </div>\n            }\n            label={\n              <span>\n                <img\n                  src={star}\n                  style={{ position: \"relative\", top: -3, left: -4 }}\n                />\n                绑定主机\n              </span>\n            }\n          >\n            <div\n              style={{\n                backgroundColor: \"#f6f6f6\",\n                minHeight: 80,\n                width: 700,\n                position: \"relative\",\n                padding: \"10px\",\n                paddingBottom: \"40px\",\n                display: \"flex\",\n                flexWrap: \"wrap\",\n                border: isShowErrMsg ? \"1px solid #ff4d4f\" : \"none\",\n              }}\n            >\n              {executionData.map((i) => (\n                <ExecutionTargetItem\n                  info={i}\n                  key={i.id}\n                  executionData={[executionData, setExecutionData]}\n                  setIsShowErrMsg={setIsShowErrMsg}\n                />\n              ))}\n              <div\n                style={{\n                  position: \"absolute\",\n                  display: \"flex\",\n                  bottom: 10,\n                  left: \"50%\",\n                  transform: \"translateX(-50%)\",\n                }}\n              >\n                <Button\n                  style={{ padding: \"3px 20px\", height: 30 }}\n                  onClick={() => {\n                    setExecutionData([]);\n                    setIsShowErrMsg(true);\n                  }}\n                >\n                  清除\n                </Button>\n                <Button\n                  style={{ padding: \"3px 20px\", height: 30, marginLeft: 15 }}\n                  onClick={() => {\n                    setIsShowErrMsg(false);\n                    queryHostList();\n                    setHostListVisible(true);\n                  }}\n                >\n                  添加\n                </Button>\n              </div>\n            </div>\n          </Form.Item>\n        </div>\n      </OmpModal>\n\n      <Modal\n        title=\"选择绑定主机\"\n        width={800}\n        afterClose={() => {\n          setCheckedList([]);\n        }}\n        onCancel={() => {\n          setHostListVisible(false);\n        }}\n        visible={hostListVisible}\n        footer={null}\n        //width={1000}\n        loading={loading}\n        bodyStyle={{\n          paddingLeft: 30,\n          paddingRight: 30,\n        }}\n        destroyOnClose\n      >\n        <div>\n          <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n            <OmpTable\n              size=\"small\"\n              // scroll={{ x: executionColumns.length * 150 }}\n              loading={loading}\n              columns={[\n                {\n                  title: \"实例名称\",\n                  key: \"instance_name\",\n                  dataIndex: \"instance_name\",\n                  align: \"center\",\n                  ellipsis: true,\n                  width: 150,\n                  fixed: \"left\",\n                  render: (text, record) => {\n                    return (\n                      <Tooltip title={text}>\n                        <div style={{ paddingTop: 2 }}>{text}</div>\n                      </Tooltip>\n                    );\n                  },\n                },\n                {\n                  title: \"IP地址\",\n                  key: \"ip\",\n                  dataIndex: \"ip\",\n                  align: \"center\",\n                  ellipsis: true,\n                  width: 120,\n                  render: (text, record) => {\n                    let v = text || \"-\";\n                    return (\n                      <Tooltip title={v}>\n                        <div style={{ paddingTop: 2 }}>{v}</div>\n                      </Tooltip>\n                    );\n                  },\n                },\n                {\n                  title: \"主机Agent\",\n                  key: \"host_agent\",\n                  dataIndex: \"host_agent\",\n                  align: \"center\",\n                  width: 120,\n                  //ellipsis: true,\n                  render: (text) => {\n                    return renderStatus(text);\n                  },\n                },\n              ]}\n              onChange={(e, filters, sorter) => {\n                setTimeout(() => {\n                  queryHostList(e);\n                }, 200);\n              }}\n              dataSource={hostList}\n              pagination={{\n                showSizeChanger: true,\n                pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n                showTotal: () => (\n                  <div\n                    style={{\n                      display: \"flex\",\n                      width: \"200px\",\n                      justifyContent: \"space-between\",\n                      lineHeight: 2.8,\n                      position: \"relative\",\n                      top: -3,\n                    }}\n                  >\n                    <p>已选中 {checkedList.length} 条</p>\n                    <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                      共计{\" \"}\n                      <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                        {hostPagination.total}\n                      </span>{\" \"}\n                      条\n                    </p>\n                  </div>\n                ),\n                ...hostPagination,\n              }}\n              rowKey={(record) => record.id}\n              checkedState={[checkedList, setCheckedList]}\n            />\n          </div>\n          <div\n            style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n          >\n            <div\n              style={{\n                width: 170,\n                display: \"flex\",\n                justifyContent: \"space-between\",\n              }}\n            >\n              <Button onClick={() => setHostListVisible(false)}>取消</Button>\n              <Button\n                type=\"primary\"\n                style={{ marginLeft: 16 }}\n                loading={loading}\n                // disabled={checkedList.length == 0}\n                onClick={() => {\n                  setExecutionData(checkedList);\n                  setHostListVisible(false);\n                }}\n              >\n                确认\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Modal>\n    </OmpContentWrapper>\n  );\n};\n\nconst ExecutionTargetItem = ({ info, executionData, setIsShowErrMsg }) => {\n  const [data, setData] = executionData;\n  return (\n    <div\n      style={{\n        backgroundColor: \"#fff\",\n        border: \"solid 1px #d9d9d9\",\n        height: 28,\n        paddingLeft: 25,\n        borderRadius: 4,\n        display: \"flex\",\n        alignItems: \"center\",\n        marginLeft: 10,\n        marginBottom: 10,\n        overflow: \"hidden\",\n        textOverflow: \" ellipsis\",\n        whiteSpace: \"nowrap\",\n        position: \"relative\",\n      }}\n    >\n      {info.ip}\n      <CloseOutlined\n        style={{ paddingLeft: 15, paddingRight: 10, cursor: \"pointer\" }}\n        onClick={() => {\n          let result = data.filter((i) => !i.id == info.id);\n          if (result.length == 0) {\n            setIsShowErrMsg(true);\n          }\n          setData(() => {\n            return result;\n          });\n        }}\n      />\n    </div>\n  );\n};\n\nexport default RuleExtend;\n"
  },
  {
    "path": "omp_web/src/pages/RuleIndicator/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpModal,\n  OmpMessageModal,\n} from \"@/components\";\nimport {\n  Button,\n  Input,\n  Form,\n  message,\n  Menu,\n  Dropdown,\n  Select,\n  Radio,\n  Cascader,\n  Tooltip,\n  InputNumber,\n  Switch,\n  Table,\n} from \"antd\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost, fetchDelete } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport {\n  SearchOutlined,\n  DownOutlined,\n  PlusSquareOutlined,\n  QuestionCircleOutlined,\n  ExclamationCircleOutlined,\n} from \"@ant-design/icons\";\nimport { useHistory } from \"react-router-dom\";\n\nconst RuleIndicator = () => {\n  const [loading, setLoading] = useState(false);\n  const [modalForm] = Form.useForm();\n\n  const [upDateForm] = Form.useForm();\n  const history = useHistory();\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  const [row, setRow] = useState({});\n\n  // 测试展示数据\n  const [testQueryResults, setTestQueryResults] = useState([]);\n  // 测试弹框控制器\n  const [testVisible, setTestVisible] = useState(false);\n\n  // 批量停用弹框控制器\n  const [stopVisible, setStopVisible] = useState(false);\n  // 单独停用弹框控制器\n  const [stopRowVisible, setStopRowVisible] = useState(false);\n\n  // 批量启用弹框控制器\n  const [startVisible, setStartVisible] = useState(false);\n  // 单独启用弹框控制器\n  const [startRowVisible, setStartRowVisible] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [selectValue, setSelectValue] = useState();\n\n  // 添加规则控制器\n  const [addMoadlVisible, setAddMoadlVisible] = useState(false);\n\n  // 修改规则控制器\n  const [upDateVisible, setUpDateVisible] = useState(false);\n\n  // 删除规则控制器\n  const [deleteMoadlVisible, setDeleteMoadlVisible] = useState(false);\n\n  // 规则类型\n  const [ruleType, setRuleType] = useState(\"0\");\n\n  // 持续时长单位\n  const [forTimeCompany, setForTimeCompany] = useState(\"s\");\n\n  // 选择内置规则联级数据\n  const [cascaderOption, setCascaderOption] = useState([]);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"规则名称\",\n      // width: 60,\n      key: \"alert\",\n      dataIndex: \"alert\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 250,\n      fixed: \"left\",\n      ellipsis: true,\n    },\n    {\n      title: \"对比规则\",\n      key: \"compare_str\",\n      width: 120,\n      dataIndex: \"compare_str\",\n      align: \"center\",\n    },\n    {\n      title: \"阈值\",\n      key: \"threshold_value\",\n      dataIndex: \"threshold_value\",\n      width: 120,\n      align: \"center\",\n    },\n    {\n      title: \"持续时长(s)\",\n      key: \"for_time\",\n      dataIndex: \"for_time\",\n      width: 120,\n      align: \"center\",\n    },\n    {\n      title: \"级别\",\n      key: \"severity\",\n      dataIndex: \"severity\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        const map = {\n          warning: \"警告\",\n          critical: \"严重\",\n        };\n        return map[text];\n      },\n    },\n    {\n      title: \"状态\",\n      key: \"status\",\n      dataIndex: \"status\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        const map = [\"已停用\", \"已启用\"];\n        return map[text];\n      },\n    },\n    {\n      title: \"指标类型\",\n      key: \"quota_type\",\n      dataIndex: \"quota_type\",\n      align: \"center\",\n      width: 120,\n      render: (text) => {\n        const map = [\"内置指标\", \"自定义promsql\"];\n        return map[text];\n      },\n    },\n    {\n      title: \"操作\",\n      width: 150,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n            onClick={() => {\n              console.log(record);\n              setRow(record);\n            }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              <a\n                onClick={() => {\n                  // // 持续时长单位\n                  // setForTimeCompany(\"s\");\n                  queryBuiltinsQuota();\n                  setUpDateVisible(true);\n\n                  setForTimeCompany(\n                    record.for_time[record.for_time.length - 1]\n                  );\n\n                  if (record.quota_type == 0) {\n                    setRuleType(\"0\");\n                    upDateForm.setFieldsValue({\n                      quota_type: \"0\",\n                      alert: record.alert,\n                      builtins_quota: [record.service, record.name],\n                      compare_str: record.compare_str,\n                      threshold_value: record.threshold_value,\n                      for_time: record.for_time.substring(\n                        0,\n                        record.for_time.length - 1\n                      ),\n                      severity: record.severity,\n                      status: record.status,\n                    });\n                  } else if (record.quota_type == 1) {\n                    setRuleType(\"1\");\n                    upDateForm.setFieldsValue({\n                      quota_type: \"1\",\n                      alert: record.alert,\n                      expr: record.expr,\n                      compare_str: record.compare_str,\n                      threshold_value: record.threshold_value,\n                      for_time: record.for_time.substring(\n                        0,\n                        record.for_time.length - 1\n                      ),\n                      service: record.service,\n                      severity: record.severity,\n                      status: record.status,\n                      summary: record.summary,\n                      description: record.description,\n                    });\n                  }\n                }}\n              >\n                修改\n              </a>\n              <a\n                style={{\n                  marginLeft: 10,\n                }}\n                onClick={() => {\n                  if (record.status == 1) {\n                    setStopRowVisible(true);\n                  } else {\n                    setStartRowVisible(true);\n                  }\n                }}\n              >\n                {record.status == 1 ? \"停用\" : \"启用\"}\n              </a>\n              <a\n                style={{\n                  marginLeft: 10,\n                  color:\n                    record.forbidden && record.forbidden == 1\n                      ? null\n                      : \"rgba(0, 0, 0, 0.25)\",\n                  cursor:\n                    record.forbidden && record.forbidden == 1\n                      ? \"pointer\"\n                      : \"not-allowed\",\n                }}\n                onClick={() => {\n                  if (record.forbidden && record.forbidden == 1) {\n                    setDeleteMoadlVisible(true);\n                  }\n                }}\n              >\n                删除\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.ruleCenter.queryPromemonitor, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  const dictionaries = useRef(null);\n\n  // 请求内置规则的选择指标联级配置\n  const queryBuiltinsQuota = () => {\n    fetchGet(apiRequest.ruleCenter.queryBuiltinsQuota)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.data) {\n            dictionaries.current = res.data;\n            let data = res.data;\n            setCascaderOption(() => {\n              return Object.keys(data).map((key) => {\n                let children = data[key].map((item) => {\n                  console.log(item);\n                  return {\n                    label: item.name,\n                    value: item.name,\n                    disabled: item.name == \"数据分区使用率\" ? true : false,\n                    // JSON.stringify({\n                    //   description: item.description,\n                    //   name: item.name,\n                    //   expr: item.expr,\n                    //   service: item.service,\n                    // }),\n                  };\n                });\n                return {\n                  value: key,\n                  label: key,\n                  children,\n                };\n              });\n            });\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  const addQuota = (data) => {\n    let queryData = {};\n    if (data.quota_type === \"0\") {\n      let builtins_quota = data.builtins_quota;\n      let result = dictionaries.current[builtins_quota[0]].filter(\n        (f) => f.name == builtins_quota[1]\n      )[0];\n\n      queryData = {\n        threshold_value: data.threshold_value,\n        compare_str: data.compare_str,\n        for_time: `${data.for_time}${forTimeCompany}`,\n        severity: data.severity,\n        alert: data.alert,\n        status: data.status ? 1 : 0,\n        quota_type: Number(data.quota_type),\n        builtins_quota: {\n          description: result.description,\n          name: result.name,\n          expr: result.expr,\n          service: result.service,\n        },\n      };\n    } else if (data.quota_type === \"1\") {\n      queryData = {\n        summary: data.summary,\n        description: data.description,\n        expr: data.expr,\n        service: data.service,\n        threshold_value: data.threshold_value,\n        compare_str: data.compare_str,\n        for_time: `${data.for_time}${forTimeCompany}`,\n        severity: data.severity,\n        alert: data.alert,\n        status: data.status ? 1 : 0,\n        quota_type: Number(data.quota_type),\n      };\n    }\n\n    setLoading(true);\n    fetchPost(apiRequest.ruleCenter.addQuota, {\n      body: {\n        env_id: 1,\n        ...queryData,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          message.success(`添加操作下发成功`);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setAddMoadlVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  // 测试promsql规则\n  function fetchTestData(str) {\n    setLoading(true);\n    fetchPost(apiRequest.ruleCenter.testPromSql, {\n      body: {\n        expr: str,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setTestQueryResults(\n            res.data.map((e) => {\n              let name = e.metric.__name__;\n              delete e.metric.__name__;\n              let str = JSON.stringify(e.metric).replace(/:/, \"=\");\n              return {\n                metric: `${name}${str}`,\n                value: e.value[1],\n              };\n            })\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  // 修改\n  const uploadQuota = (data) => {\n    let queryData = {};\n    if (data.quota_type === \"0\") {\n      let builtins_quota = data.builtins_quota;\n      let result = dictionaries.current[builtins_quota[0]].filter(\n        (f) => f.name == builtins_quota[1]\n      )[0];\n\n      if (row.name == \"数据分区使用率\") {\n        result.expr = row.expr;\n      }\n\n      queryData = {\n        threshold_value: data.threshold_value,\n        compare_str: data.compare_str,\n        for_time: `${data.for_time}${forTimeCompany}`,\n        severity: data.severity,\n        alert: data.alert,\n        // status: data.status ? 1 : 0,\n        status: row.status,\n        quota_type: Number(data.quota_type),\n        builtins_quota: {\n          description: result?.description,\n          name: result?.name,\n          expr: result?.expr,\n          service: result?.service,\n        },\n      };\n    } else if (data.quota_type === \"1\") {\n      queryData = {\n        summary: data.summary,\n        description: data.description,\n        expr: data.expr,\n        service: data.service,\n        threshold_value: data.threshold_value,\n        compare_str: data.compare_str,\n        for_time: `${data.for_time}${forTimeCompany}`,\n        severity: data.severity,\n        alert: data.alert,\n        status: data.status ? 1 : 0,\n        quota_type: Number(data.quota_type),\n      };\n    }\n\n    setLoading(true);\n    fetchPost(apiRequest.ruleCenter.addQuota, {\n      body: {\n        env_id: 1,\n        ...queryData,\n        id: row.id,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          message.success(`修改操作下发成功`);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setUpDateVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  // 删除接口\n  const deleteQuota = (data) => {\n    setLoading(true);\n    fetchDelete(apiRequest.ruleCenter.deleteQuota, {\n      params: {\n        id: data.id,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          message.success(`删除操作下发成功`);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setDeleteMoadlVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  // 停用或者启用操作\n  const statusUpdate = (ids, status) => {\n    setLoading(true);\n    fetchPost(apiRequest.ruleCenter.batchUpdateRule, {\n      body: {\n        ids,\n        status,\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (status == 1) {\n            message.success(`启用操作下发成功`);\n          } else {\n            message.success(`停用操作下发成功`);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        // 批量停用弹框控制器\n        setStopVisible(false);\n        // 单独停用弹框控制器\n        setStopRowVisible(false);\n\n        // 批量启用弹框控制器\n        setStartVisible(false);\n        // 单独启用弹框控制器\n        setStartRowVisible(false);\n        fetchData(\n          {\n            current: 1,\n            pageSize: pagination.pageSize,\n          },\n          {\n            ...pagination.searchParams,\n          }\n        );\n      });\n  };\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          onClick={() => {\n            setRuleType(\"0\");\n            // 持续时长单位\n            setForTimeCompany(\"s\");\n            queryBuiltinsQuota();\n            setAddMoadlVisible(true);\n          }}\n        >\n          添加\n        </Button>\n        <Dropdown\n          //placement=\"bottomLeft\"\n          overlay={\n            <Menu>\n              <Menu.Item\n                key=\"openMaintain\"\n                style={{ textAlign: \"center\" }}\n                onClick={() => setStartVisible(true)}\n                disabled={checkedList.map((item) => item.id).length == 0}\n              >\n                启用规则\n              </Menu.Item>\n              <Menu.Item\n                key=\"closeMaintain\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  setStopVisible(true);\n                }}\n              >\n                停用规则\n              </Menu.Item>\n            </Menu>\n          }\n          placement=\"bottomCenter\"\n        >\n          <Button style={{ marginLeft: 10, paddingRight: 10, paddingLeft: 15 }}>\n            更多\n            <DownOutlined />\n          </Button>\n        </Dropdown>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 80, display: \"flex\", alignItems: \"center\" }}>\n            规则名称:\n          </span>\n          <Input\n            placeholder=\"请输入规则名称\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    alert: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  alert: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  alert: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { search: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  justifyContent: \"space-between\",\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n      <OmpModal\n        loading={loading}\n        formLabelCol={{ span: 5 }}\n        formWrapperCol={{ span: 18 }}\n        width={800}\n        setLoading={setLoading}\n        visibleHandle={[addMoadlVisible, setAddMoadlVisible]}\n        okBtnText={\"确定\"}\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <PlusSquareOutlined />\n            </span>\n            <span>添加指标规则</span>\n          </span>\n        }\n        form={modalForm}\n        onFinish={(data) => {\n          addQuota(data);\n        }}\n        initialValues={{\n          compare_str: \">=\",\n          threshold_value: 30,\n          quota_type: \"0\",\n          for_time: 60,\n          severity: \"warning\",\n          status: true,\n        }}\n      >\n        <div\n          style={{\n            transition: \"all .2s ease-in\",\n            position: \"relative\",\n            left: -10,\n          }}\n        >\n          <Form.Item\n            label=\"规则名称\"\n            name=\"alert\"\n            key=\"alert\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入规则名称\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"alert\">\n              <Input style={{ width: 520 }} placeholder={\"请输入规则名称\"} />\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 1,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"指标规则的名称，方便识别\">\n                <QuestionCircleOutlined />\n              </Tooltip>\n            </span>\n          </Form.Item>\n\n          <Form.Item\n            label=\"规则类型\"\n            name=\"quota_type\"\n            key=\"quota_type\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择规则类型\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"quota_type\">\n              <Radio.Group\n                value={ruleType}\n                onChange={(e) => {\n                  setRuleType(e.target.value);\n                }}\n              >\n                <Radio.Button value=\"0\">内置指标</Radio.Button>\n                <Radio.Button value=\"1\">自定义PromSQL</Radio.Button>\n              </Radio.Group>\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 1,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"内置PromSQL指标方便使用，也可以添加自定义PromSQL规则\">\n                <QuestionCircleOutlined />\n              </Tooltip>\n            </span>\n          </Form.Item>\n\n          {ruleType === \"0\" && (\n            <>\n              <Form.Item\n                label=\"选择指标\"\n                name=\"builtins_quota\"\n                key=\"builtins_quota\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择指标\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"builtins_quota\">\n                  <Cascader\n                    style={{ width: 520 }}\n                    options={cascaderOption}\n                    placeholder=\"请选择指标\"\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"选择需要监控的指标\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"触发条件\"\n                name=\"compare_str\"\n                key=\"compare_str\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发条件\",\n                  },\n                ]}\n              >\n                <Select placeholder=\"请选择触发条件\" style={{ width: 520 }}>\n                  <Select.Option value=\">=\">{`${\">=\"}`}</Select.Option>\n                  <Select.Option value=\">\">{`${\">\"}`}</Select.Option>\n                  <Select.Option value=\"==\">{`${\"==\"}`}</Select.Option>\n                  <Select.Option value=\"!=\">{`${\"!=\"}`}</Select.Option>\n                  <Select.Option value=\"<=\">{`${\"<=\"}`}</Select.Option>\n                  <Select.Option value=\"<\">{`${\"<\"}`}</Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                label=\"触发阈值\"\n                name=\"threshold_value\"\n                key=\"threshold_value\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发阈值\",\n                  },\n                  // {\n                  //   validator: (rule, value, callback) => {\n                  //     if (value == 0) {\n                  //       return Promise.reject(`只支持大于等于0的数字`);\n                  //     } else {\n                  //       return Promise.resolve(\"success\");\n                  //     }\n                  //   },\n                  // },\n                ]}\n              >\n                <Form.Item noStyle name=\"threshold_value\">\n                  <InputNumber style={{ width: 520 }} min={0} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"只支持大于等于0的数字\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"持续时长\"\n                name=\"for_time\"\n                key=\"for_time\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择持续时长\",\n                  },\n                  {\n                    validator: (rule, value, callback) => {\n                      if (value == 0) {\n                        return Promise.reject(`只支持大于0的数字`);\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"for_time\">\n                  <InputNumber\n                    style={{ width: 520 }}\n                    addonAfter={\n                      <Select\n                        style={{ width: 80 }}\n                        value={forTimeCompany}\n                        onChange={(e) => {\n                          setForTimeCompany(e);\n                        }}\n                      >\n                        <Select.Option value=\"s\">秒</Select.Option>\n                        <Select.Option value=\"m\">分</Select.Option>\n                        <Select.Option value=\"h\">时</Select.Option>\n                      </Select>\n                    }\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 5,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"通在持续时长内均匹配规则后会触发告警\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警等级\"\n                name=\"severity\"\n                key=\"severity\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择告警等级\",\n                  },\n                ]}\n              >\n                <Radio.Group>\n                  <Radio value=\"warning\">警告</Radio>\n                  <Radio value=\"critical\">严重</Radio>\n                </Radio.Group>\n              </Form.Item>\n\n              <Form.Item\n                label=\"启用状态\"\n                name=\"status\"\n                key=\"status\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择启用状态\",\n                  },\n                ]}\n                valuePropName=\"checked\"\n              >\n                <Switch />\n              </Form.Item>\n            </>\n          )}\n\n          {ruleType === \"1\" && (\n            <>\n              <Form.Item\n                label=\"PromSQL\"\n                name=\"expr\"\n                key=\"expr\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入PromSQL\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"expr\">\n                  <Input style={{ width: 400 }} placeholder={\"请输入PromSQL\"} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    // top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Button\n                    type=\"primary\"\n                    onClick={() => {\n                      fetchTestData(modalForm.getFieldValue(\"expr\"));\n                      setTestVisible(true);\n                    }}\n                  >\n                    测试查询\n                  </Button>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"触发条件\"\n                name=\"compare_str\"\n                key=\"compare_str\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发条件\",\n                  },\n                ]}\n              >\n                <Select placeholder=\"请选择触发条件\" style={{ width: 520 }}>\n                  <Select.Option value=\">=\">{`${\">=\"}`}</Select.Option>\n                  <Select.Option value=\">\">{`${\">\"}`}</Select.Option>\n                  <Select.Option value=\"==\">{`${\"==\"}`}</Select.Option>\n                  <Select.Option value=\"!=\">{`${\"!=\"}`}</Select.Option>\n                  <Select.Option value=\"<=\">{`${\"<=\"}`}</Select.Option>\n                  <Select.Option value=\"<\">{`${\"<\"}`}</Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                label=\"触发阈值\"\n                name=\"threshold_value\"\n                key=\"threshold_value\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发阈值\",\n                  },\n                  // {\n                  //   validator: (rule, value, callback) => {\n                  //     if (value == 0) {\n                  //       return Promise.reject(`只支持大于等于0的数字`);\n                  //     } else {\n                  //       return Promise.resolve(\"success\");\n                  //     }\n                  //   },\n                  // },\n                ]}\n              >\n                <Form.Item noStyle name=\"threshold_value\">\n                  <InputNumber style={{ width: 520 }} min={0} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"只支持大于等于0的数字\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"持续时长\"\n                name=\"for_time\"\n                key=\"for_time\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择持续时长\",\n                  },\n                  {\n                    validator: (rule, value, callback) => {\n                      if (value == 0) {\n                        return Promise.reject(`只支持大于0的数字`);\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"for_time\">\n                  <InputNumber\n                    style={{ width: 520 }}\n                    addonAfter={\n                      <Select\n                        style={{ width: 80 }}\n                        value={forTimeCompany}\n                        onChange={(e) => {\n                          setForTimeCompany(e);\n                        }}\n                      >\n                        <Select.Option value=\"s\">秒</Select.Option>\n                        <Select.Option value=\"m\">分</Select.Option>\n                        <Select.Option value=\"h\">时</Select.Option>\n                      </Select>\n                    }\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 5,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"通在持续时长内均匹配规则后会触发告警\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"关联服务\"\n                name=\"service\"\n                key=\"service\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入关联服务\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"service\">\n                  <Input\n                    style={{ width: 520 }}\n                    placeholder={\"请输入关联服务\"}\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"输入关联服务后，该指标的告警会归类于该服务名，如“mysql”，如需关联到主机，请填写“node”\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"告警等级\"\n                name=\"severity\"\n                key=\"severity\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择告警等级\",\n                  },\n                ]}\n              >\n                <Radio.Group>\n                  <Radio value=\"warning\">警告</Radio>\n                  <Radio value=\"critical\">严重</Radio>\n                </Radio.Group>\n              </Form.Item>\n\n              <Form.Item\n                label=\"启用状态\"\n                name=\"status\"\n                key=\"status\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择启用状态\",\n                  },\n                ]}\n                valuePropName=\"checked\"\n              >\n                <Switch />\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警标题\"\n                name=\"summary\"\n                key=\"summary\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"选择规则名称\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"summary\">\n                  <Input style={{ width: 520 }} placeholder={\"选择规则名称\"} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"标题中可配置Prometheus中的标签，如 {{ $labels.instance }}\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警消息\"\n                name=\"description\"\n                key=\"description\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"选择告警消息\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"description\">\n                  <Input.TextArea\n                    rows={4}\n                    style={{ width: 520 }}\n                    placeholder={\"输入告警消息\"}\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: -70,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"消息中可配Prometheus中的标签，如 {{ $labels.instance }}\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n            </>\n          )}\n        </div>\n      </OmpModal>\n\n      {/* 删除操作 */}\n      <OmpMessageModal\n        visibleHandle={[deleteMoadlVisible, setDeleteMoadlVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          deleteQuota(row);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{row.alert}</span> 规则{\" \"}\n          <span style={{ fontWeight: 500 }}>下发删除命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        width={900}\n        visibleHandle={[testVisible, setTestVisible]}\n        noFooter\n        style={{ position: \"relative\", top: 180 }}\n        title={<span>PromSQL查询结果</span>}\n        loading={loading}\n      >\n        <div style={{ border: \"1px solid #d6d6d6\" }}>\n          <Table\n            showHeader={false}\n            scroll={{ x: 1200 }}\n            columns={[\n              {\n                title: \"内存使用率\",\n                key: \"metric\",\n                dataIndex: \"metric\",\n                align: \"center\",\n              },\n              {\n                title: \"内存使用率12\",\n                key: \"value\",\n                dataIndex: \"value\",\n                align: \"center\",\n                width: 120,\n                fixed: \"right\",\n              },\n            ]}\n            dataSource={testQueryResults.map((i, idx) => ({ ...i, key: idx }))}\n          />\n        </div>\n      </OmpMessageModal>\n\n      {/* 修改规则 */}\n      <OmpModal\n        loading={loading}\n        width={800}\n        formLabelCol={{ span: 5 }}\n        formWrapperCol={{ span: 18 }}\n        setLoading={setLoading}\n        visibleHandle={[upDateVisible, setUpDateVisible]}\n        okBtnText={\"确定\"}\n        title={\n          <span>\n            <span style={{ position: \"relative\", left: \"-10px\" }}>\n              <PlusSquareOutlined />\n            </span>\n            <span>修改指标规则</span>\n          </span>\n        }\n        form={upDateForm}\n        onFinish={(data) => {\n          uploadQuota(data);\n          // console.log(upDateForm.getFieldValue());\n        }}\n        initialValues={{\n          compare_str: \">=\",\n          threshold_value: 30,\n          quota_type: \"0\",\n          for_time: 60,\n          severity: \"warning\",\n          status: true,\n        }}\n      >\n        <div\n          style={{\n            transition: \"all .2s ease-in\",\n            position: \"relative\",\n            left: -10,\n          }}\n        >\n          <Form.Item\n            label=\"规则名称\"\n            name=\"alert\"\n            key=\"alert\"\n            rules={[\n              {\n                required: true,\n                message: \"请输入规则名称\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"alert\">\n              <Input\n                disabled={row.forbidden == 2}\n                style={{ width: 520 }}\n                placeholder={\"请输入规则名称\"}\n              />\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 1,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"指标规则的名称，方便识别\">\n                <QuestionCircleOutlined onClick={() => console.log(row)} />\n              </Tooltip>\n            </span>\n          </Form.Item>\n\n          <Form.Item\n            label=\"规则类型\"\n            name=\"quota_type\"\n            key=\"quota_type\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择规则类型\",\n              },\n            ]}\n          >\n            <Form.Item noStyle name=\"quota_type\">\n              <Radio.Group\n                disabled={true}\n                value={ruleType}\n                onChange={(e) => {\n                  setRuleType(e.target.value);\n                }}\n              >\n                <Radio.Button value=\"0\">内置指标</Radio.Button>\n                <Radio.Button value=\"1\">自定义PromSQL</Radio.Button>\n              </Radio.Group>\n            </Form.Item>\n            <span\n              name=\"tishi\"\n              style={{\n                paddingLeft: 20,\n                position: \"relative\",\n                top: 1,\n              }}\n            >\n              {\" \"}\n              <Tooltip title=\"内置PromSQL指标方便使用，也可以添加自定义PromSQL规则\">\n                <QuestionCircleOutlined />\n              </Tooltip>\n            </span>\n          </Form.Item>\n\n          {ruleType === \"0\" && (\n            <>\n              <Form.Item\n                label=\"选择指标\"\n                name=\"builtins_quota\"\n                key=\"builtins_quota\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择指标\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"builtins_quota\">\n                  <Cascader\n                    disabled={true}\n                    style={{ width: 520 }}\n                    options={cascaderOption}\n                    placeholder=\"请选择指标\"\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"选择需要监控的指标\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"触发条件\"\n                name=\"compare_str\"\n                key=\"compare_str\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发条件\",\n                  },\n                ]}\n              >\n                <Select placeholder=\"请选择触发条件\" style={{ width: 520 }}>\n                  <Select.Option value=\">=\">{`${\">=\"}`}</Select.Option>\n                  <Select.Option value=\">\">{`${\">\"}`}</Select.Option>\n                  <Select.Option value=\"==\">{`${\"==\"}`}</Select.Option>\n                  <Select.Option value=\"!=\">{`${\"!=\"}`}</Select.Option>\n                  <Select.Option value=\"<=\">{`${\"<=\"}`}</Select.Option>\n                  <Select.Option value=\"<\">{`${\"<\"}`}</Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                label=\"触发阈值\"\n                name=\"threshold_value\"\n                key=\"threshold_value\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发阈值\",\n                  },\n                  // {\n                  //   validator: (rule, value, callback) => {\n                  //     if (value == 0) {\n                  //       return Promise.reject(`只支持大于等于0的数字`);\n                  //     } else {\n                  //       return Promise.resolve(\"success\");\n                  //     }\n                  //   },\n                  // },\n                ]}\n              >\n                <Form.Item noStyle name=\"threshold_value\">\n                  <InputNumber style={{ width: 520 }} min={0} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"只支持大于等于0的数字\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"持续时长\"\n                name=\"for_time\"\n                key=\"for_time\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择持续时长\",\n                  },\n                  {\n                    validator: (rule, value, callback) => {\n                      if (value == 0) {\n                        return Promise.reject(`只支持大于0的数字`);\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"for_time\">\n                  <InputNumber\n                    style={{ width: 520 }}\n                    addonAfter={\n                      <Select\n                        style={{ width: 80 }}\n                        value={forTimeCompany}\n                        onChange={(e) => {\n                          setForTimeCompany(e);\n                        }}\n                      >\n                        <Select.Option value=\"s\">秒</Select.Option>\n                        <Select.Option value=\"m\">分</Select.Option>\n                        <Select.Option value=\"h\">时</Select.Option>\n                      </Select>\n                    }\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 5,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"通在持续时长内均匹配规则后会触发告警\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警等级\"\n                name=\"severity\"\n                key=\"severity\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择告警等级\",\n                  },\n                ]}\n              >\n                <Radio.Group>\n                  <Radio value=\"warning\">警告</Radio>\n                  <Radio value=\"critical\">严重</Radio>\n                </Radio.Group>\n              </Form.Item>\n\n              {/* <Form.Item\n                label=\"启用状态\"\n                name=\"status\"\n                key=\"status\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择启用状态\",\n                  },\n                ]}\n                valuePropName=\"checked\"\n              >\n                <Switch disabled={true} />\n              </Form.Item> */}\n            </>\n          )}\n\n          {ruleType === \"1\" && (\n            <>\n              <Form.Item\n                label=\"PromSQL\"\n                name=\"expr\"\n                key=\"expr\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入PromSQL\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"expr\">\n                  <Input style={{ width: 400 }} placeholder={\"请输入PromSQL\"} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    // top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Button\n                    type=\"primary\"\n                    onClick={() => {\n                      fetchTestData(upDateForm.getFieldValue(\"expr\"));\n                      setTestVisible(true);\n                    }}\n                  >\n                    测试查询\n                  </Button>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"触发条件\"\n                name=\"compare_str\"\n                key=\"compare_str\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发条件\",\n                  },\n                ]}\n              >\n                <Select placeholder=\"请选择触发条件\" style={{ width: 520 }}>\n                  <Select.Option value=\">=\">{`${\">=\"}`}</Select.Option>\n                  <Select.Option value=\">\">{`${\">\"}`}</Select.Option>\n                  <Select.Option value=\"==\">{`${\"==\"}`}</Select.Option>\n                  <Select.Option value=\"!=\">{`${\"!=\"}`}</Select.Option>\n                  <Select.Option value=\"<=\">{`${\"<=\"}`}</Select.Option>\n                  <Select.Option value=\"<\">{`${\"<\"}`}</Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                label=\"触发阈值\"\n                name=\"threshold_value\"\n                key=\"threshold_value\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择触发阈值\",\n                  },\n                  // {\n                  //   validator: (rule, value, callback) => {\n                  //     if (value == 0) {\n                  //       return Promise.reject(`只支持大于等于0的数字`);\n                  //     } else {\n                  //       return Promise.resolve(\"success\");\n                  //     }\n                  //   },\n                  // },\n                ]}\n              >\n                <Form.Item noStyle name=\"threshold_value\">\n                  <InputNumber style={{ width: 520 }} min={0} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"只支持大于等于0的数字\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"持续时长\"\n                name=\"for_time\"\n                key=\"for_time\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择持续时长\",\n                  },\n                  {\n                    validator: (rule, value, callback) => {\n                      if (value == 0) {\n                        return Promise.reject(`只支持大于0的数字`);\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"for_time\">\n                  <InputNumber\n                    style={{ width: 520 }}\n                    addonAfter={\n                      <Select\n                        style={{ width: 80 }}\n                        value={forTimeCompany}\n                        onChange={(e) => {\n                          setForTimeCompany(e);\n                        }}\n                      >\n                        <Select.Option value=\"s\">秒</Select.Option>\n                        <Select.Option value=\"m\">分</Select.Option>\n                        <Select.Option value=\"h\">时</Select.Option>\n                      </Select>\n                    }\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 5,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"通在持续时长内均匹配规则后会触发告警\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"关联服务\"\n                name=\"service\"\n                key=\"service\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请输入关联服务\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"service\">\n                  <Input\n                    style={{ width: 520 }}\n                    placeholder={\"请输入关联服务\"}\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"输入关联服务后，该指标的告警会归类于该服务名，如“mysql”，如需关联到主机，请填写“node”\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n              <Form.Item\n                label=\"告警等级\"\n                name=\"severity\"\n                key=\"severity\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择告警等级\",\n                  },\n                ]}\n              >\n                <Radio.Group>\n                  <Radio value=\"warning\">警告</Radio>\n                  <Radio value=\"critical\">严重</Radio>\n                </Radio.Group>\n              </Form.Item>\n\n              <Form.Item\n                label=\"启用状态\"\n                name=\"status\"\n                key=\"status\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"请选择启用状态\",\n                  },\n                ]}\n                valuePropName=\"checked\"\n              >\n                <Switch />\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警标题\"\n                name=\"summary\"\n                key=\"summary\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"选择规则名称\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"summary\">\n                  <Input style={{ width: 520 }} placeholder={\"选择规则名称\"} />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"标题中可配置Prometheus中的标签，如 {{ $labels.instance }}\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n\n              <Form.Item\n                label=\"告警消息\"\n                name=\"description\"\n                key=\"description\"\n                rules={[\n                  {\n                    required: true,\n                    message: \"选择告警消息\",\n                  },\n                ]}\n              >\n                <Form.Item noStyle name=\"description\">\n                  <Input.TextArea\n                    rows={4}\n                    style={{ width: 520 }}\n                    placeholder={\"输入告警消息\"}\n                  />\n                </Form.Item>\n                <span\n                  name=\"tishi\"\n                  style={{\n                    paddingLeft: 20,\n                    position: \"relative\",\n                    top: -70,\n                  }}\n                >\n                  {\" \"}\n                  <Tooltip title=\"消息中可配Prometheus中的标签，如 {{ $labels.instance }}\">\n                    <QuestionCircleOutlined />\n                  </Tooltip>\n                </span>\n              </Form.Item>\n            </>\n          )}\n        </div>\n      </OmpModal>\n\n      {/* 批量停用操作 */}\n      <OmpMessageModal\n        visibleHandle={[stopVisible, setStopVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          // deleteQuota(row);\n          statusUpdate(\n            checkedList.map((i) => i.id),\n            0\n          );\n          setCheckedList([]);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{checkedList.length}</span>{\" \"}\n          条规则 <span style={{ fontWeight: 500 }}>下发停用命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      {/* 单独停用操作 */}\n      <OmpMessageModal\n        visibleHandle={[stopRowVisible, setStopRowVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          statusUpdate([row.id], 0);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{row.alert}</span> 规则{\" \"}\n          <span style={{ fontWeight: 500 }}>下发停用命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      {/* 批量启用操作 */}\n      <OmpMessageModal\n        visibleHandle={[startVisible, setStartVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          statusUpdate(\n            checkedList.map((i) => i.id),\n            1\n          );\n          setCheckedList([]);\n          // deleteQuota(row);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{checkedList.length}</span>{\" \"}\n          条规则 <span style={{ fontWeight: 500 }}>下发启用命令</span> ？\n        </div>\n      </OmpMessageModal>\n\n      {/* 单独启用操作 */}\n      <OmpMessageModal\n        visibleHandle={[startRowVisible, setStartRowVisible]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          statusUpdate([row.id], 1);\n          // deleteQuota(row);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>{row.alert}</span> 规则{\" \"}\n          <span style={{ fontWeight: 500 }}>下发启用命令</span> ？\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default RuleIndicator;\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingRecord/config/columns.js",
    "content": "import { renderDisc } from \"@/utils/utils\";\nimport { Tooltip, Badge } from \"antd\";\nimport moment from \"moment\";\n\nconst renderStatus = (state) => {\n  switch (state) {\n    case 1:\n      return (\n        <span>\n          {renderDisc(\"normal\", 7, -1)}\n          自愈成功\n        </span>\n      );\n      break;\n    case 0:\n      return (\n        <span>\n          {renderDisc(\"critical\", 7, -1)}\n          自愈失败\n        </span>\n      );\n      break;\n    case 2:\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          自愈中\n        </span>\n      );\n      break;\n    default:\n      return \"-\";\n      break;\n  }\n};\n\nconst getColumnsConfig = (\n  queryRequest,\n  setShowIframe,\n  updateAlertRead,\n  history\n) => {\n  return [\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      fixed: \"left\",\n      sorter: (a, b) => a.instance_name - b.instance_name,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <Badge dot={record.is_read === 0} offset={[5, 2]}>\n              <span style={{ fontSize: 12 }}>\n                {record.instance_name ? record.instance_name : \"-\"}\n              </span>\n            </Badge>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"host_ip\",\n      width: 140,\n      dataIndex: \"host_ip\",\n      ellipsis: true,\n      sorter: (a, b) => a.host_ip - b.host_ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n    },\n    {\n      title: \"自愈状态\",\n      key: \"state\",\n      dataIndex: \"state\",\n      align: \"center\",\n      width: 120,\n      // sorter: (a, b) => a.severity - b.severity,\n      // sortDirections: [\"descend\", \"ascend\"],\n      //ellipsis: true,\n      //width:120,\n      usefilter: true,\n      queryRequest: queryRequest,\n      filterMenuList: [\n        {\n          value: \"1\",\n          text: \"自愈成功\",\n        },\n        {\n          value: \"0\",\n          text: \"自愈失败\",\n        },\n        {\n          value: \"2\",\n          text: \"自愈中\",\n        },\n      ],\n      render: renderStatus,\n    },\n    {\n      title: \"重试次数\",\n      key: \"healing_count\",\n      dataIndex: \"healing_count\",\n      align: \"center\",\n      //ellipsis: true,\n      width: 80,\n      render: (text) => {\n        return text ? `${text}次` : \"-\";\n      },\n    },\n    {\n      title: \"故障时间\",\n      width: 180,\n      key: \"alert_time\",\n      dataIndex: \"alert_time\",\n      align: \"center\",\n      //ellipsis: true,\n      // sorter: (a, b) => a.alert_time - b.alert_time,\n      // sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        if (text) {\n          let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n          return str;\n        }\n        return \"-\";\n      },\n    },\n    {\n      title: \"结束时间\",\n      width: 180,\n      key: \"end_time\",\n      dataIndex: \"end_time\",\n      align: \"center\",\n      //ellipsis: true,\n      // sorter: (a, b) => a.create_time - b.create_time,\n      // sortDirections: [\"descend\", \"ascend\"],\n      render: (text) => {\n        if (text) {\n          let str = moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n          return str;\n        }\n        return \"-\";\n      },\n    },\n    {\n      title: \"故障描述\",\n      key: \"alert_content\",\n      dataIndex: \"alert_content\",\n      align: \"center\",\n      width: 300,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text}>\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"自愈日志\",\n      key: \"healing_log\",\n      dataIndex: \"healing_log\",\n      align: \"center\",\n      width: 220,\n      ellipsis: true,\n      render: (text) => {\n        return (\n          <Tooltip title={text} placement=\"topLeft\">\n            <span>{text ? text : \"-\"}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"操作\",\n      width: 140,\n      key: \"\",\n      dataIndex: \"\",\n      fixed: \"right\",\n      align: \"center\",\n      render: function renderFunc(text, record, index) {\n        //console.log(record);\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              {record.instance_name ? (\n                <a\n                  onClick={() => {\n                    console.log(record);\n                    history.push({\n                      pathname: \"/application-monitoring/alarm-log\",\n                      state: {\n                        alert_instance_name: record.instance_name,\n                        time: record.alert_time,\n                      },\n                    });\n                  }}\n                >\n                  关联告警\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>关联告警</span>\n              )}\n\n              {record.monitor_log ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() => {\n                    record.is_read == 0 && updateAlertRead([record.id]);\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_log,\n                      record: {\n                        ...record,\n                        ip: record.host_ip,\n                      },\n                      isLog: true,\n                    });\n                  }}\n                >\n                  服务日志\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  服务日志\n                </span>\n              )}\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingRecord/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpSelect,\n  OmpDatePicker,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, Select, message, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport getColumnsConfig from \"./config/columns\";\nimport { SearchOutlined } from \"@ant-design/icons\";\nimport moment from \"moment\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst SelfHealingRecord = () => {\n  const history = useHistory();\n\n  const location = useLocation();\n\n  const initIp = location.state?.ip;\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [ipListSource, setIpListSource] = useState([]);\n\n  const [selectValue, setSelectValue] = useState(initIp);\n\n  const [instanceSelectValue, setInstanceSelectValue] = useState();\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 筛选label\n  const [labelControl, setLabelControl] = useState(\n    initIp ? \"ip\" : \"instance_name\"\n  );\n\n  const [showIframe, setShowIframe] = useState({});\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams = {},\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.faultSelfHealing.querySelfHealingList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchIPlist();\n        //fetchNameList();\n      });\n  }\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  const updateAlertRead = (ids = []) => {\n    setLoading(true);\n    fetchPost(apiRequest.faultSelfHealing.selfHeadlingIsRead, {\n      body: {\n        ids: ids,\n        is_read: 1,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          message.success(\"已读成功\");\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setCheckedList([]);\n        setLoading(false);\n        fetchData(\n          { current: pagination.current, pageSize: pagination.pageSize },\n          { ...pagination.searchParams },\n          pagination.ordering\n        );\n      });\n  };\n\n  useEffect(() => {\n    fetchData(pagination, { alert_host_ip: location.state?.ip });\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n        <Button\n          type=\"primary\"\n          disabled={checkedList.length == 0}\n          onClick={() => {\n            let ids = checkedList.map((item) => item.id);\n            updateAlertRead(ids);\n          }}\n        >\n          批量已读\n        </Button>\n        <div style={{ display: \"flex\" }}>\n          <OmpDatePicker\n            onChange={(e) => {\n              if (!e) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    query_start_time: null,\n                    query_end_time: null,\n                  },\n                  pagination.ordering\n                );\n              } else {\n                let result = e.filter((item) => item);\n                if (result.length == 2) {\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      query_start_time: moment(e[0]).format(\n                        \"YYYY-MM-DD HH:mm:ss\"\n                      ),\n                      query_end_time: moment(e[1]).format(\n                        \"YYYY-MM-DD HH:mm:ss\"\n                      ),\n                    },\n                    pagination.ordering\n                  );\n                }\n              }\n            }}\n          />\n          <div style={{ display: \"flex\", marginLeft: \"10px\" }}>\n            <Input.Group compact style={{ display: \"flex\" }}>\n              <Select\n                value={labelControl}\n                style={{ width: 100 }}\n                onChange={(e) => {\n                  setLabelControl(e);\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      host_ip: null,\n                      instance_name: null,\n                    },\n                    pagination.ordering\n                  );\n                  setInstanceSelectValue();\n                  setSelectValue();\n                }}\n              >\n                <Select.Option value=\"ip\"> IP地址</Select.Option>\n                <Select.Option value=\"instance_name\">实例名称</Select.Option>\n              </Select>\n              {labelControl === \"ip\" && (\n                <OmpSelect\n                  searchLoading={searchLoading}\n                  selectValue={selectValue}\n                  listSource={ipListSource}\n                  setSelectValue={setSelectValue}\n                  fetchData={(value) => {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      { ...pagination.searchParams, host_ip: value },\n                      pagination.ordering\n                    );\n                  }}\n                  pagination={pagination}\n                />\n              )}\n              {labelControl === \"instance_name\" && (\n                <Input\n                  placeholder=\"输入实例名称\"\n                  style={{ width: 200 }}\n                  allowClear\n                  value={instanceSelectValue}\n                  onChange={(e) => {\n                    setInstanceSelectValue(e.target.value);\n                    if (!e.target.value) {\n                      fetchData(\n                        {\n                          current: 1,\n                          pageSize: pagination.pageSize,\n                        },\n                        {\n                          ...pagination.searchParams,\n                          instance_name: null,\n                        },\n                        pagination.ordering\n                      );\n                    }\n                  }}\n                  onBlur={() => {\n                    if (instanceSelectValue) {\n                      fetchData(\n                        {\n                          current: 1,\n                          pageSize: pagination.pageSize,\n                        },\n                        {\n                          ...pagination.searchParams,\n                          instance_name: instanceSelectValue,\n                        },\n                        pagination.ordering\n                      );\n                    }\n                  }}\n                  onPressEnter={() => {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      {\n                        ...pagination.searchParams,\n                        instance_name: instanceSelectValue,\n                      },\n                      pagination.ordering\n                    );\n                  }}\n                  suffix={\n                    !instanceSelectValue && (\n                      <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n                    )\n                  }\n                />\n              )}\n            </Input.Group>\n\n            <Button\n              style={{ marginLeft: 10 }}\n              onClick={() => {\n                //   dispatch(refreshTime());\n                fetchData(\n                  {\n                    current: pagination.current,\n                    pageSize: pagination.pageSize,\n                  },\n                  { ...pagination.searchParams },\n                  pagination.ordering\n                );\n              }}\n            >\n              刷新\n            </Button>\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //scroll={{ x: 1400 }}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(\n            (params) => {\n              // console.log(pagination.searchParams)\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                { ...pagination.searchParams, ...params },\n                pagination.ordering\n              );\n            },\n            setShowIframe,\n            updateAlertRead,\n            history\n          )}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  justifyContent: \"space-between\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n    </OmpContentWrapper>\n  );\n};\n\nexport default SelfHealingRecord;\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingStrategy/StrategyModal.js",
    "content": "import {\n  Button,\n  Modal,\n  Divider,\n  Form,\n  InputNumber,\n  Spin,\n  Switch,\n  Select,\n} from \"antd\";\nimport { PlusSquareOutlined, FormOutlined } from \"@ant-design/icons\";\n\nexport const AddStrategyModal = ({\n  strategyModalType,\n  addStrategy,\n  updateStrategy,\n  loading,\n  modalForm,\n  addModalVisibility,\n  setAddModalVisibility,\n  canHealingIns,\n  strategyFormInit,\n  keyArr,\n  setKeyArr,\n}) => {\n  return (\n    <Modal\n      style={{ marginTop: 10 }}\n      width={600}\n      onCancel={() => {\n        setAddModalVisibility(false);\n        setKeyArr([]);\n        modalForm.setFieldsValue(strategyFormInit);\n      }}\n      visible={addModalVisibility}\n      title={\n        <span>\n          <span style={{ position: \"relative\", left: \"-10px\" }}>\n            {strategyModalType === \"add\" ? (\n              <PlusSquareOutlined />\n            ) : (\n              <FormOutlined />\n            )}\n          </span>\n          <span>\n            {strategyModalType === \"add\" ? \"添加自愈策略\" : \"编辑自愈策略\"}\n          </span>\n        </span>\n      }\n      zIndex={1004}\n      footer={null}\n      destroyOnClose\n    >\n      <Spin spinning={loading}>\n        <Form\n          name=\"strategy\"\n          labelCol={{ span: 4 }}\n          wrapperCol={{ span: 18 }}\n          onFinish={(data) => {\n            if (strategyModalType === \"add\") {\n              addStrategy(data);\n            } else {\n              updateStrategy(data);\n            }\n          }}\n          form={modalForm}\n          initialValues={strategyFormInit}\n        >\n          {/* <Form.Item\n            label=\"自愈实例\"\n            name=\"repair_instance\"\n            key=\"repair_instance\"\n            rules={[\n              {\n                required: true,\n                message: \"自愈实例\",\n              },\n            ]}\n          >\n            <Select\n              mode=\"multiple\"\n              placeholder=\"请选择自愈实例\"\n              allowClear\n              maxTagCount=\"responsive\"\n              onChange={(e) => setKeyArr(e)}\n              dropdownRender={(menu) => (\n                <div>\n                  {menu}\n                  <Divider\n                    style={{\n                      marginTop: 2,\n                      marginBottom: 5,\n                    }}\n                  />\n                  <Button\n                    size=\"small\"\n                    type=\"primary\"\n                    disabled={canHealingIns?.service_name?.length === 0}\n                    style={{ marginLeft: 10 }}\n                    onClick={() => {\n                      modalForm.setFieldsValue({\n                        repair_instance: canHealingIns.service_name,\n                      });\n                      setKeyArr(canHealingIns.service_name);\n                    }}\n                  >\n                    全选\n                  </Button>\n                  <span style={{ float: \"right\", marginRight: 10 }}>\n                    已选 {keyArr.length} 个\n                  </span>\n                </div>\n              )}\n            >\n              <Select.Option\n                key=\"all\"\n                value=\"all\"\n                disabled={\n                  !canHealingIns.all ||\n                  (keyArr.length !== 0 && keyArr[0] !== \"all\")\n                }\n              >\n                所有服务\n              </Select.Option>\n              {canHealingIns.service_name?.map((e) => {\n                return (\n                  <Select.Option\n                    key={e}\n                    value={e}\n                    disabled={keyArr.length === 1 && keyArr[0] === \"all\"}\n                  >\n                    {e}\n                  </Select.Option>\n                );\n              })}\n            </Select>\n          </Form.Item> */}\n          <Form.Item\n            label=\"自愈维度\"\n            name=\"repair_instance\"\n            key=\"repair_instance\"\n            rules={[\n              {\n                required: true,\n                message: \"自愈维度\",\n              },\n            ]}\n          >\n            <Select\n              mode=\"multiple\"\n              placeholder=\"请选择自愈维度\"\n              allowClear\n              maxTagCount=\"responsive\"\n              // onChange={(e) => setKeyArr(e)}\n            >\n              <Select.Option key=\"host\" value=\"host\">\n                主机监控Agent\n              </Select.Option>\n              <Select.Option key=\"component\" value=\"component\">\n                所有公共组件\n              </Select.Option>\n              <Select.Option key=\"service\" value=\"service\">\n                所有自研服务\n              </Select.Option>\n            </Select>\n          </Form.Item>\n          <Form.Item\n            label=\"探测周期\"\n            name=\"fresh_rate\"\n            key=\"fresh_rate\"\n            rules={[\n              {\n                required: true,\n                message: \"请填写探测周期\",\n              },\n            ]}\n          >\n            <InputNumber\n              min={1}\n              max={60}\n              style={{\n                width: 140,\n              }}\n              addonAfter=\"分钟\"\n            />\n          </Form.Item>\n\n          <Form.Item\n            label=\"自愈类型\"\n            name=\"instance_tp\"\n            key=\"instance_tp\"\n            rules={[\n              {\n                required: true,\n                message: \"请选择自愈类型\",\n              },\n            ]}\n          >\n            <Select placeholder=\"自愈类型\" style={{ width: 140 }}>\n              <Select.Option key=\"start\" value={0}>\n                启动 [start]\n              </Select.Option>\n              <Select.Option key=\"restart\" value={1}>\n                重启 [restart]\n              </Select.Option>\n            </Select>\n          </Form.Item>\n\n          <Form.Item\n            label=\"重试次数\"\n            name=\"max_healing_count\"\n            key=\"max_healing_count\"\n            rules={[\n              {\n                required: true,\n                message: \"请填写重试次数\",\n              },\n            ]}\n          >\n            <InputNumber\n              min={1}\n              max={20}\n              style={{\n                width: 140,\n              }}\n              addonAfter=\"次\"\n            />\n          </Form.Item>\n\n          <Form.Item label=\"开启策略\">\n            <Form.Item name=\"used\" noStyle valuePropName=\"checked\">\n              <Switch style={{ borderRadius: \"10px\" }} />\n            </Form.Item>\n          </Form.Item>\n\n          <Form.Item\n            wrapperCol={{ span: 24 }}\n            style={{ textAlign: \"center\", position: \"relative\", top: 10 }}\n          >\n            <Button\n              style={{ marginRight: 16 }}\n              onClick={() => {\n                setAddModalVisibility(false);\n                setKeyArr([]);\n                modalForm.setFieldsValue(strategyFormInit);\n              }}\n            >\n              取消\n            </Button>\n            <Button type=\"primary\" htmlType=\"submit\">\n              确定\n            </Button>\n          </Form.Item>\n        </Form>\n      </Spin>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingStrategy/config/columns.js",
    "content": "import { renderDisc } from \"@/utils/utils\";\nimport { Tooltip } from \"antd\";\n\nconst getColumnsConfig = (\n  setStrategyRow,\n  setDeleteStrategyModal,\n  setStrategyModalType,\n  setStrategyModalVisibility,\n  strategyForm,\n  queryCanHealing\n) => {\n  return [\n    {\n      title: \"序号\",\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      align: \"center\",\n      width: 40,\n      fixed: \"left\",\n    },\n    {\n      title: \"自愈实例\",\n      key: \"repair_instance\",\n      dataIndex: \"repair_instance\",\n      align: \"center\",\n      width: 200,\n      ellipsis: true,\n      render: (text) => {\n        // if (text.length > 0 && text[0] === \"all\") return \"所有服务\";\n        const textMap = {\n          host: \"主机监控Agent\",\n          component: \"基础组件\",\n          service: \"自研服务\",\n        };\n        const resText = text.map((e) => textMap[e]);\n        return (\n          <Tooltip title={resText.join(\", \")}>\n            <span>{resText.join(\", \")}</span>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"自愈类型\",\n      key: \"instance_tp\",\n      dataIndex: \"instance_tp\",\n      align: \"center\",\n      width: 60,\n      ellipsis: true,\n      render: (text) => {\n        if (text === 0) {\n          return \"启动 [start]\";\n        } else {\n          return \"重启 [restart]\";\n        }\n      },\n    },\n    {\n      title: \"探测周期\",\n      key: \"fresh_rate\",\n      dataIndex: \"fresh_rate\",\n      align: \"center\",\n      width: 100,\n      ellipsis: true,\n      render: (text) => {\n        return `${text} min`;\n      },\n    },\n    {\n      title: \"重试次数\",\n      key: \"max_healing_count\",\n      dataIndex: \"max_healing_count\",\n      align: \"center\",\n      width: 100,\n      ellipsis: true,\n    },\n    {\n      title: \"是否生效\",\n      key: \"used\",\n      dataIndex: \"used\",\n      align: \"center\",\n      width: 60,\n      ellipsis: true,\n      render: (text) => {\n        if (text) {\n          return <span>{renderDisc(\"normal\", 7, -1)}是</span>;\n        } else {\n          return <span>{renderDisc(\"critical\", 7, -1)}否</span>;\n        }\n      },\n    },\n\n    {\n      title: \"操作\",\n      width: 100,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: (text, record, index) => {\n        return (\n          <div\n            style={{ margin: \"auto\" }}\n            onClick={() => setStrategyRow(record)}\n          >\n            <a\n              style={{ marginLeft: 10 }}\n              onClick={() => {\n                queryCanHealing();\n                setStrategyModalType(\"update\");\n                setStrategyModalVisibility(true);\n                strategyForm.setFieldsValue({\n                  repair_instance: record.repair_instance,\n                  fresh_rate: record.fresh_rate,\n                  instance_tp: record.instance_tp,\n                  max_healing_count: record.max_healing_count,\n                  used: record.used,\n                });\n              }}\n            >\n              编辑\n            </a>\n\n            <a\n              style={{ marginLeft: 10 }}\n              onClick={() => setDeleteStrategyModal(true)}\n            >\n              删除\n            </a>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingStrategy/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpMessageModal } from \"@/components\";\nimport { Form, Button, message } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse } from \"@/utils/utils\";\nimport { fetchGet, fetchDelete, fetchPost, fetchPut } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { ExclamationCircleOutlined } from \"@ant-design/icons\";\nimport getColumnsConfig from \"./config/columns\";\nimport { AddStrategyModal } from \"./StrategyModal\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst BackupStrategy = () => {\n  const location = useLocation();\n\n  const history = useHistory();\n\n  const [loading, setLoading] = useState(false);\n\n  const [dataSource, setDataSource] = useState([]);\n\n  // 自定义参数\n\n  // 增/改自愈策略共用\n  const [strategyRow, setStrategyRow] = useState({});\n  const [strategyModalType, setStrategyModalType] = useState(\"add\");\n  const [strategyModalVisibility, setStrategyModalVisibility] = useState(false);\n  const [strategyLoading, setStrategyLoading] = useState(false);\n  const [keyArr, setKeyArr] = useState([]);\n\n  // 自愈策略表单\n  const [strategyForm] = Form.useForm();\n\n  // 自愈组件全量数据\n  const [canHealingIns, setcanHealingIns] = useState([]);\n\n  // 删除策略\n  const [deleteStrategyModal, setDeleteStrategyModal] = useState(false);\n\n  // 策略表单初始值\n  const strategyFormInit = {\n    repair_instance: [],\n    fresh_rate: 30,\n    max_healing_count: 5,\n    instance_tp: 0,\n    used: false,\n  };\n\n  // 策略列表查询\n  const fetchData = () => {\n    setLoading(true);\n    fetchGet(apiRequest.faultSelfHealing.selfHealingStrategy)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1,\n              };\n            })\n          );\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  // 查询可自愈实例\n  const queryCanHealing = () => {\n    setStrategyLoading(true);\n    fetchGet(apiRequest.faultSelfHealing.selfHealingStrategy, {\n      params: {\n        instance: true,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, () => {\n          setcanHealingIns(res.data.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 添加自愈策略\n  const addStrategy = (data) => {\n    setStrategyLoading(true);\n    fetchPost(apiRequest.faultSelfHealing.selfHealingStrategy, {\n      body: data,\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"添加自愈策略成功\");\n            strategyForm.setFieldsValue(strategyFormInit);\n            fetchData();\n            setKeyArr([]);\n            setStrategyModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 编辑自愈策略\n  const updateStrategy = (data) => {\n    setStrategyLoading(true);\n    fetchPut(\n      `${apiRequest.faultSelfHealing.selfHealingStrategy}${strategyRow.id}/`,\n      {\n        body: data,\n      }\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"修改自愈策略成功\");\n            strategyForm.setFieldsValue(strategyFormInit);\n            fetchData();\n            setKeyArr([]);\n            setStrategyModalVisibility(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setStrategyLoading(false);\n      });\n  };\n\n  // 删除自愈策略\n  const deleteStrategy = () => {\n    setLoading(true);\n    fetchDelete(\n      `${apiRequest.faultSelfHealing.selfHealingStrategy}${strategyRow.id}/`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            message.success(\"删除成功\");\n            fetchData();\n            setDeleteStrategyModal(false);\n          } else {\n            message.warning(res.message);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData();\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          style={{ marginRight: 10 }}\n          type=\"primary\"\n          onClick={() => {\n            // 暂时写死三个维度，无需查询\n            // queryCanHealing();\n            setStrategyModalType(\"add\");\n            setStrategyModalVisibility(true);\n          }}\n        >\n          添加策略\n        </Button>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Button style={{ marginLeft: 10 }} onClick={() => fetchData()}>\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          columns={getColumnsConfig(\n            setStrategyRow,\n            setDeleteStrategyModal,\n            setStrategyModalType,\n            setStrategyModalVisibility,\n            strategyForm,\n            queryCanHealing\n          )}\n          dataSource={dataSource}\n          pagination={{\n            pageSize: 10,\n          }}\n          rowKey={(record) => record.id}\n          noScroll={true}\n        />\n      </div>\n\n      <AddStrategyModal\n        strategyModalType={strategyModalType}\n        addStrategy={addStrategy}\n        updateStrategy={updateStrategy}\n        loading={strategyLoading}\n        modalForm={strategyForm}\n        addModalVisibility={strategyModalVisibility}\n        setAddModalVisibility={setStrategyModalVisibility}\n        canHealingIns={canHealingIns}\n        strategyFormInit={strategyFormInit}\n        keyArr={keyArr}\n        setKeyArr={setKeyArr}\n      />\n\n      <OmpMessageModal\n        visibleHandle={[deleteStrategyModal, setDeleteStrategyModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          deleteStrategy();\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定 <span style={{ fontWeight: 500 }}>删除</span> 该策略吗？\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default BackupStrategy;\n"
  },
  {
    "path": "omp_web/src/pages/SelfHealingStrategy/index.module.less",
    "content": ".header {\n    padding: 10px;\n    padding-left: 20px;\n    background-color: #f7f7f7;\n  }\n  \n  .content {\n    padding-left: 40px;\n    padding-top: 30px;\n    display: flex;\n    align-items: center;\n    .label {\n      padding-right: 30px;\n    }\n  }\n  \n  .tips {\n    margin-top: 30px;\n    padding-left: 40px;\n    font-size: 13px;\n  }\n  \n  .saveButtonWrapper {\n    height: 50px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n  \n  .saveButton {\n    margin-left: 50%;\n    transform: translateX(-50%);\n  }\n  "
  },
  {
    "path": "omp_web/src/pages/ServiceManagement/config/columns.js",
    "content": "import {\n  nonEmptyProcessing,\n  renderDisc,\n  RenderStatusForResult,\n} from \"@/utils/utils\";\nimport { OmpToolTip } from \"@/components\";\nimport { DesktopOutlined } from \"@ant-design/icons\";\nimport { Dropdown, Menu, Drawer, Tooltip, Spin, Timeline } from \"antd\";\nimport moment from \"moment\";\nimport styles from \"../index.module.less\";\nimport { useSelector } from \"react-redux\";\nimport { useRef } from \"react\";\n\nconst colorConfig = {\n  normal: null,\n  warning: \"#ffbf00\",\n  critical: \"#f04134\",\n};\n\nexport const DetailHost = ({\n  isShowDrawer,\n  setIsShowDrawer,\n  loading,\n  data,\n  setInstallationRecordModal,\n  queryServiceInstallHistoryDetail,\n}) => {\n  // 视口宽度\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  const wrapperRef = useRef(null);\n  return (\n    <Drawer\n      title={\n        <div style={{ display: \"flex\" }}>\n          <DesktopOutlined style={{ position: \"relative\", top: 3, left: -5 }} />\n          服务信息面板\n          <span style={{ paddingLeft: 30, fontWeight: 400, fontSize: 15 }}>\n            实例名称: {isShowDrawer.record?.service_instance_name}\n          </span>\n        </div>\n      }\n      headerStyle={{\n        padding: \"19px 24px\",\n      }}\n      placement=\"right\"\n      closable={true}\n      width={`calc(100% - 200px)`}\n      style={{\n        height: \"calc(100%)\",\n        // paddingTop: \"60px\",\n      }}\n      onClose={() => {\n        setIsShowDrawer({\n          ...isShowDrawer,\n          isOpen: false,\n        });\n      }}\n      visible={isShowDrawer.isOpen}\n      bodyStyle={{\n        padding: 10,\n        //paddingLeft:10,\n        backgroundColor: \"#e7e9f0\", //\"#f4f6f8\"\n        height: \"calc(100%)\",\n      }}\n      destroyOnClose={true}\n    >\n      <div\n        style={{ height: \"calc(100% - 15px)\", width: \"100%\", display: \"flex\" }}\n      >\n        <div style={{ flex: 4 }}>\n          <div\n            style={{\n              height: \"calc(50%)\",\n              width: \"100%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              //flex: 4,\n              padding: 20,\n            }}\n          >\n            <div style={{ paddingBottom: 15, fontSize: 15, fontWeight: 500 }}>\n              基本信息\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                //paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>实例名称</div>\n              <div style={{ flex: 3 }}>\n                <OmpToolTip maxLength={30}>\n                  {isShowDrawer.record?.service_instance_name}\n                </OmpToolTip>\n              </div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>服务名称</div>\n              <div style={{ flex: 3 }}>\n                <OmpToolTip maxLength={30}>\n                  {nonEmptyProcessing(isShowDrawer.record?.app_name)}\n                </OmpToolTip>\n              </div>\n            </div>\n            {/* <div\n            style={{\n              display: \"flex\",\n              paddingTop: 15,\n              paddingBottom: 5,\n              borderBottom: \"solid 1px rgb(220,220,220)\",\n            }}\n          >\n            <div style={{ flex: 1 }}>IP地址</div>\n            <div style={{ flex: 1 }}>{isShowDrawer.record.ip}</div>\n          </div> */}\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>版本</div>\n              <div style={{ flex: 3 }}>{isShowDrawer.record?.app_version}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>服务分类</div>\n              <div style={{ flex: 3 }}>{isShowDrawer.record?.label_name}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>集群模式</div>\n              <div style={{ flex: 3 }}>{isShowDrawer.record?.cluster_type}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>IP地址</div>\n              <div style={{ flex: 3 }}>{isShowDrawer.record?.ip}</div>\n            </div>\n          </div>\n          <div\n            style={{\n              marginTop: \"3%\",\n              height: \"calc(48%)\",\n              width: \"100%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              //flex: 4,\n              padding: 20,\n            }}\n          >\n            <div\n              style={{\n                display: \"flex\",\n                justifyContent: \"space-between\",\n                paddingBottom: 15,\n                fontSize: 15,\n                fontWeight: 500,\n              }}\n            >\n              安装信息\n              <a\n                onClick={() => {\n                  setInstallationRecordModal(true);\n                  queryServiceInstallHistoryDetail(data.id);\n                }}\n                style={{ fontSize: 13, fontWeight: 400 }}\n              >\n                查看安装记录\n              </a>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                //paddingTop: 15,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>安装目录</div>\n              <div style={{ flex: 3 }}>\n                <OmpToolTip maxLength={32}>\n                  {data.install_info?.base_dir}\n                </OmpToolTip>\n              </div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>数据目录</div>\n              <div style={{ flex: 3 }}>\n                <OmpToolTip maxLength={32}>\n                  {data.install_info?.data_dir}\n                </OmpToolTip>\n              </div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>日志目录</div>\n              <div style={{ flex: 3 }}>\n                <OmpToolTip maxLength={32}>\n                  {data.install_info?.log_dir}\n                </OmpToolTip>\n              </div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>端口号</div>\n              <div style={{ flex: 3 }}>{data.install_info?.service_port}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>用户名</div>\n              <div style={{ flex: 3 }}>{data.install_info?.username}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>密码</div>\n              <div style={{ flex: 3 }}>{data.install_info?.password}</div>\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                paddingTop: 8,\n                paddingBottom: 5,\n                borderBottom: \"solid 1px rgb(220,220,220)\",\n              }}\n            >\n              <div style={{ flex: 2 }}>安装时间</div>\n              <div style={{ flex: 3 }}>\n                {data?.created\n                  ? moment(data?.created).format(\"YYYY-MM-DD HH:mm:ss\")\n                  : \"-\"}\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div\n          style={{\n            height: \"100%\",\n            width: \"100%\",\n            flex: 7,\n            marginLeft: 20,\n            display: \"flex\",\n            flexWrap: \"wrap\",\n          }}\n        >\n          <div\n            ref={wrapperRef}\n            style={{\n              height: \"calc(100%)\",\n              marginTop: 0,\n              width: \"99%\",\n              //border: \"solid 1px rgb(220,220,220)\",\n              borderRadius: \"5px\",\n              backgroundColor: \"#fff\",\n              //height:200\n              padding: 20,\n              //overflow:\"hidden\"\n            }}\n          >\n            <div style={{ paddingBottom: 20, fontSize: 15, fontWeight: 500 }}>\n              历史记录\n            </div>\n            <Spin spinning={loading} wrapperClassName={styles.omp_spin_wrapper}>\n              <Timeline\n                style={{\n                  overflowY: \"scroll\",\n                  paddingTop: 10,\n                  //height: \"100%\",\n                  height: wrapperRef.current\n                    ? wrapperRef.current?.offsetHeight - 100\n                    : 100,\n                }}\n              >\n                {data.history?.map((item) => {\n                  return (\n                    <Timeline.Item key={item.created}>\n                      <p style={{ color: \"#595959\" }}>\n                        <RenderStatusForResult result={item?.result} />[\n                        {item.username}] {item.description}\n                      </p>\n                      <p style={{ color: \"#595959\" }}>\n                        {moment(item.created).format(\"YYYY-MM-DD HH:mm:ss\")}\n                      </p>\n                    </Timeline.Item>\n                  );\n                })}\n              </Timeline>\n            </Spin>\n          </div>\n        </div>\n      </div>\n    </Drawer>\n  );\n};\n\n//操作\nconst renderMenu = (\n  // setUpdateMoadlVisible,\n  // setCloseMaintainModal,\n  // setOpenMaintainModal,\n\n  record,\n  setOperateAciton,\n  setServiceAcitonModal,\n  queryDeleteMsg,\n  deleteConditionReset\n) => {\n  return (\n    <Menu>\n      <Menu.Item\n        disabled={!record.operable}\n        style={{ textAlign: \"center\" }}\n        key=\"start\"\n        onClick={() => {\n          setOperateAciton(1);\n          setServiceAcitonModal(true);\n        }}\n      >\n        <span style={{ fontSize: 12, paddingLeft: 5, paddingRight: 5 }}>\n          启动\n        </span>\n      </Menu.Item>\n      <Menu.Item\n        disabled={!record.operable}\n        key=\"close\"\n        onClick={() => {\n          setOperateAciton(2);\n          setServiceAcitonModal(true);\n        }}\n      >\n        <span style={{ fontSize: 12, paddingLeft: 5, paddingRight: 5 }}>\n          停止\n        </span>\n      </Menu.Item>\n      <Menu.Item\n        disabled={!record.operable}\n        key=\"reStart\"\n        onClick={() => {\n          setOperateAciton(3);\n          setServiceAcitonModal(true);\n        }}\n      >\n        <span style={{ fontSize: 12, paddingLeft: 5, paddingRight: 5 }}>\n          重启\n        </span>\n      </Menu.Item>\n      <Menu.Item\n        //disabled={!record.operable}\n        key=\"delete\"\n        onClick={() => {\n          queryDeleteMsg([record]);\n          setOperateAciton(4);\n          setServiceAcitonModal(true);\n          deleteConditionReset();\n        }}\n      >\n        <span style={{ fontSize: 12, paddingLeft: 5, paddingRight: 5 }}>\n          卸载\n        </span>\n      </Menu.Item>\n    </Menu>\n  );\n};\n\nconst renderStatus = (text) => {\n  switch (text) {\n    case \"未监控\":\n      return (\n        <span>\n          {renderDisc(\"notMonitored\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"启动中\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"停止中\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"重启中\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"未知\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"安装中\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"待安装\":\n      return (\n        <span>\n          {renderDisc(\"warning\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"停止\":\n      return (\n        <span>\n          {renderDisc(\"critical\", 7, -1)}\n          {text}\n        </span>\n      );\n    case \"安装失败\":\n      return (\n        <span>\n          {renderDisc(\"critical\", 7, -1)}\n          {text}\n        </span>\n      );\n    default:\n      return (\n        <span>\n          {renderDisc(\"normal\", 7, -1)}\n          {text}\n        </span>\n      );\n  }\n};\n\nconst getColumnsConfig = (\n  setIsShowDrawer,\n  setRow,\n  //setUpdateMoadlVisible,\n  fetchHistoryData,\n  // setCloseMaintainModal,\n  // setOpenMaintainModal,\n  //setShowIframe,\n  history,\n  labelsData,\n  queryRequest,\n  initfilterAppType,\n  initfilterLabelName,\n  setShowIframe,\n  setOperateAciton,\n  setServiceAcitonModal,\n  queryDeleteMsg,\n  // 删除的前置条件重置\n  deleteConditionReset\n) => {\n  return [\n    {\n      title: \"实例名称\",\n      key: \"service_instance_name\",\n      dataIndex: \"service_instance_name\",\n      sorter: (a, b) => a.service_instance_name - b.service_instance_name,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      ellipsis: true,\n      fixed: \"left\",\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <a\n              onClick={() => {\n                fetchHistoryData(record.id);\n                setIsShowDrawer({\n                  isOpen: true,\n                  record: record,\n                });\n              }}\n            >\n              {text ? text : \"-\"}\n            </a>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      sorter: (a, b) => a.ip - b.ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      //width: 140,\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        if (str == \"-\") {\n          return \"-\";\n        } else {\n          return <span>{str}</span>;\n        }\n      },\n      //ellipsis: true,\n    },\n    {\n      title: \"CPU使用率\",\n      key: \"cpu_usage\",\n      dataIndex: \"cpu_usage\",\n      align: \"center\",\n      sorter: (a, b) => a.cpu_usage - b.cpu_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{ color: colorConfig[record.cpu_status], fontWeight: 500 }}\n          >\n            {str}%\n          </span>\n        );\n      },\n    },\n    {\n      title: \"内存使用率\",\n      key: \"mem_usage\",\n      dataIndex: \"mem_usage\",\n      sorter: (a, b) => a.mem_usage - b.mem_usage,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        let str = nonEmptyProcessing(text);\n        return str == \"-\" ? (\n          \"-\"\n        ) : (\n          <span\n            style={{ color: colorConfig[record.mem_status], fontWeight: 500 }}\n          >\n            {str}%\n          </span>\n        );\n      },\n    },\n    {\n      title: \"状态\",\n      key: \"service_status\",\n      dataIndex: \"service_status\",\n      align: \"center\",\n      //ellipsis: true,\n      render: (text) => {\n        return renderStatus(text);\n      },\n    },\n    {\n      title: \"告警次数\",\n      key: \"alert_count\",\n      dataIndex: \"alert_count\",\n      align: \"center\",\n      render: (text, record) => {\n        if (text == \"-\" || text == \"0次\") {\n          return text;\n        } else {\n          return (\n            <a\n              onClick={() => {\n                text &&\n                  history.push({\n                    pathname: \"/application-monitoring/alarm-log\",\n                    state: {\n                      ip: record.ip,\n                    },\n                  });\n              }}\n            >\n              {text}\n            </a>\n          );\n        }\n      },\n      //ellipsis: true,\n    },\n    {\n      title: \"端口\",\n      key: \"port\",\n      dataIndex: \"port\",\n      align: \"center\",\n      ellipsis: true,\n      render: (text) => {\n        return <Tooltip title={text}>{text ? text : \"-\"}</Tooltip>;\n      },\n    },\n    {\n      title: \"服务名称\",\n      key: \"app_name\",\n      dataIndex: \"app_name\",\n      align: \"center\",\n      ellipsis: true,\n    },\n    {\n      title: \"版本\",\n      key: \"app_version\",\n      dataIndex: \"app_version\",\n      align: \"center\",\n      ellipsis: true,\n    },\n    {\n      title: \"功能模块\",\n      key: \"label_name\",\n      dataIndex: \"label_name\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      ellipsis: true,\n      initfilter: initfilterLabelName,\n      filterMenuList: labelsData.map((item) => ({ value: item, text: item })),\n      align: \"center\",\n      render: (text) => {\n        return <Tooltip title={text}>{text ? text : \"-\"}</Tooltip>;\n      },\n    },\n    {\n      title: \"服务类型\",\n      key: \"app_type\",\n      dataIndex: \"app_type\",\n      align: \"center\",\n      usefilter: true,\n      queryRequest: queryRequest,\n      initfilter: initfilterAppType,\n      filterMenuList: [\n        {\n          value: 0,\n          text: \"基础组件\",\n        },\n        {\n          value: 1,\n          text: \"应用服务\",\n        },\n      ],\n      render: (text) => {\n        return text ? \"应用服务\" : \"基础组件\";\n      },\n      //ellipsis: true,\n    },\n\n    {\n      title: \"集群模式\",\n      key: \"cluster_type\",\n      dataIndex: \"cluster_type\",\n      align: \"center\",\n      //ellipsis: true,\n    },\n    {\n      title: \"操作\",\n      //width: 100,\n      width: 140,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div\n            onClick={() => {\n              setRow(record);\n            }}\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n          >\n            <div style={{ margin: \"auto\" }}>\n              {record.monitor_url ? (\n                <a\n                  onClick={() => {\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.monitor_url,\n                      record: record,\n                      isLog: false,\n                    });\n                  }}\n                >\n                  监控\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\" }}>监控</span>\n              )}\n\n              {record.log_url ? (\n                <a\n                  style={{ marginLeft: 10 }}\n                  onClick={() => {\n                    setShowIframe({\n                      isOpen: true,\n                      src: record.log_url,\n                      record: record,\n                      isLog: true,\n                    });\n                  }}\n                >\n                  日志\n                </a>\n              ) : (\n                <span style={{ color: \"rgba(0, 0, 0, 0.25)\", marginLeft: 10 }}>\n                  日志\n                </span>\n              )}\n\n              <Dropdown\n                arrow\n                placement=\"bottomCenter\"\n                overlay={renderMenu(\n                  // setUpdateMoadlVisible,\n                  // setCloseMaintainModal,\n                  // setOpenMaintainModal,\n                  record,\n                  setOperateAciton,\n                  setServiceAcitonModal,\n                  queryDeleteMsg,\n                  deleteConditionReset\n                )}\n              >\n                <a style={{ marginLeft: 10 }}>\n                  更多\n                  {/* <DownOutlined style={{ position: \"relative\", top: 1 }} /> */}\n                </a>\n              </Dropdown>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n};\n\nexport default getColumnsConfig;\n"
  },
  {
    "path": "omp_web/src/pages/ServiceManagement/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpMessageModal,\n  OmpSelect,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, message, Menu, Dropdown, Input, Select, Checkbox } from \"antd\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { handleResponse, _idxInit, refreshTime } from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { useDispatch } from \"react-redux\";\nimport getColumnsConfig, { DetailHost } from \"./config/columns\";\nimport {\n  DownOutlined,\n  ExclamationCircleOutlined,\n  SearchOutlined,\n} from \"@ant-design/icons\";\nimport { useHistory, useLocation } from \"react-router-dom\";\n\nconst ServiceManagement = () => {\n  const location = useLocation();\n\n  const initIp = location.state?.ip;\n\n  console.log(initIp);\n\n  const history = useHistory();\n\n  const dispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [ipListSource, setIpListSource] = useState([]);\n  const [selectValue, setSelectValue] = useState(initIp);\n\n  const [labelsData, setLabelsData] = useState([]);\n\n  const [instanceSelectValue, setInstanceSelectValue] = useState(\"\");\n\n  const [labelControl, setLabelControl] = useState(\n    initIp ? \"ip\" : \"instance_name\"\n  );\n\n  const [installationRecordModal, setInstallationRecordModal] = useState(false);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const [isShowDrawer, setIsShowDrawer] = useState({\n    isOpen: false,\n    src: \"\",\n    record: {},\n  });\n\n  const [showIframe, setShowIframe] = useState({});\n\n  // 定义row存数据\n  const [row, setRow] = useState({});\n\n  // 服务详情历史数据\n  const [historyData, setHistoryData] = useState([]);\n\n  // 服务详情loading\n  const [historyLoading, setHistoryLoading] = useState([]);\n\n  //const [showIframe, setShowIframe] = useState({});\n\n  const [serviceAcitonModal, setServiceAcitonModal] = useState(false);\n  const [currentSerAcitonModal, setCurrentSerAcitonModal] = useState(false);\n\n  // 1启动，2停止，3重启，4删除\n  let operateObj = {\n    1: \"启动\",\n    2: \"停止\",\n    3: \"重启\",\n    4: \"删除\",\n  };\n  const [operateAciton, setOperateAciton] = useState(1);\n\n  // 删除操作的提示语\n  const [deleteMsg, setDeleteMsg] = useState(\"\");\n\n  // 删除操作的再次确认\n  const [confirmDeletion, setConfirmDeletion] = useState(false);\n\n  // 确认删除的维度\n  const [deleteDimension, setDeleteDimension] = useState(false);\n\n  // 列表查询\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.appStore.services, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        fetchIPlist();\n        fetchSearchlist();\n      });\n  }\n\n  const fetchIPlist = () => {\n    setSearchLoading(true);\n    fetchGet(apiRequest.machineManagement.ipList)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setIpListSource(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setSearchLoading(false);\n      });\n  };\n\n  // 功能模块筛选\n  const fetchSearchlist = () => {\n    //setSearchLoading(true);\n    fetchGet(apiRequest.appStore.queryLabels)\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setLabelsData(res.data);\n          //console.log(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        //setSearchLoading(false);\n      });\n  };\n\n  const fetchHistoryData = (id) => {\n    setHistoryLoading(true);\n    fetchGet(`${apiRequest.appStore.servicesDetail}/${id}/`, {\n      // params: {\n      //   id: id,\n      // },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setHistoryData(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setHistoryLoading(false);\n      });\n  };\n\n  const t = useRef(null);\n\n  // 服务的启动｜停止｜重启\n  const operateService = (data, operate, del_file) => {\n    setLoading(true);\n    fetchPost(apiRequest.appStore.servicesAction, {\n      body: {\n        data: data.map((i) => ({\n          action: operate,\n          id: i.id,\n          del_file: del_file || null,\n          operation_user: localStorage.getItem(\"username\"),\n        })),\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          message.success(`${operateObj[operateAciton]}操作下发成功`);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n        setServiceAcitonModal(false);\n        setCurrentSerAcitonModal(false);\n        // setRestartHostAgentModal(false);\n        setCheckedList([]);\n        setRow({});\n        setLoading(true);\n        t.current = setTimeout(() => {\n          fetchData(\n            { current: pagination.current, pageSize: pagination.pageSize },\n            {\n              ...pagination.searchParams,\n              ip: selectValue,\n              service_instance_name: instanceSelectValue,\n            },\n            pagination.ordering\n          );\n        }, 1500);\n      });\n  };\n\n  const containerRef = useRef(null);\n\n  const timer = useRef(null);\n\n  const [log, setLog] = useState(\"\");\n\n  const queryServiceInstallHistoryDetail = (id) => {\n    fetchGet(apiRequest.appStore.serviceInstallHistoryDetail, {\n      params: {\n        id: id,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setLog(res.data[0].log);\n          if (\n            res.data[0].install_step_status == 1 ||\n            res.data[0].install_step_status == 0\n          ) {\n            timer.current = setTimeout(() => {\n              queryServiceInstallHistoryDetail(id);\n            }, 2000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        containerRef.current.scrollTop = containerRef.current.scrollHeight;\n      });\n  };\n\n  // 删除操作的提示语获取\n  const queryDeleteMsg = (data) => {\n    fetchPost(apiRequest.appStore.servicesDeleteMsg, {\n      body: {\n        data: data.map((i) => ({\n          id: i.id,\n          action: \"4\",\n          operation_user: localStorage.getItem(\"username\"),\n        })),\n      },\n    })\n      .then((res) => {\n        //console.log(operateObj[operateAciton])\n        handleResponse(res, (res) => {\n          if (res && res.data) {\n            let key = res.data?.split(\":\")[0];\n            let values = res.data?.split(\":\")[1];\n            let arr = values?.split(\",\");\n            let dom = (\n              <div>\n                <div>{key}</div>\n                <div\n                  style={{\n                    overflow: \"auto\",\n                    maxHeight: \"240px\",\n                  }}\n                >\n                  <ExpandCollapseMsg length={6} all={arr} />\n                </div>\n              </div>\n            );\n            setDeleteMsg(dom);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {});\n  };\n\n  useEffect(() => {\n    fetchData(\n      { current: pagination.current, pageSize: pagination.pageSize },\n      {\n        ip: location.state?.ip,\n        app_type: location.state?.app_type,\n        label_name: location.state?.label_name,\n      }\n    );\n    return () => {\n      if (t.current) {\n        clearTimeout(t.current);\n      }\n    };\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <Button\n          type=\"primary\"\n          onClick={() => {\n            history.push(\"/application_management/app_store\");\n          }}\n        >\n          安装\n        </Button>\n        <Button\n          type=\"primary\"\n          style={{ marginLeft: 10 }}\n          disabled={checkedList.length == 0}\n          onClick={() => {\n            setOperateAciton(1);\n            setServiceAcitonModal(true);\n          }}\n        >\n          启动\n        </Button>\n\n        <Dropdown\n          //placement=\"bottomLeft\"\n          overlay={\n            <Menu>\n              {/* <Menu.Item\n                key=\"openMaintain\"\n                style={{ textAlign: \"center\" }}\n                onClick={() => {\n                  setOperateAciton(1);\n                  setServiceAcitonModal(true);\n                }}\n                disabled={\n                  checkedList.filter((e) => {\n                    return e.operable;\n                  }).length == 0\n                }\n              >\n                启动\n              </Menu.Item> */}\n              <Menu.Item\n                key=\"closeMaintain\"\n                style={{ textAlign: \"center\" }}\n                disabled={\n                  checkedList.filter((e) => {\n                    return e.operable;\n                  }).length == 0\n                }\n                onClick={() => {\n                  setOperateAciton(2);\n                  setServiceAcitonModal(true);\n                }}\n              >\n                停止\n              </Menu.Item>\n              <Menu.Item\n                key=\"reStartHost\"\n                style={{ textAlign: \"center\" }}\n                disabled={\n                  checkedList.filter((e) => {\n                    return e.operable;\n                  }).length == 0\n                }\n                onClick={() => {\n                  setOperateAciton(3);\n                  setServiceAcitonModal(true);\n                }}\n              >\n                重启\n              </Menu.Item>\n              <Menu.Item\n                key=\"reStartMonitor\"\n                style={{ textAlign: \"center\" }}\n                disabled={checkedList.length == 0}\n                onClick={() => {\n                  queryDeleteMsg(checkedList);\n                  setOperateAciton(4);\n                  setServiceAcitonModal(true);\n                  setConfirmDeletion(true);\n                  setDeleteDimension(false);\n                }}\n              >\n                删除\n              </Menu.Item>\n            </Menu>\n          }\n          placement=\"bottomCenter\"\n        >\n          <Button style={{ marginLeft: 10, paddingRight: 10, paddingLeft: 15 }}>\n            更多\n            <DownOutlined />\n          </Button>\n        </Dropdown>\n\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <Input.Group compact style={{ display: \"flex\" }}>\n            <Select\n              value={labelControl}\n              defaultValue=\"ip\"\n              style={{ width: 100 }}\n              onChange={(e) => {\n                setLabelControl(e);\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    ip: null,\n                    service_instance_name: null,\n                  },\n                  pagination.ordering\n                );\n                setInstanceSelectValue();\n                setSelectValue();\n              }}\n            >\n              <Select.Option value=\"ip\"> IP地址</Select.Option>\n              <Select.Option value=\"instance_name\">实例名称</Select.Option>\n            </Select>\n            {labelControl === \"ip\" && (\n              <OmpSelect\n                searchLoading={searchLoading}\n                selectValue={selectValue}\n                listSource={ipListSource}\n                setSelectValue={setSelectValue}\n                fetchData={(value) => {\n                  fetchData(\n                    { current: 1, pageSize: pagination.pageSize },\n                    { ip: value },\n                    pagination.ordering\n                  );\n                }}\n              />\n            )}\n            {labelControl === \"instance_name\" && (\n              <Input\n                placeholder=\"输入实例名称\"\n                style={{ width: 200 }}\n                allowClear\n                value={instanceSelectValue}\n                onChange={(e) => {\n                  setInstanceSelectValue(e.target.value);\n                  if (!e.target.value) {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      {\n                        ...pagination.searchParams,\n                        service_instance_name: null,\n                      },\n                      pagination.ordering\n                    );\n                  }\n                }}\n                onBlur={() => {\n                  if (instanceSelectValue) {\n                    fetchData(\n                      {\n                        current: 1,\n                        pageSize: pagination.pageSize,\n                      },\n                      {\n                        ...pagination.searchParams,\n                        service_instance_name: instanceSelectValue,\n                      },\n                      pagination.ordering\n                    );\n                  }\n                }}\n                onPressEnter={() => {\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      service_instance_name: instanceSelectValue,\n                    },\n                    pagination.ordering\n                  );\n                }}\n                suffix={\n                  !instanceSelectValue && (\n                    <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n                  )\n                }\n              />\n            )}\n          </Input.Group>\n\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              //location.state = {}\n              dispatch(refreshTime());\n              setCheckedList([]);\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                {\n                  ...pagination.searchParams,\n                  ip: selectValue,\n                  service_instance_name: instanceSelectValue,\n                },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          loading={loading}\n          //scroll={{ x: 1900 }}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={getColumnsConfig(\n            setIsShowDrawer,\n            setRow,\n            fetchHistoryData,\n            history,\n            labelsData,\n            (params) => {\n              fetchData(\n                { current: 1, pageSize: pagination.pageSize },\n                { ...pagination.searchParams, ...params },\n                pagination.ordering\n              );\n            },\n            location.state?.app_type,\n            location.state?.label_name,\n            setShowIframe,\n            setOperateAciton,\n            setCurrentSerAcitonModal,\n            queryDeleteMsg,\n            () => {\n              setConfirmDeletion(true);\n              setDeleteDimension(false);\n            }\n          )}\n          notSelectable={(record) => ({\n            // 部署中的不能选中\n            disabled: !(\n              record.service_status === \"正常\" ||\n              record.service_status === \"停止\" ||\n              record.service_status === \"未监控\"\n            ),\n          })}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  justifyContent: \"space-between\",\n                  lineHeight: 2.8,\n                }}\n              >\n                <p>已选中 {checkedList.length} 条</p>\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n          checkedState={[checkedList, setCheckedList]}\n        />\n      </div>\n      <OmpDrawer showIframe={showIframe} setShowIframe={setShowIframe} />\n      <DetailHost\n        isShowDrawer={isShowDrawer}\n        setIsShowDrawer={setIsShowDrawer}\n        loading={historyLoading}\n        data={historyData}\n        setInstallationRecordModal={setInstallationRecordModal}\n        queryServiceInstallHistoryDetail={(id) =>\n          queryServiceInstallHistoryDetail(id)\n        }\n      />\n      <OmpMessageModal\n        visibleHandle={[serviceAcitonModal, setServiceAcitonModal]}\n        // disabled={operateAciton == 4 ? confirmDeletion:false}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          let data = null;\n          if (operateAciton === 4) {\n            data = checkedList;\n          } else {\n            data = checkedList.filter((e) => {\n              return e.operable;\n            });\n          }\n          operateService(data, operateAciton, deleteDimension);\n          // fetchMaintainChange(false, [row]);\n        }}\n      >\n        <div style={{ padding: \"20px\", paddingBottom: \"10px\" }}>\n          确定要对{\" \"}\n          <span style={{ fontWeight: 500 }}> {checkedList.length}</span> 个\n          服务下发{\" \"}\n          <span style={{ fontWeight: 500 }}>{operateObj[operateAciton]}</span>{\" \"}\n          操作？\n          {operateAciton == 4 && deleteMsg && (\n            <>\n              <div style={{ paddingTop: 10 }}>{deleteMsg}</div>\n              <div style={{ position: \"relative\", top: 15 }}>\n                <Checkbox\n                  checked={deleteDimension}\n                  onChange={(e) => {\n                    setDeleteDimension(e.target.checked);\n                  }}\n                >\n                  <span style={{ fontSize: 14 }}>同时卸载服务</span>\n                </Checkbox>\n              </div>\n              {/* <div style={{ position:\"relative\", top:15, display:\"flex\", justifyContent:\"center\" }}>\n                <Checkbox checked={!confirmDeletion}\n                  onChange={(e)=>{\n                    setConfirmDeletion(!e.target.checked)\n                  }}\n                ><span style={{fontSize:14}}>确认删除</span></Checkbox>\n              </div> */}\n            </>\n          )}\n        </div>\n      </OmpMessageModal>\n      <OmpMessageModal\n        // disabled={operateAciton == 4 ? confirmDeletion:false}\n        visibleHandle={[currentSerAcitonModal, setCurrentSerAcitonModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n          operateService([row], operateAciton, deleteDimension);\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>\n          确定要对 <span style={{ fontWeight: 500 }}>当前</span> 服务下发{\" \"}\n          <span style={{ fontWeight: 500 }}>{operateObj[operateAciton]}</span>{\" \"}\n          操作？\n          {operateAciton == 4 && deleteMsg && (\n            <>\n              <div style={{ paddingTop: 10 }}>{deleteMsg}</div>\n              <div style={{ position: \"relative\", top: 15 }}>\n                <Checkbox\n                  checked={deleteDimension}\n                  onChange={(e) => {\n                    setDeleteDimension(e.target.checked);\n                  }}\n                >\n                  <span style={{ fontSize: 14 }}>同时卸载服务</span>\n                </Checkbox>\n              </div>\n              {/* <div style={{ position:\"relative\", top:15, display:\"flex\", justifyContent:\"center\" }}>\n              <Checkbox checked={!confirmDeletion}\n                onChange={(e)=>{\n                  setConfirmDeletion(!e.target.checked)\n                }}\n              ><span style={{fontSize:14}}>确认删除</span></Checkbox>\n            </div> */}\n            </>\n          )}\n        </div>\n      </OmpMessageModal>\n      <OmpMessageModal\n        title=\"安装记录\"\n        bodyStyle={{\n          backgroundColor: \"#000\",\n          color: \"#fff\",\n          padding: 0,\n        }}\n        style={{\n          top: 200,\n        }}\n        afterClose={() => {\n          if (timer.current) {\n            clearTimeout(timer.current);\n          }\n        }}\n        noFooter={true}\n        visibleHandle={[installationRecordModal, setInstallationRecordModal]}\n      >\n        <div\n          ref={containerRef}\n          style={{\n            padding: 10,\n            // marginTop: 10,\n            // padding: 10,\n            minHeight: 30,\n            height: 300,\n            // transition: \"all .2s ease-in\",\n            // overflow: \"hidden\",\n            color: \"#fff\",\n            backgroundColor: \"#000\",\n            wordWrap: \"break-word\",\n            wordBreak: \"break-all\",\n            whiteSpace: \"pre-line\",\n            overflowY: \"auto\",\n            overflowX: \"hidden\",\n          }}\n        >\n          {log ? log : \"正在安装...\"}\n        </div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nconst ExpandCollapseMsg = ({ length, all }) => {\n  const [isOpen, setIsOpen] = useState(false);\n  if (!all) {\n    return <></>;\n  }\n  if (isOpen) {\n    return (\n      <>\n        {all.map((item) => {\n          return <div key={item}>{item}</div>;\n        })}\n        <a onClick={() => setIsOpen(false)}>收起</a>\n      </>\n    );\n  } else {\n    return (\n      <>\n        {all?.slice(0, length).map((item) => {\n          return <div key={item}>{item}</div>;\n        })}\n        {all.length > length && <a onClick={() => setIsOpen(true)}>...展开</a>}\n      </>\n    );\n  }\n};\n\nexport default ServiceManagement;\n"
  },
  {
    "path": "omp_web/src/pages/ServiceManagement/index.module.less",
    "content": ".serviceManagement {\n    display: flex;\n  }\n  \n  .subMenu {\n    width: 160px;\n  }\n  \n  .warningSearch {\n    display: flex;\n    margin-top: 10px;\n    margin-bottom: 10px;\n  \n    & > div:nth-child(1) {\n      margin-right: 10px;\n    }\n  \n    & > div:last-child {\n      margin-left: auto;\n    }\n  }\n  \n  .antdTableExpandedRow {\n    margin: 0;\n    padding: 0;\n    height: 20px;\n    display: flex;\n    span {\n      margin-left: 60px;\n    }\n  }\n  \n  .redType {\n    background-color: #ff4d4f;\n    color: #fff;\n    border-color:#ff4d4f;\n  }\n  \n  .formItem {\n    margin-bottom: 15px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  \n    & > span:nth-child(1) {\n      display: inline-block;\n      width: 100px;\n      font-size: 14px;\n      //font-weight: 500;\n      color: #333;\n      text-align: right;\n    }\n  \n    & > input:nth-child(2) {\n      width: 240px;\n    }\n  }\n  .serviceTable {\n    //cursor: url('../../public/conf/logo.svg'),default;\n    cursor: pointer;\n  }\n.omp_spin_wrapper{\n  height: calc(100%);\n}\n:global {\n  .ant-dropdown-menu-item:hover, .ant-dropdown-menu-submenu-title:hover {\n    background-color:#e6f1f6;\n    color:#2e7cee\n  }\n  //悬停样式会覆盖disable样式，在这里把disable权限提高\n  .ant-dropdown-menu-item-disabled {\n    background-color: #fff!important;\n    color:rgba(0, 0, 0, 0.25)!important\n  } \n}"
  },
  {
    "path": "omp_web/src/pages/SystemLog/index.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { Button, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit, nonEmptyProcessing } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { SearchOutlined } from \"@ant-design/icons\";\n\nconst SystemLog = () => {\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [userListSource, setUserListSource] = useState([]);\n  const [searchValue, setSearchValue] = useState(\"\");\n  const [selectValue, setSelectValue] = useState();\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"序号\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n      fixed: \"left\",\n    },\n    {\n      title: \"用户名\",\n      key: \"username\",\n      width: 100,\n      dataIndex: \"username\",\n      sorter: (a, b) => a.username - b.username,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"IP地址\",\n      key: \"request_ip\",\n      dataIndex: \"request_ip\",\n      sorter: (a, b) => a.request_ip - b.request_ip,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      width: 100,\n      render: nonEmptyProcessing,\n      // render: (text) => {\n      //   if (text) {\n      //     return \"正常\";\n      //   } else {\n      //     return \"停用\";\n      //   }\n      // },\n    },\n    {\n      title: \"操作类型\",\n      key: \"request_method\",\n      width: 100,\n      dataIndex: \"request_method\",\n      // sorter: (a, b) => a.request_method - b.request_method,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"描述\",\n      key: \"description\",\n      width: 100,\n      dataIndex: \"description\",\n      // sorter: (a, b) => a.description - b.description,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"创建时间\",\n      key: \"create_time\",\n      dataIndex: \"create_time\",\n      align: \"center\",\n      width: 100,\n      sorter: (a, b) => a.create_time - b.create_time,\n      sortDirections: [\"descend\", \"ascend\"],\n      render: nonEmptyProcessing,\n      // render: (text) => {\n      //   if (text) {\n      //     return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n      //   } else {\n      //     return \"-\";\n      //   }\n      // },\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.operationRecord.querySystemLog, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (!searchParams) {\n            setUserListSource(res.data.results.map((item) => item.username));\n          }\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 60, display: \"flex\", alignItems: \"center\" }}>\n            用户名:\n          </span>\n          {/* <Input.Search placeholder=\"请输入用户名\"\n          allowClear\n          onSearch={(e)=>{\n              setSelectValue(e)\n              console.log(e)\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                {username:e},\n                pagination.ordering\n              );\n          }}\n          style={{ width: 200 }} /> */}\n          <Input\n            placeholder=\"请输入用户名\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    username: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { username: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n    </OmpContentWrapper>\n  );\n};\n\nexport default SystemLog;\n"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpMessageModal,\n} from \"@/components\";\nimport {\n  message,\n  Switch,\n} from \"antd\";\nimport { useState } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n} from \"@/utils/utils\";\nimport { fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport styles from \"./index.module.less\";\nimport {\n  ToolFilled,\n  ExclamationCircleOutlined,\n} from \"@ant-design/icons\";\nimport { getMaintenanceChangeAction } from \"./store/actionsCreators\";\nimport { useSelector, useDispatch } from \"react-redux\";\n\nconst SystemManagement = () => {\n  const [loading, setLoading] = useState(false);\n  const dispatch = useDispatch();\n\n  //是否展示维护模式提示词\n  const isMaintenance = useSelector(\n    (state) => state.systemManagement.isMaintenance\n  );\n\n  const [closeMaintenanceModal, setCloseMaintenanceModal] = useState(false);\n\n  const [openMaintenanceModal, setOpenMaintenanceModal] = useState(false);\n\n    // 更改维护模式\n    const changeMaintain = (e)=>{\n        setLoading(true);\n        fetchPost(apiRequest.environment.queryMaintainState, {\n          body: {\n            matcher_name:\"env\",\n            matcher_value:\"default\"\n          },\n        })\n          .then((res) => {\n            handleResponse(res, (res) => {\n              if(res.code == 0){\n                if (e) {   \n                    message.success(\"已进入全局维护模式\")\n                    dispatch(getMaintenanceChangeAction(true));\n                  } else {\n                    message.success(\"已退出全局维护模式\")\n                    dispatch(getMaintenanceChangeAction(false));\n                  }\n              }\n              if(res.code == 1){\n                message.warning(res.message)\n              }\n            });\n          })\n          .catch((e) => console.log(e))\n          .finally(() => {\n            setLoading(false);\n            setOpenMaintenanceModal(false);\n            setCloseMaintenanceModal(false);\n          });\n    }\n\n  return (\n    <OmpContentWrapper>\n      <div className={styles.header}>\n        <ToolFilled style={{ paddingRight: 5 }} />\n        维护模式\n      </div>\n      <div className={styles.content}>\n        <span className={styles.label}>启用: </span>\n        <Switch\n          checked={isMaintenance}\n          onChange={(e) => {\n            if (e) {\n              setOpenMaintenanceModal(true);\n            } else {\n              setCloseMaintenanceModal(true);\n            }\n          }}\n        />\n      </div>\n      <p className={styles.tips}>\n        <ExclamationCircleOutlined\n          style={{\n            position: \"relative\",\n            top: 1,\n            paddingRight: 10,\n            fontSize: 18,\n          }}\n        />\n        开启维护模式后，将暂停平台异常告警功能；此功能适用于计划性升级、变更操作期间，避免造成误报带来的影响。\n      </p>\n\n      <OmpMessageModal\n        visibleHandle={[openMaintenanceModal, setOpenMaintenanceModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n            changeMaintain(true)\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>确定进入全局维护模式 ？</div>\n      </OmpMessageModal>\n\n      <OmpMessageModal\n        visibleHandle={[closeMaintenanceModal, setCloseMaintenanceModal]}\n        title={\n          <span>\n            <ExclamationCircleOutlined\n              style={{\n                fontSize: 20,\n                color: \"#f0a441\",\n                paddingRight: \"10px\",\n                position: \"relative\",\n                top: 2,\n              }}\n            />\n            提示\n          </span>\n        }\n        loading={loading}\n        onFinish={() => {\n            changeMaintain(false)\n        }}\n      >\n        <div style={{ padding: \"20px\" }}>确定退出全局维护模式 ？</div>\n      </OmpMessageModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default SystemManagement;\n"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/index.module.less",
    "content": ".header {\n  padding: 10px;\n  padding-left: 20px;\n  background-color: #f7f7f7;\n}\n\n.content {\n    padding-left: 40px;\n    padding-top: 30px;\n    display: flex;\n    align-items: center;\n    .label {\n        padding-right: 30px;\n    }\n}\n\n.tips {\n    margin-top: 30px;\n    padding-left: 40px;\n    font-size: 13px;\n}"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/store/actionsCreators.js",
    "content": "import * as actionTypes from \"./constants\";\n\nexport const getMaintenanceChangeAction = (value) => ({\n    type: actionTypes.CHANGE_MAINTENANCE,\n    payload: {\n        isMaintenance:value\n    }\n});"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/store/constants.js",
    "content": "export const CHANGE_MAINTENANCE = \"CHANGE_MAINTENANCE\";"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/store/index.js",
    "content": "import reducer from \"./reduer\";\n\n export {\n    reducer\n };"
  },
  {
    "path": "omp_web/src/pages/SystemManagement/store/reduer.js",
    "content": "import * as actionTypes from \"./constants\";\n\nconst defaultState = {\n    isMaintenance:false\n};\n\nfunction reducer(state = defaultState,action){\n    switch(action.type){\n        case actionTypes.CHANGE_MAINTENANCE:\n            return {...state, isMaintenance: action.payload.isMaintenance};\n        default:\n            return state;\n    }\n}\n\nexport default reducer;"
  },
  {
    "path": "omp_web/src/pages/TaskRecord/index.js",
    "content": "import { OmpContentWrapper, OmpTable } from \"@/components\";\nimport { Button, Input } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  nonEmptyProcessing,\n  renderDisc,\n} from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport moment from \"moment\";\nimport { SearchOutlined } from \"@ant-design/icons\";\nimport { useHistory } from \"react-router-dom\";\n\nconst kindMap = [\"管理工具\", \"检查工具\", \"安全工具\", \"其他工具\"];\n\nconst TaskRecord = () => {\n  const [loading, setLoading] = useState(false);\n\n  const history = useHistory();\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [selectValue, setSelectValue] = useState();\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  const columns = [\n    {\n      title: \"任务标题\",\n      width: 100,\n      key: \"task_name\",\n      dataIndex: \"task_name\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      fixed: \"left\",\n      render: (text, record) => {\n        if (!text) {\n          return \"-\";\n        }\n        return (\n          <a\n            onClick={() => {\n              history.push(\n                `/utilitie/tool-management/tool-execution-results/${record.id}`\n              );\n            }}\n          >\n            {text}\n          </a>\n        );\n      },\n    },\n    {\n      title: \"分类\",\n      key: \"kind\",\n      width: 100,\n      dataIndex: \"kind\",\n      align: \"center\",\n      usefilter: true,\n      queryRequest: (params) => {\n        fetchData(\n          { current: 1, pageSize: pagination.pageSize },\n          { ...pagination.searchParams, ...params },\n          pagination.ordering\n        );\n      },\n      filterMenuList: [\n        {\n          value: 0,\n          text: \"管理工具\",\n        },\n        {\n          value: 1,\n          text: \"检查工具\",\n        },\n        {\n          value: 2,\n          text: \"安全工具\",\n        },\n        {\n          value: 3,\n          text: \"其他工具\",\n        },\n      ],\n      render: (text) => {\n        return kindMap[text];\n      },\n    },\n    {\n      title: \"执行时间\",\n      key: \"start_time\",\n      dataIndex: \"start_time\",\n      width: 100,\n      sorter: (a, b) => a.start_time - b.start_time,\n      sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text) => {\n        return text ? moment(text).format(\"YYYY-MM-DD HH:mm:ss\") : \"-\";\n      },\n    },\n    {\n      title: \"状态\",\n      key: \"status\",\n      dataIndex: \"status\",\n      width: 100,\n      align: \"center\",\n      render: (text, record, index) => {\n        if (!text && text !== 0) {\n          return \"-\";\n        } else if (text === 0) {\n          return <div>{renderDisc(\"warning\", 7, -1)}待执行</div>;\n        } else if (text === 1) {\n          return <div>{renderDisc(\"warning\", 7, -1)}执行中</div>;\n        } else if (text === 2) {\n          return <div>{renderDisc(\"normal\", 7, -1)}执行成功</div>;\n        } else if (text === 3) {\n          return <div>{renderDisc(\"critical\", 7, -1)}执行失败</div>;\n        } else {\n          return text;\n        }\n      },\n    },\n    {\n      title: \"执行用时\",\n      key: \"duration\",\n      dataIndex: \"duration\",\n      align: \"center\",\n      width: 100,\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"操作\",\n      width: 60,\n      key: \"\",\n      dataIndex: \"\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div style={{ display: \"flex\", justifyContent: \"space-around\" }}>\n            <div style={{ margin: \"auto\" }}>\n              <a\n                onClick={() => {\n                  history.push(\n                    `/utilitie/tool-management/tool-execution-results/${record.id}`\n                  );\n                }}\n              >\n                查看\n              </a>\n            </div>\n          </div>\n        );\n      },\n    },\n  ];\n\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.utilitie.queryHistory, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 50, display: \"flex\", alignItems: \"center\" }}>\n            名称:\n          </span>\n          <Input\n            placeholder=\"搜索名称\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    search: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  search: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  search: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { search: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n    </OmpContentWrapper>\n  );\n};\n\nexport default TaskRecord;\n"
  },
  {
    "path": "omp_web/src/pages/ToolExecution/index.js",
    "content": "import { useEffect, useState } from \"react\";\nimport {\n  Button,\n  Form,\n  Spin,\n  Input,\n  InputNumber,\n  Tooltip,\n  Checkbox,\n  Modal,\n  Select,\n  Upload,\n  message,\n} from \"antd\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { OmpContentWrapper, OmpTable } from \"@/components\";\nimport styles from \"./index.module.less\";\nimport { handleResponse, _idxInit } from \"@/utils/utils\";\nimport {\n  QuestionCircleOutlined,\n  CloseOutlined,\n  UploadOutlined,\n} from \"@ant-design/icons\";\nimport star from \"./asterisk.svg\";\n\nconst ToolExecution = () => {\n  const history = useHistory();\n  const locationArr = useLocation().pathname.split(\"/\");\n\n  const [loading, setLoading] = useState(false);\n\n  const [executionLoading, setExecutionLoading] = useState(false);\n\n  const [form] = Form.useForm();\n\n  const [conf, setConf] = useState();\n\n  // 是否采用纳管用户\n  const [isUseManagement, setIsUseManagement] = useState(true);\n\n  // 执行对象弹框控制器\n  const [executionTarget, setExecutionTarget] = useState(false);\n\n  // 选中的数据\n  const [checkedList, setCheckedList] = useState([]);\n\n  // 执行对象数据\n  const [executionData, setExecutionData] = useState([]);\n\n  // 是否展示执行对象校验信息\n  const [isShowErrMsg, setIsShowErrMsg] = useState(false);\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  // 执行对象列表\n  const [executionList, setExecutionList] = useState([]);\n\n  const initColumns = [\n    {\n      title: \"实例名称\",\n      key: \"instance_name\",\n      dataIndex: \"instance_name\",\n      align: \"center\",\n      ellipsis: true,\n      width: 150,\n      fixed: \"left\",\n      render: (text, record) => {\n        return (\n          <Tooltip title={text}>\n            <div style={{ paddingTop: 2 }}>{text}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"IP地址\",\n      key: \"ip\",\n      dataIndex: \"ip\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v = text || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n    {\n      title: \"Agent状态\",\n      key: \"host_agent_state\",\n      dataIndex: \"host_agent_state\",\n      align: \"center\",\n      ellipsis: true,\n      width: 120,\n      render: (text, record) => {\n        let v = text || \"-\";\n        return (\n          <Tooltip title={v}>\n            <div style={{ paddingTop: 2 }}>{v}</div>\n          </Tooltip>\n        );\n      },\n    },\n  ];\n\n  // 执行对象columns\n  const [executionColumns, setExecutionColumn] = useState(initColumns);\n\n  // 扩展\n  const [extendForm, setExtendForm] = useState([]);\n\n  const queryConf = () => {\n    setLoading(true);\n    fetchGet(\n      `${apiRequest.utilitie.queryFormConf}${\n        locationArr[locationArr.length - 1]\n      }/`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setConf(res.data);\n          if (!res.data.default_form.runuser) {\n            setIsUseManagement(true);\n          } else {\n            setIsUseManagement(false);\n          }\n          // 设置扩展表单组件默认值\n          if (res.data?.script_args) {\n            let script_args = res.data?.script_args;\n            setExtendForm(script_args);\n            script_args.forEach((item) => {\n              console.log(item);\n              form.setFieldsValue({\n                [item.key]: item.default,\n              });\n            });\n          }\n\n          // 设置固定表单默认值\n          form.setFieldsValue({\n            task_name: res?.data?.default_form?.task_name,\n            timeout: res?.data?.default_form?.timeout,\n            runuser: res?.data?.default_form?.runuser,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  const queryExecutionList = (pageParams = { current: 1, pageSize: 10 }) => {\n    setExecutionLoading(true);\n    fetchGet(\n      `${apiRequest.utilitie.queryFormConf}${\n        locationArr[locationArr.length - 1]\n      }/target-object`,\n      {\n        params: {\n          page: pageParams.current,\n          size: pageParams.pageSize,\n        },\n      }\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          // 当有需要扩展的column时 // setExecutionColumn\n          if (\n            res.data.results &&\n            res.data.results[0] &&\n            res.data.results[0].modifiable_kwargs\n          ) {\n            let extendItems = [];\n            let modifiableKwargs = res.data.results[0].modifiable_kwargs;\n            for (const key in modifiableKwargs) {\n              extendItems.push({\n                title: key,\n                key: key,\n                dataIndex: key,\n                align: \"center\",\n                ellipsis: true,\n                width: 150,\n                render: (text, record) => {\n                  return (\n                    <Tooltip title={text}>\n                      <div style={{ paddingTop: 2 }}>{text}</div>\n                    </Tooltip>\n                  );\n                },\n              });\n            }\n            setExecutionColumn([...initColumns, ...extendItems]);\n          }\n          setExecutionList(\n            res.data.results.map((m, idx) => {\n              return {\n                ...m,\n                ...m?.modifiable_kwargs,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setExecutionTarget(true);\n        setExecutionLoading(false);\n      });\n  };\n\n  const getExtendFormComponent = (item) => {\n    switch (item.type) {\n      case \"input\":\n        return (\n          <Form.Item\n            label={item.name}\n            name={item.key}\n            key={item.key}\n            rules={[{ required: item.required, message: `请输入${item.name}` }]}\n          >\n            <Input\n              placeholder={`请输入${item.name}`}\n              style={{\n                width: 460,\n              }}\n            />\n          </Form.Item>\n        );\n        break;\n      case \"select\":\n        return (\n          <Form.Item\n            label={item.name}\n            name={item.key}\n            key={item.key}\n            rules={[{ required: item.required, message: `请输入${item.name}` }]}\n          >\n            <Select\n              placeholder={`请选择${item.name}`}\n              style={{ width: 460 }}\n              allowClear\n            >\n              {item.options.map((i) => (\n                <Select.Option value={i} key={i}>\n                  {i}\n                </Select.Option>\n              ))}\n            </Select>\n          </Form.Item>\n        );\n        break;\n      case \"file\":\n        return (\n          <Form.Item\n            label={item.name}\n            name={item.key}\n            key={item.key}\n            rules={[\n              { required: item.required, message: `请将文件传入${item.name}` },\n            ]}\n          >\n            <Upload\n              name=\"file\"\n              action={apiRequest.utilitie.uploadFile}\n              maxCount={1}\n              data={{\n                module: \"ToolInfo\",\n                module_id: locationArr[locationArr.length - 1],\n              }}\n              beforeUpload={(file, fileList) => {\n                const fileSize = file.size / 1024 / 1024; //单位是mb\n                if (Math.ceil(fileSize) > 20) {\n                  message.error(\"仅支持传入20MB以内文件\");\n                  return Upload.LIST_IGNORE;\n                }\n                // return Upload.LIST_IGNORE;\n              }}\n            >\n              <Button icon={<UploadOutlined />}>点击上传</Button>\n            </Upload>\n          </Form.Item>\n        );\n        break;\n      default:\n        return \"暂无类型\";\n        break;\n    }\n  };\n\n  // 执行任务下发\n  const performTasks = () => {\n    setLoading(true);\n    let formData = form.getFieldsValue();\n\n    let defaultForm = { ...conf.default_form };\n    let scriptArgs = [...conf.script_args];\n    // defaultForm填充数据\n    for (const key in defaultForm) {\n      defaultForm[key] = formData[key];\n    }\n    defaultForm.target_objs = executionData;\n\n    // scriptArgs数据填充\n    scriptArgs = scriptArgs.map((item) => {\n      if (item.type == \"file\") {\n        let defaultData = {};\n        // 为了可读性，分两层写\n        console.log(formData);\n        if (\n          formData[item.key] &&\n          formData[item.key].file &&\n          formData[item.key].file.status == \"done\"\n        ) {\n          if (formData[item.key].file.response.code == 0) {\n            defaultData.file_name =\n              formData[item.key].file.response.data.file_name;\n            defaultData.file_url =\n              formData[item.key].file.response.data.file_url;\n            defaultData.union_id =\n              formData[item.key].file.response.data.union_id;\n          }\n        }\n\n        return {\n          ...item,\n          default: defaultData,\n        };\n      }\n      return {\n        ...item,\n        default: formData[item.key],\n      };\n    });\n\n    fetchPost(\n      `${apiRequest.utilitie.queryFormConf}${\n        locationArr[locationArr.length - 1]\n      }/answer`,\n      {\n        body: {\n          default_form: defaultForm,\n          script_args: scriptArgs,\n        },\n      }\n    )\n      .then((res) => {\n        if (res && res.data) {\n          if (res.data.code == 1) {\n            message.warning(res.data.message);\n          }\n          if (res.data.code == 0) {\n            message.success(\"执行命令下发成功\");\n            setTimeout(() => {\n              history.push(\n                `/utilitie/tool-management/tool-execution-results/${res.data.data.id}`\n              );\n            }, 300);\n          }\n        }\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryConf();\n  }, []);\n\n  return (\n    <OmpContentWrapper\n      wrapperStyle={{ padding: 0, paddingBottom: 30, backgroundColor: \"#fff\" }}\n    >\n      <div className={styles.header}>\n        <div>创建任务: {conf?.name}</div>\n        <Button\n          style={{ padding: \"3px 20px\", height: 30 }}\n          onClick={() => {\n            history?.goBack();\n          }}\n        >\n          返回\n        </Button>\n      </div>\n      <div style={{ paddingTop: 20 }}>\n        <Spin spinning={loading}>\n          <Form\n            name=\"implement\"\n            labelCol={{ span: 3 }}\n            wrapperCol={{ span: 6 }}\n            style={{ paddingTop: 10 }}\n            onFinish={() => {\n              if (executionData.length !== 0) {\n                console.log(\"通过校验了\");\n                performTasks();\n              }\n            }}\n            form={form}\n          >\n            <Form.Item\n              label=\"任务标题\"\n              name=\"task_name\"\n              rules={[{ required: true, message: \"请输入任务标题\" }]}\n            >\n              <Input\n                placeholder=\"请输入任务标题\"\n                style={{\n                  width: 460,\n                }}\n              />\n            </Form.Item>\n            <Form.Item\n              name=\"target_objs\"\n              extra={\n                <div\n                  style={{\n                    position: \"relative\",\n                    overflow: \"hidden\",\n                    height: 20,\n                  }}\n                >\n                  <span\n                    style={{\n                      transition: \"all .2s ease-in\",\n                      color: \"#ff4d4f\",\n                      position: \"absolute\",\n                      top: isShowErrMsg ? 0 : -20,\n                    }}\n                  >\n                    请选择执行对象\n                  </span>\n                </div>\n              }\n              label={\n                <span>\n                  <img\n                    src={star}\n                    style={{ position: \"relative\", top: -3, left: -4 }}\n                  />\n                  执行对象\n                </span>\n              }\n            >\n              <div\n                style={{\n                  backgroundColor: \"#f6f6f6\",\n                  minHeight: 80,\n                  width: 800,\n                  position: \"relative\",\n                  padding: \"10px\",\n                  paddingBottom: \"40px\",\n                  display: \"flex\",\n                  flexWrap: \"wrap\",\n                  border: isShowErrMsg ? \"1px solid #ff4d4f\" : \"none\",\n                }}\n              >\n                {executionData.map((i) => (\n                  <ExecutionTargetItem\n                    info={i}\n                    key={i.id}\n                    executionData={[executionData, setExecutionData]}\n                    setIsShowErrMsg={setIsShowErrMsg}\n                  />\n                ))}\n                <div\n                  style={{\n                    position: \"absolute\",\n                    display: \"flex\",\n                    bottom: 10,\n                    left: \"50%\",\n                    transform: \"translateX(-50%)\",\n                  }}\n                >\n                  <Button\n                    style={{ padding: \"3px 20px\", height: 30 }}\n                    onClick={() => {\n                      setExecutionData([]);\n                      setIsShowErrMsg(true);\n                    }}\n                  >\n                    清除\n                  </Button>\n                  <Button\n                    style={{ padding: \"3px 20px\", height: 30, marginLeft: 15 }}\n                    onClick={() => {\n                      setIsShowErrMsg(false);\n                      queryExecutionList();\n                      setCheckedList(executionData);\n                    }}\n                  >\n                    添加\n                  </Button>\n                </div>\n              </div>\n            </Form.Item>\n            <Form.Item\n              label=\"超时时间\"\n              name=\"timeout\"\n              rules={[{ required: true, message: \"请输入超时时间\" }]}\n            >\n              <Form.Item\n                name=\"timeout\"\n                noStyle\n                rules={[\n                  {\n                    validator: (rule, value, callback) => {\n                      console.log(\"执行了\");\n                      let reg = new RegExp(/^[1-9]\\d*$/, \"g\");\n                      if (value == 0) {\n                        return Promise.reject(\"请输入正整数\");\n                      }\n                      if (value) {\n                        if (!reg.test(value)) {\n                          return Promise.reject(\"请输入正整数\");\n                        }\n                        return Promise.resolve(\"success\");\n                      } else {\n                        return Promise.resolve(\"success\");\n                      }\n                    },\n                  },\n                ]}\n              >\n                <InputNumber />\n              </Form.Item>\n              <span name=\"miao\" style={{ paddingLeft: 5 }}>\n                秒\n              </span>\n              <span\n                name=\"tishi\"\n                style={{\n                  paddingLeft: 20,\n                  position: \"relative\",\n                  top: 1,\n                }}\n              >\n                {\" \"}\n                <Tooltip title=\"工具在目标主机执行的超时时间，超过该时间后，任务将退出\">\n                  <QuestionCircleOutlined />\n                </Tooltip>\n              </span>\n            </Form.Item>\n            <Form.Item\n              name=\"runuser\"\n              label=\"执行用户\"\n              rules={[\n                { required: !isUseManagement, message: \"请输入执行用户\" },\n              ]}\n            >\n              <Form.Item name=\"runuser\" noStyle>\n                <Input\n                  disabled={isUseManagement}\n                  style={{ width: 140 }}\n                  placeholder=\"请输入执行用户\"\n                />\n              </Form.Item>\n              <span style={{ paddingLeft: 15 }}>\n                <Checkbox\n                  onChange={(e) => {\n                    form.setFieldsValue({\n                      runuser: null,\n                    });\n                    setIsUseManagement(e.target.checked);\n                  }}\n                  checked={isUseManagement}\n                >\n                  使用纳管用户\n                </Checkbox>\n                <Tooltip title=\"工具在目标主机执行的用户名，请确保具有相应用户权限\">\n                  <QuestionCircleOutlined\n                    style={{ position: \"relative\", left: 15 }}\n                  />\n                </Tooltip>\n              </span>\n            </Form.Item>\n            {extendForm.map((item) => {\n              return getExtendFormComponent(item);\n            })}\n            <div\n              style={{\n                paddingTop: 30,\n                width: \"80%\",\n                display: \"flex\",\n                justifyContent: \"center\",\n              }}\n            >\n              <Form.Item>\n                <Button\n                  type=\"primary\"\n                  htmlType=\"submit\"\n                  loading={loading}\n                  onClick={() => {\n                    if (executionData.length == 0) {\n                      setIsShowErrMsg(true);\n                    }\n                  }}\n                >\n                  执行\n                </Button>\n              </Form.Item>\n            </div>\n          </Form>\n        </Spin>\n      </div>\n      <Modal\n        title=\"选择执行对象\"\n        width={800}\n        afterClose={() => {\n          setCheckedList([]);\n        }}\n        onCancel={() => {\n          setExecutionTarget(false);\n        }}\n        visible={executionTarget}\n        footer={null}\n        //width={1000}\n        loading={executionLoading}\n        bodyStyle={{\n          paddingLeft: 30,\n          paddingRight: 30,\n        }}\n        destroyOnClose\n      >\n        <div>\n          <div style={{ border: \"1px solid rgb(235, 238, 242)\" }}>\n            <OmpTable\n              size=\"small\"\n              scroll={{ x: executionColumns.length * 150 }}\n              loading={executionLoading}\n              columns={executionColumns}\n              onChange={(e, filters, sorter) => {\n                setTimeout(() => {\n                  queryExecutionList(e);\n                }, 200);\n              }}\n              dataSource={executionList}\n              pagination={{\n                showSizeChanger: true,\n                pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n                showTotal: () => (\n                  <div\n                    style={{\n                      display: \"flex\",\n                      width: \"200px\",\n                      justifyContent: \"space-between\",\n                      lineHeight: 2.8,\n                      position: \"relative\",\n                      top: -3,\n                    }}\n                  >\n                    <p>已选中 {checkedList.length} 条</p>\n                    <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                      共计{\" \"}\n                      <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                        {pagination.total}\n                      </span>{\" \"}\n                      条\n                    </p>\n                  </div>\n                ),\n                ...pagination,\n              }}\n              rowKey={(record) => record.id}\n              checkedState={[checkedList, setCheckedList]}\n            />\n          </div>\n          <div\n            style={{ display: \"flex\", justifyContent: \"center\", marginTop: 30 }}\n          >\n            <div\n              style={{\n                width: 170,\n                display: \"flex\",\n                justifyContent: \"space-between\",\n              }}\n            >\n              <Button onClick={() => setExecutionTarget(false)}>取消</Button>\n              <Button\n                type=\"primary\"\n                style={{ marginLeft: 16 }}\n                loading={executionLoading}\n                // disabled={checkedList.length == 0}\n                onClick={() => {\n                  setExecutionData(checkedList);\n                  setExecutionTarget(false);\n                }}\n              >\n                确认\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Modal>\n    </OmpContentWrapper>\n  );\n};\n\nconst ExecutionTargetItem = ({ info, executionData, setIsShowErrMsg }) => {\n  const [data, setData] = executionData;\n  return (\n    <div\n      style={{\n        backgroundColor: \"#fff\",\n        border: \"solid 1px #d9d9d9\",\n        height: 28,\n        paddingLeft: 25,\n        borderRadius: 4,\n        display: \"flex\",\n        alignItems: \"center\",\n        marginLeft: 10,\n        marginBottom: 10,\n        overflow: \"hidden\",\n        textOverflow: \" ellipsis\",\n        whiteSpace: \"nowrap\",\n        position: \"relative\",\n      }}\n    >\n      {info.instance_name}\n      <CloseOutlined\n        style={{ paddingLeft: 15, paddingRight: 10, cursor: \"pointer\" }}\n        onClick={() => {\n          let result = data.filter((i) => !i.id == info.id);\n          if (result.length == 0) {\n            setIsShowErrMsg(true);\n          }\n          setData(() => {\n            return result;\n          });\n        }}\n      />\n    </div>\n  );\n};\n\nexport default ToolExecution;\n"
  },
  {
    "path": "omp_web/src/pages/ToolExecution/index.module.less",
    "content": ".header {\n    height: 54px;\n    display: flex;\n    align-items: center;\n    border-bottom: 1px solid #d6d6d6;\n    padding-top: 5px;\n    padding-left: 20px;\n    font-size: 17px;\n    color: #222222;\n    justify-content: space-between;\n    padding-right: 35px;\n}"
  },
  {
    "path": "omp_web/src/pages/ToolExecutionResults/index.js",
    "content": "import {\n  OmpContentWrapper,\n  OmpTable,\n  OmpMessageModal,\n  OmpSelect,\n  OmpDatePicker,\n  OmpDrawer,\n} from \"@/components\";\nimport { Button, Collapse, Tooltip, Table, Spin } from \"antd\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport styles from \"./index.module.less\";\nimport {\n  CaretRightOutlined,\n  QuestionCircleOutlined,\n  DownloadOutlined,\n} from \"@ant-design/icons\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport {\n  handleResponse,\n  _idxInit,\n  refreshTime,\n  downloadFile,\n} from \"@/utils/utils\";\nimport moment from \"moment\";\n\nconst { Panel } = Collapse;\n\nconst statusTextMap = [\n  \"等待执行\",\n  \"执行中\",\n  \"执行成功\",\n  \"执行失败\",\n  \"任务超时\",\n];\n\nconst statusColorMap = [\"#ffbf00\", \"#ffbf00\", \"#76ca68\", \"#f04134\", \"#f04134\"];\n\nconst ToolExecutionResults = () => {\n  const history = useHistory();\n  const locationArr = useLocation().pathname.split(\"/\");\n  const [loading, setLoading] = useState(false);\n  const [info, setInfo] = useState({});\n\n  // 当前选中的ip\n  const [currentIp, setCurrentIp] = useState(null);\n  // 当前选中的状态\n  const [currentStatus, setCurrentStatus] = useState(null);\n\n  const timer = useRef(null);\n\n  const queryData = (init) => {\n    init && setLoading(true);\n    fetchGet(\n      `${apiRequest.utilitie.queryResult}${\n        locationArr[locationArr.length - 1]\n      }/`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setInfo(res.data);\n          if (res.data.tool_detail) {\n            currentIp == null && setCurrentIp(res.data.tool_detail[0].ip);\n            currentStatus == null &&\n              setCurrentStatus(res.data.tool_detail[0].status);\n          }\n\n          // 等待执行 和 执行中 要继续请求\n          if (res.data.status == 0 || res.data.status == 1) {\n            timer.current = setTimeout(() => {\n              queryData();\n            }, 5000);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        init && setLoading(false);\n      });\n  };\n\n  // 执行结果当前选中的项\n  const currentItem = info.tool_detail?.filter((i) => i.ip == currentIp)[0];\n\n  // 确定执行结果的tab状态\n  const tabRenderStatus = (status) => {\n    return info.tool_detail?.filter((i) => i.status == status).length;\n  };\n\n  useEffect(() => {\n    queryData(\"init\");\n    return ()=>{\n      timer.current && clearTimeout(timer.current)\n    }\n  }, []);\n  return (\n    <OmpContentWrapper\n      wrapperStyle={{ padding: \"10px 30px 30px 30px\", backgroundColor: \"#fff\" }}\n    >\n      <Spin spinning={loading}>\n        <div className={styles.resultTitle}>\n          <div style={{ display: \"flex\" }}>\n            {info.task_name || \"-\"}{\" \"}\n            <span\n              className={styles.resultTitleStatus}\n              style={{ color: statusColorMap[info.status] }}\n            >\n              {statusTextMap[info.status]}\n            </span>\n          </div>\n          <a style={{ fontSize: 14 }} onClick={() => history?.goBack()}>\n            返回\n          </a>\n        </div>\n        <Collapse\n          bordered={false}\n          defaultActiveKey={[\"baseInfo\", \"executionInfo\", \"executionResult\"]}\n          onChange={() => {}}\n          style={{ marginTop: 0, border: \"none\", backgroundColor: \"#fff\" }}\n          expandIcon={({ isActive }) => (\n            <CaretRightOutlined rotate={isActive ? 90 : 0} />\n          )}\n        >\n          <Panel\n            header=\"基本信息\"\n            key=\"baseInfo\"\n            className={styles.panelItem}\n            style={{ paddingBottom: 1 }}\n          >\n            <div className={styles.baseTable}>\n              <div className={styles.baseTableFirstRow}>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>\n                    操作用户{\" \"}\n                    <Tooltip title=\"执行本次任务的平台用户\">\n                      <QuestionCircleOutlined\n                        style={{\n                          cursor: \"pointer\",\n                          fontWeight: 400,\n                          marginLeft: 5,\n                        }}\n                      />\n                    </Tooltip>\n                  </div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.operator || \"-\"}\n                  </div>\n                </div>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>\n                    执行对象{\" \"}\n                    <Tooltip title=\"实用工具操作的目标对象类型，可以是主机或者具体服务\">\n                      <QuestionCircleOutlined\n                        style={{\n                          cursor: \"pointer\",\n                          fontWeight: 400,\n                          marginLeft: 5,\n                        }}\n                      />\n                    </Tooltip>\n                  </div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.tool?.target_name || \"-\"}\n                  </div>\n                </div>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>\n                    目标数量{\" \"}\n                    <Tooltip title=\"本次执行对象的数量\">\n                      <QuestionCircleOutlined\n                        style={{\n                          cursor: \"pointer\",\n                          fontWeight: 400,\n                          marginLeft: 5,\n                        }}\n                      />\n                    </Tooltip>\n                  </div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.count || \"-\"}\n                  </div>\n                </div>\n                <div\n                  className={styles.baseTableItem}\n                  style={{ borderRight: \"none\" }}\n                >\n                  <div className={styles.baseTableItemLabel}>\n                    执行用户\n                    <Tooltip title=\"工具在目标主机执行的用户名\">\n                      <QuestionCircleOutlined\n                        style={{\n                          cursor: \"pointer\",\n                          fontWeight: 400,\n                          marginLeft: 5,\n                        }}\n                      />\n                    </Tooltip>\n                  </div>\n                  <div className={styles.baseTableItemContent}>\n                    {\" \"}\n                    {info.run_user || \"-\"}\n                  </div>\n                </div>\n              </div>\n              <div className={styles.baseTableSecondRow}>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>\n                    超时时间 (s)\n                    <Tooltip title=\"工具在目标主机的执行的超时时间\">\n                      <QuestionCircleOutlined\n                        style={{\n                          cursor: \"pointer\",\n                          fontWeight: 400,\n                          marginLeft: 5,\n                        }}\n                      />\n                    </Tooltip>\n                  </div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.time_out || \"-\"}\n                  </div>\n                </div>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>开始时间</div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.start_time\n                      ? moment(info.start_time).format(\"YYYY-MM-DD HH:mm:ss\")\n                      : \"-\"}\n                  </div>\n                </div>\n                <div className={styles.baseTableItem}>\n                  <div className={styles.baseTableItemLabel}>结束时间</div>\n                  <div className={styles.baseTableItemContent}>\n                    {\" \"}\n                    {info.end_time\n                      ? moment(info.end_time).format(\"YYYY-MM-DD HH:mm:ss\")\n                      : \"-\"}\n                  </div>\n                </div>\n                <div\n                  className={styles.baseTableItem}\n                  style={{ borderRight: \"none\" }}\n                >\n                  <div className={styles.baseTableItemLabel}>总耗时</div>\n                  <div className={styles.baseTableItemContent}>\n                    {info.duration || \"-\"}\n                  </div>\n                </div>\n              </div>\n            </div>\n          </Panel>\n          <Panel\n            header=\"执行参数信息\"\n            key=\"executionInfo\"\n            className={styles.panelItem}\n            style={{ marginTop: 25 }}\n          >\n            <div style={{ border: \"1px solid #d6d6d6\", marginTop: 10 }}>\n              <Table\n                size=\"middle\"\n                columns={[\n                  {\n                    title: \"参数名称\",\n                    key: \"name\",\n                    dataIndex: \"name\",\n                    align: \"center\",\n                    width: 120,\n                    render: (text) => text || \"-\",\n                  },\n                  {\n                    title: \"参数值\",\n                    key: \"value\",\n                    dataIndex: \"value\",\n                    width: 120,\n                    align: \"center\",\n                    // render: (text) => (text ? argType[text] : \"-\"),\n                  },\n                ]}\n                pagination={false}\n                dataSource={info.tool_args}\n              />\n            </div>\n          </Panel>\n          <Panel\n            header=\"执行结果\"\n            key=\"executionResult\"\n            className={styles.panelItem}\n            style={{ marginTop: 25 }}\n          >\n            <div style={{ marginTop: 10 }}>\n              <div\n                style={{\n                  height: 36,\n                  width: 450,\n                  display: \"flex\",\n                  borderTop: \"1px solid #d6d6d6\",\n                  borderLeft: \"1px solid #d6d6d6\",\n                  borderRight: \"1px solid #d6d6d6\",\n                  backgroundColor: \"#fff\",\n                  position: \"relative\",\n                  top: 1,\n                }}\n              >\n                <div\n                  style={{\n                    flex: 1,\n                    height: 35,\n                    lineHeight: \"35px\",\n                    textAlign: \"center\",\n                    color:\n                      currentStatus == 0\n                        ? \"#007bf3\"\n                        : tabRenderStatus(0) == 0 && \"rgba(0, 0, 0, 0.25)\",\n                    cursor:\n                      currentStatus == 0\n                        ? \"pointer\"\n                        : tabRenderStatus(0) == 0\n                        ? \"not-allowed\"\n                        : \"pointer\",\n                  }}\n                  onClick={() => {\n                    if (tabRenderStatus(0) !== 0) {\n                      setCurrentStatus(0);\n                      setCurrentIp(\n                        info.tool_detail?.filter((i) => i.status == 0)[0].ip\n                      );\n                    }\n                  }}\n                >\n                  待执行({tabRenderStatus(0)})\n                </div>\n                <div\n                  style={{\n                    flex: 1,\n                    height: 35,\n                    lineHeight: \"35px\",\n                    textAlign: \"center\",\n                    color:\n                      currentStatus == 1\n                        ? \"#007bf3\"\n                        : tabRenderStatus(1) == 0 && \"rgba(0, 0, 0, 0.25)\",\n                    cursor:\n                      currentStatus == 1\n                        ? \"pointer\"\n                        : tabRenderStatus(1) == 0\n                        ? \"not-allowed\"\n                        : \"pointer\",\n                  }}\n                  onClick={() => {\n                    if (tabRenderStatus(1) !== 0) {\n                      setCurrentStatus(1);\n                      setCurrentIp(\n                        info.tool_detail?.filter((i) => i.status == 1)[0].ip\n                      );\n                    }\n                  }}\n                >\n                  执行中({tabRenderStatus(1)})\n                </div>\n                <div\n                  style={{\n                    flex: 1,\n                    height: 35,\n                    lineHeight: \"35px\",\n                    textAlign: \"center\",\n                    color:\n                      currentStatus == 2\n                        ? \"#007bf3\"\n                        : tabRenderStatus(2) == 0 && \"rgba(0, 0, 0, 0.25)\",\n                    cursor:\n                      currentStatus == 2\n                        ? \"pointer\"\n                        : tabRenderStatus(2) == 0\n                        ? \"not-allowed\"\n                        : \"pointer\",\n                  }}\n                  onClick={() => {\n                    if (tabRenderStatus(2) !== 0) {\n                      setCurrentStatus(2);\n                      setCurrentIp(\n                        info.tool_detail?.filter((i) => i.status == 2)[0].ip\n                      );\n                    }\n                  }}\n                >\n                  执行成功({tabRenderStatus(2)})\n                </div>\n                <div\n                  style={{\n                    flex: 1,\n                    height: 35,\n                    lineHeight: \"35px\",\n                    textAlign: \"center\",\n                    color:\n                      currentStatus == 3\n                        ? \"#007bf3\"\n                        : tabRenderStatus(3) == 0 && \"rgba(0, 0, 0, 0.25)\",\n                    cursor:\n                      currentStatus == 3\n                        ? \"pointer\"\n                        : tabRenderStatus(3) == 0\n                        ? \"not-allowed\"\n                        : \"pointer\",\n                  }}\n                  onClick={() => {\n                    if (tabRenderStatus(3) !== 0) {\n                      setCurrentStatus(3);\n                      setCurrentIp(\n                        info.tool_detail?.filter((i) => i.status == 3)[0].ip\n                      );\n                    }\n                  }}\n                >\n                  执行失败({tabRenderStatus(3)})\n                </div>\n                <div\n                  style={{\n                    flex: 1,\n                    height: 35,\n                    lineHeight: \"35px\",\n                    textAlign: \"center\",\n                    color:\n                      currentStatus == 4\n                        ? \"#007bf3\"\n                        : tabRenderStatus(4) == 0 && \"rgba(0, 0, 0, 0.25)\",\n                    cursor:\n                      currentStatus == 4\n                        ? \"pointer\"\n                        : tabRenderStatus(4) == 0\n                        ? \"not-allowed\"\n                        : \"pointer\",\n                  }}\n                  onClick={() => {\n                    if (tabRenderStatus(4) !== 0) {\n                      setCurrentStatus(4);\n                      setCurrentIp(\n                        info.tool_detail?.filter((i) => i.status == 4)[0].ip\n                      );\n                    }\n                  }}\n                >\n                  任务超时({tabRenderStatus(4)})\n                </div>\n              </div>\n              <div\n                style={{\n                  height: 400,\n                  border: \"1px solid #d6d6d6\",\n                  display: \"flex\",\n                }}\n              >\n                <div\n                  style={{\n                    width: 160,\n                    height: \"100%\",\n                    overflowY: \"auto\",\n                    // paddingTop: 5,\n                  }}\n                >\n                  {info.tool_detail\n                    ?.filter((i) => i.status == currentStatus)\n                    .map((item) => {\n                      return (\n                        <div\n                          onClick={() => {\n                            setCurrentIp(item.ip);\n                          }}\n                          style={{\n                            cursor: \"pointer\",\n                            padding: \"10px 0px\",\n                            backgroundColor:\n                              currentIp == item.ip ? \"#2f7bed\" : \"#fff\",\n                            color: currentIp == item.ip ? \"#fff\" : \"#37474d\",\n                            textAlign: \"center\",\n                          }}\n                        >\n                          {item.ip}\n                        </div>\n                      );\n                    })}\n                </div>\n                <div\n                  style={{\n                    flex: 1,\n                    height: \"100%\",\n                    backgroundColor: \"#f6f6f6\",\n                    padding: 10,\n                  }}\n                >\n                  {currentItem && currentItem.url && (\n                    <Button\n                      style={{ marginBottom: 10 }}\n                      icon={<DownloadOutlined />}\n                      onClick={() => {\n                        downloadFile(`/${currentItem.url}`);\n                      }}\n                    >\n                      <span style={{ color: \"#818181\" }}>下载文件</span>\n                    </Button>\n                  )}\n                  <div\n                    style={{\n                      height:\n                        currentItem && currentItem.url\n                          ? \"calc(100% - 40px)\"\n                          : \"100%\",\n                      backgroundColor: \"#000000\",\n                      padding: 20,\n                      paddingTop: 25,\n                      color: \"#fff\",\n                      wordWrap: \"break-word\",\n                      wordBreak: \"break-all\",\n                      whiteSpace: \"pre-line\",\n                      overflowY: \"auto\",\n                      overflowX: \"hidden\",\n                    }}\n                  >\n                    {currentItem?.log}\n                  </div>\n                </div>\n              </div>\n            </div>\n          </Panel>\n        </Collapse>\n      </Spin>\n    </OmpContentWrapper>\n  );\n};\n\nexport default ToolExecutionResults;\n"
  },
  {
    "path": "omp_web/src/pages/ToolExecutionResults/index.module.less",
    "content": ".resultTitle {\n  display: flex;\n  justify-content: space-between;\n  height: 62px;\n  // background-color: red;\n  line-height: 62px;\n  padding-left: 0px;\n  font-size: 18px;\n  color: rgb(34, 34, 34);\n  .resultTitleStatus {\n    font-size: 14px;\n    height: 62px;\n    display: block;\n    padding-top: 1px;\n    padding-left: 20px;\n  }\n}\n\n.panelItem {\n  background: #f6f6f6;\n  border-radius: 4px;\n  border: 0;\n  overflow: hidden;\n  border-bottom: 0 !important;\n}\n.panelItem > div:nth-child(1) {\n  padding: 8px 15px;\n}\n\n.panelItem > div:nth-child(2) > div {\n  background-color: #fff !important;\n  padding: 0;\n}\n\n.baseTable {\n  margin-top: 10px;\n  border: 1px solid #d6d6d6;\n  .baseTableFirstRow {\n    display: flex;\n    width: 100%;\n    height: 42px;\n    .baseTableItem {\n      flex: 1;\n      display: flex;\n      align-items: center;\n      border-right: 1px solid #d6d6d6;\n      .baseTableItemLabel {\n        flex: 2;\n        border-right: 1px solid #d6d6d6;\n        height: 100%;\n        line-height: 42px;\n        padding-left: 15px;\n        background-color: #f6f6f6;\n        font-weight: 500;\n      }\n      .baseTableItemContent {\n        flex: 3;\n        padding-left: 15px;\n      }\n    }\n  }\n  .baseTableSecondRow {\n    display: flex;\n    width: 100%;\n    height: 42px;\n    .baseTableItem {\n      flex: 1;\n      display: flex;\n      align-items: center;\n      border-top: 1px solid #d6d6d6;\n      border-right: 1px solid #d6d6d6;\n      .baseTableItemLabel {\n        flex: 2;\n        border-right: 1px solid #d6d6d6;\n        height: 100%;\n        line-height: 42px;\n        padding-left: 15px;\n        background-color: #f6f6f6;\n        font-weight: 500;\n      }\n      .baseTableItemContent {\n        flex: 3;\n        padding-left: 15px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/config/card.js",
    "content": "import styles from \"./index.module.less\";\nimport { useState } from \"react\";\nimport initLogo from \"../initLogo/tools.svg\";\n\nconst kindMap = [\"管理工具\", \"检查工具\", \"安全工具\", \"其他工具\"];\n\nconst Card = ({ idx, history, info, tabKey }) => {\n  const [isHover, setIsHover] = useState(false);\n  // let href = window.location.href.split(\"#\")[0];\n  // console.log(href)\n  return (\n    <div\n      className={styles.cardContainer}\n      style={{\n        transition: \"all .2s ease-in-out\",\n        width: \"calc(97.7% / 4)\",\n        marginLeft: (idx - 1) % 4 !== 0 && \"0.75%\",\n        height: 145,\n        boxSizing: \"border-box\",\n        //border: \"1px solid #000\",\n        marginTop: 10,\n        position: \"relative\",\n        top: 0,\n        backgroundColor: \"#fff\",\n        paddingLeft: 10,\n        paddingRight: 10,\n      }}\n      onMouseEnter={() => {\n        setIsHover(true);\n      }}\n      onMouseLeave={() => {\n        setIsHover(false);\n      }}\n      onClick={() => {\n        history?.push({\n          pathname: `/utilitie/tool-management/tool-management-detail/${info.id}`,\n        });\n      }}\n    >\n      <div className={styles.cardContent}>\n        <div style={{ width: \"100%\", paddingTop: 5, display: \"flex\" }}>\n          {!info.logo ? (\n            <div\n              style={{\n                width: 50,\n                height: 50,\n                borderRadius: \"50%\",\n                border: \"1px solid #a8d0f8\",\n                display: \"flex\",\n                justifyContent: \"center\",\n                alignItems: \"center\",\n                marginLeft: 10,\n                marginRight: 10,\n                overflow: \"hidden\",\n                fontSize: 22,\n                // backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n                backgroundColor: \"#f5f5f5\",\n                color: \"#fff\",\n              }}\n            >\n              <div\n                style={{\n                  textAlign: \"center\",\n                  position: \"relative\",\n                  display: \"flex\",\n                  alignItems: \"center\",\n                }}\n              >\n                {/* {info.name && info.name[0].toLocaleUpperCase()} */}\n                <img\n                  style={{\n                    width: \"35px\",\n                    height: \"35px\",\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                  src={initLogo}\n                />\n              </div>\n            </div>\n          ) : (\n            <div\n              style={{\n                width: 50,\n                height: 50,\n                borderRadius: \"50%\",\n                border: \"1px solid #a8d0f8\",\n                display: \"flex\",\n                justifyContent: \"center\",\n                alignItems: \"center\",\n                marginLeft: 10,\n                marginRight: 10,\n                overflow: \"hidden\",\n                fontSize: 22,\n                // backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n                backgroundColor: \"#f5f5f5\",\n                color: \"#fff\",\n              }}\n            >\n              <div\n                style={{\n                  textAlign: \"center\",\n                  position: \"relative\",\n                  display: \"flex\",\n                  alignItems: \"center\",\n                }}\n              >\n                {/* {info.name && info.name[0].toLocaleUpperCase()} */}\n                <img\n                  style={{\n                    width: \"35px\",\n                    height: \"35px\",\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                  src={`${info.logo}`}\n                />\n              </div>\n            </div>\n          )}\n\n          <div\n            style={{\n              flex: 1,\n              marginLeft: 10,\n              display: \"flex\",\n              alignItems: \"center\",\n              color: isHover ? \"#247fe6\" : \"#222222\",\n            }}\n          >\n            {info.name}\n          </div>\n        </div>\n        <div\n          style={{\n            //flex: 1,\n            fontSize: 13,\n            color: \"#a2a2a2\",\n            position: \"relative\",\n            width: \"calc(100%)\",\n          }}\n          onClick={() => {}}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              justifyContent: \"space-between\",\n              fontSize: 12,\n              padding: \"8px 10px 10px 10px\",\n              //fontSize:12\n            }}\n          >\n            <span style={{ color: \"#777373\" }}>[ {kindMap[info.kind]} ]</span>\n            <span style={{ color: \"#383838\" }}>\n              使用次数：{info.used_number}\n            </span>\n          </div>\n          <p className={styles.text}>\n            {/* <Tooltip placement=\"top\" title={info[nameObj[tabKey].description]}> */}\n            {info.description}\n            {/* </Tooltip> */}\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst InitLogo = ({ name }) => {\n  return (\n    <div\n      style={{\n        width: 50,\n        height: 50,\n        borderRadius: \"50%\",\n        border: \"1px solid #a8d0f8\",\n        display: \"flex\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        marginLeft: 10,\n        marginRight: 10,\n        overflow: \"hidden\",\n        fontSize: 22,\n        backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n        backgroundColor: \"#f5f5f5\",\n        color: \"#fff\",\n      }}\n    >\n      <div\n        style={{\n          textAlign: \"center\",\n          position: \"relative\",\n          display: \"flex\",\n          alignItems: \"center\",\n        }}\n      >\n        {name && name[0].toLocaleUpperCase()}\n      </div>\n    </div>\n  );\n};\n\nexport default Card;\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/config/index.module.less",
    "content": ".cardContainer:hover {\n    // top: -4px !important;\n    box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);\n    color: black !important;\n  }\n  \n  .cardContainer {\n    border-radius: 4px;\n    cursor: pointer;\n    .cardContent {\n      height: 100%;\n      // display: flex;\n      padding-top: 10px;\n      .text {\n        font-size: 12px;\n        display: -webkit-box;\n        -webkit-line-clamp: 1;\n        -webkit-box-orient: vertical;\n        // height: r(210);\n        // font-size: r(42);\n        // white-space: nowrap;\n        text-overflow: ellipsis;\n        overflow: hidden;\n        width: 100%;\n        margin: 0;\n        color: #4f4f4f;\n        padding-left: 10px;\n      }\n    }\n    .cardBtn {\n      display: flex;\n      //justify-content: space-around;\n      border-top: solid 1px #e7e7e7;\n      align-items: center;\n      height: 24%;\n      // /color: #818181;\n      font-size: 13px;\n      & > div {\n        width: 50%;\n        text-align: center;\n      }\n      // & > div:hover {\n      //   color: rgb(46, 124, 238);\n      // }\n    }\n  }\n  .detailContainer {\n    background-color: #fff;\n    padding-top: 10px;\n    padding-left: 3px;\n    padding-right: 3px;\n    .detailHeader {\n      overflow: hidden;\n      background-color: #f5f6f5;\n      height: 40px;\n      line-height: 40px;\n      padding-left: 20px;\n      display: flex;\n      justify-content: space-between;\n    }\n  \n    .detailTitle {\n      height: 120px;\n      display: flex;\n      padding-left: 20px;\n      padding-top: 0px;\n      padding-right: 200px;\n      // background-color: aliceblue;\n      align-items: center;\n      .detailTitleDescribe {\n        height: 80px;\n        padding-left: 60px;\n        //word-wrap: break-word;\n        width: calc(100% - 120px);\n        color: #4f4f4f;\n        position: relative;\n        .detailTitleDescribeText {\n          display: -webkit-box;\n          -webkit-line-clamp: 2;\n          -webkit-box-orient: vertical;\n          //height: r(210);\n          //font-size: r(42);\n          // white-space: nowrap;\n          text-overflow: ellipsis;\n          overflow: hidden;\n          // /width: 100%;\n        }\n      }\n    }\n    .detailContent {\n      color: #4f4f4f;\n      padding-left: 30px;\n      .detailContentItem {\n        display: flex;\n        padding-top: 15px;\n        .detailContentItemLabel {\n          width: 120px;\n        }\n      }\n    }\n    .detailDependence {\n      padding-left: 30px;\n      margin-top: 40px;\n      font-size: 16px;\n      //background-color: red;\n      .detailDependenceTable {\n        //width: 100%;\n        margin-right: 20px;\n        border: 1px solid #d6d6d6;\n        margin-top: 20px;\n      }\n    }\n  }\n  .backIcon:hover {\n    color: rgb(46, 124, 238);\n  }"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/detail/Readme.js",
    "content": "import * as markdown from \"markdown-it\";\nimport * as colors from \"markdown-it-colors\";\nimport hljs from \"highlight.js\";\n\nimport \"highlight.js/styles/monokai-sublime.css\";\nconsole.log(hljs);\n\nconst Readme = ({ text = \"\" }) => {\n  var md = markdown({\n    gfm: true,\n    pedantic: false,\n    sanitize: false,\n    tables: true,\n    breaks: false,\n    smartLists: true,\n    smartypants: false,\n    highlight: function (code) {\n      return hljs.highlightAuto(code).value;\n    },\n  });\n\n  console.log(md);\n  console.log(markdown());\n  return (\n    <div\n      dangerouslySetInnerHTML={{\n        __html: md.render(text),\n      }}\n    ></div>\n  );\n};\n\nexport default Readme;\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/detail/index.js",
    "content": "import { OmpContentWrapper } from \"@/components\";\nimport { Button, Table, Tooltip, Spin } from \"antd\";\nimport { useState, useEffect } from \"react\";\nimport { handleResponse, _idxInit, downloadFile } from \"@/utils/utils\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { QuestionCircleOutlined, FileProtectOutlined } from \"@ant-design/icons\";\nimport { useHistory, useLocation } from \"react-router-dom\";\nimport styles from \"./index.module.less\";\nimport Readme from \"./Readme.js\";\nimport initLogo from \"../initLogo/tools.svg\";\n\nconst kindMap = [\"管理工具\", \"检查工具\", \"安全工具\", \"其他工具\"];\n\nconst argType = {\n  select: \"单选\",\n  file: \"文件\",\n  input: \"单行文本\",\n};\n\nconst Details = () => {\n  const history = useHistory();\n  const [loading, setLoading] = useState(false);\n  // console.log([...location].pop())\n  // let href = window.location.href.split(\"#\")[0];\n  const locationArr = useLocation().pathname.split(\"/\");\n\n  const [info, setInfo] = useState({});\n\n  const queryInfo = () => {\n    setLoading(true);\n    fetchGet(\n      `${apiRequest.utilitie.queryList}${locationArr[locationArr.length - 1]}/`\n    )\n      .then((res) => {\n        handleResponse(res, (res) => {\n          setInfo(res.data);\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    queryInfo();\n  }, []);\n\n  return (\n    <OmpContentWrapper wrapperStyle={{ padding: \"20px 30px\" }}>\n      <Spin spinning={loading}>\n        <div className={styles.header}>\n          {!info.logo ? (\n            <div className={styles.icon}>\n              <div\n                style={{\n                  width: 80,\n                  height: 80,\n                  // borderRadius: \"50%\",\n                  // border: \"1px solid #a8d0f8\",\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  alignItems: \"center\",\n                  // marginLeft: 10,\n                  // marginRight: 10,\n                  overflow: \"hidden\",\n                  fontSize: 22,\n                  // backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n                  backgroundColor: \"#f5f5f5\",\n                  color: \"#fff\",\n                }}\n              >\n                <div\n                  style={{\n                    textAlign: \"center\",\n                    position: \"relative\",\n                    display: \"flex\",\n                    alignItems: \"center\",\n                  }}\n                >\n                  {/* {info.name && info.name[0].toLocaleUpperCase()} */}\n                  <img\n                    style={{\n                      width: \"65px\",\n                      height: \"65px\",\n                      position: \"relative\",\n                      top: 1,\n                    }}\n                    src={initLogo}\n                  />\n                </div>\n              </div>\n            </div>\n          ) : (\n            <div className={styles.icon}>\n              <div\n                style={{\n                  width: 80,\n                  height: 80,\n                  // borderRadius: \"50%\",\n                  // border: \"1px solid #a8d0f8\",\n                  display: \"flex\",\n                  justifyContent: \"center\",\n                  alignItems: \"center\",\n                  // marginLeft: 10,\n                  // marginRight: 10,\n                  overflow: \"hidden\",\n                  fontSize: 22,\n                  // backgroundImage: \"linear-gradient(to right, #4f85f6, #669aee)\",\n                  backgroundColor: \"#f5f5f5\",\n                  color: \"#fff\",\n                }}\n              >\n                <div\n                  style={{\n                    textAlign: \"center\",\n                    position: \"relative\",\n                    display: \"flex\",\n                    alignItems: \"center\",\n                  }}\n                >\n                  {/* {info.name && info.name[0].toLocaleUpperCase()} */}\n                  <img\n                    style={{\n                      width: \"65px\",\n                      height: \"65px\",\n                      position: \"relative\",\n                      top: 1,\n                    }}\n                    src={`${info.logo}`}\n                  />\n                </div>\n              </div>\n            </div>\n          )}\n          <div className={styles.headerContent}>\n            <div className={styles.headerContentTitle}>{info.name}</div>\n            <div className={styles.headerContentDescribe}>\n              {info.description}\n            </div>\n            <div className={styles.headerContentBtn}>\n              <Button\n                style={{ padding: \"3px 20px\", height: 30 }}\n                type=\"primary\"\n                onClick={() => {\n                  history?.push({\n                    pathname: `/utilitie/tool-management/tool-execution/${\n                      locationArr[locationArr.length - 1]\n                    }`,\n                  });\n                }}\n              >\n                执行\n              </Button>\n              <Button\n                style={{ padding: \"3px 20px\", height: 30, marginLeft: 20 }}\n                onClick={() => {\n                  downloadFile(`/${info.tar_url}`);\n                }}\n              >\n                下载\n              </Button>\n            </div>\n          </div>\n          <div className={styles.headerBtn}>\n            <Button\n              style={{ padding: \"3px 20px\", height: 30 }}\n              onClick={() => {\n                history?.goBack();\n              }}\n            >\n              返回\n            </Button>\n          </div>\n        </div>\n        <div className={styles.detailInfo}>\n          <div className={styles.detailItem}>\n            <div className={styles.detailItemLabel}>工具类别</div>\n            <div>{kindMap[info.kind]}</div>\n          </div>\n          {/* <div className={styles.detailItem}>\n          <div className={styles.detailItemLabel}>\n            执行位置\n            <Tooltip placement=\"right\" title={\"提示信息\"}>\n              <QuestionCircleOutlined\n                style={{\n                  marginLeft: 10,\n                  fontSize: 16,\n                  position: \"relative\",\n                  top: 1,\n                }}\n              />\n            </Tooltip>\n          </div>\n          <div>目标主机</div>\n        </div> */}\n          <div className={styles.detailItem} style={{ paddingBottom: 10 }}>\n            <div className={styles.detailItemLabel}>\n              执行对象\n              <Tooltip\n                placement=\"right\"\n                title={\"实用工具操作的目标对象类型，可以是主机或者具体服务\"}\n              >\n                <QuestionCircleOutlined\n                  style={{\n                    marginLeft: 10,\n                    fontSize: 16,\n                    position: \"relative\",\n                    top: 1,\n                  }}\n                />\n              </Tooltip>\n            </div>\n            <div>{info.target_name == \"host\" ? \"主机\" : info.target_name}</div>\n          </div>\n        </div>\n        <div className={styles.detailContent}>\n          <div className={styles.detailContentTitle}>执行参数</div>\n          <div className={styles.tableContainer}>\n            <Table\n              size=\"middle\"\n              columns={[\n                {\n                  title: \"名称\",\n                  key: \"name\",\n                  dataIndex: \"name\",\n                  align: \"center\",\n                  render: (text) => text || \"-\",\n                },\n                {\n                  title: \"类型\",\n                  key: \"type\",\n                  dataIndex: \"type\",\n                  align: \"center\",\n                  render: (text) => (text ? argType[text] : \"-\"),\n                },\n                {\n                  title: \"默认值\",\n                  key: \"default\",\n                  dataIndex: \"default\",\n                  align: \"center\",\n                  render: (text) => text || \"-\",\n                },\n                {\n                  title: \"必填字段\",\n                  key: \"required\",\n                  dataIndex: \"required\",\n                  align: \"center\",\n                  render: (text) => `${text}`,\n                },\n              ]}\n              pagination={false}\n              dataSource={info.script_args}\n            />\n          </div>\n        </div>\n        {info && info.templates && info.templates.length > 0 && (\n          <div className={styles.detailContent}>\n            <div className={styles.detailContentTitle}>下载示例文件</div>\n            <div className={styles.tableContainer}>\n              <Table\n                size=\"middle\"\n                columns={[\n                  {\n                    title: \"名称\",\n                    key: \"name\",\n                    dataIndex: \"name\",\n                    align: \"center\",\n                    width: 300,\n                  },\n                  {\n                    title: \"操作\",\n                    key: \"sub_url\",\n                    dataIndex: \"sub_url\",\n                    align: \"center\",\n                    width: 100,\n                    render: (text) => {\n                      return (\n                        <a\n                          onClick={() => {\n                            downloadFile(`/${text}`);\n                          }}\n                        >\n                          下载\n                        </a>\n                      );\n                    },\n                  },\n                ]}\n                pagination={false}\n                dataSource={info.templates}\n              />\n            </div>\n          </div>\n        )}\n\n        <div className={styles.readme}>\n          <div className={styles.readmeTitle}>\n            <FileProtectOutlined style={{ fontSize: 16, marginRight: 10 }} />\n            <span style={{ color: \"rgb(34, 34, 34)\" }}>README.md</span>\n          </div>\n          <div className={styles.readmeContent}>\n            <Readme text={info.readme_info} />\n          </div>\n        </div>\n      </Spin>\n    </OmpContentWrapper>\n  );\n};\n\nexport default Details;\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/detail/index.module.less",
    "content": ".header {\n  display: flex;\n  height: 115px;\n  .icon {\n    width: 100px;\n    height: 100px;\n    background-color: #f5f5f5;\n    border-radius: 5px;\n    padding: 10px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n  .headerContent {\n    flex: 1;\n    height: 100px;\n    padding-left: 15px;\n    .headerContentTitle {\n      font-size: 17px;\n      padding-bottom: 10px;\n      color: rgb(34, 34, 34);\n    }\n    .headerContentDescribe {\n      padding-bottom: 10px;\n      //   padding-top: 8px;\n    }\n    // .headerContentBtn {\n\n    // }\n  }\n  .headerBtn {\n    width: 80px;\n  }\n}\n\n.detailInfo {\n  background-color: #f5f5f5;\n  margin-top: 10px;\n  border-radius: 5px;\n  border: 1px solid #e9e9e9;\n  padding: 10px;\n  .detailItem {\n    display: flex;\n    padding-bottom: 25px;\n    .detailItemLabel {\n      width: 180px;\n      color: rgb(34, 34, 34);\n    }\n  }\n}\n\n.detailContent {\n  padding-top: 20px;\n  .detailContentTitle {\n    color: rgb(34, 34, 34);\n  }\n  .tableContainer {\n    border: 1px solid #d6d6d6;\n    margin-top: 20px;\n  }\n}\n\n.readme {\n  border: 1px solid #d6d6d6;\n  margin-top: 20px;\n  .readmeTitle {\n    background-color: #f6f6f6;\n    height: 45px;\n    line-height: 45px;\n    border-bottom: 1px solid #d6d6d6;\n    padding-left: 20px;\n  }\n  .readmeContent {\n    padding: 20px;\n    padding-top: 20px;\n    // background-color: rgba(210,210,210,.2);\n    .bread-div {\n      padding: 0.5rem;\n      border-bottom: 1px solid #eee;\n      background-color: #e1f0ff;\n    }\n    .detailed-title {\n      font-size: 1.8rem;\n      text-align: center;\n      padding: 1rem;\n    }\n    .center {\n      text-align: center;\n    }\n    .detailed-content {\n      padding: 1.3rem;\n      font-size: 1rem;\n    }\n    pre {\n      display: block;\n      background-color: #f3f3f3;\n      padding: 0.5rem !important;\n      overflow-y: auto;\n      font-weight: 300;\n      font-family: Menlo, monospace;\n      border-radius: 0.3rem;\n    }\n    pre {\n      background-color: #283646 !important;\n    }\n    pre > code {\n      border: 0px !important;\n      background-color: #283646 !important;\n      color: #fff;\n    }\n    code {\n      display: inline-block;\n      background-color: #f3f3f3;\n      border: 1px solid #fdb9cc;\n      border-radius: 3px;\n      font-size: 12px;\n      padding-left: 5px;\n      padding-right: 5px;\n      color: #4f4f4f;\n      margin: 0px 3px;\n    }\n\n    .title-anchor {\n      color: #888 !important;\n      padding: 4px !important;\n      margin: 0rem !important;\n      height: auto !important;\n      line-height: 1.2rem !important;\n      font-size: 0.7rem !important;\n      border-bottom: 1px dashed #eee;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n    .active {\n      color: rgb(30, 144, 255) !important;\n    }\n    .nav-title {\n      text-align: center;\n      color: #888;\n      border-bottom: 1px solid rgb(30, 144, 255);\n    }\n    .article-menu {\n      font-size: 12px;\n    }\n    iframe {\n      height: 34rem;\n    }\n    .detailed-content img {\n      width: 100%;\n      border: 1px solid #f3f3f3;\n    }\n    .title-level3 {\n      display: none !important;\n    }\n    .ant-anchor-link-title {\n      font-size: 12px !important;\n    }\n    .ant-anchor-wrapper {\n      padding: 5px !important;\n    }\n    code,\n    pre {\n      border-radius: 3px;\n      background-color: #f7f7f7;\n      color: inherit;\n    }\n\n    code {\n      font-family: Consolas, Monaco, Andale Mono, monospace;\n      margin: 0 2px;\n    }\n\n    pre {\n      line-height: 1.7em;\n      overflow: auto;\n      padding: 6px 10px;\n      // border-left: 5px solid #6ce26c;\n    }\n\n    pre > code {\n      border: 0;\n      display: inline;\n      max-width: initial;\n      padding: 0;\n      margin: 0;\n      overflow: initial;\n      line-height: inherit;\n      font-size: 0.85em;\n      white-space: pre;\n      background: 0 0;\n    }\n\n    code {\n      color: #666555;\n    }\n    table {\n      *border-collapse: collapse; /* IE7 and lower */\n      border-spacing: 0;\n      width: 100%;\n    }\n    table {\n      border:solid #d6d6d6 1px;\n      -moz-border-radius: 2px;\n      -webkit-border-radius: 2px;\n      border-radius: 2px;\n      /*-webkit-box-shadow: 0 1px 1px #ccc;\n      -moz-box-shadow: 0 1px 1px #ccc;\n      box-shadow: 0 1px 1px #ccc;   */\n    }\n    table tr:hover {\n      background: #fbf8e9;\n      -o-transition: all 0.1s ease-in-out;\n      -webkit-transition: all 0.1s ease-in-out;\n      -moz-transition: all 0.1s ease-in-out;\n      -ms-transition: all 0.1s ease-in-out;\n      transition: all 0.1s ease-in-out;\n    }\n    table td,\n    .table th {\n      border-left: none;\n      border-top: 1px solid #f0f0f0;\n      padding: 10px;\n      text-align: center;\n      border-bottom: 1px solid #f0f0f0;\n      font-size: 12px;\n    }\n\n    table th {\n      background-color: #f9fafd;\n      // background-image: -webkit-gradient(\n      //   linear,\n      //   left top,\n      //   left bottom,\n      //   from(#ebf3fc),\n      //   to(#dce9f9)\n      // );\n      // background-image: -webkit-linear-gradient(top, #ebf3fc, #dce9f9);\n      // background-image: -moz-linear-gradient(top, #ebf3fc, #dce9f9);\n      // background-image: -ms-linear-gradient(top, #ebf3fc, #dce9f9);\n      // background-image: -o-linear-gradient(top, #ebf3fc, #dce9f9);\n      // background-image: linear-gradient(top, #ebf3fc, #dce9f9);\n      /*-webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset;\n      -moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset;\n      box-shadow: 0 1px 0 rgba(255,255,255,.8) inset;*/\n      border-top: none;\n      text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n      padding: 5px;\n      padding-top: 12px;\n      padding-bottom: 12px;\n      font-size: 12px;\n    }\n\n    table td:first-child,\n    table th:first-child {\n      border-left: none;\n    }\n\n    table th:first-child {\n      -moz-border-radius: 6px 0 0 0;\n      -webkit-border-radius: 6px 0 0 0;\n      border-radius: 6px 0 0 0;\n    }\n    table th:last-child {\n      -moz-border-radius: 0 6px 0 0;\n      -webkit-border-radius: 0 6px 0 0;\n      border-radius: 0 6px 0 0;\n    }\n    table th:only-child {\n      -moz-border-radius: 6px 6px 0 0;\n      -webkit-border-radius: 6px 6px 0 0;\n      border-radius: 6px 6px 0 0;\n    }\n    table tr:last-child td {\n      -moz-border-radius: 0 0 6px 0;\n      -webkit-border-radius: 0 0 6px 0;\n      border-radius: 0 0 6px 0;\n      border-bottom: solid #d6d6d6 1px;\n    }\n  }\n}\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/index.js",
    "content": "import { Input, Pagination, Empty, Spin } from \"antd\";\nimport { useEffect, useState } from \"react\";\nimport styles from \"./index.module.less\";\nimport { SearchOutlined } from \"@ant-design/icons\";\nimport Card from \"./config/card.js\";\nimport { useSelector } from \"react-redux\";\nimport { useHistory } from \"react-router-dom\";\nimport { fetchGet } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\nimport { handleResponse } from \"@/utils/utils\";\n\nconst ToolManagement = () => {\n  // 视口高度\n  const viewHeight = useSelector((state) => state.layouts.viewSize.height);\n  const history = useHistory();\n  const [tabKey, setTabKey] = useState();\n\n  const [searchName, setSearchName] = useState(\"\");\n\n  const [total, setTotal] = useState(0);\n\n  const [loading, setLoading] = useState(false);\n  const [dataSource, setDataSource] = useState([]);\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: viewHeight > 955 ? 16 : 12,\n    total: 0,\n    searchParams: {},\n  });\n\n  function fetchData(pageParams = { current: 1, pageSize: 8 }, searchParams) {\n    setLoading(true);\n    fetchGet(apiRequest.utilitie.queryList, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          // 获得真正的总数，要查询条件都为空时\n          let obj = { ...searchParams };\n          delete obj.tabKey;\n          let arr = Object.values(obj).filter((i) => i);\n          if (arr.length == 0) {\n            setTotal(res.data.count);\n          }\n          setDataSource(res.data.results);\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        location.state = {};\n        setLoading(false);\n        // fetchSearchlist();\n        //fetchIPlist();\n      });\n  }\n\n  useEffect(() => {\n    fetchData(\n      { current: 1, pageSize: pagination.pageSize },\n      {\n        ...pagination.searchParams,\n        kind: tabKey,\n      }\n    );\n  }, [tabKey]);\n\n  return (\n    <div>\n      <div className={styles.header}>\n        <div className={styles.headerTabRow}>\n          <div\n            className={styles.headerTab}\n            onClick={(e) => {\n              setPagination({\n                current: 1,\n                pageSize: viewHeight > 955 ? 16 : 12,\n                total: 0,\n                searchParams: {},\n              });\n              if (e.target.innerHTML == \"全部\") {\n                setTabKey();\n              } else if (e.target.innerHTML == \"管理工具\") {\n                setTabKey(0);\n              } else if (e.target.innerHTML == \"安全工具\") {\n                setTabKey(2);\n              }\n            }}\n          >\n            <div\n              style={tabKey == undefined ? { color: \"rgb(46, 124, 238)\" } : {}}\n            >\n              全部\n            </div>\n            <div>|</div>\n            <div style={tabKey == \"0\" ? { color: \"rgb(46, 124, 238)\" } : {}}>\n              管理工具\n            </div>\n            <div>|</div>\n            <div style={tabKey == \"2\" ? { color: \"rgb(46, 124, 238)\" } : {}}>\n              安全工具\n            </div>\n          </div>\n          <div className={styles.headerBtn}>\n            <Input\n              placeholder=\"请输入工具名称\"\n              suffix={\n                !searchName && <SearchOutlined style={{ color: \"#b6b6b6\" }} />\n              }\n              style={{ marginRight: 10, width: 280 }}\n              value={searchName}\n              allowClear\n              onChange={(e) => {\n                setSearchName(e.target.value);\n                if (!e.target.value) {\n                  fetchData(\n                    {\n                      current: 1,\n                      pageSize: pagination.pageSize,\n                    },\n                    {\n                      ...pagination.searchParams,\n                      kind: tabKey,\n                      name: null,\n                    }\n                  );\n                }\n              }}\n              onBlur={() => {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    name: searchName,\n                    kind: tabKey,\n                  }\n                );\n              }}\n              onPressEnter={() => {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    name: searchName,\n                    kind: tabKey,\n                  }\n                );\n              }}\n            />\n          </div>\n        </div>\n\n        <hr className={styles.headerHr} />\n        <div className={styles.headerSearch}>\n          <div className={styles.headerSearchCondition}>\n            {/* <p\n                style={searchKey == \"全部\" ? { color: \"rgb(46, 124, 238)\" } : {}}\n              >\n                全部\n              </p> */}\n          </div>\n          <div className={styles.headerSearchInfo} style={{ paddingTop: 8 }}>\n            {/* <Button\n                style={{ marginRight: 15, fontSize: 13 }}\n                icon={<DownloadOutlined />}\n                onClick={() => {\n                  downloadFile(apiRequest.appStore.applicationTemplate);\n                }}\n              >\n                <span style={{ color: \"#818181\" }}>下载发布说明</span>\n              </Button> */}\n            共收录 {total} 个实用工具\n          </div>\n        </div>\n      </div>\n      <Spin spinning={loading}>\n        <div style={{ display: \"flex\", flexWrap: \"wrap\" }}>\n          {dataSource.length == 0 ? (\n            <Empty\n              style={{\n                width: \"100%\",\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                height: viewHeight > 955 ? 500 : 300,\n                flexDirection: \"column\",\n              }}\n              description={\"暂无使用工具\"}\n            />\n          ) : (\n            <>\n              {dataSource.map((item, idx) => {\n                return (\n                  <Card\n                    history={history}\n                    key={idx}\n                    idx={idx + 1}\n                    info={item}\n                    tabKey={tabKey}\n                  />\n                );\n              })}\n            </>\n          )}\n        </div>\n      </Spin>\n      {dataSource.length !== 0 && (\n        <div\n          style={{\n            display: \"flex\",\n            justifyContent: \"center\",\n            position: \"relative\",\n            top: 25,\n          }}\n        >\n          <Pagination\n            onChange={(e) => {\n              fetchData(\n                { ...pagination, current: e },\n                {\n                  ...pagination.searchParams,\n                  kind: tabKey,\n                }\n              );\n            }}\n            current={pagination.current}\n            pageSize={pagination.pageSize}\n            total={pagination.total}\n          />\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default ToolManagement;\n"
  },
  {
    "path": "omp_web/src/pages/ToolManagement/index.module.less",
    "content": ".header {\n    background-color: #fff;\n    padding-bottom: 14px;\n    //display: flex;\n    //padding:20px;\n    .headerTabRow {\n      display: flex;\n      //align-items: center;\n      justify-content: space-between;\n      .headerTab {\n        display: flex;\n        color: #4f4f4f;\n        font-size: 14px;\n        margin-top: 20px;\n        & > div:nth-child(1) {\n          padding-left: 20px;\n          //margin-top: 20px;\n          cursor: pointer;\n        }\n        & > div:nth-child(2) {\n          padding-left: 10px;\n          padding-right: 10px;\n          //margin-top: 20px;\n          color: #b9b9b9;\n        }\n        & > div:nth-child(3) {\n          //margin-top: 20px;\n          cursor: pointer;\n        }\n        & > div:nth-child(4) {\n          padding-left: 10px;\n          padding-right: 10px;\n          //margin-top: 20px;\n          color: #b9b9b9;\n        }\n        & > div:nth-child(3) {\n         // margin-top: 20px;\n          cursor: pointer;\n        }\n        & > div:nth-child(5) {\n          // margin-top: 20px;\n           cursor: pointer;\n         }\n      }\n      .headerBtn {\n          display: flex;\n          padding-top: 10px;\n          padding-bottom: 5px;\n          position: relative;\n          top: 3px;\n          padding-right:20px\n      }\n    }\n  \n    .headerHr {\n      border-top: solid 1px #e7e7e7;\n      border-left: solid 1px #e7e7e7;\n    }\n  \n    .headerSearch {\n      padding-left: 20px;\n      display: flex;\n      justify-content: space-between;\n      font-size: 12px;\n      .headerSearchCondition {\n          position: relative;\n          top:5px;\n          display: flex;\n          & > p {\n              padding-right: 20px;\n              cursor: pointer;\n          }\n      }\n      .headerSearchInfo {\n          color: #818181;\n          padding-right: 10px;\n      }\n    }\n  }"
  },
  {
    "path": "omp_web/src/pages/UserManagement/index.js",
    "content": "import { OmpContentWrapper, OmpTable, OmpModal } from \"@/components\";\nimport { Button, Input, Form, message } from \"antd\";\nimport { useState, useEffect, useRef } from \"react\";\nimport {\n  handleResponse,\n  _idxInit,\n  refreshTime,\n  nonEmptyProcessing,\n  logout,\n  isPassword,\n  encrypt,\n} from \"@/utils/utils\";\nimport { fetchGet, fetchPost } from \"@/utils/request\";\nimport { apiRequest } from \"@/config/requestApi\";\n//import updata from \"@/store_global/globalStore\";\nimport { useDispatch } from \"react-redux\";\nimport moment from \"moment\";\nimport { SearchOutlined } from \"@ant-design/icons\";\n\nconst UserManagement = () => {\n  const dispatch = useDispatch();\n\n  const [loading, setLoading] = useState(false);\n\n  const [searchLoading, setSearchLoading] = useState(false);\n\n  //table表格数据\n  const [dataSource, setDataSource] = useState([]);\n  const [userListSource, setUserListSource] = useState([]);\n  const [searchValue, setSearchValue] = useState(\"\");\n  const [selectValue, setSelectValue] = useState();\n\n  const [pagination, setPagination] = useState({\n    current: 1,\n    pageSize: 10,\n    total: 0,\n    ordering: \"\",\n    searchParams: {},\n  });\n\n  //修改密码弹框\n  const [showModal, setShowModal] = useState(false);\n\n  const columns = [\n    {\n      title: \"序列\",\n      width: 40,\n      key: \"_idx\",\n      dataIndex: \"_idx\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n      fixed: \"left\",\n    },\n    {\n      title: \"用户名\",\n      key: \"username\",\n      width: 100,\n      dataIndex: \"username\",\n      //sorter: (a, b) => a.username - b.username,\n      // sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: nonEmptyProcessing,\n    },\n    {\n      title: \"角色\",\n      key: \"is_superuser\",\n      dataIndex: \"is_superuser\",\n      width: 100,\n      //sorter: (a, b) => a.is_superuser - b.is_superuser,\n      //sortDirections: [\"descend\", \"ascend\"],\n      align: \"center\",\n      render: (text, record) => {\n        if (text) {\n          return \"普通管理员\";\n        } else {\n          if (record.username == \"omp\") {\n            return \"只读用户\";\n          }\n          return \"普通用户\";\n        }\n      },\n    },\n    {\n      title: \"用户状态\",\n      key: \"is_active\",\n      dataIndex: \"is_active\",\n      align: \"center\",\n      width: 100,\n      render: (text) => {\n        if (text) {\n          return \"正常\";\n        } else {\n          return \"停用\";\n        }\n      },\n    },\n    {\n      title: \"创建时间\",\n      key: \"date_joined\",\n      dataIndex: \"date_joined\",\n      align: \"center\",\n      width: 100,\n      render: (text) => {\n        if (text) {\n          return moment(text).format(\"YYYY-MM-DD HH:mm:ss\");\n        } else {\n          return \"-\";\n        }\n      },\n    },\n    // {\n    //   title: \"描述\",\n    //   key: \"describe\",\n    //   dataIndex: \"describe\",\n    //   align: \"center\",\n    //   render: nonEmptyProcessing,\n    // },\n    {\n      title: \"用户操作\",\n      key: \"1\",\n      width: 50,\n      dataIndex: \"1\",\n      align: \"center\",\n      fixed: \"right\",\n      render: function renderFunc(text, record, index) {\n        return (\n          <div\n            onClick={() => {\n              setRow(record);\n              setShowModal(true);\n            }}\n            style={{ display: \"flex\", justifyContent: \"space-around\" }}\n          >\n            <a>修改密码</a>\n          </div>\n        );\n      },\n    },\n  ];\n\n  const msgRef = useRef(null);\n\n  //select 的onblur函数拿不到最新的search value,使用useref存(是最新的，但是因为失去焦点时会自动触发清空search，还是得使用ref存)\n  const searchValueRef = useRef(null);\n\n  // 定义row存数据\n  const [row, setRow] = useState({});\n\n  //auth/users\n  function fetchData(\n    pageParams = { current: 1, pageSize: 10 },\n    searchParams,\n    ordering\n  ) {\n    setLoading(true);\n    fetchGet(apiRequest.auth.users, {\n      params: {\n        page: pageParams.current,\n        size: pageParams.pageSize,\n        ordering: ordering ? ordering : null,\n        ...searchParams,\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (!searchParams) {\n            setUserListSource(res.data.results.map((item) => item.username));\n          }\n          setDataSource(\n            res.data.results.map((item, idx) => {\n              return {\n                ...item,\n                _idx: idx + 1 + (pageParams.current - 1) * pageParams.pageSize,\n              };\n            })\n          );\n          setPagination({\n            ...pagination,\n            total: res.data.count,\n            pageSize: pageParams.pageSize,\n            current: pageParams.current,\n            ordering: ordering,\n            searchParams: searchParams,\n          });\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  }\n\n  const onPassWordChange = (data) => {\n    setLoading(true);\n    fetchPost(apiRequest.auth.changePassword, {\n      body: {\n        username: encrypt(row.username),\n        old_password: encrypt(data.old_password),\n        new_password: encrypt(data.new_password2),\n      },\n    })\n      .then((res) => {\n        handleResponse(res, (res) => {\n          if (res.code == 0) {\n            if (localStorage.getItem(\"username\") == row.username) {\n              message.success(\"修改密码成功, 请重新登录\");\n              setTimeout(() => {\n                logout();\n              }, 1000);\n            } else {\n              message.success(\"修改密码成功\");\n            }\n            setShowModal(false);\n          }\n        });\n      })\n      .catch((e) => console.log(e))\n      .finally(() => {\n        setLoading(false);\n      });\n  };\n\n  useEffect(() => {\n    fetchData(pagination);\n  }, []);\n\n  //console.log(checkedList)\n  // 防止在校验进入死循环\n  const flag = useRef(null);\n\n  return (\n    <OmpContentWrapper>\n      <div style={{ display: \"flex\" }}>\n        <div style={{ display: \"flex\", marginLeft: \"auto\" }}>\n          <span style={{ width: 60, display: \"flex\", alignItems: \"center\" }}>\n            用户名:\n          </span>\n          {/* <Input.Search placeholder=\"请输入用户名\"\n          allowClear\n          onSearch={(e)=>{\n              setSelectValue(e)\n              console.log(e)\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                {username:e},\n                pagination.ordering\n              );\n          }}\n          style={{ width: 200 }} /> */}\n          <Input\n            placeholder=\"请输入用户名\"\n            style={{ width: 200 }}\n            allowClear\n            value={selectValue}\n            onChange={(e) => {\n              setSelectValue(e.target.value);\n              if (!e.target.value) {\n                fetchData(\n                  {\n                    current: 1,\n                    pageSize: pagination.pageSize,\n                  },\n                  {\n                    ...pagination.searchParams,\n                    username: null,\n                  }\n                );\n              }\n            }}\n            onBlur={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                }\n              );\n            }}\n            onPressEnter={() => {\n              fetchData(\n                {\n                  current: 1,\n                  pageSize: pagination.pageSize,\n                },\n                {\n                  ...pagination.searchParams,\n                  username: selectValue,\n                },\n                pagination.ordering\n              );\n            }}\n            suffix={\n              !selectValue && (\n                <SearchOutlined style={{ fontSize: 12, color: \"#b6b6b6\" }} />\n              )\n            }\n          />\n          <Button\n            style={{ marginLeft: 10 }}\n            onClick={() => {\n              dispatch(refreshTime());\n              // console.log(pagination, \"hosts/hosts/?page=1&size=10\");\n              fetchData(\n                { current: pagination.current, pageSize: pagination.pageSize },\n                { username: selectValue },\n                pagination.ordering\n              );\n            }}\n          >\n            刷新\n          </Button>\n        </div>\n      </div>\n      <div\n        style={{\n          border: \"1px solid #ebeef2\",\n          backgroundColor: \"white\",\n          marginTop: 10,\n        }}\n      >\n        <OmpTable\n          noScroll={true}\n          loading={loading}\n          onChange={(e, filters, sorter) => {\n            let ordering = sorter.order\n              ? `${sorter.order == \"descend\" ? \"\" : \"-\"}${sorter.columnKey}`\n              : null;\n            setTimeout(() => {\n              fetchData(e, pagination.searchParams, ordering);\n            }, 200);\n          }}\n          columns={columns}\n          dataSource={dataSource}\n          pagination={{\n            showSizeChanger: true,\n            pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n            showTotal: () => (\n              <div\n                style={{\n                  display: \"flex\",\n                  width: \"200px\",\n                  lineHeight: 2.8,\n                  flexDirection: \"row-reverse\",\n                }}\n              >\n                <p style={{ color: \"rgb(152, 157, 171)\" }}>\n                  共计{\" \"}\n                  <span style={{ color: \"rgb(63, 64, 70)\" }}>\n                    {pagination.total}\n                  </span>{\" \"}\n                  条\n                </p>\n              </div>\n            ),\n            ...pagination,\n          }}\n          rowKey={(record) => record.id}\n        />\n      </div>\n      <OmpModal\n        loading={loading}\n        onFinish={onPassWordChange}\n        visibleHandle={[showModal, setShowModal]}\n        title=\"修改密码\"\n        beForeOk={() => {\n          flag.current = true;\n        }}\n        afterClose={() => {\n          flag.current = null;\n        }}\n      >\n        <Form.Item\n          label=\"当前密码\"\n          name=\"old_password\"\n          key=\"old_password\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入当前用户密码\",\n            },\n            {\n              validator: (rule, value, callback) => {\n                if (value) {\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    return Promise.resolve(\"success\");\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请输入当前密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"新密码\"\n          name=\"new_password1\"\n          key=\"new_password1\"\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            {\n              required: true,\n              message: \"请输入新密码\",\n            },\n            {\n              validator: (rule, value, callback, passwordModalForm) => {\n                if (value) {\n                  if (!flag.current) {\n                    passwordModalForm.validateFields([\"new_password2\"]);\n                  }\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    return Promise.resolve(\"success\");\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请设置新密码\" />\n        </Form.Item>\n        <Form.Item\n          label=\"确认密码\"\n          name=\"new_password2\"\n          key=\"new_password2\"\n          useforminstanceinvalidator=\"true\"\n          rules={[\n            {\n              required: true,\n              message: \"请再次输入新密码\",\n            },\n            {\n              validator: (rule, value, callback, passwordModalForm) => {\n                if (value) {\n                  if (!isPassword(value)) {\n                    if (value.length < 8) {\n                      return Promise.reject(\"密码长度需大于8位\");\n                    }\n                    if (\n                      passwordModalForm.getFieldValue().new_password1 ===\n                        value ||\n                      !value\n                    ) {\n                      return Promise.resolve(\"success\");\n                    } else {\n                      return Promise.reject(\"两次密码输入不一致\");\n                    }\n                  } else {\n                    return Promise.reject(\n                      `密码只支持数字、字母以及常用英文符号`\n                    );\n                  }\n                } else {\n                  return Promise.resolve(\"success\");\n                }\n              },\n            },\n          ]}\n        >\n          <Input.Password placeholder=\"请再次输入新密码\" />\n        </Form.Item>\n      </OmpModal>\n    </OmpContentWrapper>\n  );\n};\n\nexport default UserManagement;\n"
  },
  {
    "path": "omp_web/src/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "omp_web/src/router.js",
    "content": "import { HashRouter as Router, Route, Switch, Redirect } from \"react-router-dom\";\nimport OmpLayout from \"@/layouts\";\nimport Login from \"@/pages/Login\";\nimport routerConfig from \"@/config/router.config\";\nimport HomePage from \"@/pages/HomePage\";\n\nconst OmpRouter = () => {\n  let routerChildArr = routerConfig.map(item=>item.children).flat()\n  return (\n    <Router>\n      <Switch>\n        <Route path=\"/login\" component={() => <Login />} />\n        <Route\n          path=\"/\"\n          component={() => (\n            <OmpLayout>\n              <Switch>\n                <Route \n                  path=\"/homepage\"\n                  key=\"/homepage\"\n                  exact\n                  render={()=><HomePage />}\n                />\n                {routerChildArr.map((item) => {\n                  return (\n                    <Route\n                      path={item.path}\n                      key={item.path}\n                      exact\n                      render={() => <item.component />}\n                    />\n                  );\n                })}\n                <Redirect exact path=\"/\" to=\"/homepage\"/>\n              </Switch>\n            </OmpLayout>\n          )}\n        />\n        ]\n      </Switch>\n    </Router>\n  );\n};\n\nexport default OmpRouter;\n"
  },
  {
    "path": "omp_web/src/store_redux/reducer.js",
    "content": "import { combineReducers } from \"redux\";\n\nimport { reducer as customBreadcrumbReducer } from \"@/components/CustomBreadcrumb/store\";\nimport { reducer as layoutsReducer } from \"@/layouts/store\";\n//import { reducer as warningRecordReducer } from \"@/pages/OperationManagement/WarningRecord/store\";\nimport { reducer as systemManagementReducer } from \"@/pages/SystemManagement/store\";\nimport { reducer as appStoreReducer } from \"@/pages/AppStore/store\";\nimport { reducer as installReducer } from \"@/pages/AppStore/config/Installation/store\";\n\nconst cReducer = combineReducers({\n  customBreadcrumb: customBreadcrumbReducer,\n  layouts: layoutsReducer,\n  // warningRecord:warningRecordReducer,\n  systemManagement: systemManagementReducer,\n  appStore: appStoreReducer,\n  installation: installReducer,\n});\n\nexport default cReducer;\n"
  },
  {
    "path": "omp_web/src/store_redux/reduxStore.js",
    "content": "import { createStore } from \"redux\";\nimport reducer from \"./reducer\";\nconst store = createStore(reducer);\n\nexport default store;"
  },
  {
    "path": "omp_web/src/utils/index.module.less",
    "content": "._bigfontSize{\n    font-size: 14px;\n  }\n\n  .listButton {\n    display: flex;\n    justify-content: center;\n    color: #1890ff;\n    cursor: pointer;\n  \n    & > div:not(:last-child) {\n      margin-right: 10px;\n    }\n  }\n\n  .loginMessageShow {\n    opacity: 1;\n    transition: all .2s ease-in;\n  }\n\n  .loginMessageHide{\n    opacity: 0;\n    transition: all .2s ease-in;\n    //animation: hide-item 2s ease-in forwards;\n  }"
  },
  {
    "path": "omp_web/src/utils/request.js",
    "content": "import axios from \"axios\";\nimport { logout } from \"./utils\"\n\nconst getBaseUrl = (env) => {\n  let base;\n  if (!base) {\n    base = \"/\";\n  }\n  return base;\n};\n\nclass NewAxios {\n  constructor() {\n    this.baseURL = getBaseUrl(process.env.NODE_ENV);\n    this.timeout = 150000;\n    this.withCredentials = true;\n  }\n\n  setInterceptors = (instance, url) => {\n    instance.interceptors.request.use(\n      (config) => {\n        // 在这里添加loading\n        // 配置token\n        return config;\n      },\n      (err) => Promise.reject(err)\n    );\n\n    instance.interceptors.response.use(\n      (response) => {\n        // 在这里移除loading\n        // todo: 想根据业务需要，对响应结果预先处理的，都放在这里\n        return response;\n      },\n      (err) => {\n        if (err.response) {\n          // 响应错误码处理\n          console.log(err.response)\n          switch (err.response.status) {\n           \n            case 403:\n              // todo: handler server forbidden error\n              break;\n            case 401:\n              logout()\n              // todo: handler server forbidden error\n              break;\n            // todo: handler other status code\n            default:\n              break;\n          }\n          return Promise.reject(err.response);\n        }\n        if (!window.navigator.onLine) {\n          // 断网处理\n          // todo: jump to offline page\n          return -1;\n        }\n        return Promise.reject(err);\n      }\n    );\n  };\n\n  request(options) {\n    // 每次请求都会创建新的axios实例。\n    const instance = axios.create();\n    const config = {\n      // 将用户传过来的参数与公共配置合并。\n      ...options,\n      baseURL: this.baseURL,\n      timeout: this.timeout,\n      withCredentials: this.withCredentials,\n    };\n    // 配置拦截器，支持根据不同url配置不同的拦截器。\n    this.setInterceptors(instance, options.url);\n    return instance(config); // 返回axios实例的执行结果\n  }\n}\n\n//为了保持和之前项目请求方式一样\n//export const fetchPost = new NewAxios()\nexport const fetchPost = (url, params) =>\n  new NewAxios().request({\n    url: url,\n    method: \"POST\",\n    data: {\n      ...params?.body,\n    },\n  });\n\nexport const fetchGet = (url, params) =>\n  new NewAxios().request({\n    url: url,\n    method: \"GET\",\n    params: {\n      ...params?.params,\n    },\n  });\n\nexport const fetchPut = (url, params) =>\n  new NewAxios().request({\n    url: url,\n    method: \"PUT\",\n    data: {\n      ...params?.body,\n    },\n  });\n\nexport const fetchDelete = (url, params) =>\n  new NewAxios().request({\n    url: url,\n    method: \"Delete\",\n    params: {\n      ...params?.params,\n    },\n  });\n\n  export const fetchPatch = (url, params) =>\n  new NewAxios().request({\n    url: url,\n    method: \"Patch\",\n    data: {\n      ...params?.body,\n    },\n  });\n"
  },
  {
    "path": "omp_web/src/utils/utils.js",
    "content": "//import { ColorfulNotice } from \"@/components\";\nimport { Badge, message, Tooltip } from \"antd\";\nimport moment from \"moment\";\nimport * as R from \"ramda\";\nimport styles from \"./index.module.less\";\nimport { getRefreshTimeChangeAction } from \"@/components/CustomBreadcrumb/store/actionsCreators\";\nimport { CloseCircleFilled, CheckCircleFilled } from \"@ant-design/icons\";\nimport JSEncrypt from \"jsencrypt\";\n\n/**\n * 正常/绿色  bg\"rgb(238, 250, 244)\"  bo:\"rgb(84, 187, 166)\"\n * 异常/红色     \"#da4e48\", \"#fbe7e6\"\n * 警告/黄色      rgba(247, 231, 24,.2)\" borderColor=\"#f5c773\"\n */\n\n/**\n * 统一的分页配置项\n * @param data\n * @returns {{total, pageSizeOptions: [string, string, string, string], showTotal: (function(): string), showSizeChanger: boolean}}\n */\n/*eslint-disable*/\nexport const paginationConfig = (data) => ({\n  showSizeChanger: true,\n  pageSizeOptions: [\"10\", \"20\", \"50\", \"100\"],\n  showTotal: () => (\n    <span style={{ color: \"rgb(152, 157, 171)\" }}>\n      共计 <span style={{ color: \"rgb(63, 64, 70)\" }}>{data.length}</span> 条\n    </span>\n  ),\n  total: data.length,\n  // onShowSizeChange: (current, pageSize) => this.changePageSize(pageSize, current),\n  // onChange: (current) => this.changePage(current),\n});\n/*eslint-disable*/\nexport const isTableTextInvalid = (text) =>\n  String(text) === \"null\" || text === \"\" || text === undefined;\n\n/**\n * 格式化 table 渲染项\n * @param text\n * @param record\n * @param index\n * @returns {JSX.Element|string|*}\n */\nexport function formatTableRenderData(text, record, index) {\n  if (isTableTextInvalid(text)) {\n    return \"-\";\n  } else if (text === 0 || text === \"active\" || text === true) {\n    return (\n      <div>{renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}正常</div>\n    );\n  } else if (text === 1 || text === \"unactive\" || text === false) {\n    return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}异常</div>;\n  } else if (text === \"CREATE\") {\n    return \"增加\";\n  } else if (text === \"UPDATE\") {\n    return \"更新\";\n  } else if (text === \"DELETE\") {\n    return \"删除\";\n  } else {\n    // /console.log(\"text\",text);\n    return text;\n  }\n}\n\nfunction report_service_RenderData(text, record, index) {\n  if (text) {\n    return text;\n  } else {\n    return \"-\";\n  }\n}\n\nexport function renderFormattedTime(text, record, index) {\n  {\n    if (isTableTextInvalid(text)) {\n      return \"-\";\n    } else {\n      let duration = \"\";\n      const second = Math.round(Number(text)),\n        days = Math.floor(second / 86400),\n        hours = Math.floor((second % 86400) / 3600),\n        minutes = Math.floor(((second % 86400) % 3600) / 60),\n        seconds = Math.floor(((second % 86400) % 3600) % 60);\n\n      if (days > 0) {\n        duration = days + \"天\" + hours + \"小时\";\n      } else if (hours > 0) {\n        duration = hours + \"小时\" + minutes + \"分\";\n      } else if (minutes > 0) {\n        duration = minutes + \"分\";\n      } else if (seconds > 0) {\n        duration = seconds + \"秒\";\n      }\n\n      return duration;\n    }\n  }\n}\n\nexport function renderInformation(text, record, index) {\n  const { cpu, memory, disk } = record;\n  const unit = 1024 * 1024 * 1024;\n\n  const cpuText = isTableTextInvalid(cpu) ? \"-\" : `${cpu}C`;\n  const memoryText = isTableTextInvalid(memory)\n    ? \"-\"\n    : `${(memory / unit).toFixed(1)}G`;\n  const diskText = isTableTextInvalid(disk)\n    ? \"-\"\n    : `${(disk / unit).toFixed(1)}G`;\n\n  if (cpuText === \"-\" && memoryText === \"-\" && diskText === \"-\") {\n    return \"-\";\n  }\n\n  return `${cpuText}|${memoryText}|${diskText}`;\n}\n\n//小圆点\nexport const renderCircular = (borderColor, backgroundColor) => {\n  return (\n    <span\n      style={{\n        width: 10,\n        height: 10,\n        borderStyle: \"solid\",\n        borderWidth: 2,\n        borderColor,\n        backgroundColor,\n        display: \"inline-block\",\n        marginRight: 5,\n        borderRadius: \"50%\",\n      }}\n    ></span>\n  );\n};\n\nexport function ColorfulNotice({\n  backgroundColor,\n  borderColor,\n  text,\n  top,\n  width = 55,\n}) {\n  //if(top)console.log(\"top\",top);\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        width,\n        height: 20,\n        margin: \"0 auto\",\n        //color: \"white\",\n        color: \"rgba(0, 0, 0, 0.6)\",\n        fontSize: \"12px\",\n        borderRadius: \"4px\",\n        border: \"1px solid #fff\",\n        backgroundColor,\n        borderColor,\n        position: \"relative\",\n        top,\n      }}\n      // className={styles.warningNotice}\n    >\n      {text}\n    </div>\n  );\n}\n\n/**\n * table组件中ip排序\n * @param a\n * @param b\n * @returns {number}\n */\nexport const tableSorter = {\n  sortIP: (a, b) => {\n    if (!a.ip || !b.ip) return 0;\n\n    const ip1 = a.ip\n      .split(\".\")\n      .map((el) => el.padStart(3, \"0\"))\n      .join(\"\");\n    const ip2 = b.ip\n      .split(\".\")\n      .map((el) => el.padStart(3, \"0\"))\n      .join(\"\");\n    return ip1 - ip2;\n  },\n  sortAlertIP: (a, b) => {\n    if (!a.alert_host_ip || !b.alert_host_ip) return 0;\n\n    const ip1 = a.alert_host_ip\n      .split(\".\")\n      .map((el) => el.padStart(3, \"0\"))\n      .join(\"\");\n    const ip2 = b.alert_host_ip\n      .split(\".\")\n      .map((el) => el.padStart(3, \"0\"))\n      .join(\"\");\n    return ip1 - ip2;\n  },\n  sortUsageRate: (a, b) => {\n    return (\n      Number(isTableTextInvalid(a) ? 0 : a) -\n      Number(isTableTextInvalid(b) ? 0 : b)\n    );\n  },\n};\n\nexport function renderToolTip(text) {\n  return (\n    <Tooltip title={text}>\n      <div\n        style={{\n          overflow: \"hidden\",\n          whiteSpace: \"nowrap\",\n          textOverflow: \"ellipsis\",\n        }}\n      >\n        {text}\n      </div>\n    </Tooltip>\n  );\n}\n\n// 汇总了所有无需额外逻辑的 table配置项\nexport const columnsConfig = {\n  idx: {\n    title: \"序列\",\n    key: \"index\",\n    render: (text, record, index) => `${index + 1}`,\n    align: \"center\",\n    width: 60,\n    //ixed: \"left\",\n  },\n  product_name: {\n    title: \"服务类型\",\n    //width: 150,\n    key: \"product_name\",\n    dataIndex: \"product_name\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.product_name);\n      const str2 = R.defaultTo(\" \", b.product_name);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  functional_module: {\n    title: \"功能模块\",\n    width: 120,\n    key: \"product_cn_name\",\n    dataIndex: \"product_cn_name\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.product_cn_name);\n      const str2 = R.defaultTo(\" \", b.product_cn_name);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_service_type: {\n    title: \"功能模块\",\n    width: 120,\n    key: \"alert_service_type\",\n    dataIndex: \"alert_service_type\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.alert_service_type);\n      const str2 = R.defaultTo(\" \", b.alert_service_type);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  service_name: {\n    title: \"服务名称\",\n    width: 180,\n    key: \"service_name\",\n    dataIndex: \"service_name\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.service_name);\n      const str2 = R.defaultTo(\" \", b.service_name);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_service_name: {\n    title: \"服务名称\",\n    width: 160,\n    key: \"alert_service_name\",\n    dataIndex: \"alert_service_name\",\n    ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.alert_service_name);\n      const str2 = R.defaultTo(\" \", b.alert_service_name);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  ip: {\n    title: \"IP地址\",\n    width: 160,\n    key: \"ip\",\n    dataIndex: \"ip\",\n    //ellipsis: true,\n    sorter: tableSorter.sortIP,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  thirdParty_ip: {\n    title: \"连接地址\",\n    //width: 120,\n    key: \"ip\",\n    dataIndex: \"ip\",\n    //ellipsis: true,\n    sorter: tableSorter.sortIP,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_host_ip: {\n    title: \"IP地址\",\n    width: 80,\n    key: \"alert_host_ip\",\n    dataIndex: \"alert_host_ip\",\n    //ellipsis: true,\n    sorter: tableSorter.sortAlertIP,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_level: {\n    title: \"告警级别\",\n    width: 100,\n    key: \"alert_level\",\n    dataIndex: \"alert_level\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.alert_level);\n      const str2 = R.defaultTo(\" \", b.alert_level);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    render: function renderFunc(text, record, index) {\n      switch (record.alert_level) {\n        case \"critical\":\n          return (\n            <ColorfulNotice\n              borderColor={\"#da4e48\"}\n              backgroundColor=\"#fbe7e6\"\n              text={\"严重\"}\n            />\n          );\n        case \"warning\":\n          return (\n            <ColorfulNotice\n              borderColor={\"#f5c773\"}\n              backgroundColor=\"rgba(247, 231, 24,.2)\"\n              text={\"警告\"}\n            />\n          );\n        default:\n          return \"-\";\n      }\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n  },\n  alert_describe: {\n    title: \"告警描述\",\n    key: \"alert_describe\",\n    dataIndex: \"alert_describe\",\n    width: 280,\n    align: \"center\",\n    ellipsis: true,\n    render: renderToolTip,\n  },\n  alert_time: {\n    title: \"告警时间\",\n    width: 140,\n    key: \"alert_time\",\n    dataIndex: \"alert_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.alert_time).valueOf() - moment(b.alert_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  // 告警记录中的告警时间，使用创建时间字段\n  warning_record_alert_time: {\n    title: \"告警时间\",\n    width: 180,\n    key: \"alert_time\",\n    dataIndex: \"create_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.create_time).valueOf() - moment(b.create_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_receiver: {\n    title: \"告警推送\",\n    //width: 150,\n    key: \"alert_receiver\",\n    dataIndex: \"alert_receiver\",\n    //ellipsis: true,\n    align: \"center\",\n    render: renderToolTip,\n  },\n  alert_resolve: {\n    title: \"解决方案\",\n    key: \"alert_resolve\",\n    dataIndex: \"alert_resolve\",\n    //width: 100,\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  operating_system: {\n    title: \"操作系统\",\n    //width: 130,\n    key: \"operating_system\",\n    dataIndex: \"operating_system\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  alert_host_system: {\n    title: \"操作系统\",\n    //width: 130,\n    key: \"alert_host_system\",\n    dataIndex: \"alert_host_system\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  port: {\n    title: \"端口\",\n    //width: 100,\n    key: \"port\",\n    dataIndex: \"port\",\n    //ellipsis: true,\n    sorter: (a, b) => a.port - b.port,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  service_port: {\n    title: \"端口\",\n    //width: 100,\n    key: \"service_port\",\n    dataIndex: \"service_port\",\n    //ellipsis: true,\n    sorter: (a, b) => a.service_port - b.service_port,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  service_version: {\n    title: \"服务版本\",\n    //width: 120,\n    key: \"service_version\",\n    dataIndex: \"service_version\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  configuration_information: {\n    title: \"配置信息\",\n    //width: 120,\n    key: \"configuration_information\",\n    dataIndex: \"configuration_information\",\n    //ellipsis: true,\n    align: \"center\",\n    render: renderInformation,\n  },\n  cpu_rate: {\n    title: \"CPU使用率\",\n    width: 110,\n    key: \"cpu_rate\",\n    dataIndex: \"cpu_rate\",\n    //ellipsis: true,\n    sorter: (a, b) => tableSorter.sortUsageRate(a.cpu_rate, b.cpu_rate),\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) return <div>-</div>;\n      const _num = Number(Number(text).toFixed(2));\n      if (record.cpu_rate_check === \"normal\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgb(238, 250, 244)\"\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.cpu_rate_check === \"critical\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"#fbe7e6\"}\n            borderColor=\"#da4e48\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.cpu_rate_check === \"warning\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgba(247, 231, 24,.2)\"\n            borderColor=\"#f5c773\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgb(238, 250, 244)\"\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      }\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n  },\n  disk_rate: {\n    title: \"(根分区)使用率\",\n    width: 150,\n    key: \"disk_rate\",\n    dataIndex: \"disk_rate\",\n    //ellipsis: true,\n    sorter: (a, b) => tableSorter.sortUsageRate(a.disk_rate, b.disk_rate),\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) return <div>-</div>;\n      const _num = Number(Number(text).toFixed(2));\n      if (record.disk_rate_check === \"normal\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgb(238, 250, 244)\"\n            borderColor=\"rgb(84, 187, 166)\"\n            top={1}\n            text={`${_num}%`}\n          />\n        );\n      } else if (record.disk_rate_check === \"critical\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"#fbe7e6\"}\n            borderColor=\"#da4e48\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.disk_rate_check === \"warning\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgba(247, 231, 24,.2)\"\n            borderColor=\"#f5c773\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"rgb(238, 250, 244)\"}\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      }\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n  },\n  disk_data_rate: {\n    title: \"(数据分区)使用率\",\n    width: 160,\n    key: \"disk_data_rate\",\n    dataIndex: \"disk_data_rate\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      tableSorter.sortUsageRate(a.disk_data_rate, b.disk_data_rate),\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) return <div>-</div>;\n      const _num = Number(Number(text).toFixed(2));\n      if (record.disk_data_check === \"normal\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"rgb(238, 250, 244)\"}\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.disk_data_check === \"critical\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"#fbe7e6\"}\n            borderColor=\"#da4e48\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.disk_data_check === \"warning\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgba(247, 231, 24,.2)\"\n            borderColor=\"#f5c773\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"rgb(238, 250, 244)\"}\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      }\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n  },\n\n  memory_rate: {\n    title: \"内存使用率\",\n    width: 100,\n    key: \"memory_rate\",\n    dataIndex: \"memory_rate\",\n    //ellipsis: true,\n    sorter: (a, b) => tableSorter.sortUsageRate(a.memory_rate, b.memory_rate),\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) return <div>-</div>;\n      const _num = Number(Number(text).toFixed(2));\n      if (record.memory_rate_check === \"normal\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"rgb(238, 250, 244)\"}\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.memory_rate_check === \"critical\") {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"#fbe7e6\"}\n            borderColor=\"#da4e48\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else if (record.memory_rate_check === \"warning\") {\n        return (\n          <ColorfulNotice\n            backgroundColor=\"rgba(247, 231, 24,.2)\"\n            borderColor=\"#f5c773\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      } else {\n        return (\n          <ColorfulNotice\n            backgroundColor={\"rgb(238, 250, 244)\"}\n            borderColor=\"rgb(84, 187, 166)\"\n            text={`${_num}%`}\n            top={1}\n          />\n        );\n      }\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n  },\n  running_time: {\n    title: \"运行时间\",\n    key: \"running_time\",\n    width: 120,\n    dataIndex: \"running_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      Number(isTableTextInvalid(a.running_time) ? 0 : a.running_time) -\n      Number(isTableTextInvalid(b.running_time) ? 0 : b.running_time),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: renderFormattedTime,\n  },\n  ssh_state: {\n    title: \"SSH状态\",\n    width: 140,\n    key: \"ssh_state\",\n    dataIndex: \"ssh_state\",\n    //ellipsis: true,\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 0) {\n        return (\n          <div>\n            {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}启用\n          </div>\n        );\n      } else if (text === 1) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}禁用</div>;\n      } else {\n        return text;\n      }\n    },\n  },\n  agent_state: {\n    title: \"Agent状态\",\n    width: 140,\n    key: \"agent_state\",\n    dataIndex: \"agent_state\",\n    //ellipsis: true,\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 0) {\n        return (\n          <div>\n            {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}正常\n          </div>\n        );\n      } else if (text === 1) {\n        return \"安装中\";\n      } else if (text === 2) {\n        return \"未安装\";\n      } else if (text === 3) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}异常</div>;\n      } else {\n        return text;\n      }\n    },\n  },\n  cluster_name: {\n    title: \"集群名称\",\n    //width: 120,\n    key: \"cluster_name\",\n    dataIndex: \"cluster_name\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.cluster_name);\n      const str2 = R.defaultTo(\" \", b.cluster_name);\n      return (\n        str1.toLowerCase().charCodeAt(0) - str2.toLowerCase().charCodeAt(0)\n      );\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  linkAddress: {\n    title: \"连接地址\",\n    //width: 150,\n    key: \"linkAddress\",\n    dataIndex: \"linkAddress\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  cluster_mode: {\n    title: \"集群模式\",\n    //width: 120,\n    key: \"cluster_mode\",\n    dataIndex: \"cluster_mode\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  quote: {\n    title: \"已引用\",\n    //width: 120,\n    key: \"quote\",\n    dataIndex: \"quote\",\n    //ellipsis: true,\n    align: \"center\",\n    render: (text) => (text === 0 ? \"否\" : \"是\"),\n  },\n  created_at: {\n    title: \"添加时间\",\n    //width: 120,\n    key: \"created_at\",\n    dataIndex: \"created_at\",\n    //ellipsis: true,\n    sorter: (a, b) => a - b,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  username: {\n    title: \"用户名\",\n    width: 120,\n    key: \"username\",\n    dataIndex: \"username\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.username);\n      const str2 = R.defaultTo(\" \", b.username);\n      return str1.charCodeAt(0) - str2.charCodeAt(0);\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  role: {\n    title: \"角色\",\n    width: 120,\n    key: \"role\",\n    dataIndex: \"role\",\n    //ellipsis: true,\n    sorter: (a, b) => {\n      const str1 = R.defaultTo(\" \", a.role);\n      const str2 = R.defaultTo(\" \", b.role);\n      return str1.charCodeAt(0) - str2.charCodeAt(0);\n    },\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  login_time: {\n    title: \"登入时间\",\n    width: 180,\n    key: \"login_time\",\n    dataIndex: \"login_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.login_time).valueOf() - moment(b.login_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  datetime: {\n    title: \"操作时间\",\n    width: 150,\n    key: \"datetime\",\n    dataIndex: \"datetime\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.datetime).valueOf() - moment(b.datetime).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  status: {\n    title: \"用户状态\",\n    width: 160,\n    key: \"status\",\n    dataIndex: \"status\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  date_joined: {\n    title: \"创建时间\",\n    width: 120,\n    key: \"date_joined\",\n    dataIndex: \"date_joined\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  desc: {\n    title: \"描述\",\n    width: 220,\n    key: \"desc\",\n    dataIndex: \"desc\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  action: {\n    title: \"操作类型\",\n    width: 120,\n    key: \"action\",\n    dataIndex: \"action\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  permission_count: {\n    title: \"权限个数\",\n    width: 120,\n    key: \"permission_count\",\n    dataIndex: \"permission_count\",\n    //ellipsis: true,\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n\n  // 巡检报告\n  inspection_operator: {\n    title: \"操作员\",\n    width: 80,\n    key: \"inspection_operator\",\n    dataIndex: \"inspection_operator\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      a.inspection_operator.charCodeAt(0) - b.inspection_operator.charCodeAt(0),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  inspection_status: {\n    title: \"巡检结果\",\n    width: 150,\n    key: \"inspection_status\",\n    dataIndex: \"inspection_status\",\n    //ellipsis: true,\n    sorter: (a, b) => a.inspection_status - b.inspection_status,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 2) {\n        return <div>{renderCircular(\"#6cbe7b\", \"#e8f5eb\")}成功</div>;\n      } else if (text === 1) {\n        return \"进行中\";\n      } else if (text === 0) {\n        return \"未开始\";\n      } else if (text === 3) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}失败</div>;\n      } else {\n        return text;\n      }\n    },\n  },\n  run_status: {\n    title: \"执行结果\",\n    width: 120,\n    key: \"inspection_status\",\n    dataIndex: \"inspection_status\",\n    //ellipsis: true,\n    sorter: (a, b) => a.inspection_status - b.inspection_status,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 2) {\n        return <div>{renderCircular(\"#6cbe7b\", \"#e8f5eb\")}成功</div>;\n      } else if (text === 1) {\n        return \"进行中\";\n      } else if (text === 0) {\n        return \"未开始\";\n      } else if (text === 3) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}失败</div>;\n      } else {\n        return text;\n      }\n    },\n  },\n\n  service_status: {\n    title: \"业务状态\",\n    //width: 120,\n    key: \"service_status\",\n    dataIndex: \"service_status\",\n    //ellipsis: true,\n    sorter: (a, b) => a.service_status - b.service_status,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  // 产品管理-服务管理中新增的字段\n  product_service_status: {\n    title: \"运行状态\",\n    width: 120,\n    key: \"product_service_status\",\n    dataIndex: \"service_status\",\n    //ellipsis: true,\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 0) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}未安装</div>\n        );\n        //return <div style={{ color: \"#389e0d\" }}>运行</div>;\n      } else if (text === 1) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}安装中</div>\n        );\n      } else if (text === 2) {\n        return (\n          <div>\n            {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}正常\n          </div>\n        );\n      } else if (text === 3) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}异常</div>;\n      } else if (text === 4) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}停止</div>;\n      } else if (text == 5) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}启动中</div>\n        );\n      } else if (text == 6) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}停止中</div>\n        );\n      } else if (text == 7) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}重启中</div>\n        );\n      } else if (text == -1) {\n        if (record.is_web_service) {\n          return (\n            <div>\n              {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}正常\n            </div>\n          );\n        } else {\n          return (\n            <div>\n              {renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}未监控\n            </div>\n          );\n        }\n      } else {\n        return text;\n      }\n    },\n  },\n  product_thrityPart_status: {\n    title: \"运行状态\",\n    //width: 100,\n    key: \"product_service_status\",\n    dataIndex: \"state\",\n    //ellipsis: true,\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else if (text === 1) {\n        return (\n          <div>\n            {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}正常\n          </div>\n        );\n      } else if (text === 2) {\n        return (\n          <div>{renderCircular(\"#f5c773\", \"rgba(247, 231, 24,.2)\")}异常</div>\n        );\n      } else if (text === 0) {\n        return <div>{renderCircular(\"#da4e48\", \"#fbe7e6\")}停止</div>;\n      } else {\n        return text;\n      }\n    },\n  },\n  host_risk: {\n    title: \"主机风险\",\n    //width: 120,\n    key: \"host_risk\",\n    dataIndex: \"host_risk\",\n    //ellipsis: true,\n    sorter: (a, b) => a.host_risk - b.host_risk,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else {\n        return `${text}个`;\n      }\n    },\n  },\n  service_risk: {\n    title: \"服务风险\",\n    //width: 120,\n    key: \"service_risk\",\n    dataIndex: \"service_risk\",\n    //ellipsis: true,\n    sorter: (a, b) => a.service_risk - b.service_risk,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text, record, index) => {\n      if (isTableTextInvalid(text)) {\n        return \"-\";\n      } else {\n        return `${text}个`;\n      }\n    },\n  },\n  start_time: {\n    title: \"开始时间\",\n    width: 160,\n    key: \"start_time\",\n    dataIndex: \"start_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.start_time).valueOf() - moment(b.start_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  patrol_start_time: {\n    title: \"开始时间\",\n    width: 200,\n    key: \"patrol_start_time\",\n    dataIndex: \"start_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.start_time).valueOf() - moment(b.start_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  patrol_end_time: {\n    title: \"结束时间\",\n    width: 160,\n    key: \"patrol_end_time\",\n    dataIndex: \"end_time\",\n    //ellipsis: true,\n    sorter: (a, b) =>\n      moment(a.end_time).valueOf() - moment(b.end_time).valueOf(),\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: formatTableRenderData,\n  },\n  duration: {\n    title: \"用时\",\n    width: 100,\n    key: \"duration\",\n    dataIndex: \"duration\",\n    //ellipsis: true,\n    sorter: (a, b) => a.duration - b.duration,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: renderFormattedTime,\n  },\n\n  // 巡检报告内容\n  // 主机风险\n  report_system: {\n    title: \"操作系统\",\n    //width: 150,\n    key: \"report_system\",\n    dataIndex: \"system\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_risk_level: {\n    title: \"风险级别\",\n    key: \"report_risk_level\",\n    dataIndex: \"risk_level\",\n    //ellipsis: true,\n    //width: 100,\n    render: function renderFunc(text, record, index) {\n      switch (record.risk_level) {\n        case \"critical\":\n          return (\n            <ColorfulNotice\n              backgroundColor={\"#fbe7e6\"}\n              borderColor=\"#da4e48\"\n              text={\"严重\"}\n            />\n          );\n        case \"warning\":\n          return (\n            <ColorfulNotice\n              backgroundColor=\"rgba(247, 231, 24,.2)\"\n              borderColor=\"#f5c773\"\n              text={`警告`}\n            />\n          );\n        default:\n          return \"-\";\n      }\n    },\n    align: \"center\",\n  },\n  report_risk_describe: {\n    width: 400,\n    title: \"风险描述\",\n    key: \"report_risk_describe\",\n    dataIndex: \"risk_describe\",\n    ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_resolve_info: {\n    title: \"解决方案\",\n    key: \"report_resolve_info\",\n    dataIndex: \"resolve_info\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  // 主机列表\n  report_release_version: {\n    title: \"操作系统\",\n    key: \"report_release_version\",\n    dataIndex: \"release_version\",\n    //ellipsis: true,\n    //width: 180,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_host_massage: {\n    title: \"配置信息\",\n    key: \"report_host_massage\",\n    dataIndex: \"host_massage\",\n    //ellipsis: true,\n    //ellipsis: true,\n    //width: 180,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_disk_usage_root: {\n    title: \"根分区使用率\",\n    width: 150,\n    key: \"report_disk_usage_root\",\n    dataIndex: \"disk_usage_root\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_disk_usage_data: {\n    title: \"数据分区使用率\",\n    width: 130,\n    key: \"report_disk_usage_data\",\n    dataIndex: \"disk_usage_data\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_sys_load: {\n    title: \"平均负载\",\n    key: \"report_sys_load\",\n    dataIndex: \"sys_load\",\n    //width:180,\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  // 服务列表、数据库列表、组件列表\n  report_idx: {\n    title: \"序列\",\n    key: \"index\",\n    width: 50,\n    render: (text, record, index) => `${index + 1}`,\n    align: \"center\",\n    //fixed: \"left\",\n    ////ellipsis:true\n  },\n  report_host_ip: {\n    title: \"IP地址\",\n    key: \"report_host_ip\",\n    dataIndex: \"host_ip\",\n    //ellipsis: true,\n    width: 150,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_log_level: {\n    title: \"日志等级\",\n    key: \"report_log_level\",\n    dataIndex: \"log_level\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_mem_usage: {\n    title: \"内存使用率\",\n    key: \"report_mem_usage\",\n    dataIndex: \"mem_usage\",\n    //ellipsis: true,\n    width: 100,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_cpu_usage: {\n    title: \"CPU使用率\",\n    key: \"report_cpu_usage\",\n    dataIndex: \"cpu_usage\",\n    //ellipsis: true,\n    width: 110,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_service_name: {\n    title: \"服务名称\",\n    //width: 200,\n    key: \"report_service_name\",\n    dataIndex: \"service_name\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_service_port: {\n    title: \"端口号\",\n    key: \"report_service_port\",\n    dataIndex: \"service_port\",\n    //ellipsis: true,\n    //width: 100,\n    render: report_service_RenderData,\n    align: \"center\",\n  },\n  report_service_status: {\n    title: \"运行状态\",\n    key: \"report_service_status\",\n    dataIndex: \"service_status\",\n    //ellipsis: true,\n    //width: 100,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_run_time: {\n    title: \"运行时间\",\n    width: 120,\n    key: \"report_run_time\",\n    dataIndex: \"run_time\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  report_cluster_name: {\n    title: \"集群名称\",\n    key: \"report_cluster_name\",\n    dataIndex: \"cluster_name\",\n    //ellipsis: true,\n    render: formatTableRenderData,\n    align: \"center\",\n  },\n  operator: {\n    title: \"操作人员\",\n    key: \"operator\",\n    dataIndex: \"operator\",\n    align: \"center\",\n    width: 80,\n  },\n  install_process: {\n    title: \"安装进度\",\n    key: \"install_process\",\n    dataIndex: \"install_process\",\n    align: \"center\",\n    width: 80,\n    render: (text) => {\n      if (text == \"0%\") {\n        return <span>{renderCircular(\"#da4e48\", \"#fbe7e6\")}失败</span>;\n      } else if (text == \"100%\") {\n        return (\n          <span>\n            {renderCircular(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\")}成功\n          </span>\n        );\n      } else {\n        return text;\n      }\n    },\n  },\n  verson_start_time: {\n    title: \"开始时间\",\n    key: \"start_time\",\n    dataIndex: \"start_time\",\n    align: \"center\",\n    width: 160,\n  },\n  verson_end_time: {\n    title: \"结束时间\",\n    key: \"end_time\",\n    dataIndex: \"end_time\",\n    align: \"center\",\n    width: 150,\n  },\n  use_time: {\n    title: \"用时\",\n    key: \"duration\",\n    dataIndex: \"duration\",\n    render: (text) => {\n      if (text && text !== \"-\") {\n        let timer = moment.duration(text, \"seconds\");\n\n        let hours = timer.hours();\n        let hoursResult = hours ? `${hours}小时` : \"\";\n\n        let minutes = timer.minutes();\n        let minutesResult = minutes % 60 ? `${minutes % 60}分钟` : \"\";\n\n        let seconds = timer.seconds();\n        let secondsResult = seconds % 60 ? `${seconds % 60}秒` : \"\";\n\n        return `${hoursResult} ${minutesResult} ${secondsResult}`;\n\n        // if(minutes >= 1){\n        //   return `${minutes.toFixed()}分钟`;\n        // }else{\n        //   return `${text}秒`;\n        // }\n      } else {\n        return \"-\";\n      }\n    },\n    align: \"center\",\n    width: 100,\n  },\n  execution_mdoal: {\n    title: \"执行方式\",\n    align: \"center\",\n    dataIndex: \"execute_type\",\n    key: \"execute_type\",\n    render: (text) => {\n      if (text == \"man\") {\n        return \"手动执行\";\n      } else if (text == \"auto\") {\n        return \"定时执行\";\n      } else {\n        return \"-\";\n      }\n    },\n    width: 80,\n  },\n  machine_idx: {\n    title: \"序列\",\n    key: \"index\",\n    render: (text, record, index) => `${record._idx}`,\n    align: \"center\",\n    width: 60,\n    //fixed: \"left\",\n  },\n  /*eslint-disable*/\n  service_idx: {\n    title: \"序列\",\n    key: \"index\",\n    dataIndex: \"_idx\",\n    //render: (text, record, index) => `${index + 1}`,\n    //ellipsis: true,\n    align: \"center\",\n    width: 60,\n    //fixed: \"left\",\n    // render:(text,record)=>{\n    //   return (\n    //     <div className=\"record-name\">\n    //       <span>{record._idx}</span>\n    //     </div>\n    //   );\n    // }\n  },\n  /*eslint-disable*/\n  service_port_new: {\n    title: \"端口\",\n    width: 100,\n    key: \"service_port\",\n    dataIndex: \"service_port\",\n    //ellipsis: true,\n    sorter: (a, b) => a.service_port - b.service_port,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text) => {\n      return text ? text : \"-\";\n    },\n  },\n  _port_new: {\n    title: \"端口\",\n    //width: 100,\n    key: \"port\",\n    dataIndex: \"port\",\n    //ellipsis: true,\n    sorter: (a, b) => a.port - b.port,\n    sortDirections: [\"descend\", \"ascend\"],\n    align: \"center\",\n    render: (text) => {\n      return text ? text : \"-\";\n    },\n  },\n};\n// 巡检报告-主机列表-连通性报告配置\nexport const host_port_connectivity_columns = [\n  {\n    title: \"服务\",\n    dataIndex: \"name\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"IP地址\",\n    dataIndex: \"ip\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"端口\",\n    dataIndex: \"port\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n  /*eslint-disable*/\n  {\n    title: \"连通性\",\n    dataIndex: \"status\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n    render: (text) => {\n      return (\n        <div style={{ color: text == \"False\" ? \"rgb(207, 19, 34)\" : null }}>\n          {text}\n        </div>\n      );\n    },\n  },\n  /*eslint-disable*/\n];\n// 巡检报告-主机列表-内存使用率配置\nexport const host_memory_top_columns = [\n  {\n    title: \"TOP\",\n    dataIndex: \"TOP\",\n    //ellipsis: true,\n    width: 50,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"PID\",\n    dataIndex: \"PID\",\n    //ellipsis: true,\n    align: \"center\",\n    width: 100,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"使用率\",\n    dataIndex: \"P_RATE\",\n    //ellipsis: true,\n    align: \"center\",\n    width: 100,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"进程\",\n    dataIndex: \"P_CMD\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n];\n\n// 巡检报告-组件列表-kafka-分区信息\nexport const kafka_partition_columns = [\n  {\n    title: \"Topic\",\n    dataIndex: \"topic\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"分区数\",\n    dataIndex: \"partition\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"副本数\",\n    dataIndex: \"replication\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n];\n\n// 巡检报告-组件列表-kafka-消费位移信息\nexport const kafka_offsets_columns = [\n  {\n    title: \"Group\",\n    dataIndex: \"group\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"Topic\",\n    dataIndex: \"topic\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"Log Offset\",\n    dataIndex: \"log_offset\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"Lag Offset\",\n    dataIndex: \"lag_offset\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n];\n\n// 巡检报告-组件列表-kafka-topic消息大小\nexport const kafka_topic_size_columns = [\n  {\n    title: \"Topic\",\n    dataIndex: \"topic\",\n    //ellipsis: true,\n    className: styles._bigfontSize,\n  },\n  {\n    title: \"Size\",\n    dataIndex: \"size\",\n    //ellipsis: true,\n    align: \"center\",\n    className: styles._bigfontSize,\n  },\n];\n\n/**\n * table组件最后一列操作按钮的跳转逻辑\n * 以=结尾的需要拼接ip地址\n * @param record\n * @param type 默认是监控跳转\n */\nexport const tableButtonHandler = (record, type = \"monitor\") => {\n  if (type === \"log\") {\n    if (isTableTextInvalid(record.monitor_log)) {\n      return message.warn(\"请确认数据采集地址是否正确\");\n    }\n    //console.log(record.monitor_log,\"===\",record.service_name,record);\n    if (record.service_name) {\n      //window.open(`${record.monitor_log}${record.service_name}&var-env=${updata()().text}`);\n      window.open(`${record.monitor_log}${record.service_name}`);\n    } else if (record.alert_service_name) {\n      //window.open(`${record.monitor_log}${record.alert_service_name}&var-env=${updata()().text}`);\n      window.open(`${record.monitor_log}${record.alert_service_name}`);\n    }\n  } else {\n    const url = record.monitor;\n    if (isTableTextInvalid(url)) {\n      return message.warn(\"请确认数据采集地址是否正确\");\n    } else if (url.endsWith(\"=\")) {\n      // 主机管理、服务管理中用的ip，告警记录里用的alert_host_ip，但二者不会共存\n      // window.open(`${url}${record.ip ? record.ip : record.alert_host_ip}&var-env=${record.is_omp_host?record.master_env_name:updata()().text}`);\n      window.open(`${url}${record.ip ? record.ip : record.alert_host_ip}`);\n    } else if (url.endsWith(\"1\")) {\n      // 服务管理-自研服务，跳转监控时拼接服务名称\n      // window.open(\n      //   `${url}&var-app=${\n      //     record.service_name ? record.service_name : record.alert_service_name\n      //   }&var-ip=${record.ip?record.ip:(record.alert_host_ip?record.alert_host_ip:undefined)}&var-env=${updata()().text}`\n      // );\n      window.open(\n        `${url}&var-app=${\n          record.service_name ? record.service_name : record.alert_service_name\n        }&var-ip=${\n          record.ip\n            ? record.ip\n            : record.alert_host_ip\n            ? record.alert_host_ip\n            : undefined\n        }`\n      );\n    } else {\n      //window.open(`${url}&var-env=${updata()().text}`);\n      window.open(`${url}`);\n    }\n  }\n};\n\n/**\n * 测试ip地址准确性\n * @param ip\n * @returns {boolean}\n */\nexport function isValidIP(ip) {\n  const reg =\n    /^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\:([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])$/;\n  return reg.test(ip);\n}\n\n/**\n * @name:\n * @test: test font\n * @msg:\n * @param {*} data\n * @return {*}\n */\n//给列表item添加idx做id用\nexport const _idxInit = (data) => {\n  let result = [...data];\n  result.map((item, i) => {\n    result[i]._idx = i + 1;\n    result[i].key = result[i].id ? result[i].id : result[i]._idx;\n  });\n  return result;\n};\n\nexport function TableRowButton({ buttonsArr }) {\n  return (\n    <div className={styles.listButton}>\n      {buttonsArr.map((item, idx) => {\n        return (\n          <div key={idx} onClick={() => item.btnHandler()}>\n            {item.btnText}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nexport const refreshTime = () => {\n  return getRefreshTimeChangeAction(moment().format(\"YYYY-MM-DD HH:mm:ss\"));\n};\n\nexport function delCookie(name) {\n  var exp = new Date();\n  exp.setTime(exp.getTime() - 1);\n  var cval = getCookie(name);\n  //console.log(cval)\n  if (cval != null)\n    document.cookie =\n      name +\n      \"=\" +\n      cval +\n      ';domin=\"localhost\"' +\n      \";expires=\" +\n      exp.toGMTString();\n}\n\nexport function getCookie(name) {\n  //console.log(document.cookie)\n  let arr = document.cookie.match(new RegExp(\"(^| )\" + name + \"=([^;]*)(;|$)\"));\n  if (arr != null) return unescape(arr[2]);\n  return null;\n}\n\nexport const logout = (login) => {\n  delCookie(\"jwtToken\");\n  localStorage.clear();\n  !login && window.__history__.replace(\"/login\");\n  return;\n};\n\n//文本非空处理\nexport const nonEmptyProcessing = (text) => {\n  if (text === \"\" || text === null || text === undefined) {\n    return \"-\";\n  } else {\n    return `${text}`;\n  }\n};\n\nexport const handleResponse = (res, succCallback, failedCallback) => {\n  if (res.data.code === 0) {\n    if (typeof succCallback === \"function\") {\n      succCallback(res.data);\n    }\n  }\n\n  if (res.data.code === 1) {\n    if (res.data.message) {\n      if (res.data.message == \"未认证\") {\n        logout();\n        return;\n      }\n      message.warn(res.data.message);\n    }\n\n    if (typeof failedCallback === \"function\") {\n      failedCallback();\n    }\n  }\n};\n\nexport const colorConfig = {\n  normal: \"#76ca68\",\n  warning: \"#ffbf00\",\n  critical: \"#f04134\",\n  notMonitored: \"rgb(170, 170, 170)\",\n};\n\nexport const renderDisc = (level = \"normal\", size = 5, top = 0, left = 0) => {\n  return (\n    <div\n      style={{\n        color: colorConfig[level],\n        width: size,\n        height: size,\n        borderStyle: \"solid\",\n        //borderWidth: 2,\n        borderColor: colorConfig[level],\n        backgroundColor: colorConfig[level],\n        display: \"inline-block\",\n        marginRight: 8,\n        borderRadius: \"50%\",\n        position: \"relative\",\n        top: top,\n        left: left,\n      }}\n    ></div>\n  );\n};\n\nexport const MessageTip = ({ setMsgShow, msgShow, msg }) => {\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        top: -10,\n        left: 10,\n        backgroundColor: \"#fbe3e2\",\n        padding: \"10px\",\n        height: \"40px\",\n        color: \"#86292e\",\n        display: \"flex\",\n        justifyContent: \"space-between\",\n        cursor: \"pointer\",\n        width: 240,\n        margin: \"0 auto\",\n        paddingLeft: 15,\n      }}\n      className={msgShow ? styles.loginMessageShow : styles.loginMessageHide}\n      onClick={() => setMsgShow(false)}\n    >\n      {msg}\n      <CloseCircleFilled\n        style={{ color: \"#fff\", fontSize: 20, marginLeft: \"10px\" }}\n      />\n    </div>\n  );\n};\n\n//校验中文\nexport const isChineseChar = (str) => {\n  var reg = /[\\u4E00-\\u9FA5\\uF900-\\uFA2D]/;\n  return reg.test(str);\n};\n\n//校验数字\nexport const isNumberChar = (str) => {\n  const reg = /^\\d+$/;\n  return reg.test(str);\n};\n\n// 校验小写\nexport const isLowercaseChar = (str) => {\n  const reg = /^[a-z]+$/;\n  return reg.test(str);\n};\n\n// 校验大写\nexport const isUppercaseChar = (str) => {\n  const reg = /^[A-Z]+$/;\n  return reg.test(str);\n};\n\n// 校验字母\nexport const isLetterChar = (str) => {\n  const reg = /^[a-zA-Z]+$/;\n  return reg.test(str);\n};\n\n// 校验ip\nexport const isValidIpChar = (ip) => {\n  var reg =\n    /^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$/;\n  return reg.test(ip);\n};\n\n// 校验表情\nexport const isExpression = (str) => {\n  var reg =\n    /[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\\u2026\\u2022\\u20ac\\r\\n]/g;\n  return reg.test(str);\n};\n\n// 校验空格\nexport const isSpace = (str) => {\n  return str.includes(\" \");\n};\n\nexport function debounce(fn, wait) {\n  return function () {\n    clearTimeout(window.timer);\n    window.timer = setTimeout(fn, wait);\n  };\n}\n\n// 校验密码\nexport function isPassword(str) {\n  var reg = /[^a-zA-Z0-9\\`\\~\\!\\?\\@\\#\\$\\%\\^\\&\\,\\(\\)\\[\\]\\{\\}\\_\\+\\_\\*\\/\\.\\;\\:]/g;\n  return reg.test(str);\n}\n\n// 下载文件\nexport const downloadFile = (url) => {\n  let a = document.createElement(\"a\");\n  a.href = url;\n  a.download = url.split(\"/\").pop();\n  document.body.appendChild(a);\n  a.click();\n  document.body.removeChild(a);\n};\n\n// 检测对象类型\nfunction checkType(any) {\n  return Object.prototype.toString.call(any).slice(8, -1);\n}\n\n// 深拷贝函数\nexport const clone = (any) => {\n  if (checkType(any) === \"Object\") {\n    // 拷贝对象\n    let o = {};\n    for (let key in any) {\n      o[key] = clone(any[key]);\n    }\n    return o;\n  } else if (checkType(any) === \"Array\") {\n    // 拷贝数组\n    var arr = [];\n    for (let i = 0, leng = any.length; i < leng; i++) {\n      arr[i] = clone(any[i]);\n    }\n    return arr;\n  } else if (checkType(any) === \"Function\") {\n    // 拷贝函数\n    return new Function(\"return \" + any.toString()).call(this);\n  } else if (checkType(any) === \"Date\") {\n    // 拷贝日期\n    return new Date(any.valueOf());\n  } else if (checkType(any) === \"RegExp\") {\n    // 拷贝正则\n    return new RegExp(any);\n  } else if (checkType(any) === \"Map\") {\n    // 拷贝Map 集合\n    let m = new Map();\n    any.forEach((v, k) => {\n      m.set(k, clone(v));\n    });\n    return m;\n  } else if (checkType(any) === \"Set\") {\n    // 拷贝Set 集合\n    let s = new Set();\n    for (let val of any.values()) {\n      s.add(clone(val));\n    }\n    return s;\n  }\n  return any;\n};\n\nexport const randomNumber = (length = 6) => {\n  let r = \"\";\n  let str = \"QWERTYUIOPLKJHGFDSAZXCVBNM123456790\";\n  new Array(length).fill(0).map((item) => {\n    let num = parseInt(Math.random() * 26);\n    r += str[num];\n  });\n  return r;\n};\n\n//定义加密函数\nexport const encrypt = (message) => {\n  var encrypt = new JSEncrypt();\n  encrypt.setPublicKey(PublicKey); //\t publicKey为公钥\n  const txt = encrypt.encrypt(message);\n  return txt;\n};\n\n// 根据result渲染状态\nexport const RenderStatusForResult = ({ result }) => {\n  if (result === \"success\") {\n    return (\n      <CheckCircleFilled\n        style={{\n          color: colorConfig[\"normal\"],\n          marginRight: \"10px\",\n        }}\n      />\n    );\n  } else {\n    return (\n      <CloseCircleFilled\n        style={{\n          color: colorConfig[\"critical\"],\n          marginRight: \"10px\",\n        }}\n      />\n    );\n  }\n};\n"
  },
  {
    "path": "omp_web/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowUnreachableCode\": true,\n    \"allowUnusedLabels\": false,\n    \"alwaysStrict\": false,\n    \"baseUrl\": \".\",\n    \"experimentalDecorators\": true,\n    \"jsx\": \"react-jsx\",\n    \"sourceMap\": true,\n    \"module\": \"esnext\",\n    \"noImplicitAny\": false,\n    \"removeComments\": true,\n    \"types\": [\n      \"node\"\n    ],\n    \"target\": \"ESNext\",\n    \"outDir\": \"./dist\",\n    \"declaration\": true,\n    \"declarationDir\": \"./lib\",\n    \"allowJs\": true,\n    \"lib\": [\n      \"es5\",\n      \"es2015\",\n      \"es2016\",\n      \"es2017\",\n      \"es2018\",\n      \"dom\"\n    ],\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "package_hub/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/_modules/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-24 12:01\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n"
  },
  {
    "path": "package_hub/_modules/arangodb_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get arangodb Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP, GetProcess_ServiceMem\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'arangodb':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\n# def GetProcess_LogLevel(pid):\n#     log_level = None\n#     if pid and type(pid).__name__ == 'int':\n#         try:\n#             p = psutil.Process(pid)\n#             arangodb_path = p.cmdline()\n#             print(arangodb_path[2])\n#             f = open('%s' % (arangodb_path[2]), 'r')\n#             for lines_list in f:\n#                 if 'level =' in lines_list:\n#                     arangodb_log_level = lines_list.strip('\\n').split()\n#                     log_level = arangodb_log_level[-1]\n#             return log_level\n#         except Exception:\n#             return None\n#     else:\n#         return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = \"INFO\"\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"arangodb\")\n    return json.dumps(process_message)\n\n\nif __name__ == \"__main__\":\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/beanstalkd_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get beanstalkd Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP, GetProcess_ServiceMem\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'beanstalkd':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\n# def GetProcess_LogLevel(pid):\n#     log_level = None\n#     if pid and type(pid).__name__ == 'int':\n#         try:\n#             p = psutil.Process(pid)\n#             beanstalkd_path = p.cmdline()\n#             print(beanstalkd_path[2])\n#             f = open('%s' % (beanstalkd_path[2]), 'r')\n#             for lines_list in f:\n#                 if 'level =' in lines_list:\n#                     beanstalkd_log_level = lines_list.strip('\\n').split()\n#                     log_level = beanstalkd_log_level[-1]\n#             return log_level\n#         except Exception:\n#             return None\n#     else:\n#         return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = \"INFO\"\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"beanstalkd\")\n    return json.dumps(process_message)\n\n\nif __name__ == \"__main__\":\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/clickhouse_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get clickhouse Inspection data\nimport json\nimport os\nimport time\nimport os.path as up\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre\n\n\ndef GetClickhouse_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'clickhouse-server':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_ServiceMem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            service_mem_list = list()\n            p = psutil.Process(pid)\n            ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n            f = open('%s/etc/clickhouse-server/users.xml' % (ch_path), 'r')\n            for lines_list in f:\n                if '<max_memory_usage>' in lines_list:\n                    service_mem_list = lines_list.strip().replace(\n                        '>', '').replace('</', '').split('max_memory_usage')\n            service_mem = int(service_mem_list[1]) / 1024 / 1024 / 1024\n            ck_mem = \"{:.2f}\".format(service_mem) + 'G'\n            return ck_mem\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    service_log_level = list()\n    if pid and type(pid).__name__ == 'int':\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        f = open('%s/etc/clickhouse-server/config.xml' % (ch_path), 'r')\n        for lines_list in f:\n            if '<level>' in lines_list:\n                service_log_level = lines_list.strip().replace(\n                    '>', '').replace('</', '').split('level')\n        log_level = service_log_level[1]\n        return log_level\n    else:\n        return None\n\n\ndef GetTable_Readonly(pid, host, port, user, password):\n    if pid and type(pid).__name__ == 'int':\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        if password:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -u %s --password %s -q ' % (\n                ch_path, host, port, user, password)\n        else:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -q' % (\n                ch_path, host, port)\n        cmd = ck_bin + '\"select database,table from  system.replicas  where is_readonly = 1;\"'\n        ck_readonly = os.popen(cmd).read().strip('\\n').replace('\\t', ' ')\n        if ck_readonly:\n            # table_readonly = ck_readonly.split('\\n')\n            return ck_readonly\n        else:\n            return \"normal\"\n    else:\n        return None\n\n\ndef GetNodeData_Size(pid, host, port, user, password):\n    if pid and type(pid).__name__ == 'int':\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        if password:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -u %s --password %s -q ' % (\n                ch_path, host, port, user, password)\n        else:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -q' % (\n                ch_path, host, port)\n        try:\n            cmd = ck_bin + \\\n                '\"select formatReadableSize(sum(data_compressed_bytes)) from system.parts;\"'\n            nodedata_size = os.popen(cmd).read().strip('\\n')\n            return nodedata_size\n        except Exception:\n            return None\n\n\ndef GetDistribute_Size(pid):\n    if pid and type(pid).__name__ == 'int':\n        p = psutil.Process(pid)\n        data_path = up.abspath(up.join(p.cwd(), \"..\"))\n        cmd = 'du -csh %s/data/tsb_distribute %s/data/_cw_distributed_db %s/data/jkb_distribute 2>/dev/null' % (\n            data_path, data_path, data_path)\n        distribute_size_list = os.popen(cmd).read().strip(\n            '\\n').replace('\\t', ' ').split('\\n')\n        distribute_size = distribute_size_list[-1].split()\n        size = distribute_size[0]\n        return size\n    else:\n        return None\n\n\ndef GetRealTime_Data(pid, host, port, user, password):\n    realtime_json = {}\n    if pid and type(pid).__name__ == 'int':\n        tsb_table_list = ['host_basic_all', 'browser_page_all',\n                          'app_request_all', 'mobile_basic_all']\n        now_time = int(time.time())\n        ago_tiem = int(now_time) - 300\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        if password:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -u %s --password %s -q ' % (\n                ch_path, host, port, user, password)\n        else:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -q' % (\n                ch_path, host, port)\n        for table in tsb_table_list:\n            cmd = ck_bin + '\"select count() from tsb_distribute.%s where current_time between toDateTime(%d) and toDateTime(%d);\"' % (\n                table, ago_tiem, now_time) + \" 2>/dev/null\"\n            realtime = os.popen(cmd).read().strip('\\n')\n            try:\n                if int(realtime) > 0:\n                    realtime_json[table] = 'True'\n                else:\n                    realtime_json[table] = 'False'\n            except Exception:\n                realtime_json[table] = 'False'\n        return realtime_json\n    else:\n        return None\n\n\ndef GetJKBRealTime_Data(pid, host, port, user, password):\n    realtime_json = {}\n    if pid and type(pid).__name__ == 'int':\n        table_list = ['api_snapshot_data_all', 'task_snapshot_all']\n        now_time = int(time.time())\n        ago_tiem = int(now_time) - 300\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        if password:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -u %s --password %s -q ' % (\n                ch_path, host, port, user, password)\n        else:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -q' % (\n                ch_path, host, port)\n        for table in table_list:\n            cmd = ck_bin + '\"select count() from jkb_distribute.%s where current_time between toDateTime(%d) and toDateTime(%d);\"' % (\n                table, ago_tiem, now_time) + \" 2>/dev/null\"\n            realtime = os.popen(cmd).read().strip('\\n')\n            try:\n                if int(realtime) > 0:\n                    realtime_json[table] = 'True'\n                else:\n                    realtime_json[table] = 'False'\n            except Exception:\n                realtime_json[table] = 'False'\n        return realtime_json\n    else:\n        return None\n\n\ndef GetCluster_IP(pid, host, port, user, password):\n    if pid and type(pid).__name__ == 'int':\n        p = psutil.Process(pid)\n        ch_path = up.abspath(up.join(p.exe(), \"../..\"))\n        if password:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -u %s --password %s -q ' % (\n                ch_path, host, port, user, password)\n        else:\n            ck_bin = '%s/bin/clickhouse-client -m -h %s --port %s -q' % (\n                ch_path, host, port)\n        try:\n            cmd = ck_bin + '''\"select host_address from system.clusters where host_address not like '127.0.0.1' group by host_address;\"'''\n            cluster_ip = os.popen(cmd).read().strip('\\n').split('\\n')\n            return cluster_ip\n        except Exception:\n            return None\n\n\ndef main(pid=GetClickhouse_Pid(), host='127.0.0.1', port='18101', user='default', password='', **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"cluser_ip\"] = GetCluster_IP(\n        pid, host, port, user, password)\n    process_message[\"table_readonly\"] = GetTable_Readonly(\n        pid, host, port, user, password)\n    process_message[\"nodedata_size\"] = GetNodeData_Size(\n        pid, host, port, user, password)\n    process_message[\"distribute_size\"] = GetDistribute_Size(pid)\n    process_message[\"tsb_realtime\"] = GetRealTime_Data(\n        pid, host, port, user, password)\n    process_message[\"jkb_realtime\"] = GetJKBRealTime_Data(\n        pid, host, port, user, password)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/elasticsearch_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get es Inspection data\nimport json\nimport ssl\nimport urllib.request\n\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'java' and 'elasticsearch' in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    log_level = None\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            es_path = p.cwd()\n            f = open('%s/config/log4j2.properties' % (es_path), 'r')\n            for lines_list in f:\n                if 'logger.action.level =' in lines_list:\n                    es_log_level = lines_list.strip('\\n').split()\n                    log_level = es_log_level[-1]\n        except Exception:\n            log_level = None\n        return log_level\n    else:\n        return None\n\n\ndef GetCluster_Status(port):\n    url = 'http://127.0.0.1' + ':' + str(port) + \"/_cluster/health\"\n    ssl._create_default_https_context = ssl._create_unverified_context\n    try:\n        cluster_list = urllib.request.urlopen('%s' % (url), timeout=15)\n        cluster_line = json.loads(cluster_list.read())\n        cluster = cluster_line[\"status\"]\n        return cluster\n    except Exception:\n        return None\n\n\ndef GetIndex_Status(port):\n    status = {}\n    url = 'http://127.0.0.1' + ':' + str(port) + \"/_cat/indices\"\n    ssl._create_default_https_context = ssl._create_unverified_context\n    try:\n        index_list = urllib.request.urlopen('%s' % (url), timeout=15)\n        index_line = index_list.read().decode().strip().split('\\n')\n        for index in index_line:\n            index_status = index.split()\n            if index_status[0] != 'green' and index_status[1] == 'open':\n                status[index_status[2]] = index_status[0]\n        if status:\n            return status\n        else:\n            return 'green'\n    except Exception:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), port=18115, json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid, is_java=True)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message['cluster_status'] = GetCluster_Status(port)\n    process_message[\"index_status\"] = GetIndex_Status(port)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"elasticsearch\")\n    return json.dumps(process_message)\n\n\nif __name__ == \"__main__\":\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/flink_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get flink Inspection data\n\nimport json\nimport os\nimport socket\nimport time\n\nimport psutil\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'java' and \"flink\" in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid):\n    if pid and type(pid).__name__ == 'int':\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid):\n    try:\n        if pid and type(pid).__name__ == 'int':\n            port = []\n            # p = psutil.Process(pid)\n            cmd = 'ss -tnlp | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[3].split(':')\n                port.append(port_aa[-1])\n            port = list(set(port))\n            return port\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetProcess_Runtime(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,etime|grep ' + str(pid)\n            etime = os.popen(cmd).read().strip('\\n').split()\n            if '-' in etime[1]:\n                runtime = etime[1].replace('-', ' day ')\n            else:\n                runtime = etime[1]\n        except Exception:\n            runtime = None\n    else:\n        runtime = None\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            pid_cpuinfo = {}\n            p = psutil.Process(pid)\n            pt = p.cpu_times()\n            st1, pt1_0, pt1_1 = timer(), pt.user, pt.system  # new\n            st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n            delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n            delta_time = st1 - st0\n            cpus_percent = ((delta_proc / delta_time) * 100)\n            pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n            cpu_usage = \"{:.2f}\".format(cpus_percent) + \"%\"\n        except Exception:\n            cpu_usage = None\n    else:\n        cpu_usage = None\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            mem_usage = \"{:.2f}\".format(process_mem) + \"%\"\n        except Exception:\n            return None\n    else:\n        mem_usage = None\n    return mem_usage\n\n\ndef GetProcess_ServiceMem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,command|grep %s' % (pid)\n            process_list = os.popen(cmd).read().strip('\\n').split('-Xms')\n            process_mem = process_list[-1].split()\n            service_mem = process_mem[0]\n            return service_mem\n        except Exception:\n            return None\n    else:\n        return None\n\n\n# def GetProcess_LogLevel(pid):\n#     if pid and type(pid).__name__ == 'int':\n#         try:\n#             p = psutil.Process(pid)\n#             domm_path = p.cwd()\n#             f = open('%s/conf/log4j2.xml' % (domm_path),'r')\n#             for lines_list in f:\n#                 if '<Root level=' in lines_list:\n#                     log_level = lines_list.strip().replace('<Root level=\"${env:CW_DOMM_LOG_LEVEL:-','').replace('}\">','')\n#         except Exception:\n#             log_level = None\n#         return log_level\n#     else:\n#         return None\n\n\ndef main(pid=GetProcess_Pid(), **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = None\n    process_message[\"cluster_ip\"] = None\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/get_agent_info.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: get_agent_info\n# Author: jon.liu@yunzhihui.com\n# Create time: 2020-12-31 19:04\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport math\nimport socket\n\nimport psutil\nimport salt.utils.network\n\n\ndef byte_to_gb(b):\n    \"\"\"\n    byte 转 gb\n    :param b:\n    :return:\n    \"\"\"\n    if not isinstance(b, int):\n        return 0\n    return math.ceil(b / 1024 / 1024 / 1024)\n\n\ndef get_cpu_info():\n    \"\"\"\n    获取cpu个数信息\n    :return:\n    \"\"\"\n    return psutil.cpu_count()\n\n\ndef get_memory_detail():\n    \"\"\"\n    获取内存使用信息\n    :return:\n    \"\"\"\n    memory = psutil.virtual_memory()\n    memory_total = int(memory.total)\n    memory_used = int(memory.used)\n    memory_free = int(memory.free)\n    memory_available = int(memory.available)\n    return {\n        \"memory_total\": byte_to_gb(memory_total),\n        \"memory_used\": byte_to_gb(memory_used),\n        \"memory_free\": byte_to_gb(memory_free),\n        \"memory_available\": byte_to_gb(memory_available)\n    }\n\n\ndef get_disk_detail():\n    \"\"\"\n    获取磁盘使用信息\n    :return:\n    \"\"\"\n    all_partitions = psutil.disk_partitions()\n    ret_dic = {}\n    for item in all_partitions:\n        # 当过滤到挂载盘中有以下关键字时，跳过此磁盘的检查\n        if \"docker/overlay\" in item.mountpoint or \\\n                \"docker/container\" in item.mountpoint or \\\n                \"/boot\" == item.mountpoint or \\\n                item.mountpoint.startswith(\"/run/media\"):\n            continue\n        disk_usage = psutil.disk_usage(item.mountpoint)\n        _disk_total = byte_to_gb(int(disk_usage.total))\n        ret_dic[item.mountpoint] = _disk_total\n    return ret_dic\n\n\ndef get_hostname():\n    \"\"\"\n    获取主机名信息\n    :return:\n    \"\"\"\n    return socket.gethostname()\n\n\ndef get_ip():\n    \"\"\"\n    获取ip地址信息\n    :return:\n    \"\"\"\n    all_ips = salt.utils.network.ip_addrs()\n    for item in all_ips:\n        if item.startswith(\"127\"):\n            continue\n        return item\n\n\ndef get_agent_info():\n    \"\"\"\n    获取agent信息\n    :return:\n    \"\"\"\n    return {\n        \"cpu\": get_cpu_info(),\n        \"disk\": get_disk_detail(),\n        \"memory\": get_memory_detail(),\n        \"hostname\": get_hostname(),\n        \"ip\": get_ip()\n    }\n\n\nif __name__ == '__main__':\n    print(get_agent_info())\n"
  },
  {
    "path": "package_hub/_modules/gotty_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get gotty Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'gotty' and 'gotty' in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = None\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"gotty\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/grafana_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get grafana Inspection data\n\nimport os\nimport re\nimport psutil\nimport time\nimport json\nimport socket\nimport os.path as up\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'grafana-server':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid):\n    if pid and type(pid).__name__ == 'int':\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid):\n    try:\n        if pid and type(pid).__name__ == 'int':\n            port = []\n            # p = psutil.Process(pid)\n            cmd = 'ss -tnlp | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[3].split(':')\n                port.append(port_aa[-1])\n            port = list(set(port))\n            return port\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetProcess_Runtime(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,etime|grep ' + str(pid)\n            etime = os.popen(cmd).read().strip('\\n').split()\n            if '-' in etime[1]:\n                runtime = etime[1].replace('-', ' day ')\n            else:\n                runtime = etime[1]\n        except Exception:\n            runtime = None\n    else:\n        runtime = None\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid):\n    try:\n        pid_cpuinfo = {}\n        p = psutil.Process(pid)\n        pt = p.cpu_times()\n        st1, pt1_0, pt1_1 = timer(), pt.user, pt.system  # new\n        st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n        delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n        delta_time = st1 - st0\n        cpus_percent = ((delta_proc / delta_time) * 100)\n        pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n        cpu_usage = \"{:.2f}\".format(cpus_percent) + \"%\"\n    except Exception:\n        cpu_usage = None\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            mem_usage = \"{:.2f}\".format(process_mem) + \"%\"\n        except Exception:\n            return None\n    else:\n        mem_usage = None\n    return mem_usage\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            ignite_path = p.cwd()\n            f = open('%s/conf/defaults.ini' % (ignite_path), 'r')\n            for lines_list in f:\n                match = re.search(r'level = ([a-z]+)', lines_list)\n                if match:\n                    ignite_log_level = match.group(0).split()\n                    log_level = ignite_log_level[-1]\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetCluster_IP(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cluster_ip = []\n            p = psutil.Process(pid)\n            grafana_path = up.abspath(up.join(p.cwd(), \"..\"))\n            cmd = 'grep grafana %s/task.list' % (grafana_path)\n            cluster_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            if int(len(cluster_list)) > 1:\n                for cluster_line in cluster_list:\n                    cluster = cluster_line.split()\n                    cluster_ip.append(cluster[0])\n            else:\n                cluster_ip = None\n            return cluster_ip\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(pid)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/hadoop_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get domm Inspection data\n\nimport json\nimport os\nimport socket\nimport time\n\nimport psutil\n\n\ndef GetProcess_Pid():\n    try:\n        cmd = \"ps -eo pid,command |grep 'Dhadoop' | grep -v grep\"\n        cmd_list = os.popen(cmd).read().strip('\\n').split()\n        pid = int(cmd_list[0])\n        return pid\n    except Exception:\n        return None\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid):\n    if pid and type(pid).__name__ == 'int':\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid):\n    try:\n        if pid and type(pid).__name__ == 'int':\n            port = []\n            # p = psutil.Process(pid)\n            cmd = 'ss -tnlp | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[3].split(':')\n                port.append(port_aa[-1])\n            port = list(set(port))\n            return port\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetProcess_Runtime(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,etime|grep ' + str(pid)\n            etime = os.popen(cmd).read().strip('\\n').split()\n            if '-' in etime[1]:\n                runtime = etime[1].replace('-', ' day ')\n            else:\n                runtime = etime[1]\n        except Exception:\n            runtime = None\n    else:\n        runtime = None\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            pid_cpuinfo = {}\n            p = psutil.Process(pid)\n            pt = p.cpu_times()\n            st1, pt1_0, pt1_1 = timer(), pt.user, pt.system  # new\n            st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n            delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n            delta_time = st1 - st0\n            cpus_percent = ((delta_proc / delta_time) * 100)\n            pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n            cpu_usage = \"{:.2f}\".format(cpus_percent) + \"%\"\n        except Exception:\n            cpu_usage = None\n    else:\n        cpu_usage = None\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            mem_usage = \"{:.2f}\".format(process_mem) + \"%\"\n        except Exception:\n            return None\n    else:\n        mem_usage = None\n    return mem_usage\n\n\ndef GetProcess_ServiceMem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,command|grep %s' % (pid)\n            process_list = os.popen(cmd).read().strip('\\n').split('-Xms')\n            process_mem = process_list[-1].split()\n            service_mem = process_mem[0]\n            return service_mem\n        except Exception:\n            return None\n    else:\n        return None\n\n\n# def GetProcess_LogLevel(pid):\n#     if pid and type(pid).__name__ == 'int':\n#         try:\n#             p = psutil.Process(pid)\n#             domm_path = p.cwd()\n#             f = open('%s/conf/log4j2.xml' % (domm_path),'r')\n#             for lines_list in f:\n#                 if '<Root level=' in lines_list:\n#                     log_level = lines_list.strip().replace('<Root level=\"${env:CW_DOMM_LOG_LEVEL:-','').replace('}\">','')\n#         except Exception:\n#             log_level = None\n#         return log_level\n#     else:\n#         return None\n\n\ndef main(pid=GetProcess_Pid(), **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = None\n    process_message[\"cluster_ip\"] = None\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/host_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get host Inspection data\n\nimport datetime\nimport json\nimport os\nimport platform\nimport re\nimport socket\nimport subprocess\nimport time\n\nimport psutil\n\n\ndef run_cmd(cmd):\n    \"\"\"\n    运行系统命令，返回标准输出，标准错误输出及执行状态码\n    :param cmd:\n    :return:\n    \"\"\"\n    p = subprocess.run(\n        cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    cmd_stdout = bytes.decode(p.stdout)\n    if cmd_stdout.endswith('\\n'):\n        cmd_stdout = cmd_stdout.strip()\n    # cmd_stderr = bytes.decode(p.stderr)\n\n    if p.returncode == '0':\n        return None\n    else:\n        # return cmd_stdout, cmd_stderr, p.returncode\n        return cmd_stdout\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetHostname_Info():\n    try:\n        host_name = socket.gethostname()\n        return host_name\n    except Exception:\n        return None\n\n\ndef GetRelease_Version():\n    try:\n        if os.path.exists('/etc/redhat-release'):\n            with open('/etc/redhat-release') as file:\n                for line in file:\n                    return line.strip('\\n')\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetKernel_Version():\n    \"\"\"\n    获取系统内核版本\n    :return:\n    \"\"\"\n    try:\n        release_version_list = platform.platform()\n        release_version_line = release_version_list.split('-with-')\n        release_version = release_version_line[0]\n        return release_version\n    except Exception:\n        return None\n\n\ndef GetSelinux_Status():\n    \"\"\"\n    获取selinux状态\n    :return:\n    \"\"\"\n    try:\n        cmd = 'getenforce'\n        selinux_status = run_cmd(cmd)\n        return selinux_status\n    except Exception:\n        return None\n\n\ndef GetUmask_Status():\n    try:\n        user_cmd = 'whoami'\n        user = run_cmd(user_cmd)\n        cmd = 'umask'\n        umask_status = run_cmd(cmd)\n        return {\"user\": user, \"umask\": umask_status}\n    except Exception:\n        return None\n\n\ndef GetUlimit_Num():\n    try:\n        cmd = 'ulimit -n'\n        ulimit_num = run_cmd(cmd)\n        return ulimit_num\n    except Exception:\n        return None\n\n\ndef GetTimeNow_Info():\n    \"\"\"\n    获取系统当前时间\n    :return:\n    \"\"\"\n    try:\n        time_now = datetime.datetime.strftime(\n            datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')\n        return time_now\n    except Exception:\n        return None\n\n\ndef GetRunTime_Info():\n    \"\"\"\n    获取系统运行时间\n    :return:\n    \"\"\"\n    try:\n        cmd = \"uptime\"\n        uptime_list = run_cmd(cmd).strip().split('up')\n        run_time = uptime_list[1].strip().split(',')\n        if 'user' in run_time[1]:\n            time = run_time[0]\n        else:\n            time = run_time[0] + run_time[1]\n        return time\n    except Exception:\n        return None\n\n\ndef GetCpu_Total():\n    try:\n        cpu_count = str(psutil.cpu_count()) + \"C\"\n        return cpu_count\n    except Exception:\n        return None\n\n\ndef GetMemory_Total():\n    try:\n        with open('/proc/meminfo') as fd:\n            for line in fd:\n                if line.startswith('MemTotal'):\n                    mem = int(line.split()[1].strip())\n                    break\n        mem = int('%.f' % (mem / 1024.0))\n        if mem > 1024:\n            mem = '%.f' % (mem / 1024.0) + 'GB'\n        else:\n            mem = str(mem) + 'MB'\n        return mem\n    except Exception:\n        return None\n\n\ndef GetDisk_Total():\n    try:\n        total_mb = 0\n        df_cmd = \"df -h | grep -v 'tmpfs' | tail -n +2\"\n        result = run_cmd(df_cmd).strip().split('\\n')\n        for total_list in result:\n            total = total_list.split()\n            if 'T' in total[1]:\n                total_tb = total[1].replace('T', '')\n                total_mb += int('%.f' %\n                                (int(float(total_tb)) * 1024.0 * 1024.0))\n            if 'G' in total[1]:\n                total_gb = total[1].replace('G', '')\n                total_mb += int('%.f' % (int(float(total_gb)) * 1024.0))\n            if 'M' in total[1]:\n                total_mb += int(float(total[1].replace('M', '')))\n        if total_mb > 1024 and total_mb < 1048576:\n            disk_total = '%.f' % (total_mb / 1024.0) + 'GB'\n        elif total_mb > 1048576:\n            disk_total = '%.f' % (total_mb / 1024.0 / 1024.0) + 'TB'\n        else:\n            disk_total = str(total_mb) + 'MB'\n        return disk_total\n    except Exception:\n        return None\n\n\ndef GetMemory_Usage():\n    \"\"\"\n    获取内存使用信息\n    :return:\n    \"\"\"\n    try:\n        svmem = psutil.virtual_memory()\n        mem_usage = str(svmem.percent) + '%'\n        return mem_usage\n    except Exception:\n        return None\n\n\ndef GetCpu_Usage():\n    \"\"\"\n    获取cpu使用率\n    :return:\n    \"\"\"\n    try:\n        cpu_usage = str(psutil.cpu_percent()) + '%'\n        return cpu_usage\n    except Exception:\n        return None\n\n\ndef GetDisk_Info(data_path):\n    \"\"\"\n    获取磁盘使用量信息\n    :return:\n    \"\"\"\n    try:\n        disk_usage_json = {}\n        df_cmd = \"df -h | grep -v 'tmpfs' | tail -n +2\"\n        result = run_cmd(df_cmd).strip().split('\\n')\n        for total_list in result:\n            total = total_list.split()\n            if total[-1] == '/':\n                disk_usage_json[total[-1]] = total[-2]\n            if total[-1] == data_path:\n                disk_usage_json[total[-1]] = total[-2]\n        return disk_usage_json\n    except Exception:\n        return None\n\n\ndef GetInode_Info(data_path):\n    \"\"\"\n    获取inode使用量信息\n    :return:\n    \"\"\"\n    try:\n        inode_usage_json = {}\n        inode_cmd = \"df -i | grep -v 'tmpfs' | tail -n +2\"\n        result = run_cmd(inode_cmd).strip().split('\\n')\n        for total_list in result:\n            total = total_list.split()\n            if total[-1] == '/':\n                inode_usage_json[total[-1]] = total[-2]\n            if total[-1] == data_path:\n                inode_usage_json[total[-1]] = total[-2]\n        return inode_usage_json\n    except Exception:\n        return None\n\n\ndef GetSysLoad_Average():\n    \"\"\"\n    获取系统平均负载信息\n    :return:\n    \"\"\"\n    try:\n        load_average_info = psutil.getloadavg()\n        load_average_dict = dict()\n        load_average_dict.update({'m1_average_load': load_average_info[0]})\n        load_average_dict.update({'m5_average_load': load_average_info[1]})\n        load_average_dict.update({'m15_average_load': load_average_info[2]})\n        load_average_json = load_average_dict\n        return load_average_json\n    except Exception:\n        return None\n\n\ndef GetServicesPort_Connectivity(port_list):\n    try:\n        port_json = []\n        for port in port_list:\n            sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sk.settimeout(2)\n            status = sk.connect_ex((port['ip'], int(port['port'])))\n            if status != 0:\n                port[\"status\"] = \"False\"\n                port_json.append(port)\n        if len(port_json) > 0:\n            return port_json\n        else:\n            return 'Ture'\n    except socket.error:\n        return None\n    sk.close()\n\n\ndef GetServer_Bandwidth():\n    try:\n        bandwidth_1 = psutil.net_io_counters()\n        time.sleep(1)\n        bandwidth_2 = psutil.net_io_counters()\n        bandwidth_sent = '%.f' % (\n            (bandwidth_2.bytes_sent - bandwidth_1.bytes_sent) / 1024)\n        bandwidth_recv = '%.f' % (\n            (bandwidth_2.bytes_recv - bandwidth_1.bytes_recv) / 1024)\n        sent = bandwidth_sent + \"KB/s\"\n        receive = bandwidth_recv + \"KB/s\"\n        return {\"sent\": sent, \"receive\": receive}\n    except Exception:\n        return None\n\n\ndef GetDisK_ReadWrite():\n    try:\n        disk_1 = psutil.disk_io_counters()\n        time.sleep(1)\n        disk_2 = psutil.disk_io_counters()\n        disk_read = '%.f' % ((disk_2.read_bytes - disk_1.read_bytes) / 1024)\n        disk_write = '%.f' % ((disk_2.write_bytes - disk_1.write_bytes) / 1024)\n        read = disk_read + \"KB/s\"\n        write = disk_write + \"KB/s\"\n        return {\"read\": read, \"write\": write}\n    except Exception:\n        return None\n\n\ndef GetDisk_IoWait():\n    try:\n        disk_iowait = 0\n        cmd = \"vmstat 1 10\"\n        iowait_list = run_cmd(cmd).strip().split('\\n')\n        for iowait in iowait_list:\n            if 'cpu' in iowait_list or 'wa' in iowait:\n                continue\n            else:\n                disk_iowait += int(iowait.split()[-2])\n        disk = disk_iowait / 10\n        return disk\n    except Exception:\n        return None\n\n\ndef GetMemory_Top10():\n    \"\"\"\n    获取占用内存前10的应用\n    :return:\n    \"\"\"\n\n    class Cwp:\n        def __init__(self, pid, memory_percent, cmdline):\n            self.pid = pid\n            self.cmdline = cmdline\n            self.memory_percent = memory_percent\n\n    all_pids = psutil.pids()\n    cw_ps = []\n    for ele in all_pids:\n        try:\n            p = psutil.Process(ele)\n            cw_p = Cwp(p.pid, p.memory_percent(), ' '.join(p.cmdline()))\n            cw_ps.append(cw_p)\n        except psutil.Error:\n            continue\n    cw_ps.sort(key=lambda c: c.memory_percent, reverse=True)\n    content_list = list()\n    for ele in cw_ps[:10]:\n        tma_dict = {\n            'TOP': str(cw_ps.index(ele) + 1),\n            'PID': ele.pid,\n            'P_RATE': str(round(ele.memory_percent, 2)) + '%',\n            'P_CMD': ele.cmdline\n        }\n        content_list.append(tma_dict)\n    top10_mem_app_json = content_list\n    return top10_mem_app_json\n\n\ndef GetCpu_Top10():\n    \"\"\"\n    获取cpu使用率前10的应用\n    :return:\n    \"\"\"\n\n    class Cwp:\n        def __init__(self, pid, cpu_percent, cmdline):\n            self.pid = pid\n            self.cmdline = cmdline\n            self.cpu_percent = cpu_percent\n\n    all_pids = psutil.pids()\n    cw_ps = []\n    for ele in all_pids:\n        try:\n            p = psutil.Process(ele)\n            if len(p.cmdline()) >= 1:\n                cpu_p = p.cpu_percent(interval=1)\n                cw_p = Cwp(p.pid, cpu_p, ' '.join(p.cmdline()))\n                cw_ps.append(cw_p)\n        except psutil.Error:\n            continue\n    cw_ps.sort(key=lambda c: c.cpu_percent, reverse=True)\n    content_list = list()\n    for ele in cw_ps[:10]:\n        tca_dict = {\n            'TOP': str(cw_ps.index(ele) + 1),\n            'PID': ele.pid,\n            'P_RATE': str(round(ele.cpu_percent, 2)) + '%',\n            'P_CMD': ele.cmdline\n        }\n        content_list.append(tca_dict)\n    top10_cpu_app_json = content_list\n    return top10_cpu_app_json\n\n\ndef GetKernel_Info():\n    try:\n        cmd = \"egrep -v '^#|^$' /etc/sysctl.conf\"\n        sysctl = run_cmd(cmd).strip().split('\\n')\n        if len(sysctl) > 0:\n            return sysctl\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetBoot_Start():\n    try:\n        boot_start = []\n        release_version_list = platform.platform()\n        if '-6.' in release_version_list:\n            cmd = \"chkconfig --list\"\n        if '-7.' in release_version_list:\n            cmd = 'systemctl list-unit-files | grep enabled'\n        boot_start_list = run_cmd(cmd).strip().split('\\n')\n        for line_list in boot_start_list:\n            line = line_list.split()\n            boot_start.append(line[0])\n        return boot_start\n    except Exception:\n        return None\n\n\ndef GetZombies_Status():\n    try:\n        cmd = \"ps -A -ostat,ppid,cmd |grep -e '^[Zz]'\"\n        zombies_status_list = run_cmd(cmd).strip().split('\\n')\n        if len(zombies_status_list) > 0:\n            return zombies_status_list\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GatRun_Process():\n    \"\"\"\n    获取正在运行的进程数\n    :return:\n    \"\"\"\n    try:\n        all_process_num = len(psutil.pids())\n        return all_process_num\n    except Exception:\n        return None\n\n\ndef GetFirewall_Info():\n    try:\n        get_firewall_cmd = \"iptables -nL\"\n        result = run_cmd(get_firewall_cmd)\n        new_str = \"\".join([s for s in result.splitlines(True) if s.strip()])\n        if len(new_str.split('\\n')) == 6:\n            return None\n        fw_block_list = result.split('\\n\\n')\n        firewall_dict = dict()\n        for block in fw_block_list:\n            block_rules_list = list()\n            tmp_rules = block.split('\\n')\n            fw_title = tmp_rules[0]\n            for line in tmp_rules:\n                rule_dict = dict()\n                if line.startswith('Chain') or line.startswith('target'):\n                    continue\n                items = re.split(r'\\s+', line)\n                rule_dict.update({'target': items[0]})\n                rule_dict.update({'port': items[1]})\n                rule_dict.update({'opt': items[2]})\n                rule_dict.update({'source': items[3]})\n                rule_dict.update({'destination': items[4]})\n                rule_dict.update({'others': ' '.join(items[5:])})\n                block_rules_list.append(rule_dict)\n            if len(block_rules_list) > 0:\n                firewall_dict.update({fw_title: block_rules_list})\n        firewall_json = firewall_dict\n        return firewall_json\n    except Exception:\n        return None\n\n\ndef main(data_path='/data', port_list=[{\"name\": \"ssh\", \"ip\": \"127.0.0.1\", \"port\": \"36000\"}], **kwargs):\n    process_message = dict()\n    # process_message[\"IP\"] = GetLocal_Ip()\n    # process_message[\"hostname\"] = GetHostname_Info()\n    process_message[\"release_version\"] = GetRelease_Version()\n    process_message[\"kernel_version\"] = GetKernel_Version()\n    process_message[\"selinux\"] = GetSelinux_Status()\n    process_message[\"umask\"] = GetUmask_Status()\n    # process_message[\"max_openfile\"] = GetUlimit_Num()\n    # process_message[\"now_time\"] = GetTimeNow_Info()\n    # process_message[\"run_time\"] = GetRunTime_Info()\n    # process_message[\"host_massage\"] = {\"cpu\": GetCpu_Total(), \"memory\": GetMemory_Total(), \"disk\": GetDisk_Total()}\n    # process_message[\"memory_usage\"] = GetMemory_Usage()\n    # process_message[\"cpu_usage\"] = GetCpu_Usage()\n    # process_message[\"disk_usage\"] = GetDisk_Info(data_path)\n    # process_message[\"inode_usage\"] = GetInode_Info(data_path)\n    # process_message[\"sys_load\"] = GetSysLoad_Average()\n    # process_message[\"port_connectivity\"] = GetServicesPort_Connectivity(port_list)\n    # process_message[\"bandwidth\"] = GetServer_Bandwidth()\n    # process_message[\"throughput\"] = GetDisK_ReadWrite()\n    # process_message[\"iowait\"] = GetDisk_IoWait()\n    process_message[\"memory_top\"] = GetMemory_Top10()\n    process_message[\"cpu_top\"] = GetCpu_Top10()\n    process_message[\"kernel_parameters\"] = GetKernel_Info()\n    # process_message[\"boot_start\"] = GetBoot_Start()\n    process_message[\"zombies_process\"] = GetZombies_Status()\n    process_message[\"run_process\"] = GatRun_Process()\n    # process_message[\"iptables\"] = GetFirewall_Info()\n    return json.dumps(process_message)\n"
  },
  {
    "path": "package_hub/_modules/httpd_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get httpd Inspection data\nimport json\nimport os.path as up\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'httpd' and 'httpd/bin/httpd' in p.exe():\n                    pid = int(p.ppid())\n                    if pid != 1:\n                        return pid\n                    else:\n                        return pnum\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            log_level = ''\n            p = psutil.Process(pid)\n            httpd_path = up.abspath(up.join(p.exe(), \"../..\"))\n            f = open('%s/conf/httpd.conf' % (httpd_path), 'r')\n            for lines_list in f:\n                if 'access_' in lines_list:\n                    log_level = 'access'\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"httpd\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/ignite_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get ignite Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'java' and 'ignite' in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    log_level = None\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            ignite_path = p.cwd()\n            f = open('%s/config/ignite-log4j.xml' % (ignite_path), 'r')\n            for lines_list in f:\n                if '<level value=\"INFO\"/>' in lines_list:\n                    ignite_log_level = lines_list.strip('\\n').split('\"')\n                    log_level = ignite_log_level[1]\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid, is_java=True)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"ignite\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/init_host.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nimport json\nimport os\nimport sys\nimport logging\nimport time\nimport logging.config\nimport subprocess\n\nPYTHON_VERSION = sys.version_info.major\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\n\nif PYTHON_VERSION == 2:\n    reload(sys)\n    sys.setdefaultencoding('utf-8')\n\n# ---- 日志定义部分 ----\n# 日志配置 建议输入绝对路径,默认生成日志会添加 时间字段\nLOG_PATH = \"/tmp/init_host_standalone.log\"\n# 屏幕输出日志级别\nCONSOLE_LOG_LEVEL = logging.INFO\n# 文件日志级别\nFILE_LOG_LEVEL = logging.DEBUG\n\n\ndef generate_log_filepath(log_path):\n    \"\"\"生成日志名称\"\"\"\n    time_str = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))\n    dirname = os.path.dirname(log_path)\n    name_split = os.path.basename(log_path).split('.')\n    if len(name_split) == 1:\n        name = \"{0}_{1}\".format(name_split[0], time_str)\n        file_path = os.path.join(dirname, name)\n    else:\n        name_split.insert(-1, time_str)\n        file_path = os.path.join(dirname, '.'.join(name_split))\n    return file_path\n\n\n# 日志配置\nlog_path = generate_log_filepath(LOG_PATH)\nlogging.basicConfig(\n    level=logging.DEBUG,\n    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',\n    datefmt='%a, %d %b %Y %H:%M:%S',\n    filename=log_path,\n    filemode='a')\nconsole = logging.StreamHandler()\nconsole.setLevel(logging.INFO)\nformatter = logging.Formatter(\n    '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')\nconsole.setFormatter(formatter)\nlogger = logging.getLogger()\nlogger.addHandler(console)\n\nKERNEL_PARAM = \"\"\"# Disable IPv6\nnet.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1\n# ARP\nnet.ipv4.conf.default.rp_filter = 0\nnet.ipv4.conf.all.rp_filter = 0\nnet.ipv4.neigh.default.gc_stale_time = 120\nnet.ipv4.conf.default.arp_announce = 2\nnet.ipv4.conf.all.arp_announce = 2\nnet.ipv4.conf.lo.arp_announce = 2\n# TCP Memory\nnet.core.rmem_default = 2097152\nnet.core.wmem_default = 2097152\nnet.core.rmem_max = 4194304\nnet.core.wmem_max = 4194304\nnet.ipv4.tcp_rmem = 4096 8192 4194304\nnet.ipv4.tcp_wmem = 4096 8192 4194304\nnet.ipv4.tcp_mem = 524288 699050 1048576\n# TCP SYN\nnet.ipv4.tcp_syncookies = 1\nnet.ipv4.tcp_synack_retries = 1\nnet.ipv4.tcp_syn_retries = 1\nnet.ipv4.tcp_max_syn_backlog = 16384\nnet.core.netdev_max_backlog = 16384\n# TIME_WAIT\nnet.ipv4.route.gc_timeout = 100\nnet.ipv4.tcp_max_tw_buckets = 5000\nnet.ipv4.tcp_tw_reuse = 1\nnet.ipv4.tcp_timestamps = 0\nnet.ipv4.tcp_fin_timeout = 2\nnet.ipv4.ip_local_port_range = 20000 50000\n# TCP keepalive\nnet.ipv4.tcp_keepalive_probes = 3\nnet.ipv4.tcp_keepalive_time = 60\nnet.ipv4.tcp_keepalive_intvl = 10\n# Other TCP\nnet.ipv4.tcp_max_orphans = 65535\nnet.core.somaxconn = 16384\nnet.ipv4.tcp_sack = 1\nnet.ipv4.tcp_window_scaling = 1\nvm.max_map_count=262144\nvm.min_free_kbytes=512000\nvm.swappiness = 0\"\"\"\n\nKERNEL_KEYWORD = [\n    \"net.ipv6.conf.all.disable_ipv6\",\n    \"net.ipv6.conf.default.disable_ipv6\",\n    \"net.ipv4.conf.default.rp_filter\",\n    \"net.ipv4.conf.all.rp_filter\",\n    \"net.ipv4.neigh.default.gc_stale_time\",\n    \"net.ipv4.conf.default.arp_announce\",\n    \"net.ipv4.conf.all.arp_announce\",\n    \"net.ipv4.conf.lo.arp_announce\",\n    \"net.core.rmem_default\",\n    \"net.core.wmem_default\",\n    \"net.core.rmem_max\",\n    \"net.core.wmem_max\",\n    \"net.ipv4.tcp_rmem\",\n    \"net.ipv4.tcp_wmem\",\n    \"net.ipv4.tcp_mem\",\n    \"net.ipv4.tcp_syncookies\",\n    \"net.ipv4.tcp_synack_retries\",\n    \"net.ipv4.tcp_syn_retries\",\n    \"net.ipv4.tcp_max_syn_backlog\",\n    \"net.core.netdev_max_backlog\",\n    \"net.ipv4.route.gc_timeout\",\n    \"net.ipv4.tcp_max_tw_buckets\",\n    \"net.ipv4.tcp_tw_reuse\",\n    \"net.ipv4.tcp_timestamps\",\n    \"net.ipv4.tcp_fin_timeout\",\n    \"net.ipv4.ip_local_port_range\",\n    \"net.ipv4.tcp_keepalive_probes\",\n    \"net.ipv4.tcp_keepalive_time\",\n    \"net.ipv4.tcp_keepalive_intvl\",\n    \"net.ipv4.tcp_max_orphans\",\n    \"net.core.somaxconn\",\n    \"net.ipv4.tcp_sack\",\n    \"net.ipv4.tcp_window_scaling\",\n    \"vm.max_map_count\",\n    \"vm.swappiness\",\n    \"vm.min_free_kbytes\"\n]\nTO_MODIFY_HOST_NAME = [\n    \"localhost\",\n    \"localhost.localhost\",\n    \"localhost.domain\",\n]\n\n\nclass BaseInit(object):\n    \"\"\" Base class 检查权限 / 执行命令方法 \"\"\"\n\n    def check_permission(self):\n        \"\"\" 检查权限 \"\"\"\n        logger.info(\"开始检查当前用户执行权限\")\n        if not os.getuid() == 0:\n            self.__check_is_sodu()\n            if not self.is_sudo:\n                logging.error('当前执行用户不是root，且此用户没有sudo NOPASSWD 权限，无法初始化！')\n                exit(1)\n        logger.info('当前用户权限正常，开始执行脚本')\n\n    def __check_is_sodu(self):\n        \"\"\" 是否具有 sodu 权限 \"\"\"\n        logger.info(\"检查是否具有sodu免密码权限\")\n        _cmd = \"sudo -n 'whoami' &>/dev/null\"\n        _, _, _code = self.cmd(_cmd)\n        self.is_sudo = _code == 0\n        logger.info(\"是否具有sodu免密码权限: {}\".format(self.is_sudo))\n\n    def cmd(self, command):\n        \"\"\" 执行shell 命令 \"\"\"\n        if hasattr(self, 'is_sudo'):\n            if command.lstrip().startswith(\"echo\"):\n                command = \"sudo sh -c '{0}'\".format(command)\n            else:\n                command = \"sudo {0}\".format(command)\n        logger.debug(\"Exec command: {0}\".format(command))\n        p = subprocess.Popen(\n            command,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            shell=True,\n        )\n        stdout, stderr = p.communicate()\n        _out, _err, _code = stdout, stderr, p.returncode\n        logger.debug(\n            \"Get command({0}) stdout: {1}; stderr: {2}; ret_code: {3}\".format(\n                command, _out, _err, _code\n            )\n        )\n        return _out, _err, _code\n\n    @staticmethod\n    def read_file(path, mode='r', res='str'):\n        \"\"\"\n        :param path 路径\n        :param mode 模式\n        :param res 返回数据类型 str/list\n        \"\"\"\n        if not os.path.exists(path):\n            logger.error('读取文件失败，文件路径错误:{}'.format(path))\n            exit(1)\n        with open(path, mode) as f:\n            data = f.read() if res == 'str' else f.readlines()\n        return data\n\n    def __get_os_version(self):\n        logging.debug('开始获取系统版本信息')\n        # match = False\n        _, _, _code = self.cmd('systemctl --version')\n        if _code != 0:\n            logger.error(\"执行失败，当前操作系统不支持本脚本\")\n            exit(1)\n        self.os_version = 7\n        logging.debug('获取系统版本信息完成')\n\n    def set_opts(self, **kwargs):\n        \"\"\" 根据kwargs 设置参数\"\"\"\n        raise Exception(\"程序错误，需实现set_opts方法\")\n\n    def run(self):\n        # 检查权限\n        if sys.argv[1] != 'valid':\n            self.check_permission()\n        self.__get_os_version()\n        logging.info(\"开始执行脚本\")\n\n        self.run_methods()\n\n    def run_methods(self):\n        try:\n            assert isinstance(self.m_list, list), \"m_list 类型错误 方法错误，请检查脚本\"\n            assert len(self.m_list) > 0, \"m_list 为空，请检查脚本\"\n            for func_info in self.m_list:\n                assert isinstance(func_info, tuple) and len(\n                    func_info) == 2, \"todo_list 方法错误，请检查脚本:{}\".format(func_info)\n                method_name, method_note = func_info\n                if hasattr(self, method_name):\n                    f = getattr(self, method_name)\n                    logger.info(\"开始 执行: {}\".format(method_note))\n                    f()\n                    logger.info(\"执行 完成: {}\".format(method_note))\n                else:\n                    logger.warn(\"安装方法列表错误，{} 方法不存在\".format(method_note))\n            else:\n                logging.info(\"执行结束, 完整日志保存在 {}\".format(log_path))\n        except TypeError:\n            logger.error(\"脚本配置错误，TypeError:\")\n        except Exception as e:\n            logger.error(e)\n            logging.info(\"执行结束, 完整日志保存在 {}\".format(log_path))\n            exit(1)\n\n\nclass InitHost(BaseInit):\n    \"\"\" 初始化节点信息 \"\"\"\n\n    def __init__(self, host_name, local_ip):\n        self.m_list = [\n            ('env_set_timezone', '设置时区'),\n            ('env_set_firewall', '关闭防火墙'),\n            ('env_set_disable_ipv6', '设置关闭ipv6'),\n            ('env_set_language', '设置语言'),\n            ('env_set_file_limit', '设置文件句柄数'),\n            ('env_set_kernel', '设置内核参数'),\n            ('env_set_disable_selinux', '关闭selinux'),\n            ('set_hostname', '设置主机名'),\n        ]\n        # TODO\n        self.hostname = host_name\n        self.local_ip = local_ip\n\n    def env_set_timezone(self):\n        \"\"\" 设置时区 \"\"\"\n        timezone = \"PRC\"\n        self.cmd(\"test -f /etc/timezone && rm -f /etc/timezone\")\n        self.cmd(\"rm -f /etc/localtimze\")\n        self.cmd(\n            \"ln -sf /usr/share/zoneinfo/{0} /etc/localtime\".format(timezone))\n\n    def env_set_firewall(self):\n        \"\"\" 关闭 firewall \"\"\"\n        _, _, _code = self.cmd(\n            \"systemctl status firewalld.service | egrep -q 'Active: .*(dead)'\"\n        )\n        if _code != 0:\n            self.cmd(\"systemctl stop firewalld.service >/dev/null 2>&1\")\n            self.cmd(\"systemctl disable firewalld.service >/dev/null 2>&1\")\n\n    def env_set_disable_ipv6(self):\n        \"\"\" 关闭ipv6 \"\"\"\n        _, _, _code = self.cmd(\"grep -q 'ipv6.disable' /etc/default/grub\")\n        if _code == 0:\n            self.cmd(\n                \"sed -i 's/ipv6.disable=[0-9]/ipv6.disable=1/g' /etc/default/grub\"\n            )\n        else:\n            self.cmd(\n                \"\"\"sed -i '/GRUB_CMDLINE_LINUX/ s/=\"/=\"ipv6.disable=1 /' /etc/default/grub\"\"\"\n            )\n        self.cmd(\"sysctl -w net.ipv6.conf.all.disable_ipv6=1\")\n\n    def env_set_language(self):\n        \"\"\" 设置语言 \"\"\"\n        self.cmd(\"localectl set-locale LANG=en_US.UTF-8\")\n\n    def env_set_file_limit(self):\n        \"\"\" 设置打开的文件句柄数 \"\"\"\n        _file_max_out, _, _ = self.cmd(\"cat /proc/sys/fs/file-max\")\n        file_max = int(_file_max_out)\n        _nr_open_out, _, _ = self.cmd(\"cat /proc/sys/fs/nr_open\")\n        nr_open = int(_nr_open_out)\n        if file_max < 655350:\n            self.cmd(\"sed -i '/fs.file-max/d' /etc/sysctl.conf\")\n            self.cmd(\"echo 'fs.file-max = 655350' >>/etc/sysctl.conf\")\n            self.cmd(\"sysctl -p 1>/dev/null\")\n            file_max = 655350\n        elif file_max > nr_open:\n            file_max = nr_open - 5000\n\n        self.cmd(\"sed -i '/nofile/d' /etc/security/limits.conf\")\n        self.cmd(\n            'echo \"*               -       nofile          {0}\" >>/etc/security/limits.conf'.format(\n                file_max\n            )\n        )\n        if os.path.exists(\"/etc/security/limits.d/20-nproc.conf\"):\n            self.cmd(\n                \"sed -i 's#4096#unlimited#g' /etc/security/limits.d/20-nproc.conf\"\n            )\n        self.cmd(\"sed -i '/^DefaultLimitCORE/d' /etc/systemd/system.conf\")\n        self.cmd(\"sed -i '/^DefaultLimitNOFILE/d' /etc/systemd/system.conf\")\n        self.cmd(\"sed -i '/^DefaultLimitNPROC/d' /etc/systemd/system.conf\")\n        c = 'echo -e \"DefaultLimitCORE=infinity\\\\nDefaultLimitNOFILE={0}\\\\nDefaultLimitNPROC={0}\" >>/etc/systemd/system.conf'.format(\n            file_max)\n        self.cmd(\n            c\n        )\n        self.cmd(\"ulimit -SHn {0}\".format(file_max))\n\n    def env_set_kernel(self):\n        \"\"\" 设置内核参数 \"\"\"\n        for item in KERNEL_KEYWORD:\n            self.cmd('sed -i \"/{0}/d\" /etc/sysctl.conf'.format(item.strip()))\n        self.cmd('sed -i \"/tables/d\" /etc/sysctl.conf')\n        self.cmd('echo \"{0}\" >>/etc/sysctl.conf'.format(KERNEL_PARAM))\n        self.cmd(\"sysctl -p 1>/dev/null\")\n\n    def env_set_disable_selinux(self):\n        \"\"\" 禁用 selinux \"\"\"\n        if os.path.exists(\"/etc/selinux/config\"):\n            self.cmd(\n                \"sed -i 's#^SELINUX=.*#SELINUX=disabled#g' /etc/selinux/config\")\n            self.cmd(\"setenforce 0\")\n\n    def set_hostname(self):\n        \"\"\"设置主机名\"\"\"\n        _out, _err, _code = self.cmd(\"echo $(hostname)\")\n        if _out.strip().lower() in TO_MODIFY_HOST_NAME or _out.strip().isdigit():\n            self.cmd('echo \"{0}\" >/etc/hostname'.format(self.hostname))\n            self.cmd('echo \"{0}\" > /proc/sys/kernel/hostname'.format(self.hostname))\n            self.cmd(\"hostname {0}\".format(self.hostname))\n            self.cmd('echo \"{0}    {1}\" >> /etc/hosts'.format(self.local_ip, self.hostname))\n\n\nclass ValidInit(BaseInit):\n    def __init__(self):\n        self.m_list = [\n            ('valid_env_timezone', '校验时区'),\n            ('valid_env_firewall', '校验防火墙'),\n            ('valid_env_language', '校验语言'),\n            ('valid_env_file_limit', '校验文件具柄数'),\n            ('valid_env_kernel', '校验内核参数'),\n            ('valid_env_disable_selinux', '校验selinux'),\n            ('valid_host_name', '校验host_name'),\n        ]\n\n    def valid_env_timezone(self):\n        \"\"\" 校验时区 \"\"\"\n        assert os.readlink(\n            '/etc/localtime') == \"/usr/share/zoneinfo/PRC\", \"时区校验失败\"\n\n    def valid_env_firewall(self):\n        \"\"\" 校验防火墙 \"\"\"\n        _, _, _code = self.cmd(\n            \"systemctl status firewalld.service | egrep -q 'Active: .*(dead)'\"\n        )\n        assert _code == 0, \"防火墙校验失败\"\n\n    def valid_env_language(self):\n        \"\"\" 校验语言 \"\"\"\n        assert self.cmd(\n            \"localectl status |grep LANG=en_US.UTF-8\")[2] == 0, \"语言环境校验失败\"\n\n    def valid_env_file_limit(self):\n        \"\"\" 校验文件具柄数 \"\"\"\n        _err = \"\"\n        _file_max_out, _, _ = self.cmd(\"cat /proc/sys/fs/file-max\")\n        file_max = int(_file_max_out)\n        _nr_open_out, _, _ = self.cmd(\"cat /proc/sys/fs/nr_open\")\n        nr_open = int(_nr_open_out)\n        if file_max < 655350:\n            _err = \"文件句柄数校验失败\"\n        elif file_max > nr_open:\n            file_max = nr_open - 5000\n\n        if self.cmd(\n                'grep \"*               -       nofile          {0}\" /etc/security/limits.conf'.format(\n                    file_max\n                )\n        )[2] != 0:\n            _err = \"文件 /etc/security/limits.conf 校验失败\"\n\n        if os.path.exists(\"/etc/security/limits.d/20-nproc.conf\"):\n            if self.cmd(\n                    \"grep unlimited /etc/security/limits.d/20-nproc.conf\"\n            )[2] != 0:\n                _err = \"文件 /etc/security/limits.d/20-nproc.conf 校验失败\"\n        if self.cmd('grep \"DefaultLimitCORE=infinity\" /etc/systemd/system.conf')[2] != 0:\n            _err = \"文件 /etc/systemd/system.conf DefaultLimitCORE 校验失败\"\n        if self.cmd('grep DefaultLimitNOFILE={0} /etc/systemd/system.conf'.format(file_max))[2] != 0:\n            _err = \"文件 /etc/systemd/system.conf DefaultLimitNOFILE 校验失败\"\n        if self.cmd('grep DefaultLimitNPROC={0} /etc/systemd/system.conf'.format(file_max))[2] != 0:\n            _err = \"文件 /etc/systemd/system.conf DefaultLimitNPROC 校验失败\"\n\n        assert _err == '', _err\n\n    def valid_env_kernel(self):\n        \"\"\" 校验内核参数 \"\"\"\n        _list = [i.strip() for i in self.read_file('/etc/sysctl.conf',\n                                                   res='list') if not i.strip().startswith('#')]\n        for i in KERNEL_PARAM.split('\\n'):\n            if i.startswith('#'):\n                continue\n            assert i.strip() in _list, \"内核参数校验失败: {}\".format(i)\n\n    def valid_env_disable_selinux(self):\n        \"\"\" 校验selinux \"\"\"\n        assert \"SELINUX=disabled\" in [\n            i.strip() for i in self.read_file('/etc/selinux/config', res='list') if\n            not i.strip().startswith('#')\n        ], \"selinux 校验失败\"\n\n    def valid_host_name(self):\n        \"\"\"校验host_name不含localhost\"\"\"\n        _out, _err, _code = self.cmd(\"echo $(hostname)\")\n        assert _out.strip().lower() not in TO_MODIFY_HOST_NAME and not _out.strip().isdigit(), \"校验主机名失败\"\n\n\ndef add_hostname_analysis(hostname_str):\n    logger.debug(\"传入主机信息：\\n{}\".format(hostname_str))\n    hostnames = json.loads(hostname_str or '[]')\n    with open(\"/etc/hosts\", \"r\") as f:\n        hosts = f.read()\n    logger.debug(\"获取主机解析：\\n{}\".format(hosts))\n    hosts_analysis_dict = {}\n    for analysis_str in hosts.split(\"\\n\"):\n        if analysis_str.lstrip().startswith(\"#\"):\n            continue\n        analysis_list = list(\n            filter(\n                lambda x: x,\n                analysis_str.strip().replace(\"\\t\", \" \").split(\" \")\n            )\n        )\n        if not analysis_list:\n            continue\n        hosts_analysis_dict[analysis_list[0]] = analysis_str\n    for hostname_dict in hostnames:\n        ip = hostname_dict.get(\"ip\")\n        hostname = hostname_dict.get(\"hostname\")\n        host_analysis_str = hosts_analysis_dict.get(ip, \"\")\n        if not host_analysis_str:\n            hosts += \"{} {}\\n\".format(ip, hostname)\n        elif hostname in host_analysis_str:\n            continue\n        else:\n            host_analysis_str_new = \"{} {}\".format(host_analysis_str, hostname)\n            hosts = hosts.replace(host_analysis_str, host_analysis_str_new)\n    logger.debug(\"对比获得最新主机信息：\\n{}\".format(hosts))\n    with open(\"/etc/hosts\", \"w\") as f:\n        f.write(hosts)\n    logger.debug(\"写入最新主机信息成功！\")\n\n\ndef usage(error=None):\n    script_full_path = os.path.join(CURRENT_DIR, os.path.basename(__file__))\n    print(\"\"\"{0} 脚本 功能为初始化节点，职能包括: 设置时区、关闭防火墙、设置文件具柄和内核参数等\n    Command:\n            init        <host_name>  <local_ip>  初始化节点\n            valid                                校验初始化结果\n            init_valid  <host_name>  <local_ip>  初始化节点，在完成初始化后执行校验\n\n    Use \"python {0} <command>\" for more information about a given command.\n    \"\"\".format(script_full_path))\n    if error is not None:\n        print(\"Error: {}\".format(error))\n        exit(1)\n    exit(0)\n\n\ndef main():\n    command_list = ('init', 'valid', 'init_valid', 'help', 'write_hostname')\n    try:\n        if sys.argv[1] not in command_list:\n            usage(error='参数错误: {}'.format(sys.argv[1:]))\n        if sys.argv[1] in ['init', 'init_valid']:\n            if len(sys.argv) != 4:\n                usage(error='参数错误: {}'.format(sys.argv[1:]))\n            host_name = sys.argv[2]\n            local_ip = sys.argv[3]\n            init = InitHost(host_name, local_ip)\n            init.run()\n            logger.info(\"init success\")\n            if sys.argv[1] == 'init_valid':\n                check = ValidInit()\n                check.run()\n                logger.info(\"valid success\")\n        elif sys.argv[1] == 'valid':\n            check = ValidInit()\n            check.run()\n            logger.info(\"valid success\")\n        elif sys.argv[1] == \"write_hostname\":\n            hosts_info = sys.argv[2]\n            # '[{\"ip\":\"10.0.9.18\",\"hostname\":\"docp-9-18\"}]'\n            add_hostname_analysis(hosts_info)\n        else:\n            usage()\n    except Exception as e:\n        usage(error=\"参数错误, {}\".format(e))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "package_hub/_modules/inspection_common.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Jayden Liu\n# Description: common function for inspection\nimport os\nimport time\nimport json\nimport socket\nimport psutil\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid):\n    if pid and isinstance(pid, int):\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid):\n    if pid and isinstance(pid, int):\n        try:\n            port = []\n            cmd = 'ss -lnput | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[4].split(':')\n                port.append(port_aa[-1])\n            port = list(set(port))\n            return port\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetProcess_Runtime(pid):\n    runtime = None\n    if pid and isinstance(pid, int):\n        try:\n            cmd = 'ps -eo pid,etime|grep ' + str(pid)\n            etime = os.popen(cmd).read().strip('\\n').split()\n            # if '-' in etime[1]:\n            #     runtime = etime[1].replace('-', ' day ')\n            # else:\n            #     runtime = etime[1]\n\n            runtime = etime[1].replace(\n                '-', '天').replace(':', '小时', 1).replace(':', '分钟', 1) + '秒'\n\n            run_time = etime[1].split(':')\n            run_time = [int(i) for i in run_time]\n            if len(run_time) == 1:\n                runtime = f\"{run_time[0]}秒\"\n            elif len(run_time) == 2:\n                runtime = f\"{run_time[0]}分钟{run_time[1]}秒\"\n            elif len(run_time) == 3:\n                runtime = f\"{run_time[0]}小时{run_time[1]}分钟{run_time[2]}秒\"\n            elif len(run_time) == 4:\n                runtime = \\\n                    f\"{run_time[0]}天{run_time[1]}小时{run_time[2]}分钟{run_time[3]}秒\"\n            elif len(run_time) == 5:\n                runtime = \\\n                    f\"{run_time[0]}年{run_time[1]}天{run_time[2]}小时\" \\\n                    f\"{run_time[3]}分钟{run_time[4]}秒\"\n        except Exception:\n            pass\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid):\n    if pid and isinstance(pid, int):\n        try:\n            pid_cpuinfo = {}\n            p = psutil.Process(pid)\n            pt = p.cpu_times()\n            st1, pt1_0, pt1_1 = timer(), pt.user, pt.system  # new\n            st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n            delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n            delta_time = st1 - st0\n            cpus_percent = ((delta_proc / delta_time) * 100)\n            pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n            cpu_usage = \"{:.2f}\".format(cpus_percent) + \"%\"\n        except Exception:\n            cpu_usage = None\n    else:\n        cpu_usage = None\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid):\n    if pid and isinstance(pid, int):\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            mem_usage = \"{:.2f}\".format(process_mem) + \"%\"\n        except Exception:\n            return None\n    else:\n        mem_usage = None\n    return mem_usage\n\n\ndef GetProcess_ServiceMem(pid, is_java=False):\n    if is_java:\n        if pid and isinstance(pid, int):\n            try:\n                cmd = 'ps -eo pid,command|grep %s' % (pid)\n                process_list = os.popen(cmd).read().strip('\\n').split('-Xms')\n                process_mem = process_list[-1].split()\n                service_mem = process_mem[0]\n                return service_mem\n            except Exception:\n                return None\n        else:\n            return None\n    else:\n        return None\n\n\ndef GetCluster_IP(json_path=\"/data/app/data.json\", service_name=\"\"):\n    cluster_ip = []\n    if json_path.endswith(\"json\"):\n        if not os.path.exists(json_path):\n            return []\n            # raise FileNotFoundError(\"json file not exist\")\n        with open(json_path, \"r\") as f:\n            content = json.load(f)\n        open_source_service = content.get(\"basics\", [])\n        internl_service = content.get(\"services\", [])\n        open_source_service.extend(internl_service)\n        all_service = open_source_service\n        for service in all_service:\n            if service_name == service.get(\"name\"):\n                service_ip = service.get(\"local_ip\")\n                cluster_ip.append(service_ip)\n        return cluster_ip\n    elif json_path.endswith(\"list\"):\n        try:\n            cmd = 'grep %s %s' % (service_name, json_path)\n            cluster_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for cluster_line in cluster_list:\n                cluster = cluster_line.split()\n                cluster_ip.append(cluster[0])\n        except Exception:\n            return cluster_ip\n    else:\n        return cluster_ip\n"
  },
  {
    "path": "package_hub/_modules/kafka_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get kafka Inspection data\nimport json\nimport os.path as up\nimport os\n\nimport psutil\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_ServiceMem, GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        cmd = \"ps -eo pid,command |grep 'kafka/bin/..' | grep -v grep\"\n        cmd_list = os.popen(cmd).read().strip('\\n').split()\n        pid = int(cmd_list[0])\n        return pid\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    log_level = None\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            kafka_path = up.abspath(up.join(p.exe(), \"../../..\"))\n            f = open('%s/kafka/config/log4j.properties' % (kafka_path), 'r')\n            for lines_list in f:\n                if 'log4j.rootLogger=' in lines_list:\n                    kafka_log_level = lines_list.strip('\\n').split('=')\n                    log_level = kafka_log_level[-1]\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetKafka_PartitionSize(pid, port):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            topic_size_json = {}\n            p = psutil.Process(pid)\n            kafka_path = up.abspath(up.join(p.exe(), \"../../..\"))\n            cmd = '%s/kafka/bin/kafka-topics.sh  --list --bootstrap-server %s:%s' % (\n                kafka_path, GetLocal_Ip(), port)\n            topic_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            f = open('%s/kafka/config/server.properties' % (kafka_path), 'r')\n            for lines_list in f:\n                if 'log.dirs=' in lines_list:\n                    kafka_data_path = lines_list.strip('\\n').split('=')\n                    data_path = kafka_data_path[-1]\n            for topic in topic_list:\n                size_cmd = 'du -csh %s/%s' % (data_path, topic) + '-*'\n                topic_size_list = os.popen(\n                    size_cmd).read().strip('\\n').split('\\n')\n                topic_size = topic_size_list[-1].split()\n                topic_size_json[topic] = topic_size[0]\n        except Exception:\n            topic_size_json = None\n        return topic_size_json\n\n\ndef GetKafka_PartitionCount(pid, port):\n    if pid and type(pid).__name__ == 'int':\n        partition_size_json = {}\n        try:\n            p = psutil.Process(pid)\n            kafka_path = up.abspath(up.join(p.exe(), \"../../..\"))\n            cmd = '%s/kafka/bin/kafka-topics.sh  --describe --bootstrap-server %s:%s' % (\n                kafka_path, GetLocal_Ip(), port)\n            topic_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for topic_partition in topic_list:\n                if 'PartitionCount' in topic_partition:\n                    partition_list = topic_partition.split()\n                    topic_line = partition_list[0].split(':')\n                    partition_line = partition_list[1].split(':')\n                    replication_line = partition_list[2].split(':')\n                    topic = topic_line[-1]\n                    replication = replication_line[-1]\n                    partition = partition_line[-1]\n                    partition_size_json[topic] = {\n                        \"partition\": partition, \"replication\": replication}\n        except Exception:\n            partition_size_json = None\n        return partition_size_json\n\n\ndef GetKafka_Offsets(pid, port):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            kafka_path = up.abspath(up.join(p.exe(), \"../../..\"))\n            cmd = '%s/kafka/bin/kafka-consumer-groups.sh  --list --bootstrap-server %s:%s' % (\n                kafka_path, GetLocal_Ip(), port)\n            group_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            offset_group_json = {}\n            for group in group_list:\n                log_offset = 0\n                lag_offset = 0\n                offset_cmd = '%s/kafka/bin/kafka-consumer-groups.sh  --group %s --describe  --bootstrap-server %s:%s|sort 2>/dev/null' % (\n                    kafka_path, group, GetLocal_Ip(), port)\n                offset_list = os.popen(\n                    offset_cmd).read().strip('\\n').split('\\n')\n                if \"Error\" in offset_list[0]:\n                    continue\n                offset_json = {}\n                for offsets in offset_list:\n                    if 'PARTITION' not in offsets:\n                        offset = offsets.split()\n                        if offset[0] not in offset_json:\n                            if offset[3] != '-':\n                                log_offset = int(offset[3])\n                            if offset[4] != '-':\n                                lag_offset = int(offset[4])\n                            offset_json[offset[0]] = {\n                                \"log_offset\": log_offset, \"lag_offset\": lag_offset}\n                        else:\n                            if offset[3] != '-':\n                                log_offset += int(offset[3])\n                            if offset[4] != '-':\n                                lag_offset += int(offset[4])\n                            offset_json[offset[0]] = {\n                                \"log_offset\": log_offset, \"lag_offset\": lag_offset}\n                offset_group_json[group] = offset_json\n            return offset_group_json\n        except Exception:\n            return None\n\n\ndef main(pid=GetProcess_Pid(), port='18108', json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid, is_java=True)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"topic_partition\"] = GetKafka_PartitionCount(pid, port)\n    process_message[\"kafka_offsets\"] = GetKafka_Offsets(pid, port)\n    process_message[\"topic_size\"] = GetKafka_PartitionSize(pid, port)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"kafka\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/minio_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Jayden Liu\n# Description: get minio Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'minio':\n                    pid = int(p.ppid())\n                    if pid != 1:\n                        return pid\n                    else:\n                        return pnum\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = None\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"minio\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/mysql_bak.sh.tmp",
    "content": "#!/bin/bash\n\n# omp自带变量\n# 注意备份路径需要单独创建 不可和其他文件共用一个路径\nbackupDir=\"${cw_o_data_dir}/backup/mysql\"\nMySQLBin=\"${cw_o_base_dir}/bin/mysql\"\nMySQLDump=\"${cw_o_base_dir}/bin/mysqldump\"\nmysqlPort=${cw_o_service_port}\nmysqlUser=\"${cw_o_username}\"\nmysqlPasswd=\"${cw_o_password}\"\nip=\"${cw_o_ip}\"\n\n\n# 自定义变量\ndb_name=${db_name}\nno_pass=${no_pass}\nneed_push=${need_push}\n\n# 公共变量\nservice_name=\"mysql\"\nBasePath=$(cd `dirname $0`; pwd)\nnowTime=$(date +%Y%m%d%H%M)\ncount=0\n# 请求重试机制\nmax_count=6\n\n\nfunction request_omp() {\n  if [[ -z \"$rq_body\" ]]; then\n    if [[ -z \"$1\" ]]; then\n      echo \"need request result\"\n      exit 1\n    fi\n    # $1 结果(必填)  $2 消息  $3 omp抓取路径 要确定是一个单独的文件\n    rq_body=\"{\\\"result\\\":\\\"$1\\\",\\\"message\\\":\\\"$2\\\",\\\"remote_path\\\":\\\"$3\\\",\\\"ip\\\":\\\"$ip\\\",\\\"need_push\\\":\\\"$need_push\\\"}\"\n  fi\n  RES=$(curl --location --request POST 'http://${cw_o_master_url}p/' --header 'Content-Type: application/json' --data \"$rq_body\"|grep \"\\\"code\\\":0\")\n  code=$?\n  # 存在返回证明有异常 或者状态码非0\n  if [[ -z \"$RES\" ]] || [[ $code != 0 ]];then\n    echo $RES\n    if [[ \"$count\" -lt \"$max_count\" ]];then\n      sleep 5\n      let count+=1\n      request_omp $1 $2 $3\n    fi\n    exit 1\n  else\n    exit 0\n  fi\n}\n\n\n# 状态码 备份路径\nfunction touch_zip() {\n  if [[ -z \"$2\" ]]; then\n    request_omp 1 \"need backup path but not provided\"\n  fi\n\n  back_length=$(echo $2 |sed 's#/# #g'|awk '{print NF}')\n  # 保护机制\n  if [[ \"$back_length\" -lt 2 ]];then\n    request_omp 1 \"backup path is not available\"\n  fi\n\n  tar_name=${service_name}-backup-${nowTime}.tar.gz\n  cd $BasePath && tar -zcf $tar_name -C $2 . --remove-files\n\n  tar_path=${BasePath}/${tar_name}\n  request_omp $1 \"success\" $tar_path\n}\n\n\nfunction check_mysql(){\n  if [[ ! -d $backupDir ]]; then\n    mkdir -p $backupDir\n  fi\n\n  if [ -n \"$no_pass\" ]; then\n    $MySQLBin -P$mysqlPort -u$mysqlUser -h'127.0.0.1' -e 'exit' >/dev/null 2>&1\n    databases=$($MySQLBin -u$mysqlUser -h'127.0.0.1' -P$mysqlPort -e 'show databases;' 2>/dev/null | egrep -v 'information_schema|binlogs|mysql|test|Database|performance_schema|hive')\n  else\n    $MySQLBin -P$mysqlPort -u$mysqlUser -p$mysqlPasswd -h'127.0.0.1' -e 'exit' >/dev/null 2>&1\n    databases=$($MySQLBin -u$mysqlUser -p$mysqlPasswd -h'127.0.0.1' -P$mysqlPort -e 'show databases;' 2>/dev/null | egrep -v 'information_schema|binlogs|mysql|Database|performance_schema|hive')\n  fi\n  if [ $? -ne 0 ]; then\n    request_omp 1 \"Error: MySQL User and Password Error.\"\n    exit 1\n  fi\n}\n\n\nfunction backup_mysql(){\n  if [ -n \"$db_name\" ]; then\n    databases=$(echo $db_name |sed 's#,# #g')\n  fi\n  for dataName in $databases; do\n    if [ -n \"$no_pass\" ]; then\n        $MySQLDump --single-transaction -P$mysqlPort -u$mysqlUser -h'127.0.0.1' -a --default-character-set=utf8 --skip-comments $dataName 2>/dev/null >$backupDir/$dataName-$nowTime.sql\n    else\n        $MySQLDump --single-transaction -P$mysqlPort -u$mysqlUser -p$mysqlPasswd -h'127.0.0.1' -a --default-character-set=utf8 --skip-comments $dataName 2>/dev/null >$backupDir/$dataName-$nowTime.sql\n    fi\n  touch_zip $? $backupDir\ndone\n}\n\ncheck_mysql\nbackup_mysql\n"
  },
  {
    "path": "package_hub/_modules/mysql_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get mysql Inspection data\nimport json\nimport subprocess\n\nimport psutil\nimport pymysql\n\nfrom inspection_common import GetProcess_Runtime, GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Mem, \\\n    GetProcessCPU_Pre\n\n\ndef run_cmd(cmd):\n    \"\"\"\n    运行系统命令，返回标准输出，标准错误输出及执行状态码\n    :param cmd:\n    :return:\n    \"\"\"\n    p = subprocess.run(\n        cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    cmd_stdout = bytes.decode(p.stdout)\n    if cmd_stdout.endswith('\\n'):\n        cmd_stdout = cmd_stdout.strip()\n    # cmd_stderr = bytes.decode(p.stderr)\n\n    if p.returncode == '0':\n        return None\n    else:\n        # return cmd_stdout, cmd_stderr, p.returncode\n        return cmd_stdout\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'mysqld':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_Threads(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            # port = []\n            p = psutil.Process(pid)\n            process_threads = p.num_threads()\n            return process_threads\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef get_connection(host, user, passwd, port, database=None):\n    try:\n        host = GetLocal_Ip()\n        if database:\n            conn = pymysql.connect(host=host, user=user, passwd=passwd, port=port, database=database, charset='utf8',\n                                   use_unicode=True, cursorclass=pymysql.cursors.DictCursor)\n        else:\n            conn = pymysql.connect(host=host, user=user, passwd=passwd, port=port, charset='utf8', use_unicode=True,\n                                   cursorclass=pymysql.cursors.DictCursor)\n        return conn\n    except pymysql.Error:\n        return None\n\n\ndef GetMysql_ConnNum(user, passwd, port, database):\n    host = GetLocal_Ip()\n    connected_sql = \"show status where Variable_name='Threads_connected';\"\n    conn_conn = get_connection(host, user, passwd, port, database)\n    if not conn_conn:\n        return None\n    conn_cursor = conn_conn.cursor()\n    try:\n        conn_cursor.execute(connected_sql)\n        conn_conn.commit()\n        exe_result = conn_cursor.fetchone()\n    except conn_conn.Error:\n        exe_result = None\n    finally:\n        conn_cursor.close()\n        conn_conn.close()\n    if exe_result:\n        conn_num = exe_result['Value']\n    else:\n        conn_num = ''\n    return conn_num\n\n\ndef GetMysql_Backup(user, passwd, port, database):\n    host = GetLocal_Ip()\n    check_sql = 'show slave status;'\n    check_conn = get_connection(host, user, passwd, port, database)\n    if not check_conn:\n        return None\n    check_cursor = check_conn.cursor()\n    try:\n        check_cursor.execute(check_sql)\n        check_conn.commit()\n        exe_result = check_cursor.fetchone()\n    except check_conn.Error:\n        exe_result = None\n        return None\n    finally:\n        check_cursor.close()\n        check_conn.close()\n    if exe_result:\n        slave_io_status = exe_result['Slave_IO_Running']\n        slave_sql_status = exe_result['Slave_SQL_Running']\n        if slave_io_status == 'Yes' and slave_sql_status == 'Yes':\n            backup_status = 'up'\n        else:\n            backup_status = 'down'\n    else:\n        backup_status = 'no slave'\n    return backup_status\n\n\ndef GetAbortedClients_Num(user, passwd, port, database):\n    host = GetLocal_Ip()\n    aborted_clients_num_sql = \"show global status like 'Aborted_clients';\"\n    aborted_clients_num_conn = get_connection(\n        host, user, passwd, port, database)\n    if not aborted_clients_num_conn:\n        return None\n    aborted_clients_num_cursor = aborted_clients_num_conn.cursor()\n    try:\n        aborted_clients_num_cursor.execute(aborted_clients_num_sql)\n        aborted_clients_num_conn.commit()\n        aborted_clients_num_result = aborted_clients_num_cursor.fetchone()\n    except aborted_clients_num_conn.Error:\n        return None\n    finally:\n        aborted_clients_num_cursor.close()\n        aborted_clients_num_conn.close()\n    aborted_clients_num = aborted_clients_num_result['Value']\n    return aborted_clients_num\n\n\ndef GetFailConnect_Num(user, passwd, port, database):\n    host = GetLocal_Ip()\n    failure_connect_num_sql = \"show global status like 'Aborted_connects';\"\n    failure_connect_num_conn = get_connection(\n        host, user, passwd, port, database)\n    if not failure_connect_num_conn:\n        return None\n    failure_connect_num_cursor = failure_connect_num_conn.cursor()\n    try:\n        failure_connect_num_cursor.execute(failure_connect_num_sql)\n        failure_connect_num_conn.commit()\n        failure_connect_num_result = failure_connect_num_cursor.fetchone()\n    except failure_connect_num_conn.Error:\n        return None\n    finally:\n        failure_connect_num_cursor.close()\n        failure_connect_num_conn.close()\n    failure_connect_num = failure_connect_num_result['Value']\n    return failure_connect_num\n\n\ndef GetSlowQuery_Num(user, passwd, port, database):\n    host = GetLocal_Ip()\n    select_slow_query_switch_sql = \"show variables where variable_name='slow_query_log';\"\n    select_slow_query_log_file_sql = \"show variables where variable_name='slow_query_log_file';\"\n    select_slow_query_log_file = ''\n    slow_query_num = ''\n    select_slow_query_num_conn = get_connection(\n        host, user, passwd, port, database)\n    if not select_slow_query_num_conn:\n        return None\n    select_slow_query_num_cursor = select_slow_query_num_conn.cursor()\n    try:\n        select_slow_query_num_cursor.execute(select_slow_query_switch_sql)\n        select_slow_query_num_conn.commit()\n        switch_exe_result = select_slow_query_num_cursor.fetchone()\n    except select_slow_query_num_conn.Error:\n        return None\n    if not switch_exe_result:\n        return None\n    if switch_exe_result['Value'] != 'ON':\n        return None\n    try:\n        select_slow_query_num_cursor.execute(select_slow_query_log_file_sql)\n        select_slow_query_num_conn.commit()\n        file_exe_result = select_slow_query_num_cursor.fetchone()\n    except select_slow_query_num_conn.Error:\n        return None\n    finally:\n        select_slow_query_num_cursor.close()\n        select_slow_query_num_conn.close()\n    if not file_exe_result:\n        return None\n    select_slow_query_log_file = file_exe_result['Value']\n    get_slow_query_num_cmd = \"grep Query_time {} | wc -l\".format(\n        select_slow_query_log_file)\n    grep_wc_result = run_cmd(get_slow_query_num_cmd)\n    if not grep_wc_result:\n        return None\n    slow_query_num = grep_wc_result\n    return slow_query_num\n\n\ndef main(pid=GetProcess_Pid(), host='127.0.0.1', user='Rootmaster', passwd='Rootmaster@777', port=18103, database=None,\n         **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message['max_memory'] = None\n    process_message[\"log_level\"] = None\n    process_message[\"process_threads\"] = GetProcess_Threads(pid)\n    process_message[\"conn_num\"] = GetMysql_ConnNum(\n        user, passwd, port, database)\n    process_message[\"aborted_clients\"] = GetAbortedClients_Num(\n        user, passwd, port, database)\n    process_message[\"failure_connect\"] = GetFailConnect_Num(\n        user, passwd, port, database)\n    process_message[\"slow_query\"] = GetSlowQuery_Num(\n        user, passwd, port, database)\n    process_message['backup_status'] = GetMysql_Backup(\n        user, passwd, port, database)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/nacos_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get nacos Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP, GetProcess_ServiceMem\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'java' and 'nacos' in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            nacos_path = p.cwd()\n            f = open('%s/conf/nacos-logback.xml' % (nacos_path), 'r')\n            context = f.readlines()\n            nacos_log_level = context[-5].strip('\\n').split('\"')\n            log_level = nacos_log_level[1]\n        except Exception:\n            log_level = None\n        return log_level\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid, is_java=True)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"nacos\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/ntpd_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Jayden Liu\n# Description: get apmConsumer Inspection data\n\nimport json\nimport os\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetProcess_ServiceMem\n\n\ndef GetProcess_Pid():\n    try:\n        cmd = \"ps -eo pid,command |grep 'ntpd' | grep -v grep\"\n        cmd_list = os.popen(cmd).read().strip('\\n').split()\n        pid = int(cmd_list[0])\n        return pid\n    except Exception:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = None\n    process_message[\"cluster_ip\"] = None\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/postgreSql_bak.sh.tmp",
    "content": "#!/bin/bash\n\n# omp自带变量\n# 注意备份路径需要单独创建 不可和其他文件共用一个路径\nPgSqlPort=${cw_o_service_port}\nPgSqlUser=\"${cw_o_username}\"\nPgSqlPasswd=\"${cw_o_password}\"\nip=\"${cw_o_ip}\"\nPgSqlBaseDir=\"${cw_o_base_dir}\"\npgSqlDataDir=\"${cw_o_data_dir}\"\nbackupDir=\"./backup/pgsql\"\npgSqlScripts=\"${PgSqlBaseDir}/scripts/postgreSql\"\nPgSqlDump=\"${PgSqlBaseDir}/bin/pg_dump\"\nPgSqlDumpAll=\"${PgSqlBaseDir}/bin/pg_dumpall\"\n\n\n# 自定义变量\ndb_name=${db_name}\nneed_push=${need_push}\nneed_app=${need_app}\n\n\n\n# 公共变量\nservice_name=\"postgreSql\"\nBasePath=$(cd `dirname $0`; pwd)\nnowTime=$(date +%Y%m%d%H%M)\ncount=0\n# 请求重试机制\nmax_count=6\n\n\nfunction request_omp() {\n  if [[ -z \"$rq_body\" ]]; then\n    if [[ -z \"$1\" ]]; then\n      echo \"need request result\"\n      exit 1\n    fi\n    # $1 结果(必填)  $2 消息  $3 omp抓取路径 要确定是一个单独的文件\n    rq_body=\"{\\\"result\\\":\\\"$1\\\",\\\"message\\\":\\\"$2\\\",\\\"remote_path\\\":\\\"$3\\\",\\\"ip\\\":\\\"$ip\\\",\\\"need_push\\\":\\\"$need_push\\\"}\"\n  fi\n  RES=$(curl --location --request POST 'http://${cw_o_master_url}p/' --header 'Content-Type: application/json' --data \"$rq_body\"|grep \"\\\"code\\\":0\")\n  code=$?\n  # 存在返回证明有异常 或者状态码非0\n  if [[ -z \"$RES\" ]] || [[ $code != 0 ]];then\n    echo $RES\n    if [[ \"$count\" -lt \"$max_count\" ]];then\n      sleep 5\n      let count+=1\n      request_omp $1 $2 $3\n    fi\n    exit 1\n  else\n    exit 0\n  fi\n}\n\n\n# 状态码 备份路径\nfunction touch_zip() {\n  if [[ -z \"$2\" ]]; then\n    request_omp 1 \"need backup path but not provided\"\n  fi\n\n  back_length=$(echo $2 |sed 's#/# #g'|awk '{print NF}')\n  # 保护机制\n  if [[ \"$back_length\" -lt 2 ]];then\n    request_omp 1 \"backup path is not available\"\n  fi\n\n  tar_name=${service_name}-backup-${nowTime}.tar.gz\n  cd $BasePath && tar -zcf $tar_name -C $2 . --remove-files\n\n  tar_path=${BasePath}/${tar_name}\n  request_omp $1 \"success\" $tar_path\n}\n\nfunction backup_pgsql() {\n  # 创建备份路径\n  if [[ ! -d $backupDir ]]; then\n    mkdir -p $backupDir\n  fi\n\n  #全库备份还是单库备份\n  if [ -n \"$db_name\" ]; then\n    databases=$(echo $db_name |sed 's#,# #g')\n    for dataName in $databases; do\n      $PgSqlDump  -U $PgSqlUserr -h$PgSqlPort -p18126 $dataName > $backupDir/$dataName.sql\n    done\n  else\n    $PgSqlDumpAll  -U $PgSqlUser -h127.0.0.1 -p$PgSqlPort > $backupDir/pg_all_$ip.sql\n  fi\n  dump_code=$?\n  cp_code=0\n  #是否升级备份\n  if [ -n \"$need_app\" ]; then\n    bash $pgSqlScripts stop\n    cp -af $PgSqlBaseDir $backupDir\n    cp -af $pgSqlDataDir  $backupDir\n    cp_code=$?\n    bash $pgSqlScripts start\n  fi\n  if [[ $cp_code != 0 ]] || [[ $dump_code != 0 ]];then\n    touch_zip 1 $backupDir\n  else\n    touch_zip 0 $backupDir\n  fi\n}\n\n\nbackup_pgsql"
  },
  {
    "path": "package_hub/_modules/postgresql_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get postgresql Inspection data\n\nimport json\nimport os\nimport os.path as up\nimport socket\nimport time\n\nimport psutil\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'postmaster' and '-D' in p.cmdline():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid):\n    if pid and type(pid).__name__ == 'int':\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid):\n    try:\n        if pid and type(pid).__name__ == 'int':\n            port = []\n            # p = psutil.Process(pid)\n            cmd = 'ss -tnlp | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[3].split(':')\n                port.append(port_aa[-1])\n            port = list(set(port))\n            return port\n        else:\n            return None\n    except Exception:\n        return None\n\n\ndef GetProcess_Runtime(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,etime|grep ' + str(pid)\n            etime = os.popen(cmd).read().strip('\\n').split()\n            if '-' in etime[1]:\n                runtime = etime[1].replace('-', ' day ')\n            else:\n                runtime = etime[1]\n        except Exception:\n            runtime = None\n    else:\n        runtime = None\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            pid_cpuinfo = {}\n            p = psutil.Process(pid)\n            pt = p.cpu_times()\n            st1, pt1_0, pt1_1 = timer(), pt.user, pt.system  # new\n            st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n            delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n            delta_time = st1 - st0\n            cpus_percent = ((delta_proc / delta_time) * 100)\n            pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n            cpu_usage = \"{:.2f}\".format(cpus_percent) + \"%\"\n        except Exception:\n            cpu_usage = None\n    else:\n        cpu_usage = None\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            mem_usage = \"{:.2f}\".format(process_mem) + \"%\"\n        except Exception:\n            return None\n    else:\n        mem_usage = None\n    return mem_usage\n\n\ndef GetProcess_ServiceMem(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cmd = 'ps -eo pid,command|grep %s' % (pid)\n            process_list = os.popen(cmd).read().strip('\\n').split('-Xms')\n            process_mem = process_list[-1].split()\n            service_mem = process_mem[0]\n            return service_mem\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            postgresql_path = up.abspath(up.join(p.exe(), \"../..\"))\n            f = open('%s/postgresql.conf' % (postgresql_path), 'r')\n            for lines_list in f:\n                if 'log_destination =' in lines_list:\n                    postgresql_log_level = lines_list.strip('\\n').split()\n                    if isinstance(postgresql_log_level[2], str):\n                        log_level = postgresql_log_level[2].replace(\"'\", \"\")\n                    else:\n                        log_level = postgresql_log_level[2]\n        except Exception:\n            log_level = None\n        return log_level\n    else:\n        return None\n\n\ndef GetCluster_IP(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            cluster_ip = []\n            p = psutil.Process(pid)\n            postgresql_path = up.abspath(up.join(p.exe(), \"../../..\"))\n            cmd = 'grep postgreSql %s/task.list' % (postgresql_path)\n            cluster_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            if int(len(cluster_list)) > 1:\n                for cluster_line in cluster_list:\n                    cluster = cluster_line.split()\n                    cluster_ip.append(cluster[0])\n            else:\n                cluster_ip = None\n            return cluster_ip\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(pid)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/prometheus_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get prometheus Inspection data\n\nimport json\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'prometheus':\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            if 'log.level=info' in p.cmdline():\n                log_level = 'info'\n            elif 'log.level=debug' in p.cmdline():\n                log_level = 'debug'\n            elif 'log.level=warn' in p.cmdline() or 'log.level=error' in p.cmdline():\n                log_level = 'error'\n            else:\n                log_level = 'info'\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"prometheus\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/rocketmq_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get rocketmq  Inspection data\n\nimport os\nimport re\n\nimport psutil\nimport time\nimport json\nimport socket\n\n\ndef GetProcess_Pid():\n    try:\n        pid_list = []\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == \"java\":\n                    for cmd_str in p.cmdline():\n                        if \"rocketmq.broker\" in cmd_str:\n                            pid_list.append(pnum)\n                        if \"rocketmq.namesrv\" in cmd_str:\n                            pid_list.append(pnum)\n            except Exception:\n                pass\n        return pid_list\n    except Exception:\n        return []\n\n\ndef GetLocal_Ip():\n    try:\n        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        csock.connect(('8.8.8.8', 80))\n        (addr, port) = csock.getsockname()\n        csock.close()\n        return addr\n    except socket.error:\n        return \"127.0.0.1\"\n\n\ndef GetProcess_Survive(pid_list):\n    if pid_list and isinstance(pid_list, list):\n        return \"True\"\n    else:\n        return \"False\"\n\n\ndef GetProcess_Port(pid_list):\n    if not pid_list or not isinstance(pid_list, list):\n        return None\n    port = []\n    for pid in pid_list:\n        try:\n            cmd = 'ss -tnlp | grep ' + str(pid)\n            port_list = os.popen(cmd).read().strip('\\n').split('\\n')\n            for line_list in port_list:\n                if not line_list:\n                    continue\n                line = line_list.split()\n                port_aa = line[3].split(':')\n                port.append(port_aa[-1])\n        except Exception:\n            return port\n    return list(set(port))\n\n\ndef GetProcess_Runtime(pid_list):\n    runtime = None\n    if not pid_list or not isinstance(pid_list, list):\n        return runtime\n    try:\n        cmd = 'ps -eo pid,etime|grep ' + str(pid_list[0])\n        etime = os.popen(cmd).read().strip('\\n').split()\n        # if '-' in etime[1]:\n        #     runtime = etime[1].replace('-', ' day ')\n        # else:\n        #     runtime = etime[1]\n\n        runtime = etime[1].replace(\n            '-', '天').replace(':', '小时', 1).replace(':', '分钟', 1) + '秒'\n        run_time = etime[1].split(':')\n        run_time = [int(i) for i in run_time]\n        if len(run_time) == 1:\n            runtime = f\"{run_time[0]}秒\"\n        elif len(run_time) == 2:\n            runtime = f\"{run_time[0]}分钟{run_time[1]}秒\"\n        elif len(run_time) == 3:\n            runtime = f\"{run_time[0]}小时{run_time[1]}分钟{run_time[2]}秒\"\n        elif len(run_time) == 4:\n            runtime = \\\n                f\"{run_time[0]}天{run_time[1]}小时{run_time[2]}分钟{run_time[3]}秒\"\n        elif len(run_time) == 5:\n            runtime = \\\n                f\"{run_time[0]}年{run_time[1]}天{run_time[2]}小时\" \\\n                f\"{run_time[3]}分钟{run_time[4]}秒\"\n    except Exception:\n        pass\n    return runtime\n\n\n_timer = getattr(time, 'monotonic', time.time)\nnum_cpus = psutil.cpu_count() or 1\n\n\ndef timer():\n    return _timer() * num_cpus\n\n\ndef GetProcessCPU_Pre(pid_list):\n    if not pid_list or not isinstance(pid_list, list):\n        return None\n    cpus_sum = 0\n    pid_cpuinfo = {}\n    for pid in pid_list:\n        try:\n            p = psutil.Process(pid)\n            pt = p.cpu_times()\n            st1, pt1_0, pt1_1 = timer(), pt.user, pt.system   # new\n            st0, pt0_0, pt0_1 = pid_cpuinfo.get(pid, (0, 0, 0))  # old\n            delta_proc = (pt1_0 - pt0_0) + (pt1_1 - pt0_1)\n            delta_time = st1 - st0\n            cpus_percent = ((delta_proc / delta_time) * 100)\n            cpus_sum += cpus_percent\n            pid_cpuinfo[pid] = [st1, pt1_0, pt1_1]\n        except Exception:\n            pass\n    cpu_usage = \"{:.2f}\".format(cpus_sum) + \"%\"\n    return cpu_usage\n\n\ndef GetProcess_Mem(pid_list):\n    if not pid_list or not isinstance(pid_list, list):\n        return None\n    process_mem_sum = 0\n    for pid in pid_list:\n        try:\n            p = psutil.Process(pid)\n            process_mem = p.memory_percent()\n            process_mem_sum += process_mem\n        except Exception:\n            pass\n    mem_usage = \"{:.2f}\".format(process_mem_sum) + \"%\"\n    return mem_usage\n\n\ndef GetProcess_ServiceMem(pid_list):\n    if not pid_list or not isinstance(pid_list, list):\n        return None\n    service_mem_sum = 0\n    for pid in pid_list:\n        try:\n            cmd = 'ps -eo pid,command|grep %s' % (pid)\n            process_list = os.popen(cmd).read().strip('\\n').split('-ms')\n            process_mem = process_list[-1].split()\n            service_mem = process_mem[0]\n            service_mem_sum += service_mem\n        except Exception:\n            pass\n    return service_mem_sum if service_mem_sum else None\n\n\ndef GetProcess_LogLevel(pid_list):\n    if not pid_list or not isinstance(pid_list, list):\n        return None\n    log_level_set = set()\n    pid = pid_list[0]\n    try:\n        p = psutil.Process(pid)\n        path_str = re.compile(r\"(.*/rocketmq/conf)/.*\")\n        project_path = \"\"\n        for cmd_str in p.cmdline():\n            if path_str.findall(cmd_str):\n                project_path = path_str.findall(cmd_str)[0]\n        if not project_path:\n            return \"\"\n        for log_file in ['logback_broker.xml', 'logback_namesrv.xml', 'logback_tools.xml']:\n            f = open(f'{project_path}/{log_file}', 'r')\n            for lines_list in f:\n                if '<level value' in lines_list:\n                    log_level = lines_list.strip().replace('<level value=\"', '').replace('\"/>', '')\n                    log_level_set.add(log_level)\n                    break\n            f.close()\n    except Exception:\n        pass\n    return \",\".join(log_level_set)\n\n\ndef main(pid_list=GetProcess_Pid(), **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid_list)\n    process_message[\"port_status\"] = GetProcess_Port(pid_list)\n    process_message['run_time'] = GetProcess_Runtime(pid_list)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid_list)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid_list)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid_list)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid_list)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/tengine_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get tengine Inspection data\nimport json\nimport os\nimport os.path as up\nimport psutil\n\nfrom inspection_common import GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Runtime, \\\n    GetProcess_Mem, GetProcessCPU_Pre, GetCluster_IP\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'nginx' and 'master' in p.cmdline():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            nginx_path = up.abspath(up.join(p.exe(), \"../..\"))\n            cmd = 'grep access_log  %s/conf/vhost/*.conf |wc -l' % (nginx_path)\n            log_list = os.popen(cmd).read().strip('\\n')\n            if int(log_list) == 0:\n                log_level = 'error'\n            else:\n                log_level = 'access'\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = None\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(\n        json_path=json_path, service_name=\"tengine\")\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/tomcat_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get mysql Inspection data\nimport json\nimport os\n\nimport psutil\n\nfrom inspection_common import GetProcess_Runtime, GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Mem, \\\n    GetProcessCPU_Pre\n\n\ndef GetProcess_Pid(process_name='tomcat'):\n    cmd = \"ps aux|grep {}|grep -v grep\".format(process_name)\n    try:\n        process_info = os.popen(cmd).read().split()\n        return int(process_info[1])\n    except Exception:\n        return None\n\n\ndef GetProcess_Threads(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            process_threads = p.num_threads()\n            return process_threads\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main():\n    pid = GetProcess_Pid()\n    if pid is None:\n        return json.dumps({})\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message['max_memory'] = None\n    process_message[\"log_level\"] = None\n    process_message[\"process_threads\"] = GetProcess_Threads(pid)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/_modules/zookeeper_check.py",
    "content": "#!/usr/bin/env python3\n# encoding: utf-8\n# Author: Darren Liu\n# Description: get zookeeper Inspection data\nimport json\nimport os\n\nimport psutil\n\nfrom inspection_common import GetProcess_Runtime, GetLocal_Ip, GetProcess_Survive, GetProcess_Port, GetProcess_Mem, \\\n    GetProcessCPU_Pre, GetProcess_ServiceMem\n\n\ndef GetProcess_Pid():\n    try:\n        for pnum in psutil.pids():\n            try:\n                p = psutil.Process(pnum)\n                if p.name() == 'java' and 'zookeeper' in p.cwd():\n                    pid = pnum\n                    return pid\n            except Exception:\n                pass\n    except Exception:\n        return None\n\n\ndef GetNode_Status(pid):\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            cmd = \"bash %s/bin/zkServer.sh status 2>/dev/null\" % (p.cwd())\n            node_status = os.popen(cmd).read().strip('\\n').split(':')\n            status = node_status[-1].strip()\n            return status\n        except Exception:\n            return None\n\n\ndef GetCluster_IP(pid):\n    zk_cluster = []\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            f = open('%s/conf/zoo.cfg' % (p.cwd()), 'r')\n            for lines_list in f:\n                if 'server' in lines_list:\n                    zk_cluster_list = lines_list.strip('\\n').split('=')\n                    zk_cluster_ip = zk_cluster_list[-1].split(':')\n                    zk_cluster.append(zk_cluster_ip[0])\n            return zk_cluster\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef GetProcess_LogLevel(pid):\n    # zk_cluster = []\n    log_level = None\n    if pid and type(pid).__name__ == 'int':\n        try:\n            p = psutil.Process(pid)\n            f = open('%s/conf/log4j.properties' % (p.cwd()), 'r')\n            for lines_list in f:\n                if 'zookeeper.root.logger=' in lines_list:\n                    zk_log_level = lines_list.strip('\\n').split('=')\n                    log_level = zk_log_level[-1]\n            return log_level\n        except Exception:\n            return None\n    else:\n        return None\n\n\ndef main(pid=GetProcess_Pid(), json_path=\"/data/app/data.json\", **kwargs):\n    process_message = dict()\n    process_message[\"IP\"] = GetLocal_Ip()\n    process_message[\"service_status\"] = GetProcess_Survive(pid)\n    process_message[\"port_status\"] = GetProcess_Port(pid)\n    process_message['run_time'] = GetProcess_Runtime(pid)\n    process_message['max_memory'] = GetProcess_ServiceMem(pid, is_java=True)\n    process_message[\"mem_usage\"] = GetProcess_Mem(pid)\n    process_message[\"cpu_usage\"] = GetProcessCPU_Pre(pid)\n    process_message[\"log_level\"] = GetProcess_LogLevel(pid)\n    process_message[\"node_status\"] = GetNode_Status(pid)\n    process_message[\"cluster_ip\"] = GetCluster_IP(pid)\n    return json.dumps(process_message)\n\n\nif __name__ == '__main__':\n    print(main())\n"
  },
  {
    "path": "package_hub/back_end_verified/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/custom_scripts/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/custom_scripts/template.py",
    "content": "# 自定义脚本模板文件\n# 1. 脚本名称须使用蛇形命名法，且以custom_开头\n# 2. 类名须为 CustomMetrics\n# 3. 函数需以get开头，使用蛇形命名法，须为静态方法，无形参，形如 get_xxx()\n# 4. 函数返回数据格式须为json格式，必须包含 help，type，metric，value 键值对，labels键值对可选\n# 5. 不可以引用第三方模块\n# 以下为参考内容\nclass CustomMetrics:\n\n    @staticmethod\n    def get_node_cpu_guest_seconds_total():\n        \"\"\"\n        获取guest占用cpu时间\n        :return:\n        \"\"\"\n        return {\n            \"help\": \"node_cpu_guest_seconds_total Seconds the cpus spent in guests (VMs) for each mode.\",\n            \"type\": \"counter\",\n            \"metric\": \"node_cpu_guest_seconds_total\",\n            \"value\": 4,\n            \"labels\": {\n                \"cpu\": \"0\",\n                \"mode\": \"nice\"\n            }\n        }\n\n    @staticmethod\n    def get_node_runtime():\n        \"\"\"\n        获取系统运行时间\n        :return:\n        \"\"\"\n        return {\n            \"help\": \"node runtime.\",\n            \"type\": \"gauge\",\n            \"metric\": \"node_runtime\",\n            \"value\": 2000,\n            \"labels\": []\n        }\n"
  },
  {
    "path": "package_hub/data_files/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/front_end_verified/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/grafana_dashboard_json/ 21-rediscluster-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 21,\n    \"uid\": \"QuS4Sq0Mz1\",\n    \"title\": \"RedisCluster 信息面板\",\n    \"panels\": [\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"s\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 2,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 9,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(max_over_time(redis_uptime_in_seconds{cluster=\\\"$cluster\\\"}[$__interval]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 1800\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Uptime\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 2,\n        \"y\": 0\n      },\n      \"hideTimeOverride\": true,\n      \"id\": 12,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"redis_connected_clients{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": \"\",\n      \"timeFrom\": \"1m\",\n      \"timeShift\": null,\n      \"title\": \"Clients\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(redis_commands_processed_total{cluster=~\\\"$cluster\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"A\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Commands Executed / sec\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": true,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(redis_keyspace_hits_total{cluster=~\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"hits {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"irate(redis_keyspace_misses_total{cluster=~\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"misses {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 240,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Hits / Misses per Sec\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"max\": \"#BF1B00\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 7,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"hideZero\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null as zero\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"redis_memory_used_bytes{cluster=~\\\"$cluster\\\"} \",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"used {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"redis_memory_max_bytes{cluster=~\\\"$cluster\\\"} \",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"max {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Total Memory Usage\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(redis_net_input_bytes_total{cluster=~\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"input {{ instance_name }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        },\n        {\n          \"expr\": \"rate(redis_net_output_bytes_total{cluster=~\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"output {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Network I/O\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 7,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"isNew\": true,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": false,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum (redis_db_keys{cluster=~\\\"$cluster\\\"}) by (db)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ db }} {{ instance_name }}\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Total Items per DB\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 7,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 13,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum (redis_db_keys{cluster=~\\\"$cluster\\\"}) - sum (redis_db_keys_expiring{cluster=~\\\"$cluster\\\"}) \",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"not expiring {{ instance_name }}\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"sum (redis_db_keys_expiring{cluster=~\\\"$cluster\\\"}) \",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"expiring {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Expiring vs Not-Expiring Keys\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"evicts\": \"#890F02\",\n        \"memcached_items_evicted_total{instance=\\\"172.17.0.1:9150\\\",job=\\\"prometheus\\\"}\": \"#890F02\",\n        \"reclaims\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"reclaims\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(redis_expired_keys_total{cluster=~\\\"$cluster\\\"}[5m])) by (instance)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"expired {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 240,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"sum(rate(redis_evicted_keys_total{cluster=~\\\"$cluster\\\"}[5m])) by (instance)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"evicted {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Expired / Evicted\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 8,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"topk(5, irate(redis_commands_total{cluster=~\\\"$cluster\\\"} [1m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ cmd }} {{ instance_name }}\",\n          \"metric\": \"redis_command_calls_total\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Command Calls / sec\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(redis_uptime_in_seconds, cluster)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"集群\",\n        \"multi\": false,\n        \"name\": \"cluster\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(redis_uptime_in_seconds, cluster)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/1-zhu-ji-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 1,\n    \"uid\": \"9CWBz0bik\",\n    \"title\": \"主机信息面板\",\n    \"panels\": [\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"decimals\": 0,\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(245, 54, 54, 0.9)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 1\n              },\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": 3\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hideTimeOverride\": true,\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"avg(time() - node_boot_time_seconds{env=\\\"$env\\\",instance=~\\\"$node\\\"})\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 40\n        }\n      ],\n      \"title\": \"运行时间\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"decimals\": 1,\n          \"mappings\": [\n            {\n              \"options\": {\n                \"0\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"value\"\n            }\n          ],\n          \"max\": 100,\n          \"min\": 0.1,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"#EAB839\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 90\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 3,\n        \"x\": 2,\n        \"y\": 0\n      },\n      \"id\": 177,\n      \"options\": {\n        \"displayMode\": \"lcd\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"100 - (avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"idle\\\"}[$interval])) * 100)\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"总CPU使用率\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"iowait\\\"}[$interval])) * 100\",\n          \"hide\": true,\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"IOwait使用率\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"(1 - (node_memory_MemAvailable_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} / (node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"})))* 100\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"内存使用率\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"(node_filesystem_size_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint=\\\"$maxmount\\\"}-node_filesystem_free_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint=\\\"$maxmount\\\"})*100 /(node_filesystem_avail_bytes {env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint=\\\"$maxmount\\\"}+(node_filesystem_size_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint=\\\"$maxmount\\\"}-node_filesystem_free_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint=\\\"$maxmount\\\"}))\",\n          \"hide\": false,\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"最大分区({{mountpoint}})使用率\",\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"(1 - ((node_memory_SwapFree_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} + 1)/ (node_memory_SwapTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} + 1))) * 100\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"交换分区使用率\",\n          \"refId\": \"F\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"transformations\": [],\n      \"type\": \"bargauge\"\n    },\n    {\n      \"columns\": [],\n      \"datasource\": \"Prometheus\",\n      \"description\": \"本看板中的：磁盘总量、使用量、可用量、使用率保持和df命令的Size、Used、Avail、Use% 列的值一致，并且Use%的值会四舍五入保留一位小数，会更加准确。\\n\\n注：df中Use%算法为：(size - free) * 100 / (avail + (size - free))，结果是整除则为该值，非整除则为该值+1，结果的单位是%。\\n参考df命令源码：\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fontSize\": \"80%\",\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 10,\n        \"x\": 5,\n        \"y\": 0\n      },\n      \"id\": 181,\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"https://github.com/coreutils/coreutils/blob/master/src/df.c\",\n          \"url\": \"https://github.com/coreutils/coreutils/blob/master/src/df.c\"\n        }\n      ],\n      \"pageSize\": null,\n      \"scroll\": true,\n      \"showHeader\": true,\n      \"sort\": {\n        \"col\": 6,\n        \"desc\": false\n      },\n      \"styles\": [\n        {\n          \"alias\": \"分区\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(50, 172, 45, 0.97)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(245, 54, 54, 0.9)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 2,\n          \"mappingType\": 1,\n          \"pattern\": \"mountpoint\",\n          \"thresholds\": [\n            \"\"\n          ],\n          \"type\": \"string\",\n          \"unit\": \"bytes\"\n        },\n        {\n          \"alias\": \"可用空间\",\n          \"align\": \"auto\",\n          \"colorMode\": \"value\",\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 1,\n          \"mappingType\": 1,\n          \"pattern\": \"Value #A\",\n          \"thresholds\": [\n            \"10000000000\",\n            \"20000000000\"\n          ],\n          \"type\": \"number\",\n          \"unit\": \"bytes\"\n        },\n        {\n          \"alias\": \"使用率\",\n          \"align\": \"auto\",\n          \"colorMode\": \"cell\",\n          \"colors\": [\n            \"rgba(50, 172, 45, 0.97)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(245, 54, 54, 0.9)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 1,\n          \"mappingType\": 1,\n          \"pattern\": \"Value #B\",\n          \"thresholds\": [\n            \"70\",\n            \"85\"\n          ],\n          \"type\": \"number\",\n          \"unit\": \"percent\"\n        },\n        {\n          \"alias\": \"总空间\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 0,\n          \"link\": false,\n          \"mappingType\": 1,\n          \"pattern\": \"Value #C\",\n          \"thresholds\": [],\n          \"type\": \"number\",\n          \"unit\": \"bytes\"\n        },\n        {\n          \"alias\": \"文件系统\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 2,\n          \"link\": false,\n          \"mappingType\": 1,\n          \"pattern\": \"fstype\",\n          \"thresholds\": [],\n          \"type\": \"string\",\n          \"unit\": \"short\"\n        },\n        {\n          \"alias\": \"设备名\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 2,\n          \"link\": false,\n          \"mappingType\": 1,\n          \"pattern\": \"device\",\n          \"preserveFormat\": false,\n          \"sanitize\": false,\n          \"thresholds\": [],\n          \"type\": \"string\",\n          \"unit\": \"short\"\n        },\n        {\n          \"alias\": \"\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"decimals\": 2,\n          \"pattern\": \"/.*/\",\n          \"preserveFormat\": true,\n          \"sanitize\": false,\n          \"thresholds\": [],\n          \"type\": \"hidden\",\n          \"unit\": \"short\"\n        }\n      ],\n      \"targets\": [\n        {\n          \"expr\": \"node_filesystem_size_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-0\",\n          \"format\": \"table\",\n          \"hide\": false,\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"总量\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"node_filesystem_avail_bytes {env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-0\",\n          \"format\": \"table\",\n          \"hide\": false,\n          \"instant\": true,\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"(node_filesystem_size_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-node_filesystem_free_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}) *100/(node_filesystem_avail_bytes {env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}+(node_filesystem_size_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-node_filesystem_free_bytes{env=\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}))\",\n          \"format\": \"table\",\n          \"hide\": false,\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"title\": \"【$node】：各分区可用空间(EXT.*/XFS)\",\n      \"transform\": \"table\",\n      \"type\": \"table-old\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"decimals\": 2,\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"max\": 100,\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 20\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 50\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 15,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"avg(rate(node_cpu_seconds_total{instance=~\\\"$node\\\",mode=\\\"iowait\\\"}[$interval])) * 100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"CPU iowait\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_cni0_in\": \"light-red\",\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_cni0_in下载\": \"green\",\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_cni0_out上传\": \"yellow\",\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_eth0_in下载\": \"purple\",\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_eth0_out\": \"purple\",\n        \"cn-shenzhen.i-wz9cq1dcb6zwc39ehw59_eth0_out上传\": \"blue\"\n      },\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 7,\n        \"x\": 17,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 183,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"show\": false,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null as zero\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 1,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeat\": null,\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*_out上传$/\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"increase(node_network_receive_bytes_total{env=\\\"$env\\\",instance=~\\\"$node\\\",device=~\\\"$device\\\"}[60m])\",\n          \"interval\": \"60m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_in下载\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 600,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"increase(node_network_transmit_bytes_total{env=\\\"$env\\\",instance=~\\\"$node\\\",device=~\\\"$device\\\"}[60m])\",\n          \"hide\": false,\n          \"interval\": \"60m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_out上传\",\n          \"refId\": \"B\",\n          \"step\": 600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"每小时流量$device\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"上传（-）/下载（+）\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(245, 54, 54, 0.9)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 1\n              },\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": 2\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 14,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"value\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"count(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\", mode='system'})\",\n          \"format\": \"time_series\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"title\": \"CPU 核数\",\n      \"type\": \"stat\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(245, 54, 54, 0.9)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 100000\n              },\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": 1000000\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 15,\n        \"y\": 2\n      },\n      \"id\": 179,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"avg(node_filesystem_files_free{instance=~\\\"$node\\\",mountpoint=\\\"$maxmount\\\",fstype=~\\\"ext.?|xfs\\\"})\",\n          \"format\": \"time_series\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"title\": \"剩余节点数:$maxmount \",\n      \"type\": \"stat\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"decimals\": 0,\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(245, 54, 54, 0.9)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 2\n              },\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": 3\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"id\": 75,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"})\",\n          \"format\": \"time_series\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"title\": \"总内存\",\n      \"type\": \"stat\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"options\": {\n                \"match\": \"null\",\n                \"result\": {\n                  \"text\": \"N/A\"\n                }\n              },\n              \"type\": \"special\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"rgba(245, 54, 54, 0.9)\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 1024\n              },\n              {\n                \"color\": \"rgba(50, 172, 45, 0.97)\",\n                \"value\": 10000\n              }\n            ]\n          },\n          \"unit\": \"locale\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 2,\n        \"x\": 15,\n        \"y\": 4\n      },\n      \"id\": 178,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"avg(node_filefd_maximum{instance=~\\\"$node\\\"})\",\n          \"format\": \"time_series\",\n          \"instant\": true,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"title\": \"总文件描述符\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {\n        \"192.168.200.241:9100_Total\": \"dark-red\",\n        \"Idle - Waiting for something to happen\": \"#052B51\",\n        \"guest\": \"#9AC48A\",\n        \"idle\": \"#052B51\",\n        \"iowait\": \"#EAB839\",\n        \"irq\": \"#BF1B00\",\n        \"nice\": \"#C15C17\",\n        \"sdb_每秒I/O操作%\": \"#d683ce\",\n        \"softirq\": \"#E24D42\",\n        \"steal\": \"#FCE2DE\",\n        \"system\": \"#508642\",\n        \"user\": \"#5195CE\",\n        \"磁盘花费在I/O操作占比\": \"#ba43a9\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"hiddenSeries\": false,\n      \"id\": 7,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sideWidth\": null,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"maxPerRow\": 6,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeat\": null,\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*总使用率/\",\n          \"color\": \"#C4162A\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"system\\\"}[$interval])) by (instance) *100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"系统使用率\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"user\\\"}[$interval])) by (instance) *100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"用户使用率\",\n          \"refId\": \"B\",\n          \"step\": 240\n        },\n        {\n          \"expr\": \"avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"iowait\\\"}[$interval])) by (instance) *100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"磁盘IO使用率\",\n          \"refId\": \"D\",\n          \"step\": 240\n        },\n        {\n          \"expr\": \"(1 - avg(rate(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\",mode=\\\"idle\\\"}[$interval])) by (instance))*100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"总使用率\",\n          \"refId\": \"F\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CPU使用率\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 0,\n          \"format\": \"percent\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"192.168.200.241:9100_总内存\": \"dark-red\",\n        \"使用率\": \"yellow\",\n        \"内存_Avaliable\": \"#6ED0E0\",\n        \"内存_Cached\": \"#EF843C\",\n        \"内存_Free\": \"#629E51\",\n        \"内存_Total\": \"#6d1f62\",\n        \"内存_Used\": \"#eab839\",\n        \"可用\": \"#9ac48a\",\n        \"总内存\": \"#bf1b00\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 6\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 156,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"总内存\",\n          \"color\": \"#C4162A\",\n          \"fill\": 0\n        },\n        {\n          \"alias\": \"使用率\",\n          \"color\": \"rgb(0, 209, 255)\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true,\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"总内存\",\n          \"refId\": \"A\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} - node_memory_MemAvailable_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"已用\",\n          \"refId\": \"B\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_MemAvailable_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"可用\",\n          \"refId\": \"F\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_Buffers_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"内存_Buffers\",\n          \"refId\": \"D\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_MemFree_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"内存_Free\",\n          \"refId\": \"C\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_Cached_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"内存_Cached\",\n          \"refId\": \"E\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} - (node_memory_Cached_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} + node_memory_Buffers_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} + node_memory_MemFree_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"})\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"G\"\n        },\n        {\n          \"expr\": \"(1 - (node_memory_MemAvailable_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"} / (node_memory_MemTotal_bytes{env=\\\"$env\\\",instance=~\\\"$node\\\"})))* 100\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"30m\",\n          \"intervalFactor\": 10,\n          \"legendFormat\": \"使用率\",\n          \"refId\": \"H\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"内存信息\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"percent\",\n          \"label\": \"内存使用率\",\n          \"logBase\": 1,\n          \"max\": \"100\",\n          \"min\": \"0\",\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"192.168.10.227:9100_em1_in下载\": \"super-light-green\",\n        \"192.168.10.227:9100_em1_out上传\": \"dark-blue\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 6\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 157,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*_out上传$/\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(node_network_receive_bytes_total{env=\\\"$env\\\",instance=~'$node',device=~\\\"$device\\\"}[$interval])*8\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_in下载\",\n          \"refId\": \"A\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"rate(node_network_transmit_bytes_total{env=\\\"$env\\\",instance=~'$node',device=~\\\"$device\\\"}[$interval])*8\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_out上传\",\n          \"refId\": \"B\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"每秒网络带宽使用$device\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bps\",\n          \"label\": \"上传（-）/下载（+）\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"15分钟\": \"#6ED0E0\",\n        \"1分钟\": \"#BF1B00\",\n        \"5分钟\": \"#CCA300\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 1,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 13,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"maxPerRow\": 6,\n      \"nullPointMode\": \"null as zero\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeat\": null,\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*总核数/\",\n          \"color\": \"#C4162A\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_load1{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"1分钟负载\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"expr\": \"node_load5{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"5分钟负载\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"node_load15{env=\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"15分钟负载\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \" sum(count(node_cpu_seconds_total{env=\\\"$env\\\",instance=~\\\"$node\\\", mode='system'}) by (cpu,instance)) by(instance)\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"CPU总核数\",\n          \"refId\": \"D\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"系统平均负载\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"vda_write\": \"#6ED0E0\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"Read bytes 每个磁盘分区每秒读取的比特数\\nWritten bytes 每个磁盘分区每秒写入的比特数\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 1,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 14\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 168,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*_读取$/\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(node_disk_read_bytes_total{env=\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_读取\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(node_disk_written_bytes_total{env=\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_写入\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"每秒磁盘读写容量\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"Bps\",\n          \"label\": \"读取（-）/写入（+）\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 1,\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 174,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sideWidth\": null,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/Inodes.*/\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"(node_filesystem_size_bytes{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-node_filesystem_free_bytes{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}) *100/(node_filesystem_avail_bytes {env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}+(node_filesystem_size_bytes{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}-node_filesystem_free_bytes{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.*|xfs\\\",mountpoint !~\\\".*pod.*\\\"}))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{mountpoint}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"node_filesystem_files_free{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.?|xfs\\\"} / node_filesystem_files{env=~\\\"$env\\\",instance=~'$node',fstype=~\\\"ext.?|xfs\\\"}\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"Inodes：{{instance}}：{{mountpoint}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"磁盘使用率\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percent\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"100\",\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"decimals\": 2,\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"vda_write\": \"#6ED0E0\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"Reads completed: 每个磁盘分区每秒读完成次数\\n\\nWrites completed: 每个磁盘分区每秒写完成次数\\n\\nIO now 每个磁盘分区每秒正在处理的输入/输出请求数\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 161,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*_读取$/\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(node_disk_reads_completed_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_读取\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(node_disk_writes_completed_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_写入\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"node_disk_io_now{env=~\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"磁盘读写速率（IOPS）\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"iops\",\n          \"label\": \"读取（-）/写入（+）I/O ops/sec\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Idle - Waiting for something to happen\": \"#052B51\",\n        \"guest\": \"#9AC48A\",\n        \"idle\": \"#052B51\",\n        \"iowait\": \"#EAB839\",\n        \"irq\": \"#BF1B00\",\n        \"nice\": \"#C15C17\",\n        \"sdb_每秒I/O操作%\": \"#d683ce\",\n        \"softirq\": \"#E24D42\",\n        \"steal\": \"#FCE2DE\",\n        \"system\": \"#508642\",\n        \"user\": \"#5195CE\",\n        \"磁盘花费在I/O操作占比\": \"#ba43a9\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"description\": \"每一秒钟的自然时间内，花费在I/O上的耗时。（wall-clock time）\\n\\nnode_disk_io_time_seconds_total：\\n磁盘花费在输入/输出操作上的秒数。该值为累加值。（Milliseconds Spent Doing I/Os）\\n\\nrate(node_disk_io_time_seconds_total[1m])：\\n计算每秒的速率：(last值-last前一个值)/时间戳差值，即：1秒钟内磁盘花费在I/O操作的时间占比。\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 175,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sideWidth\": null,\n        \"sort\": null,\n        \"sortDesc\": null,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"maxPerRow\": 6,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(node_disk_io_time_seconds_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_每秒I/O操作%\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"每1秒内I/O操作耗时占比\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"vda\": \"#6ED0E0\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"Read time seconds 每个磁盘分区读操作花费的秒数\\n\\nWrite time seconds 每个磁盘分区写操作花费的秒数\\n\\nIO time seconds 每个磁盘分区输入/输出操作花费的秒数\\n\\nIO time weighted seconds每个磁盘分区输入/输出操作花费的加权秒数\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 1,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 22\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 160,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null as zero\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/,*_读取$/\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(node_disk_read_time_seconds_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval]) / rate(node_disk_reads_completed_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_读取\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rate(node_disk_write_time_seconds_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval]) / rate(node_disk_writes_completed_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_写入\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rate(node_disk_io_time_seconds_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(node_disk_io_time_weighted_seconds_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{device}}_加权\",\n          \"refId\": \"D\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"每次IO读写的耗时（参考：小于100ms）(beta)\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": \"读取（-）/写入（+）\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"192.168.200.241:9100_TCP_alloc\": \"semi-dark-blue\",\n        \"TCP\": \"#6ED0E0\",\n        \"TCP_alloc\": \"blue\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"Sockets_used - 已使用的所有协议套接字总量\\n\\nCurrEstab - 当前状态为 ESTABLISHED 或 CLOSE-WAIT 的 TCP 连接数\\n\\nTCP_alloc - 已分配（已建立、已申请到sk_buff）的TCP套接字数量\\n\\nTCP_tw - 等待关闭的TCP连接数\\n\\nUDP_inuse - 正在使用的 UDP 套接字数量\\n\\nRetransSegs - TCP 重传报文数\\n\\nOutSegs - TCP 发送的报文数\\n\\nInSegs - TCP 接收的报文数\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 16,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"height\": \"300\",\n      \"hiddenSeries\": false,\n      \"id\": 158,\n      \"interval\": \"\",\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": false,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": null,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/.*Sockets_used/\",\n          \"color\": \"#E02F44\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true,\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_netstat_Tcp_CurrEstab{env=~\\\"$env\\\",instance=~'$node'}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"CurrEstab\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"node_sockstat_TCP_tw{env=~\\\"$env\\\",instance=~'$node'}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"TCP_tw\",\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"node_sockstat_sockets_used{env=~\\\"$env\\\",instance=~'$node'}\",\n          \"hide\": false,\n          \"interval\": \"30m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sockets_used\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"node_sockstat_UDP_inuse{env=~\\\"$env\\\",instance=~'$node'}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"UDP_inuse\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"node_sockstat_TCP_alloc{env=~\\\"$env\\\",instance=~'$node'}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"TCP_alloc\",\n          \"refId\": \"E\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_Tcp_PassiveOpens{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}}_Tcp_PassiveOpens\",\n          \"refId\": \"G\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_Tcp_ActiveOpens{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}}_Tcp_ActiveOpens\",\n          \"refId\": \"F\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_Tcp_InSegs{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"Tcp_InSegs\",\n          \"refId\": \"H\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_Tcp_OutSegs{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"Tcp_OutSegs\",\n          \"refId\": \"I\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_Tcp_RetransSegs{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"Tcp_RetransSegs\",\n          \"refId\": \"J\"\n        },\n        {\n          \"expr\": \"rate(node_netstat_TcpExt_ListenDrops{env=~\\\"$env\\\",instance=~'$node'}[$interval])\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"K\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"网络Socket连接信息\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"transformations\": [],\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"已使用的所有协议套接字总量\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"filefd_192.168.200.241:9100\": \"super-light-green\",\n        \"switches_192.168.200.241:9100\": \"semi-dark-red\",\n        \"使用的文件描述符_10.118.72.128:9100\": \"red\",\n        \"每秒上下文切换次数_10.118.71.245:9100\": \"yellow\",\n        \"每秒上下文切换次数_10.118.72.128:9100\": \"yellow\"\n      },\n      \"bars\": false,\n      \"cacheTimeout\": null,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 1,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 31\n      },\n      \"hiddenSeries\": false,\n      \"hideTimeOverride\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 1,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/每秒上下文切换次数.*/\",\n          \"color\": \"#FADE2A\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true,\n          \"yaxis\": 2\n        },\n        {\n          \"alias\": \"/使用的文件描述符.*/\",\n          \"color\": \"#F2495C\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_filefd_allocated{env=~\\\"$env\\\",instance=~\\\"$node\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 5,\n          \"legendFormat\": \"使用的文件描述符\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rate(node_context_switches_total{env=~\\\"$env\\\",instance=~\\\"$node\\\"}[$interval])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 5,\n          \"legendFormat\": \"每秒上下文切换次数\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"  (node_filefd_allocated{env=~\\\"$env\\\",instance=~\\\"$node\\\"}/node_filefd_maximum{env=~\\\"$env\\\",instance=~\\\"$node\\\"}) *100\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 5,\n          \"legendFormat\": \"使用的文件描述符占比_{{instance}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"打开的文件描述符(左 )/每秒上下文切换次数(右)\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"使用的文件描述符\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"context_switches\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": \"\",\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(origin_prometheus)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"数据源\",\n        \"multi\": false,\n        \"name\": \"origin_prometheus\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(origin_prometheus)\",\n          \"refId\": \"Prometheus-origin_prometheus-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 5,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\"}, job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"JOB\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 5,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\"}, env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\"}, env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\",job=~\\\"$job\\\",env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Instance\",\n        \"multi\": false,\n        \"multiFormat\": \"regex values\",\n        \"name\": \"node\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(node_uname_info{origin_prometheus=~\\\"$origin_prometheus\\\",job=~\\\"$job\\\",env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 5,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(node_network_info{origin_prometheus=~\\\"$origin_prometheus\\\",device!~'tap.*|veth.*|br.*|docker.*|virbr.*|lo.*|cni.*'},device)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"网卡\",\n        \"multi\": false,\n        \"multiFormat\": \"regex values\",\n        \"name\": \"device\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(node_network_info{origin_prometheus=~\\\"$origin_prometheus\\\",device!~'tap.*|veth.*|br.*|docker.*|virbr.*|lo.*|cni.*'},device)\",\n          \"refId\": \"Prometheus-device-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"auto\": false,\n        \"auto_count\": 100,\n        \"auto_min\": \"10s\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"2m\",\n          \"value\": \"2m\"\n        },\n        \"datasource\": null,\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"label\": \"时间间隔\",\n        \"name\": \"interval\",\n        \"options\": [\n          {\n            \"selected\": false,\n            \"text\": \"30s\",\n            \"value\": \"30s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1m\",\n            \"value\": \"1m\"\n          },\n          {\n            \"selected\": true,\n            \"text\": \"2m\",\n            \"value\": \"2m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"3m\",\n            \"value\": \"3m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"5m\",\n            \"value\": \"5m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"10m\",\n            \"value\": \"10m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"30m\",\n            \"value\": \"30m\"\n          }\n        ],\n        \"query\": \"30s,1m,2m,3m,5m,10m,30m\",\n        \"queryValue\": \"\",\n        \"refresh\": 2,\n        \"skipUrlSync\": false,\n        \"type\": \"interval\"\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"query_result(topk(1,sort_desc (max(node_filesystem_size_bytes{origin_prometheus=~\\\"$origin_prometheus\\\",instance=~'$node',fstype=~\\\"ext.?|xfs\\\",mountpoint!~\\\".*pods.*\\\"}) by (mountpoint))))\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"最大挂载目录\",\n        \"multi\": false,\n        \"name\": \"maxmount\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"query_result(topk(1,sort_desc (max(node_filesystem_size_bytes{origin_prometheus=~\\\"$origin_prometheus\\\",instance=~'$node',fstype=~\\\"ext.?|xfs\\\",mountpoint!~\\\".*pods.*\\\"}) by (mountpoint))))\",\n          \"refId\": \"Prometheus-maxmount-Variable-Query\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"/.*\\\\\\\"(.*)\\\\\\\".*/\",\n        \"skipUrlSync\": false,\n        \"sort\": 5,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/10-ignite-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 10,\n    \"uid\": \"m7a3BRPMk\",\n    \"title\": \"Ignite 信息面板\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"up time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"ms\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"up_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"UpTime \",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"the num of started thread\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"ignite_started_thread_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"IgniteStartedThreadCount\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of sent messages\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 30,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"sent_messages_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"SentMessagesCount\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of received messages\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 32,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"ignite_received_messages_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"IgniteReceivedMessagesCount\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"average time of job wait\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"average_job_wait_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"AverageJobWaitTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"time of current job wait\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 4\n      },\n      \"hiddenSeries\": false,\n      \"id\": 26,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_job_wait_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CurrentJobWaitTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"max time of job wait\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 12\n      },\n      \"hiddenSeries\": false,\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"maximum_job_wait_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MaximumJobWaitTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"average time of job execute\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 12\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"average_job_execute_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"AverageJobExecuteTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"time of current job executed\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"hiddenSeries\": false,\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_job_execute_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CurrentJobExecuteTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"max time of job execute\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 20\n      },\n      \"hiddenSeries\": false,\n      \"id\": 18,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"maximum_job_execute_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MaximumJobExecuteTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"busy time percentage\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 28\n      },\n      \"hiddenSeries\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"busy_time_percentage{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}*100\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"BusyTimePercentage\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"total busy time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"ms\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 28\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(total_busy_time{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"IgniteBusyTimeTotal\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"total idle time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"ms\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 12,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(ignite_idle_time_total{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"IgniteIdleTimeTotal\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"current daemon thread count\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_daemon_thread_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CurrentDaemonThreadCount\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"maximum thread count\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 44\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"maximum_thread_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MaximumThreadCount\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"current thread count\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 44\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_thread_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CurrentThreadCount\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(ignite_started_thread_count,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(ignite_started_thread_count,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(ignite_started_thread_count{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instancce\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(ignite_started_thread_count{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"datasource\": null,\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"label\": \"job\",\n        \"name\": \"job\",\n        \"query\": \"igniteExporter\",\n        \"skipUrlSync\": false,\n        \"type\": \"constant\"\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/11-kafka-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 11,\n    \"uid\": \"jwPKIsniz\",\n    \"title\": \"Kafka 信息面板\",\n    \"panels\": [\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"kafka brokers\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"interval\": null,\n      \"links\": [],\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"kafka_brokers{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"KafkaBrokers\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"process open fds\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_open_fds{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ProcessOpenFds\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Resident memory size \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_resident_memory_bytes{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ProcessResidentMemoryBytes\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Total user and system CPU time spent in seconds\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 8\n      },\n      \"id\": 26,\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"process_cpu_seconds_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"ProcessCpuSecondsTotal\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"cacheTimeout\": null,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Number of partitions for this Topic\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"hiddenSeries\": false,\n      \"id\": 24,\n      \"interval\": null,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": false\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"kafka_topic_partitions{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"kafkaTopicPartitions\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"kafka topic partition current offset\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"hiddenSeries\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": 500,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(delta(kafka_topic_partition_current_offset{env=~\\\"$env\\\",instance=~'$instance'}[5m])/5) by (topic)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{topic}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"KafkaTopicPartitionCurrentOffset\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Number of Replicas for this Topic/Partition\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"hiddenSeries\": false,\n      \"id\": 30,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"kafka_topic_partition_replicas{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"kafkaExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"KafkaTopicPartitionReplicas\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 39\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": false,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": 420,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum by(topic) (kafka_topic_partitions{env=~\\\"$env\\\",instance=\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{topic}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Partitions per Topic\",\n      \"tooltip\": {\n        \"shared\": false,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"series\",\n        \"name\": null,\n        \"show\": false,\n        \"values\": [\n          \"current\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"hiddenSeries\": false,\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(kafka_consumergroup_lag{env=~\\\"$env$\\\",instance=~\\\"$instance$\\\"}) by (consumergroup)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeRegions\": [],\n      \"title\": \"Consumergroup\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n    ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(kafka_brokers, job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(kafka_brokers, job)\",\n          \"refId\": \"Prometheus-job-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(kafka_brokers, env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(kafka_brokers, env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(kafka_brokers{job=~\\\"$job\\\",env=\\\"$env\\\"}, instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(kafka_brokers{job=~\\\"$job\\\",env=\\\"$env\\\"}, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/12-mysql-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 12,\n    \"uid\": \"MQWgroiiz\",\n    \"title\": \"MySQL 信息面板\",\n    \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 382,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 1,\n      \"description\": \"**MySQL Uptime**\\n\\nThe amount of time since the last restart of the MySQL server process.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"s\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 12,\n      \"interval\": \"\",\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"s\",\n      \"postfixFontSize\": \"80%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_uptime{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"300,3600\",\n      \"title\": \"MySQL Uptime\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Current QPS**\\n\\nBased on the queries reported by MySQL's ``SHOW STATUS`` command, it is the number of statements executed by the server within the last second. This variable includes statements executed within stored programs, unlike the Questions variable. It does not count \\n``COM_PING`` or ``COM_STATISTICS`` commands.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"short\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 13,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Server Status Variables\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Queries\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_queries{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_queries{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": \"35,75\",\n      \"title\": \"Current QPS\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**InnoDB Buffer Pool Size**\\n\\nInnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.  Knowing how the InnoDB buffer pool works, and taking advantage of it to keep frequently accessed data in memory, is one of the most important aspects of MySQL tuning. The goal is to keep the working set in memory. In most cases, this should be between 60%-90% of available memory on a dedicated database host, but depends on many factors.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 51,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"Tuning the InnoDB Buffer Pool Size\",\n          \"url\": \"https://www.percona.com/blog/2015/06/02/80-ram-tune-innodb_buffer_pool_size/\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_innodb_buffer_pool_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"90,95\",\n      \"title\": \"InnoDB Buffer Pool Size\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**InnoDB Buffer Pool Size % of Total RAM**\\n\\nInnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.  Knowing how the InnoDB buffer pool works, and taking advantage of it to keep frequently accessed data in memory, is one of the most important aspects of MySQL tuning. The goal is to keep the working set in memory. In most cases, this should be between 60%-90% of available memory on a dedicated database host, but depends on many factors.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"percent\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 52,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"Tuning the InnoDB Buffer Pool Size\",\n          \"url\": \"https://www.percona.com/blog/2015/06/02/80-ram-tune-innodb_buffer_pool_size/\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"repeat\": null,\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"(mysql_global_variables_innodb_buffer_pool_size{env=\\\"$env\\\",instance=\\\"$host\\\"} * 100) / on (instance) node_memory_MemTotal{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"40,80\",\n      \"title\": \"Buffer Pool Size of Total RAM\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 5\n      },\n      \"id\": 383,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Connections\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**Max Connections** \\n\\nMax Connections is the maximum permitted number of simultaneous client connections. By default, this is 151. Increasing this value increases the number of file descriptors that mysqld requires. If the required number of descriptors are not available, the server reduces the value of Max Connections.\\n\\nmysqld actually permits Max Connections + 1 clients to connect. The extra connection is reserved for use by accounts that have the SUPER privilege, such as root.\\n\\nMax Used Connections is the maximum number of connections that have been in use simultaneously since the server started.\\n\\nConnections is the number of connection attempts (successful or not) to the MySQL server.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 92,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Server System Variables\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_connections\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Max Connections\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max(max_over_time(mysql_global_status_threads_connected{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])  or mysql_global_status_threads_connected{env=\\\"$env\\\",instance=\\\"$host\\\"} )\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Connections\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_max_used_connections{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Used Connections\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_max_connections{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Connections\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Active Threads**\\n\\nThreads Connected is the number of open connections, while Threads Running is the number of threads not sleeping.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 6\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Peak Threads Running\",\n          \"color\": \"#E24D42\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true\n        },\n        {\n          \"alias\": \"Peak Threads Connected\",\n          \"color\": \"#1F78C1\"\n        },\n        {\n          \"alias\": \"Avg Threads Running\",\n          \"color\": \"#EAB839\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max_over_time(mysql_global_status_threads_connected{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or\\nmax_over_time(mysql_global_status_threads_connected{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Peak Threads Connected\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max_over_time(mysql_global_status_threads_running{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or\\nmax_over_time(mysql_global_status_threads_running{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Peak Threads Running\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"avg_over_time(mysql_global_status_threads_running{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or \\navg_over_time(mysql_global_status_threads_running{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Avg Threads Running\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Client Thread Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": [\n          \"total\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"Threads\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 13\n      },\n      \"id\": 384,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Table Locks\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"description\": \"**MySQL Questions**\\n\\nThe number of statements executed by the server. This includes only statements sent to the server by clients and not statements executed within stored programs, unlike the Queries used in the QPS calculation. \\n\\nThis variable does not count the following commands:\\n* ``COM_PING``\\n* ``COM_STATISTICS``\\n* ``COM_STMT_PREPARE``\\n* ``COM_STMT_CLOSE``\\n* ``COM_STMT_RESET``\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 53,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Queries and Questions\",\n          \"url\": \"https://www.percona.com/blog/2014/05/29/how-mysql-queries-and-questions-are-measured/\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_questions{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_questions{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Questions\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Questions\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Thread Cache**\\n\\nThe thread_cache_size variable sets how many threads the server should cache to reuse. When a client disconnects, the client's threads are put in the cache if the cache is not full. It is autosized in MySQL 5.6.8 and above (capped to 100). Requests for threads are satisfied by reusing threads taken from the cache if possible, and only when the cache is empty is a new thread created.\\n\\n* *Threads_created*: The number of threads created to handle connections.\\n* *Threads_cached*: The number of threads in the thread cache.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 11,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Tuning information\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_thread_cache_size\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Threads Created\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_thread_cache_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Thread Cache Size\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_threads_cached{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Threads Cached\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_threads_created{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_threads_created{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Threads Created\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Thread Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 385,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Temporary Objects\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_created_tmp_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Tables\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_disk_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_created_tmp_disk_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Disk Tables\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_files{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_created_tmp_files{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Files\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Temporary Objects\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Select Types**\\n\\nAs with most relational databases, selecting based on indexes is more efficient than scanning an entire table's data. Here we see the counters for selects not done with indexes.\\n\\n* ***Select Scan*** is how many queries caused full table scans, in which all the data in the table had to be read and either discarded or returned.\\n* ***Select Range*** is how many queries used a range scan, which means MySQL scanned all rows in a given range.\\n* ***Select Full Join*** is the number of joins that are not joined on an index, this is usually a huge performance hit.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 22\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 311,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_full_join{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_select_full_join{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Full Join\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_full_range_join{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_select_full_range_join{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Full Range Join\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_range{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_select_range{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Range\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_range_check{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_select_range_check{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Range Check\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_scan{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_select_scan{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Scan\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Select Types\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 29\n      },\n      \"id\": 386,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Sorts\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Sorts**\\n\\nDue to a query's structure, order, or other requirements, MySQL sorts the rows before returning them. For example, if a table is ordered 1 to 10 but you want the results reversed, MySQL then has to sort the rows to return 10 to 1.\\n\\nThis graph also shows when sorts had to scan a whole table or a given range of a table in order to return the results and which could not have been sorted via an index.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"hiddenSeries\": false,\n      \"id\": 30,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_rows{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_sort_rows{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Rows\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_range{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_sort_range{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Range\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_merge_passes{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_sort_merge_passes{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Merge Passes\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_scan{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_sort_scan{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Scan\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Sorts\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Slow Queries**\\n\\nSlow queries are defined as queries being slower than the long_query_time setting. For example, if you have long_query_time set to 3, all queries that take longer than 3 seconds to complete will show on this graph.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 30\n      },\n      \"hiddenSeries\": false,\n      \"id\": 48,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_slow_queries{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_slow_queries{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Slow Queries\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Slow Queries\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 387,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Aborted\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Aborted Connections**\\n\\nWhen a given host connects to MySQL and the connection is interrupted in the middle (for example due to bad credentials), MySQL keeps that info in a system table (since 5.6 this table is exposed in performance_schema).\\n\\nIf the amount of failed requests without a successful connection reaches the value of max_connect_errors, mysqld assumes that something is wrong and blocks the host from further connection.\\n\\nTo allow connections from that host again, you need to issue the ``FLUSH HOSTS`` statement.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 38\n      },\n      \"hiddenSeries\": false,\n      \"id\": 47,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_aborted_connects{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_aborted_connects{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Aborted Connects (attempts)\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_aborted_clients{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_aborted_clients{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Aborted Clients (timeout)\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Aborted Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Table Locks**\\n\\nMySQL takes a number of different locks for varying reasons. In this graph we see how many Table level locks MySQL has requested from the storage engine. In the case of InnoDB, many times the locks could actually be row locks as it only takes table level locks in a few specific cases.\\n\\nIt is most useful to compare Locks Immediate and Locks Waited. If Locks waited is rising, it means you have lock contention. Otherwise, Locks Immediate rising and falling is normal activity.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 38\n      },\n      \"hiddenSeries\": false,\n      \"id\": 32,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_table_locks_immediate{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_locks_immediate{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Locks Immediate\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_table_locks_waited{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_locks_waited{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Locks Waited\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Locks\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"id\": 388,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Network\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Network Traffic**\\n\\nHere we can see how much network traffic is generated by MySQL. Outbound is network traffic sent from MySQL and Inbound is network traffic MySQL has received.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"hiddenSeries\": false,\n      \"id\": 9,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_bytes_received{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_bytes_received{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inbound\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_bytes_sent{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_bytes_sent{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Outbound\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Network Traffic\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Network Usage Hourly**\\n\\nHere we can see how much network traffic is generated by MySQL per hour. You can use the bar graph to compare data sent by MySQL and data received by MySQL.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 46\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 381,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"increase(mysql_global_status_bytes_received{env=\\\"$env\\\",instance=\\\"$host\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Received\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"increase(mysql_global_status_bytes_sent{env=\\\"$env\\\",instance=\\\"$host\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sent\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Network Usage Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 53\n      },\n      \"id\": 389,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Memory\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"***System Memory***: Total Memory for the system.\\\\\\n***InnoDB Buffer Pool Data***: InnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.\\\\\\n***TokuDB Cache Size***: Similar in function to the InnoDB Buffer Pool,  TokuDB will allocate 50% of the installed RAM for its own cache.\\\\\\n***Key Buffer Size***: Index blocks for MYISAM tables are buffered and are shared by all threads. key_buffer_size is the size of the buffer used for index blocks.\\\\\\n***Adaptive Hash Index Size***: When InnoDB notices that some index values are being accessed very frequently, it builds a hash index for them in memory on top of B-Tree indexes.\\\\\\n ***Query Cache Size***: The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. The query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time.\\\\\\n***InnoDB Dictionary Size***: The data dictionary is InnoDB ‘s internal catalog of tables. InnoDB stores the data dictionary on disk, and loads entries into memory while the server is running.\\\\\\n***InnoDB Log Buffer Size***: The MySQL InnoDB log buffer allows transactions to run without having to write the log to disk before the transactions commit.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 54\n      },\n      \"hiddenSeries\": false,\n      \"id\": 50,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Detailed descriptions about metrics\",\n          \"url\": \"https://www.percona.com/doc/percona-monitoring-and-management/dashboard.mysql-overview.html#mysql-internal-memory-overview\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"System Memory\",\n          \"fill\": 0,\n          \"stack\": false\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_memory_MemTotal{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"System Memory\",\n          \"refId\": \"G\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_page_size{env=\\\"$env\\\",instance=\\\"$host\\\"} * on (instance) mysql_global_status_buffer_pool_pages{env=\\\"$env\\\",instance=\\\"$host\\\",state=\\\"data\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Buffer Pool Data\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_innodb_log_buffer_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Log Buffer Size\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_innodb_additional_mem_pool_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"InnoDB Additional Memory Pool Size\",\n          \"refId\": \"H\",\n          \"step\": 40\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_mem_dictionary{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Dictionary Size\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_key_buffer_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Key Buffer Size\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_query_cache_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Query Cache Size\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_mem_adaptive_hash{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Adaptive Hash Index Size\",\n          \"refId\": \"E\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_tokudb_cache_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"TokuDB Cache Size\",\n          \"refId\": \"I\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Internal Memory Overview\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 61\n      },\n      \"id\": 390,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Command, Handlers, Processes\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Top Command Counters**\\n\\nThe Com_{{xxx}} statement counter variables indicate the number of times each xxx statement has been executed. There is one status variable for each type of statement. For example, Com_delete and Com_update count [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements, respectively. Com_delete_multi and Com_update_multi are similar but apply to [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements that use multiple-table syntax.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 62\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"hideZero\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (Com_xxx)\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Com_xxx\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, rate(mysql_global_status_commands_total{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])>0) or topk(5, irate(mysql_global_status_commands_total{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])>0)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Com_{{ command }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Command Counters\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Top Command Counters Hourly**\\n\\nThe Com_{{xxx}} statement counter variables indicate the number of times each xxx statement has been executed. There is one status variable for each type of statement. For example, Com_delete and Com_update count [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements, respectively. Com_delete_multi and Com_update_multi are similar but apply to [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements that use multiple-table syntax.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 69\n      },\n      \"hiddenSeries\": false,\n      \"id\": 39,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (Com_xxx)\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Com_xxx\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, increase(mysql_global_status_commands_total{env=\\\"$env\\\",instance=\\\"$host\\\"}[1h])>0)\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Com_{{ command }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Command Counters Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Handlers**\\n\\nHandler statistics are internal statistics on how MySQL is selecting, updating, inserting, and modifying rows, tables, and indexes.\\n\\nThis is in fact the layer between the Storage Engine and MySQL.\\n\\n* `read_rnd_next` is incremented when the server performs a full table scan and this is a counter you don't really want to see with a high value.\\n* `read_key` is incremented when a read is done with an index.\\n* `read_next` is incremented when the storage engine is asked to 'read the next index entry'. A high value means a lot of index scans are being done.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 76\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_handlers_total{env=\\\"$env\\\",instance=\\\"$host\\\", handler!~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m]) or irate(mysql_global_status_handlers_total{env=\\\"$env\\\",instance=\\\"$host\\\", handler!~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ handler }}\",\n          \"metric\": \"\",\n          \"refId\": \"J\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Handlers\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 83\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_handlers_total{env=\\\"$env\\\",instance=\\\"$host\\\", handler=~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m]) or irate(mysql_global_status_handlers_total{env=\\\"$env\\\",instance=\\\"$host\\\", handler=~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ handler }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Transaction Handlers\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 90\n      },\n      \"hiddenSeries\": false,\n      \"id\": 40,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_info_schema_threads{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ state }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Process States\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 97\n      },\n      \"hiddenSeries\": false,\n      \"id\": 49,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, avg_over_time(mysql_info_schema_threads{env=\\\"$env\\\",instance=\\\"$host\\\"}[1h]))\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ state }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Process States Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 104\n      },\n      \"id\": 391,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Query Cache\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Query Cache Memory**\\n\\nThe query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time. This serialization is true not only for SELECTs, but also for INSERT/UPDATE/DELETE.\\n\\nThis also means that the larger the `query_cache_size` is set to, the slower those operations become. In concurrent environments, the MySQL Query Cache quickly becomes a contention point, decreasing performance. MariaDB and AWS Aurora have done work to try and eliminate the query cache contention in their flavors of MySQL, while MySQL 8.0 has eliminated the query cache feature.\\n\\nThe recommended settings for most environments is to set:\\n  ``query_cache_type=0``\\n  ``query_cache_size=0``\\n\\nNote that while you can dynamically change these values, to completely remove the contention point you have to restart the database.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 105\n      },\n      \"hiddenSeries\": false,\n      \"id\": 46,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_qcache_free_memory{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Free Memory\",\n          \"metric\": \"\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_query_cache_size{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Query Cache Size\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Query Cache Memory\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Query Cache Activity**\\n\\nThe query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time. This serialization is true not only for SELECTs, but also for INSERT/UPDATE/DELETE.\\n\\nThis also means that the larger the `query_cache_size` is set to, the slower those operations become. In concurrent environments, the MySQL Query Cache quickly becomes a contention point, decreasing performance. MariaDB and AWS Aurora have done work to try and eliminate the query cache contention in their flavors of MySQL, while MySQL 8.0 has eliminated the query cache feature.\\n\\nThe recommended settings for most environments is to set:\\n``query_cache_type=0``\\n``query_cache_size=0``\\n\\nNote that while you can dynamically change these values, to completely remove the contention point you have to restart the database.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 105\n      },\n      \"height\": \"\",\n      \"hiddenSeries\": false,\n      \"id\": 45,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_qcache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Hits\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_inserts{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_qcache_inserts{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inserts\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_not_cached{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_qcache_not_cached{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Not Cached\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_lowmem_prunes{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_qcache_lowmem_prunes{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Prunes\",\n          \"metric\": \"\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_qcache_queries_in_cache{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Queries in Cache\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Query Cache Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 112\n      },\n      \"id\": 392,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Files and Tables\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 113\n      },\n      \"hiddenSeries\": false,\n      \"id\": 43,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_opened_files{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_opened_files{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Openings\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL File Openings\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 113\n      },\n      \"hiddenSeries\": false,\n      \"id\": 41,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_files{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Files\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_open_files_limit{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Files Limit\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_num_open_files{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Open Files\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Open Files\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 120\n      },\n      \"id\": 393,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Table Openings\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Table Open Cache Status**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 121\n      },\n      \"hiddenSeries\": false,\n      \"id\": 44,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Table Open Cache Hit Ratio\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_opened_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_opened_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Openings\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Hits\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_misses{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_misses{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Misses\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_overflows{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_overflows{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Misses due to Overflows\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"(rate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))/((rate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))+(rate(mysql_global_status_table_open_cache_misses{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_table_open_cache_misses{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Open Cache Hit Ratio\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Open Cache Status\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"percentunit\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Open Tables**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 121\n      },\n      \"hiddenSeries\": false,\n      \"id\": 42,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_tables{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Tables\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_table_open_cache{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Open Cache\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Open Tables\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 128\n      },\n      \"id\": 394,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"MySQL Table Definition Cache\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Table Definition Cache**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 129\n      },\n      \"hiddenSeries\": false,\n      \"id\": 54,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Opened Table Definitions\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_table_definitions{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Table Definitions\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_table_definition_cache{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Definitions Cache Size\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_opened_table_definitions{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or irate(mysql_global_status_opened_table_definitions{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Opened Table Definitions\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Definition Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 136\n      },\n      \"id\": 395,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"System Charts\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 137\n      },\n      \"hiddenSeries\": false,\n      \"id\": 31,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pgpgin{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 1024 or irate(node_vmstat_pgpgin{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 1024\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Page In\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pgpgout{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 1024 or irate(node_vmstat_pgpgout{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 1024\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Page Out\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"I/O Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 137\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 37,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_MemTotal{env=\\\"$env\\\",instance=\\\"$host\\\"} - (node_memory_MemFree{env=\\\"$env\\\",instance=\\\"$host\\\"} + node_memory_Buffers{env=\\\"$env\\\",instance=\\\"$host\\\"} + node_memory_Cached{env=\\\"$env\\\",instance=\\\"$host\\\"})\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Used\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_MemFree{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Free\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_Buffers{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Buffers\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_Cached{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Cached\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory Distribution\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Load 1m\": \"#58140C\",\n        \"Max Core Utilization\": \"#bf1b00\",\n        \"iowait\": \"#e24d42\",\n        \"nice\": \"#1f78c1\",\n        \"softirq\": \"#806eb7\",\n        \"system\": \"#eab839\",\n        \"user\": \"#508642\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 137\n      },\n      \"height\": \"\",\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Max Core Utilization\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true,\n          \"stack\": false\n        },\n        {\n          \"alias\": \"Load 1m\",\n          \"color\": \"#58140C\",\n          \"fill\": 2,\n          \"stack\": false,\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"clamp_max(((avg by (mode) ( (clamp_max(rate(node_cpu{env=\\\"$env\\\",instance=\\\"$host\\\",mode!=\\\"idle\\\"}[5m]),1)) or (clamp_max(irate(node_cpu{env=\\\"$env\\\",instance=\\\"$host\\\",mode!=\\\"idle\\\"}[5m]),1)) ))*100 or (avg_over_time(node_cpu_average{env=\\\"$env\\\",instance=~\\\"$host\\\", mode!=\\\"total\\\", mode!=\\\"idle\\\"}[5m]) or avg_over_time(node_cpu_average{env=\\\"$env\\\",instance=~\\\"$host\\\", mode!=\\\"total\\\", mode!=\\\"idle\\\"}[5m]))),100)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ mode }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clamp_max(max by () (sum  by (cpu) ( (clamp_max(rate(node_cpu{env=\\\"$env\\\",instance=\\\"$host\\\",mode!=\\\"idle\\\",mode!=\\\"iowait\\\"}[5m]),1)) or (clamp_max(irate(node_cpu{env=\\\"$env\\\",instance=\\\"$host\\\",mode!=\\\"idle\\\",mode!=\\\"iowait\\\"}[5m]),1)) ))*100,100)\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Core Utilization\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"node_load1{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Load 1m\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CPU Usage / Load\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 1,\n          \"format\": \"percent\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": 100,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 144\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 36,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 1,\n      \"points\": true,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum((rate(node_disk_read_time_ms{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) / rate(node_disk_reads_completed{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) or (irate(node_disk_read_time_ms{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) / irate(node_disk_reads_completed{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))\\nor avg_over_time(aws_rds_read_latency_average{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or avg_over_time(aws_rds_read_latency_average{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Read\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum((rate(node_disk_write_time_ms{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) / rate(node_disk_writes_completed{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) or (irate(node_disk_write_time_ms{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) / irate(node_disk_writes_completed{device!~\\\"dm-.+\\\", env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) or \\navg_over_time(aws_rds_write_latency_average{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) or avg_over_time(aws_rds_write_latency_average{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Write\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Disk Latency\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 2,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 144\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 21,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Outbound\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum(rate(node_network_receive_bytes{env=\\\"$env\\\",instance=\\\"$host\\\", device!=\\\"lo\\\"}[5m])) or sum(irate(node_network_receive_bytes{env=\\\"$env\\\",instance=\\\"$host\\\", device!=\\\"lo\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_rx{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_rx{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) \",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inbound\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum(rate(node_network_transmit_bytes{env=\\\"$env\\\",instance=\\\"$host\\\", device!=\\\"lo\\\"}[5m])) or sum(irate(node_network_transmit_bytes{env=\\\"$env\\\",instance=\\\"$host\\\", device!=\\\"lo\\\"}[5m])) or\\nsum(max_over_time(rdsosmetrics_network_tx{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_tx{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Outbound\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Network Traffic\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 144\n      },\n      \"hiddenSeries\": false,\n      \"id\": 38,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pswpin{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 4096 or irate(node_vmstat_pswpin{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 4096\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Swap In (Reads)\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pswpout{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 4096 or irate(node_vmstat_pswpout{env=\\\"$env\\\",instance=\\\"$host\\\"}[5m]) * 4096\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Swap Out (Writes)\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Swap Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"auto\": true,\n        \"auto_count\": 200,\n        \"auto_min\": \"1s\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"auto\",\n          \"value\": \"$__auto_interval_interval\"\n        },\n        \"datasource\": \"Prometheus\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"Interval\",\n        \"multi\": false,\n        \"multiFormat\": \"glob\",\n        \"name\": \"interval\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"auto\",\n            \"value\": \"$__auto_interval_interval\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1s\",\n            \"value\": \"1s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"5s\",\n            \"value\": \"5s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1m\",\n            \"value\": \"1m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"5m\",\n            \"value\": \"5m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1h\",\n            \"value\": \"1h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"6h\",\n            \"value\": \"6h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1d\",\n            \"value\": \"1d\"\n          }\n        ],\n        \"query\": \"1s,5s,1m,5m,1h,6h,1d\",\n        \"queryValue\": \"\",\n        \"refresh\": 2,\n        \"skipUrlSync\": false,\n        \"type\": \"interval\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(mysql_up, env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(mysql_up, env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(mysql_up{env=\\\"$env\\\"}, instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Host\",\n        \"multi\": false,\n        \"multiFormat\": \"regex values\",\n        \"name\": \"host\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(mysql_up{env=\\\"$env\\\"}, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"refresh_on_load\": false,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": null,\n        \"tags\": [],\n        \"tagsQuery\": null,\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/13-nacos-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 13,\n    \"uid\": \"Bz_QALEiz1\",\n    \"title\": \"Nacos 信息面板\",\n    \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 80,\n      \"panels\": [],\n      \"title\": \"nacos monitor\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 89,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(nacos_monitor{name=\\\"configCount\\\",env=\\\"$env\\\",instance=\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"UP\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 3,\n        \"y\": 1\n      },\n      \"id\": 90,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='serviceCount',env=\\\"$env\\\",instance=\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"service count\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 6,\n        \"y\": 1\n      },\n      \"id\": 93,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='ipCount',env=\\\"$env\\\",instance=\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"ip count\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 9,\n        \"y\": 1\n      },\n      \"id\": 92,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='configCount',env=\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"config count\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"id\": 91,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='longPolling',env=\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"long polling\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 15,\n        \"y\": 1\n      },\n      \"id\": 88,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='getConfig',env=\\\"$env\\\",instance=~\\\"$instance\\\"}) by (name)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"config push total\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 1\n      },\n      \"id\": 82,\n      \"links\": [],\n      \"options\": {\n        \"content\": \"<html>\\n<body>\\n<center>\\n<a href=\\\"https://nacos.io\\\">\\n<img src=\\\"https://nacos.io/img/nacos.png\\\" style=\\\"height: 50px;\\\" >\\n</a>\\n</center>\\n</body>\\n</html>\",\n        \"mode\": \"html\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"title\": \"\",\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"id\": 33,\n      \"interval\": \"\",\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"%\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"repeat\": null,\n      \"repeatDirection\": \"h\",\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(system_cpu_usage{env=\\\"$env\\\",instance=~\\\"$instance\\\"}) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"50,80\",\n      \"title\": \"cpu\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 70,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 9,\n        \"y\": 4\n      },\n      \"id\": 32,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"%\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(jvm_memory_used_bytes{area=\\\"heap\\\", env=\\\"$env\\\",instance=~'$instance'})/sum(jvm_memory_max_bytes{area=\\\"heap\\\", env=~\\\"$env\\\",instance=~'$instance'}) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"50,70\",\n      \"title\": \"memory\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"dashboardFilter\": \"\",\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"folderId\": null,\n      \"gridPos\": {\n        \"h\": 16,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 4\n      },\n      \"id\": 48,\n      \"limit\": 10,\n      \"links\": [],\n      \"nameFilter\": \"\",\n      \"onlyAlertsOnDashboard\": false,\n      \"repeat\": null,\n      \"show\": \"current\",\n      \"sortOrder\": 1,\n      \"stateFilter\": [],\n      \"title\": \"alert list\",\n      \"type\": \"alertlist\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 1500,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 29,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(jvm_threads_daemon_threads{env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"800,1500\",\n      \"title\": \"threads\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 20,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 9,\n        \"y\": 8\n      },\n      \"id\": 30,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(system_load_average_1m{env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"5,10\",\n      \"title\": \"load\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 5000,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 0,\n        \"y\": 12\n      },\n      \"id\": 61,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"ms\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(nacos_timer_seconds_sum{env=~\\\"$env\\\",instance=~'$instance'}[1m]))/sum(rate(nacos_timer_seconds_count{env=~\\\"$env\\\",instance=~'$instance'}[1m])) * 1000\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"3000,5000\",\n      \"title\": \"notify rt\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 5000,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 9,\n        \"y\": 12\n      },\n      \"id\": 26,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"ms\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_sum{env=~\\\"$env\\\",instance=~'$instance'}[1m]))/sum(rate(http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~'$instance'}[1m])) * 1000\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"3000,5000\",\n      \"title\": \"rt\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 2000,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 25,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~'$instance'}[1m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"1000,2000\",\n      \"title\": \"qps\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorPrefix\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 5000,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 9,\n        \"x\": 9,\n        \"y\": 16\n      },\n      \"id\": 70,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"ms\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='avgPushCost', env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"1000,5000\",\n      \"title\": \"avgPushCost\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 78,\n      \"panels\": [],\n      \"title\": \"nacos detail\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_sum{uri=~'/v1/cs/configs|/nacos/v1/ns', env=~\\\"$env\\\",instance=~'$instance'}[1m])/rate(http_server_requests_seconds_count{uri=~'/v1/cs/configs|/nacos/v1/ns/instance|/nacos/v1/ns/health', env=~\\\"$env\\\",instance=~'$instance'}[1m])) by (method,uri) * 1000\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_sum{env=~\\\"$env\\\",instance=~'$instance'}[1m]))/sum(rate(http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~'$instance'}[1m])) * 1000\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"all\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rt\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 41,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeat\": \"group\",\n      \"repeatDirection\": \"h\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='longPolling', env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"long polling\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 37,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(system_load_average_1m{env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"load 1m\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"hiddenSeries\": false,\n      \"id\": 18,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_count{uri=~'/v1/cs/configs|/nacos/v1/ns/instance|/nacos/v1/ns/health', env=~\\\"$env\\\",instance=~'$instance'}[1m])) by (method,uri)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~'$instance'}[1m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"qps\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 26\n      },\n      \"hiddenSeries\": false,\n      \"id\": 52,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='leaderStatus', env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"leaderStatus\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 26\n      },\n      \"hiddenSeries\": false,\n      \"id\": 50,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='avgPushCost', env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"avgPushCost\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"hiddenSeries\": false,\n      \"id\": 53,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='maxPushCost', env=~\\\"$env\\\",instance=~'$instance'})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"maxPushCost\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 31\n      },\n      \"hiddenSeries\": false,\n      \"id\": 83,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='publish', env=~\\\"$env\\\",instance=~'$instance'}) by (name)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"publish config\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(nacos_monitor{name='getConfig', env=~\\\"$env\\\",instance=~'$instance'}) by (name)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"get config\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"config statistics\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 31\n      },\n      \"hiddenSeries\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(nacos_monitor{name=~'.*HealthCheck', env=~\\\"$env\\\",instance=~'$instance'}[1m])) by (name) * 60\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"health check\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"id\": 74,\n      \"panels\": [],\n      \"title\": \"nacos alert\",\n      \"type\": \"row\"\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                50\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"for\": \"1m\",\n        \"frequency\": \"1m\",\n        \"handler\": 1,\n        \"name\": \"cpu alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": [\n          {\n            \"id\": 1\n          }\n        ]\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"hiddenSeries\": false,\n      \"id\": 45,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(system_cpu_usage{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 50,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"cpu alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                15\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"load 1m alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 37\n      },\n      \"hiddenSeries\": false,\n      \"id\": 86,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(system_load_average_1m{env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 15,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"load  alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                60\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"5m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"memory alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 37\n      },\n      \"hiddenSeries\": false,\n      \"id\": 46,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(jvm_memory_used_bytes{area=\\\"heap\\\",env=~\\\"$env\\\",instance=~\\\"$instance\\\"})/sum(jvm_memory_max_bytes{area=\\\"heap\\\",env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 60,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"memory alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                500\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"threads alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 42\n      },\n      \"hiddenSeries\": false,\n      \"id\": 39,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(jvm_threads_daemon_threads{env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 500,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"threads alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                5\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"for\": \"1m\",\n        \"frequency\": \"1m\",\n        \"handler\": 1,\n        \"message\": \"too many full gc\",\n        \"name\": \"gc alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": [\n          {\n            \"id\": 1\n          }\n        ]\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 42\n      },\n      \"hiddenSeries\": false,\n      \"id\": 38,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(rate(jvm_gc_pause_seconds_count{action=\\\"end of major GC\\\",env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])) * 300\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 5,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"gc alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                10\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"notify task alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 42\n      },\n      \"hiddenSeries\": false,\n      \"id\": 49,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='notifyTask',env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 10,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"notify task alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                5000\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"B\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"rt alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 47\n      },\n      \"hiddenSeries\": false,\n      \"id\": 85,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(http_server_requests_seconds_sum{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[1m]))/sum(rate(http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[1m])) * 1000\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 5000,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rt alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                5000\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"long polling alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 47\n      },\n      \"hiddenSeries\": false,\n      \"id\": 84,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeatDirection\": \"h\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(nacos_monitor{name='longPolling',env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 5000,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"long polling alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"alert\": {\n        \"conditions\": [\n          {\n            \"evaluator\": {\n              \"params\": [\n                1\n              ],\n              \"type\": \"gt\"\n            },\n            \"operator\": {\n              \"type\": \"and\"\n            },\n            \"query\": {\n              \"params\": [\n                \"A\",\n                \"1m\",\n                \"now\"\n              ]\n            },\n            \"reducer\": {\n              \"params\": [],\n              \"type\": \"avg\"\n            },\n            \"type\": \"query\"\n          }\n        ],\n        \"executionErrorState\": \"keep_state\",\n        \"frequency\": \"60s\",\n        \"handler\": 1,\n        \"name\": \"failedPush alert\",\n        \"noDataState\": \"ok\",\n        \"notifications\": []\n      },\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 47\n      },\n      \"hiddenSeries\": false,\n      \"id\": 51,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nacos_monitor{name='failedPush',env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [\n        {\n          \"colorMode\": \"critical\",\n          \"fill\": true,\n          \"line\": true,\n          \"op\": \"gt\",\n          \"value\": 1,\n          \"visible\": true\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"failed push alert\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"nacosExporter\\\"},env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"nacosExporter\\\"},env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*:8848\",\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"nacosExporter\\\",env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"nacosExporter\\\",env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/14-postgresql-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 14,\n    \"uid\": \"000000039\",\n    \"title\": \"PostgreSQL 信息面板\",\n    \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"title\": \"General Counters, CPU, Memory and File Descriptor Stats\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"#299c46\",\n        \"#7eb26d\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 36,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_static{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{short_version}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Version\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"name\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"description\": \"start time of the process\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"dateTimeFromNow\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 4,\n        \"y\": 1\n      },\n      \"id\": 28,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"110%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"110%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_postmaster_start_time_seconds{env=~\\\"$env\\\",instance=\\\"$instance\\\"} * 1000\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Start Time\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"70%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"avg\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"decbytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 8,\n        \"y\": 1\n      },\n      \"height\": \"200px\",\n      \"id\": 10,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"SUM(pg_stat_database_tup_fetched{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Current fetch data\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"decbytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"height\": \"200px\",\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"SUM(pg_stat_database_tup_inserted{release=\\\"$release\\\", datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Current insert data\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"decbytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 1\n      },\n      \"height\": \"200px\",\n      \"id\": 12,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"SUM(pg_stat_database_tup_updated{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Current update data\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 1\n      },\n      \"id\": 38,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_max_connections{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Max Connections\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"avg\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Average user and system CPU time spent in seconds.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 3\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"avg(rate(process_cpu_seconds_total{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m]) * 1000)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"CPU Time\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Average CPU Usage\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Virtual and Resident memory size in bytes, averages over 5 min interval\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 3\n      },\n      \"hiddenSeries\": false,\n      \"id\": 24,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"avg(rate(process_resident_memory_bytes{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Resident Mem\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"avg(rate(process_virtual_memory_bytes{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Virtual Mem\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Average Memory Usage\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Number of open file descriptors\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 3\n      },\n      \"hiddenSeries\": false,\n      \"id\": 26,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_open_fds{release=\\\"$release\\\", env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Open FD\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Open File Descriptors\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 32,\n      \"panels\": [],\n      \"title\": \"Settings\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 11\n      },\n      \"id\": 40,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_shared_buffers_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Shared Buffers\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 3,\n        \"y\": 11\n      },\n      \"id\": 42,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_effective_cache_size_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Effective Cache\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 6,\n        \"y\": 11\n      },\n      \"id\": 44,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_maintenance_work_mem_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Maintenance Work Mem\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 9,\n        \"y\": 11\n      },\n      \"id\": 46,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_work_mem_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Work Mem\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 1,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 12,\n        \"y\": 11\n      },\n      \"id\": 48,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_max_wal_size_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Max WAL Size\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 3,\n        \"x\": 15,\n        \"y\": 11\n      },\n      \"id\": 50,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_random_page_cost{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Random Page Cost\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 2,\n        \"x\": 18,\n        \"y\": 11\n      },\n      \"id\": 52,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_seq_page_cost{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Seq Page Cost\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 2,\n        \"x\": 20,\n        \"y\": 11\n      },\n      \"id\": 54,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_max_worker_processes{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Max Worker Processes\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"avg\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 2,\n        \"x\": 22,\n        \"y\": 11\n      },\n      \"id\": 56,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"pg_settings_max_parallel_workers{env=~\\\"$env\\\",instance=\\\"$instance\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Max Parallel Workers\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"id\": 30,\n      \"panels\": [],\n      \"title\": \"Database Stats\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 3,\n      \"points\": true,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_activity_count{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\", state=\\\"active\\\"} !=0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}, s: {{state}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Active sessions\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 0,\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 15\n      },\n      \"hiddenSeries\": false,\n      \"id\": 60,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(pg_stat_database_xact_commit{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{datname}} commits\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_database_xact_rollback{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{datname}} rollbacks\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Transactions\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 15\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": null,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_tup_updated{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Update data\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_tup_fetched{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Fetch data (SELECT)\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_tup_inserted{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Insert data\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_locks_count{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\", mode=~\\\"$mode\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}},{{mode}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Lock tables\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 0,\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 29\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"total\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_tup_returned{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Return data\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 29\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": false,\n        \"current\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": false,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_activity_count{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\", state=~\\\"idle|idle in transaction|idle in transaction (aborted)\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}, s: {{state}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Idle sessions\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 29\n      },\n      \"hiddenSeries\": false,\n      \"id\": 7,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_tup_deleted{datname=~\\\"$datname\\\", env=~\\\"$env\\\",instance=~\\\"$instance\\\"} != 0\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\",\n          \"step\": 2\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Delete data\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 62,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"pg_stat_database_blks_hit{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"} / (pg_stat_database_blks_read{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"} + pg_stat_database_blks_hit{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ datname }}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Cache Hit Rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 4,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 64,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_buffers_backend{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"buffers_backend\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_buffers_alloc{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"buffers_alloc\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_buffers_backend_fsync{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"backend_fsync\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_buffers_checkpoint{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"buffers_checkpoint\",\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_buffers_clean{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"buffers_clean\",\n          \"refId\": \"E\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Buffers (bgwriter)\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 66,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(pg_stat_database_conflicts{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{datname}} conflicts\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_database_deadlocks{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{datname}} deadlocks\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Conflicts/Deadlocks\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Total amount of data written to temporary files by queries in this database. All temporary files are counted, regardless of why the temporary file was created, and regardless of the log_temp_files setting.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 43\n      },\n      \"hiddenSeries\": false,\n      \"id\": 68,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(pg_stat_database_temp_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\", datname=~\\\"$datname\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{datname}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Temp File (Bytes)\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 16,\n        \"x\": 8,\n        \"y\": 43\n      },\n      \"hiddenSeries\": false,\n      \"id\": 70,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_checkpoint_write_time{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"write_time - Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk.\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"irate(pg_stat_bgwriter_checkpoint_sync_time{env=~\\\"$env\\\",instance=\\\"$instance\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sync_time - Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk.\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Checkpoint Stats\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"query_result(pg_exporter_last_scrape_duration_seconds)\",\n          \"refId\": \"Prometheus-namespace-Variable-Query\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"/.*kubernetes_namespace=\\\"([^\\\"]+).*/\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"Release\",\n        \"multi\": false,\n        \"name\": \"release\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"query_result(pg_exporter_last_scrape_duration_seconds{kubernetes_namespace=\\\"$namespace\\\"})\",\n          \"refId\": \"Prometheus-release-Variable-Query\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"/.*release=\\\"([^\\\"]+)/\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(pg_static,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(pg_static,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"query_result(pg_up{release=\\\"$release\\\",env=\\\"$env\\\"})\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"query_result(pg_up{release=\\\"$release\\\",env=\\\"$env\\\"})\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"/.*instance=\\\"([^\\\"]+).*/\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"Database\",\n        \"multi\": true,\n        \"name\": \"datname\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(datname)\",\n          \"refId\": \"Prometheus-datname-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"Lock table\",\n        \"multi\": true,\n        \"name\": \"mode\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({mode=~\\\"accessexclusivelock|accesssharelock|exclusivelock|rowexclusivelock|rowsharelock|sharelock|sharerowexclusivelock|shareupdateexclusivelock\\\"}, mode)\",\n          \"refId\": \"Prometheus-mode-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/15-redis-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 15,\n    \"uid\": \"QuS4Sq0Mz\",\n    \"title\": \"Redis 信息面板\",\n    \"panels\": [\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"rgba(245, 54, 54, 0.9)\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"rgba(50, 172, 45, 0.97)\"\n        ],\n        \"datasource\": \"Prometheus\",\n        \"decimals\": 0,\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"format\": \"s\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 2,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 9,\n        \"interval\": null,\n        \"isNew\": true,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"max(max_over_time(redis_uptime_in_seconds{env=\\\"$env\\\",instance=~\\\"$instance\\\"}[$__interval]))\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"\",\n            \"metric\": \"\",\n            \"refId\": \"A\",\n            \"step\": 1800\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"Uptime\",\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"70%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"rgba(245, 54, 54, 0.9)\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"rgba(50, 172, 45, 0.97)\"\n        ],\n        \"datasource\": \"Prometheus\",\n        \"decimals\": 0,\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 6,\n          \"x\": 2,\n          \"y\": 0\n        },\n        \"hideTimeOverride\": true,\n        \"id\": 12,\n        \"interval\": null,\n        \"isNew\": true,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"redis_connected_clients{env=\\\"$env\\\",instance=~\\\"$instance\\\"}\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"\",\n            \"metric\": \"\",\n            \"refId\": \"A\",\n            \"step\": 2\n          }\n        ],\n        \"thresholds\": \"\",\n        \"timeFrom\": \"1m\",\n        \"timeShift\": null,\n        \"title\": \"Clients\",\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"80%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 0\n        },\n        \"hiddenSeries\": false,\n        \"id\": 2,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"rate(redis_commands_processed_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[1m])\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"\",\n            \"metric\": \"A\",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Commands Executed / sec\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"cumulative\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"decimals\": 2,\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 0\n        },\n        \"hiddenSeries\": false,\n        \"id\": 1,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": true,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"irate(redis_keyspace_hits_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"hits\",\n            \"metric\": \"\",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          },\n          {\n            \"expr\": \"irate(redis_keyspace_misses_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"misses\",\n            \"metric\": \"\",\n            \"refId\": \"B\",\n            \"step\": 240,\n            \"target\": \"\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Hits / Misses per Sec\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": 0,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {\n          \"max\": \"#BF1B00\"\n        },\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 7\n        },\n        \"hiddenSeries\": false,\n        \"id\": 7,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"hideEmpty\": false,\n          \"hideZero\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null as zero\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"redis_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$instance\\\"} \",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"used\",\n            \"metric\": \"\",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          },\n          {\n            \"expr\": \"redis_memory_max_bytes{env=~\\\"$env\\\",instance=~\\\"$instance\\\"} \",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"max\",\n            \"refId\": \"B\",\n            \"step\": 240\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Total Memory Usage\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"cumulative\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": 0,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 7\n        },\n        \"hiddenSeries\": false,\n        \"id\": 10,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"rate(redis_net_input_bytes_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"input\",\n            \"refId\": \"A\",\n            \"step\": 240\n          },\n          {\n            \"expr\": \"rate(redis_net_output_bytes_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"output\",\n            \"refId\": \"B\",\n            \"step\": 240\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Network I/O\",\n        \"tooltip\": {\n          \"msResolution\": true,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"cumulative\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 7,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 14\n        },\n        \"hiddenSeries\": false,\n        \"id\": 5,\n        \"isNew\": true,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": false,\n          \"current\": true,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": true,\n          \"show\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": true,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum (redis_db_keys{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) by (db)\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"{{ db }} \",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Total Items per DB\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"none\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 7,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 14\n        },\n        \"hiddenSeries\": false,\n        \"id\": 13,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": true,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum (redis_db_keys{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) - sum (redis_db_keys_expiring{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) \",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"not expiring\",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          },\n          {\n            \"expr\": \"sum (redis_db_keys_expiring{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}) \",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"expiring\",\n            \"metric\": \"\",\n            \"refId\": \"B\",\n            \"step\": 240\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Expiring vs Not-Expiring Keys\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {\n          \"evicts\": \"#890F02\",\n          \"memcached_items_evicted_total{instance=\\\"172.17.0.1:9150\\\",job=\\\"prometheus\\\"}\": \"#890F02\",\n          \"reclaims\": \"#3F6833\"\n        },\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 21\n        },\n        \"hiddenSeries\": false,\n        \"id\": 8,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"reclaims\",\n            \"yaxis\": 2\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(redis_expired_keys_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])) by (instance)\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"expired\",\n            \"metric\": \"\",\n            \"refId\": \"A\",\n            \"step\": 240,\n            \"target\": \"\"\n          },\n          {\n            \"expr\": \"sum(rate(redis_evicted_keys_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])) by (instance)\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"evicted\",\n            \"refId\": \"B\",\n            \"step\": 240\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Expired / Evicted\",\n        \"tooltip\": {\n          \"msResolution\": false,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"cumulative\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"editable\": true,\n        \"error\": false,\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 8,\n        \"fillGradient\": 0,\n        \"grid\": {},\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 21\n        },\n        \"hiddenSeries\": false,\n        \"id\": 14,\n        \"isNew\": true,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"connected\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": true,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"topk(5, irate(redis_commands_total{env=~\\\"$env\\\",instance=~\\\"$instance\\\"} [1m]))\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"{{ cmd }}\",\n            \"metric\": \"redis_command_calls_total\",\n            \"refId\": \"A\",\n            \"step\": 240\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Command Calls / sec\",\n        \"tooltip\": {\n          \"msResolution\": true,\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"cumulative\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      }\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(redis_uptime_in_seconds, env)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"环境\",\n          \"multi\": false,\n          \"name\": \"env\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(redis_uptime_in_seconds, env)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(redis_up{env=\\\"$env\\\"}, instance)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": null,\n          \"multi\": false,\n          \"name\": \"instance\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(redis_up{env=\\\"$env\\\"}, instance)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/16-tengine-nginx-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 16,\n    \"uid\": \"9MOLXSbMz\",\n    \"title\": \"Tengine (Nginx) 信息面板\",\n    \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"nginx_server_connections{env=~\\\"$env\\\",instance=~\\\"$instance\\\",status=~\\\"active|writing|reading|waiting\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{status}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_cache{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])) by (status)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ status }}\",\n          \"metric\": \"nginx_server_cache\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_requests{env=~\\\"$env\\\",instance=~\\\"$instance\\\", code!=\\\"total\\\"}[5m])) by (code)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ code }}\",\n          \"metric\": \"nginx_server_requests\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Requests\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_bytes{env=~\\\"$env\\\",instance=~\\\"$instance\\\"}[5m])) by (direction)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ direction }}\",\n          \"metric\": \"nginx_server_bytes\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Bytes\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"This one is providing aggregated error codes, but it's still possible to graph these per upstream.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_upstream_requests{env=~\\\"$env\\\",instance=~\\\"$instance\\\", upstream=~\\\"^$upstream$\\\",code!=\\\"total\\\"}[5m])) by (code)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ code }}\",\n          \"metric\": \"nginx_upstream_requests\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Requests\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_upstream_bytes{env=~\\\"$env\\\",instance=~\\\"$instance\\\", upstream=~\\\"^$upstream$\\\"}[5m])) by (direction)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ direction }}\",\n          \"metric\": \"nginx_upstream_bytes\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Bytes\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 7,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nginx_upstream_responseMsec{env=~\\\"$env\\\",instance=~\\\"$instance\\\", upstream=~\\\"^$upstream$\\\"}) by (backend)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ backend }}\",\n          \"metric\": \"nginx_upstream_response\",\n          \"refId\": \"A\",\n          \"step\": 120\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Backend Response\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(nginx_server_bytes,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(nginx_server_bytes,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(nginx_server_bytes{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(nginx_server_bytes{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(nginx_upstream_bytes{env=\\\"$env\\\",instance=\\\"$instance\\\"}, upstream)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"upstream\",\n        \"multi\": false,\n        \"name\": \"upstream\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(nginx_upstream_bytes{env=\\\"$env\\\",instance=\\\"$instance\\\"}, upstream)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/17-zookeeper-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 17,\n    \"uid\": \"SDE76m7Zzz\",\n    \"title\": \"ZooKeeper 信息面板\",\n    \"panels\": [\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": true,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#F2495C\",\n        \"#F2495C\",\n        \"#F2495C\"\n      ],\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"ms\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 225,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"pluginVersion\": \"6.2.2\",\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"zk_server_leader{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"leader\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"120%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"name\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 212,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_watch_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} watch_count\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"watch_count\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"max\": 1024,\n          \"min\": 0,\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"index\": 0,\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"index\": 1,\n                \"value\": 800\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 122,\n      \"links\": [],\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"last\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"zk_open_file_descriptor_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"open_file_descriptor\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"hiddenSeries\": false,\n      \"id\": 224,\n      \"interval\": \"\",\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": true,\n        \"hideEmpty\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_znode_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} znode_count\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"zk_ephemerals_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} ephemerals\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"znode_count\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 16\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"interval\": \"\",\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": true,\n        \"hideEmpty\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(zk_znode_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} znode_count_rate\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rate(zk_ephemerals_count{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} ephemerals_rate\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"znode_count_rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ops\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"hiddenSeries\": false,\n      \"id\": 132,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_approximate_data_size{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}} approximate_data_size\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"approximate_data_size\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 24\n      },\n      \"hiddenSeries\": false,\n      \"id\": 190,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_num_alive_connections{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}} num_alive_connections\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"num_alive_connections\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"hiddenSeries\": false,\n      \"id\": 90,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_packets_received{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} packets_received\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"packets_received\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 32\n      },\n      \"hiddenSeries\": false,\n      \"id\": 56,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_packets_sent{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} packets_sent\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"packets_sent\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 40\n      },\n      \"hiddenSeries\": false,\n      \"id\": 92,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_outstanding_requests{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} outstanding_changes_queued\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"zk_outstanding_requests\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$DS_PROMETHEUS\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 40\n      },\n      \"hiddenSeries\": false,\n      \"id\": 100,\n      \"interval\": \"\",\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": false,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"zk_min_latency{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} max_latency\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"zk_max_latency{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} min_latency\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"zk_avg_latency{instance=~\\\"$instance\\\",job=~\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}} avg_latency\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"max_min_avg_latency\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"Prometheus\",\n          \"value\": \"Prometheus\"\n        },\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"Datasource\",\n        \"multi\": false,\n        \"name\": \"DS_PROMETHEUS\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"queryValue\": \"\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$DS_PROMETHEUS\",\n        \"definition\": \"label_values(zk_up,job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(zk_up,job)\",\n          \"refId\": \"Prometheus-job-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(zk_up{job=~\\\"$job\\\"}, env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(zk_up{job=~\\\"$job\\\"}, env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"$DS_PROMETHEUS\",\n        \"definition\": \"label_values(zk_up{job=~\\\"$job\\\",env=\\\"$env\\\"}, instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Instance\",\n        \"multi\": true,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(zk_up{job=~\\\"$job\\\",env=\\\"$env\\\"}, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/18-exporter-status.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 18,\n    \"uid\": \"dj43F8hT9\",\n    \"title\": \"Exporter Status\",\n    \"panels\": [\n    {\n      \"cacheTimeout\": null,\n      \"columns\": [],\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fontSize\": \"100%\",\n      \"gridPos\": {\n        \"h\": 18,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"links\": [],\n      \"pageSize\": null,\n      \"pluginVersion\": \"6.5.2\",\n      \"showHeader\": true,\n      \"sort\": {\n        \"col\": 1,\n        \"desc\": false\n      },\n      \"styles\": [\n        {\n          \"alias\": \"Time\",\n          \"align\": \"auto\",\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"pattern\": \"Time\",\n          \"type\": \"hidden\"\n        },\n        {\n          \"alias\": \"Exporter\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 2,\n          \"mappingType\": 1,\n          \"pattern\": \"Metric\",\n          \"thresholds\": [],\n          \"type\": \"number\",\n          \"unit\": \"short\"\n        },\n        {\n          \"alias\": \"Status\",\n          \"align\": \"auto\",\n          \"colorMode\": \"cell\",\n          \"colors\": [\n            \"#C4162A\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n          \"decimals\": 2,\n          \"mappingType\": 1,\n          \"pattern\": \"Value\",\n          \"thresholds\": [\n            \"0\",\n            \"1\"\n          ],\n          \"type\": \"string\",\n          \"unit\": \"short\",\n          \"valueMaps\": [\n            {\n              \"text\": \"正常\",\n              \"value\": \"1\"\n            },\n            {\n              \"text\": \"异常\",\n              \"value\": \"0\"\n            }\n          ]\n        },\n        {\n          \"alias\": \"\",\n          \"align\": \"auto\",\n          \"colorMode\": null,\n          \"colors\": [\n            \"rgba(245, 54, 54, 0.9)\",\n            \"rgba(237, 129, 40, 0.89)\",\n            \"rgba(50, 172, 45, 0.97)\"\n          ],\n          \"decimals\": 2,\n          \"pattern\": \"/.*/\",\n          \"thresholds\": [],\n          \"type\": \"number\",\n          \"unit\": \"short\"\n        }\n      ],\n      \"targets\": [\n        {\n          \"expr\": \"exporter_status{env=~\\\"$env\\\",instance=~\\\"$ip\\\",app=~\\\"$app\\\"}\",\n          \"instant\": true,\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}}/{{app}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Exporter Status\",\n      \"transform\": \"timeseries_to_rows\",\n      \"type\": \"table-old\"\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(exporter_status,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(exporter_status,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(exporter_status{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"ip\",\n        \"multi\": true,\n        \"name\": \"ip\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(exporter_status{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(exporter_status{env=\\\"$env\\\",instance=~\\\"$ip\\\"},app)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": true,\n        \"name\": \"app\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(exporter_status{env=\\\"$env\\\",instance=~\\\"$ip\\\"},app)\",\n          \"refId\": \"Prometheus-app-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/19-jvm-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 20,\n    \"panels\": [\n      {\n        \"cacheTimeout\": null,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"decimals\": 0,\n            \"mappings\": [\n              {\n                \"from\": \"\",\n                \"id\": 1,\n                \"text\": \"正常\",\n                \"to\": \"\",\n                \"type\": 1,\n                \"value\": \"1\"\n              },\n              {\n                \"from\": \"\",\n                \"id\": 2,\n                \"text\": \"异常\",\n                \"to\": \"\",\n                \"type\": 1,\n                \"value\": \"0\"\n              }\n            ],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 6,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 2,\n        \"interval\": null,\n        \"links\": [],\n        \"options\": {\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"repeat\": null,\n        \"repeatDirection\": \"h\",\n        \"targets\": [\n          {\n            \"expr\": \"probe_success{env=~\\\"$env\\\",instance=~\\\"$ip\\\",app=~\\\"$app\\\",app!=\\\"node\\\"}\",\n            \"format\": \"table\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"服务状态\",\n        \"transformations\": [\n          {\n            \"id\": \"organize\",\n            \"options\": {\n              \"excludeByName\": {\n                \"Time\": true,\n                \"Value\": false,\n                \"__name__\": true,\n                \"app\": false,\n                \"env\": true,\n                \"instance\": true,\n                \"job\": true\n              },\n              \"indexByName\": {},\n              \"renameByName\": {\n                \"Value\": \"服务状态\",\n                \"app\": \"服务名称\"\n              }\n            }\n          },\n          {\n            \"id\": \"seriesToRows\",\n            \"options\": {}\n          }\n        ],\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"s\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 6,\n          \"y\": 0\n        },\n        \"id\": 50,\n        \"options\": {\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"process_uptime_seconds{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"服务运行时间\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            },\n            \"unit\": \"none\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 11,\n          \"y\": 0\n        },\n        \"id\": 60,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"system_cpu_usage{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"} * 100\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"cpu使用率\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 4,\n          \"x\": 16,\n          \"y\": 0\n        },\n        \"id\": 52,\n        \"options\": {\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"process_files_open_files{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"应用当前打开文件描述符数量\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 4,\n          \"x\": 20,\n          \"y\": 0\n        },\n        \"id\": 58,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"system_load_average_1m{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"系统一分钟负载占用情况\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"dateTimeAsLocal\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 6,\n          \"x\": 0,\n          \"y\": 7\n        },\n        \"id\": 48,\n        \"interval\": null,\n        \"links\": [],\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"process_start_time_seconds{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"} * 1000 \",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"服务启动时间\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 6,\n          \"y\": 7\n        },\n        \"id\": 72,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_active_current_sessions{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"Tomcat当前活跃session数量\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 11,\n          \"y\": 7\n        },\n        \"id\": 54,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"process_files_max_files{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"应用可打开的最大文件描述符数量\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 4,\n          \"x\": 16,\n          \"y\": 7\n        },\n        \"id\": 20,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"jvm_threads_daemon_threads{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"JVM线程的当前数量\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            },\n            \"unit\": \"none\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 8,\n          \"y\": 14\n        },\n        \"id\": 79,\n        \"options\": {\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(jvm_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\", area=\\\"heap\\\"})*100/sum(jvm_memory_max_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\", area=\\\"heap\\\"})\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Heap 使用率\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            },\n            \"unit\": \"none\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 5,\n          \"x\": 13,\n          \"y\": 14\n        },\n        \"id\": 80,\n        \"options\": {\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(jvm_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\", area=\\\"nonheap\\\"})*100/sum(jvm_memory_max_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\", area=\\\"nonheap\\\"})\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Non-Heap 使用率\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"custom\": {},\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            }\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 4,\n          \"x\": 20,\n          \"y\": 7\n        },\n        \"id\": 56,\n        \"options\": {\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"showThresholdLabels\": false,\n          \"showThresholdMarkers\": true,\n          \"text\": {}\n        },\n        \"pluginVersion\": \"7.4.3\",\n        \"targets\": [\n          {\n            \"expr\": \"system_cpu_count{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"java虚拟机可用的cpu数量\",\n        \"type\": \"gauge\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 14\n        },\n        \"hiddenSeries\": false,\n        \"id\": 4,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_pause_seconds_count{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_pause_seconds_count\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 21\n        },\n        \"hiddenSeries\": false,\n        \"id\": 8,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_pause_seconds_max{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_pause_seconds_max\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 30\n        },\n        \"hiddenSeries\": false,\n        \"id\": 6,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_pause_seconds_sum{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_pause_seconds_sum\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 39\n        },\n        \"hiddenSeries\": false,\n        \"id\": 12,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_classes_loaded_classes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_classes_loaded_classes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 39\n        },\n        \"hiddenSeries\": false,\n        \"id\": 10,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_live_data_size_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_live_data_size_bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 47\n        },\n        \"hiddenSeries\": false,\n        \"id\": 40,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"nonheap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_used_bytes(nonheap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 47\n        },\n        \"hiddenSeries\": false,\n        \"id\": 14,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"heap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_used_bytes(heap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 55\n        },\n        \"hiddenSeries\": false,\n        \"id\": 42,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_max_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"nonheap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_max_bytes(nonheap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 55\n        },\n        \"hiddenSeries\": false,\n        \"id\": 16,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_max_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"heap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_max_bytes(heap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 63\n        },\n        \"hiddenSeries\": false,\n        \"id\": 44,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_committed_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"nonheap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_committed_bytes(nonheap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 63\n        },\n        \"hiddenSeries\": false,\n        \"id\": 18,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_memory_committed_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",area=\\\"heap\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_memory_committed_bytes(heap)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 71\n        },\n        \"hiddenSeries\": false,\n        \"id\": 22,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_classes_unloaded_classes_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_classes_unloaded_classes_total\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 71\n        },\n        \"hiddenSeries\": false,\n        \"id\": 24,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_max_data_size_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_max_data_size_bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 79\n        },\n        \"hiddenSeries\": false,\n        \"id\": 28,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_threads_live_threads{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_threads_live_threads\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 79\n        },\n        \"hiddenSeries\": false,\n        \"id\": 30,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_buffer_memory_used_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_buffer_memory_used_bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 87\n        },\n        \"hiddenSeries\": false,\n        \"id\": 34,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_buffer_count_buffers{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_buffer_count_buffers\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 87\n        },\n        \"hiddenSeries\": false,\n        \"id\": 32,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_threads_peak_threads{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_threads_peak_threads\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 95\n        },\n        \"hiddenSeries\": false,\n        \"id\": 64,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_created_sessions_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Tomcat创建的session总数\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 95\n        },\n        \"hiddenSeries\": false,\n        \"id\": 36,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_memory_promoted_bytes_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_memory_promoted_bytes_total\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 103\n        },\n        \"hiddenSeries\": false,\n        \"id\": 68,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_expired_sessions_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Tomcat过期的session总数\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 103\n        },\n        \"hiddenSeries\": false,\n        \"id\": 38,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_buffer_total_capacity_bytes{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_buffer_total_capacity_bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 111\n        },\n        \"hiddenSeries\": false,\n        \"id\": 70,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_alive_max_seconds{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Tomcat存活session最大数\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 111\n        },\n        \"hiddenSeries\": false,\n        \"id\": 62,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_active_max_sessions{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Tomcat最多活跃session数持续时间\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 119\n        },\n        \"hiddenSeries\": false,\n        \"id\": 46,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"jvm_gc_memory_allocated_bytes_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"jvm_gc_memory_allocated_bytes_total\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 119\n        },\n        \"hiddenSeries\": false,\n        \"id\": 66,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"tomcat_sessions_rejected_sessions_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"超过session最大配置后，拒绝的session个数\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"custom\": {}\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 127\n        },\n        \"hiddenSeries\": false,\n        \"id\": 78,\n        \"legend\": {\n          \"alignAsTable\": false,\n          \"avg\": false,\n          \"current\": false,\n          \"hideEmpty\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": true,\n          \"show\": true,\n          \"sideWidth\": 750,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"log4j2_events_total{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"日志级别事件个数\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:242\",\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:243\",\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": true,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {},\n            \"custom\": {},\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": []\n            }\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 135\n        },\n        \"hiddenSeries\": false,\n        \"id\": 74,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"http请求接口次数统计\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 150\n        },\n        \"hiddenSeries\": false,\n        \"id\": 82,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"http_server_requests_seconds_count{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\",status=~\\\"5..\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"http请求接口--5xx errors 次数统计\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {},\n            \"custom\": {},\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": []\n            }\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 143\n        },\n        \"hiddenSeries\": false,\n        \"id\": 76,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"7.4.3\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"http_server_requests_seconds_sum{env=~\\\"$env\\\",instance=~\\\"$ip\\\",job=~\\\"$job\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"queryType\": \"randomWalk\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"http请求接口耗时统计\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      }\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allFormat\": \"glob\",\n          \"allValue\": null,\n          \"current\": {\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success,env)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"环境\",\n          \"multi\": false,\n          \"name\": \"env\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success,env)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allFormat\": \"glob\",\n          \"allValue\": null,\n          \"current\": {\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success{env=\\\"$env\\\"},instance)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"ip\",\n          \"multi\": false,\n          \"name\": \"ip\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success{env=\\\"$env\\\"},instance)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allFormat\": \"glob\",\n          \"allValue\": null,\n          \"current\": {\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success{env=\\\"$env\\\",instance=\\\"$ip\\\"},app)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"app\",\n          \"multi\": false,\n          \"name\": \"app\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success{env=\\\"$env\\\",instance=\\\"$ip\\\"},app)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allFormat\": \"glob\",\n          \"allValue\": null,\n          \"current\": {\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success{env=\\\"$env\\\",instance=~\\\"$ip\\\",app=\\\"$app\\\"},job)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"job\",\n          \"multi\": false,\n          \"name\": \"job\",\n          \"options\": [\n          ],\n          \"query\": {\n            \"query\": \"label_values(probe_success{env=\\\"$env\\\",instance=~\\\"$ip\\\",app=\\\"$app\\\"},job)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 0,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"title\": \"JavaSpringBoot 信息面板\",\n    \"uid\": \"VOtGCaInz\",\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/2-fu-wu-zhuang-tai-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 2,\n    \"uid\": \"9CSxoPAGz\",\n    \"title\": \"服务状态 信息面板\",\n    \"panels\": [\n      {\n        \"cacheTimeout\": null,\n        \"columns\": [],\n        \"datasource\": \"Prometheus\",\n        \"fontSize\": \"100%\",\n        \"gridPos\": {\n          \"h\": 18,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 2,\n        \"links\": [],\n        \"pageSize\": null,\n        \"pluginVersion\": \"6.5.2\",\n        \"showHeader\": true,\n        \"sort\": {\n          \"col\": 1,\n          \"desc\": false\n        },\n        \"styles\": [\n          {\n            \"alias\": \"Time\",\n            \"align\": \"auto\",\n            \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n            \"pattern\": \"Time\",\n            \"type\": \"hidden\"\n          },\n          {\n            \"alias\": \"Service\",\n            \"align\": \"auto\",\n            \"colorMode\": null,\n            \"colors\": [\n              \"rgba(245, 54, 54, 0.9)\",\n              \"rgba(237, 129, 40, 0.89)\",\n              \"rgba(50, 172, 45, 0.97)\"\n            ],\n            \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n            \"decimals\": 2,\n            \"mappingType\": 1,\n            \"pattern\": \"Metric\",\n            \"thresholds\": [],\n            \"type\": \"number\",\n            \"unit\": \"short\"\n          },\n          {\n            \"alias\": \"Status\",\n            \"align\": \"auto\",\n            \"colorMode\": \"cell\",\n            \"colors\": [\n              \"#C4162A\",\n              \"rgba(237, 129, 40, 0.89)\",\n              \"rgba(50, 172, 45, 0.97)\"\n            ],\n            \"dateFormat\": \"YYYY-MM-DD HH:mm:ss\",\n            \"decimals\": 2,\n            \"mappingType\": 1,\n            \"pattern\": \"Value\",\n            \"thresholds\": [\n              \"0\",\n              \"1\"\n            ],\n            \"type\": \"string\",\n            \"unit\": \"short\",\n            \"valueMaps\": [\n              {\n                \"text\": \"正常\",\n                \"value\": \"1\"\n              },\n              {\n                \"text\": \"异常\",\n                \"value\": \"0\"\n              }\n            ]\n          },\n          {\n            \"alias\": \"\",\n            \"align\": \"auto\",\n            \"colorMode\": null,\n            \"colors\": [\n              \"rgba(245, 54, 54, 0.9)\",\n              \"rgba(237, 129, 40, 0.89)\",\n              \"rgba(50, 172, 45, 0.97)\"\n            ],\n            \"decimals\": 2,\n            \"pattern\": \"/.*/\",\n            \"thresholds\": [],\n            \"type\": \"number\",\n            \"unit\": \"short\"\n          }\n        ],\n        \"targets\": [\n          {\n            \"expr\": \"probe_success{env=~\\\"$env\\\",instance=~\\\"$ip\\\",app=~\\\"$app\\\",app!~\\\"node\\\"}\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"legendFormat\": \"{{instance}}/{{app}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"Service Status\",\n        \"transform\": \"timeseries_to_rows\",\n        \"type\": \"table-old\"\n      }\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success,env)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"环境\",\n          \"multi\": false,\n          \"name\": \"env\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success,env)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success{env=\\\"$env\\\"},instance)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": true,\n          \"label\": \"ip\",\n          \"multi\": true,\n          \"name\": \"ip\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success{env=\\\"$env\\\"},instance)\",\n            \"refId\": \"StandardVariableQuery\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(probe_success{env=\\\"$env\\\",instance=~\\\"$ip\\\"},app)\",\n          \"description\": null,\n          \"error\": null,\n          \"hide\": 0,\n          \"includeAll\": true,\n          \"label\": null,\n          \"multi\": true,\n          \"name\": \"app\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(probe_success{env=\\\"$env\\\",instance=~\\\"$ip\\\"},app)\",\n            \"refId\": \"Prometheus-app-Variable-Query\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/20-clickhousecluster-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 20,\n    \"uid\": \"VqK1fIBMkk\",\n    \"title\": \"ClickhouseCluster 信息面板\",\n    \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(clickhouse_query{cluster=~\\\"$cluster\\\"}[1m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Query\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/merge.*/\",\n          \"bars\": false,\n          \"lines\": true\n        },\n        {\n          \"alias\": \"/rate.*/\",\n          \"bars\": true,\n          \"lines\": false,\n          \"yaxis\": 2,\n          \"zindex\": -1\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": true,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_merge{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"merge {{instance}}\",\n          \"refId\": \"A\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"rate(clickhouse_merged_rows_total{cluster=~\\\"$cluster\\\"}[1m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"rate {{instance}}\",\n          \"refId\": \"B\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Merge\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"id\": 6,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(clickhouse_readonly_replica{cluster=~\\\"$cluster\\\"})\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 60\n        }\n      ],\n      \"thresholds\": \"1,1\",\n      \"title\": \"ReadOnly replica\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_replicated_checks{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clickhouse_replicated_fetch{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clickhouse_replicated_send{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Replication\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_read{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_write{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Read/Write\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 9,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_background_pool_task{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Pool Tasks\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_tcp_connection{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"tcp {{instance}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_http_connection{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"http {{instance}}\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_interserver_connection{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"interserver {{instance}}\",\n          \"refId\": \"C\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_memory_tracking{cluster=~\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(clickhouse_query,cluster)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"集群\",\n        \"multi\": false,\n        \"name\": \"cluster\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(clickhouse_query,cluster)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/22-mysqlcluster-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 22,\n    \"uid\": \"MQWgroiiz1\",\n    \"title\": \"MysqlCluster 信息面板\",\n    \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 382,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 1,\n      \"description\": \"**MySQL Uptime**\\n\\nThe amount of time since the last restart of the MySQL server process.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"s\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 12,\n      \"interval\": \"\",\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"s\",\n      \"postfixFontSize\": \"80%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_uptime{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"300,3600\",\n      \"title\": \"MySQL Uptime\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Current QPS**\\n\\nBased on the queries reported by MySQL's ``SHOW STATUS`` command, it is the number of statements executed by the server within the last second. This variable includes statements executed within stored programs, unlike the Questions variable. It does not count \\n``COM_PING`` or ``COM_STATISTICS`` commands.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"short\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 13,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Server Status Variables\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Queries\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_queries{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_queries{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": \"35,75\",\n      \"title\": \"Current QPS\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**InnoDB Buffer Pool Size**\\n\\nInnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.  Knowing how the InnoDB buffer pool works, and taking advantage of it to keep frequently accessed data in memory, is one of the most important aspects of MySQL tuning. The goal is to keep the working set in memory. In most cases, this should be between 60%-90% of available memory on a dedicated database host, but depends on many factors.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 51,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"Tuning the InnoDB Buffer Pool Size\",\n          \"url\": \"https://www.percona.com/blog/2015/06/02/80-ram-tune-innodb_buffer_pool_size/\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_innodb_buffer_pool_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"90,95\",\n      \"title\": \"InnoDB Buffer Pool Size\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(245, 54, 54, 0.9)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(50, 172, 45, 0.97)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**InnoDB Buffer Pool Size % of Total RAM**\\n\\nInnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.  Knowing how the InnoDB buffer pool works, and taking advantage of it to keep frequently accessed data in memory, is one of the most important aspects of MySQL tuning. The goal is to keep the working set in memory. In most cases, this should be between 60%-90% of available memory on a dedicated database host, but depends on many factors.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"percent\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 1\n      },\n      \"height\": \"125px\",\n      \"id\": 52,\n      \"interval\": \"\",\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"Tuning the InnoDB Buffer Pool Size\",\n          \"url\": \"https://www.percona.com/blog/2015/06/02/80-ram-tune-innodb_buffer_pool_size/\"\n        }\n      ],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"80%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"repeat\": null,\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"10m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"(mysql_global_variables_innodb_buffer_pool_size{cluster=\\\"$cluster\\\"} * 100) / on (instance) node_memory_MemTotal{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"5m\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 300\n        }\n      ],\n      \"thresholds\": \"40,80\",\n      \"title\": \"Buffer Pool Size of Total RAM\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [],\n      \"valueName\": \"current\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 5\n      },\n      \"id\": 383,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Connections\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"**Max Connections** \\n\\nMax Connections is the maximum permitted number of simultaneous client connections. By default, this is 151. Increasing this value increases the number of file descriptors that mysqld requires. If the required number of descriptors are not available, the server reduces the value of Max Connections.\\n\\nmysqld actually permits Max Connections + 1 clients to connect. The extra connection is reserved for use by accounts that have the SUPER privilege, such as root.\\n\\nMax Used Connections is the maximum number of connections that have been in use simultaneously since the server started.\\n\\nConnections is the number of connection attempts (successful or not) to the MySQL server.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 92,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Server System Variables\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_connections\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Max Connections\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max(max_over_time(mysql_global_status_threads_connected{cluster=\\\"$cluster\\\"}[5m])  or mysql_global_status_threads_connected{cluster=\\\"$cluster\\\"} )\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Connections {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_max_used_connections{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Used Connections {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_max_connections{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Connections {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Active Threads**\\n\\nThreads Connected is the number of open connections, while Threads Running is the number of threads not sleeping.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 6\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Peak Threads Running\",\n          \"color\": \"#E24D42\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true\n        },\n        {\n          \"alias\": \"Peak Threads Connected\",\n          \"color\": \"#1F78C1\"\n        },\n        {\n          \"alias\": \"Avg Threads Running\",\n          \"color\": \"#EAB839\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max_over_time(mysql_global_status_threads_connected{cluster=\\\"$cluster\\\"}[5m]) or\\nmax_over_time(mysql_global_status_threads_connected{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Peak Threads Connected {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"max_over_time(mysql_global_status_threads_running{cluster=\\\"$cluster\\\"}[5m]) or\\nmax_over_time(mysql_global_status_threads_running{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Peak Threads Running {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"avg_over_time(mysql_global_status_threads_running{cluster=\\\"$cluster\\\"}[5m]) or \\navg_over_time(mysql_global_status_threads_running{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Avg Threads Running {{ instance_name }}\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Client Thread Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": [\n          \"total\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"Threads\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 13\n      },\n      \"id\": 384,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Table Locks\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"description\": \"**MySQL Questions**\\n\\nThe number of statements executed by the server. This includes only statements sent to the server by clients and not statements executed within stored programs, unlike the Queries used in the QPS calculation. \\n\\nThis variable does not count the following commands:\\n* ``COM_PING``\\n* ``COM_STATISTICS``\\n* ``COM_STMT_PREPARE``\\n* ``COM_STMT_CLOSE``\\n* ``COM_STMT_RESET``\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 53,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"targetBlank\": true,\n          \"title\": \"MySQL Queries and Questions\",\n          \"url\": \"https://www.percona.com/blog/2014/05/29/how-mysql-queries-and-questions-are-measured/\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_questions{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_questions{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Questions {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Questions\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Thread Cache**\\n\\nThe thread_cache_size variable sets how many threads the server should cache to reuse. When a client disconnects, the client's threads are put in the cache if the cache is not full. It is autosized in MySQL 5.6.8 and above (capped to 100). Requests for threads are satisfied by reusing threads taken from the cache if possible, and only when the cache is empty is a new thread created.\\n\\n* *Threads_created*: The number of threads created to handle connections.\\n* *Threads_cached*: The number of threads in the thread cache.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 11,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Tuning information\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_thread_cache_size\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Threads Created\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_thread_cache_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Thread Cache Size {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_threads_cached{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Threads Cached {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_threads_created{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_threads_created{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Threads Created {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Thread Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 385,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Temporary Objects\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_tables{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_created_tmp_tables{cluster=\\\"$cluster\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Tables {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_disk_tables{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_created_tmp_disk_tables{cluster=\\\"$cluster\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Disk Tables {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_created_tmp_files{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_created_tmp_files{cluster=\\\"$cluster\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Created Tmp Files {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Temporary Objects\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Select Types**\\n\\nAs with most relational databases, selecting based on indexes is more efficient than scanning an entire table's data. Here we see the counters for selects not done with indexes.\\n\\n* ***Select Scan*** is how many queries caused full table scans, in which all the data in the table had to be read and either discarded or returned.\\n* ***Select Range*** is how many queries used a range scan, which means MySQL scanned all rows in a given range.\\n* ***Select Full Join*** is the number of joins that are not joined on an index, this is usually a huge performance hit.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 22\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 311,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_full_join{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_select_full_join{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Full Join {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_full_range_join{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_select_full_range_join{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Full Range Join {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_range{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_select_range{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Range {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_range_check{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_select_range_check{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Range Check {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_select_scan{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_select_scan{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Select Scan {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Select Types\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 29\n      },\n      \"id\": 386,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Sorts\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Sorts**\\n\\nDue to a query's structure, order, or other requirements, MySQL sorts the rows before returning them. For example, if a table is ordered 1 to 10 but you want the results reversed, MySQL then has to sort the rows to return 10 to 1.\\n\\nThis graph also shows when sorts had to scan a whole table or a given range of a table in order to return the results and which could not have been sorted via an index.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"hiddenSeries\": false,\n      \"id\": 30,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_rows{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_sort_rows{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Rows {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_range{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_sort_range{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Range {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_merge_passes{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_sort_merge_passes{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Merge Passes {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_sort_scan{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_sort_scan{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sort Scan {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Sorts\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Slow Queries**\\n\\nSlow queries are defined as queries being slower than the long_query_time setting. For example, if you have long_query_time set to 3, all queries that take longer than 3 seconds to complete will show on this graph.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 30\n      },\n      \"hiddenSeries\": false,\n      \"id\": 48,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_slow_queries{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_slow_queries{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Slow Queries {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Slow Queries\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 387,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Aborted\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Aborted Connections**\\n\\nWhen a given host connects to MySQL and the connection is interrupted in the middle (for example due to bad credentials), MySQL keeps that info in a system table (since 5.6 this table is exposed in performance_schema).\\n\\nIf the amount of failed requests without a successful connection reaches the value of max_connect_errors, mysqld assumes that something is wrong and blocks the host from further connection.\\n\\nTo allow connections from that host again, you need to issue the ``FLUSH HOSTS`` statement.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 38\n      },\n      \"hiddenSeries\": false,\n      \"id\": 47,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_aborted_connects{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_aborted_connects{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Aborted Connects (attempts) {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_aborted_clients{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_aborted_clients{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Aborted Clients (timeout) {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Aborted Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Table Locks**\\n\\nMySQL takes a number of different locks for varying reasons. In this graph we see how many Table level locks MySQL has requested from the storage engine. In the case of InnoDB, many times the locks could actually be row locks as it only takes table level locks in a few specific cases.\\n\\nIt is most useful to compare Locks Immediate and Locks Waited. If Locks waited is rising, it means you have lock contention. Otherwise, Locks Immediate rising and falling is normal activity.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 38\n      },\n      \"hiddenSeries\": false,\n      \"id\": 32,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_table_locks_immediate{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_locks_immediate{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Locks Immediate {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_table_locks_waited{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_locks_waited{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Locks Waited {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Locks\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"id\": 388,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Network\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Network Traffic**\\n\\nHere we can see how much network traffic is generated by MySQL. Outbound is network traffic sent from MySQL and Inbound is network traffic MySQL has received.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"hiddenSeries\": false,\n      \"id\": 9,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_bytes_received{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_bytes_received{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inbound {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_bytes_sent{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_bytes_sent{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Outbound {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Network Traffic\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Network Usage Hourly**\\n\\nHere we can see how much network traffic is generated by MySQL per hour. You can use the bar graph to compare data sent by MySQL and data received by MySQL.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 46\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 381,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"increase(mysql_global_status_bytes_received{cluster=\\\"$cluster\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Received {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"increase(mysql_global_status_bytes_sent{cluster=\\\"$cluster\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sent {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Network Usage Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 53\n      },\n      \"id\": 389,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Memory\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 0,\n      \"description\": \"***System Memory***: Total Memory for the system.\\\\\\n***InnoDB Buffer Pool Data***: InnoDB maintains a storage area called the buffer pool for caching data and indexes in memory.\\\\\\n***TokuDB Cache Size***: Similar in function to the InnoDB Buffer Pool,  TokuDB will allocate 50% of the installed RAM for its own cache.\\\\\\n***Key Buffer Size***: Index blocks for MYISAM tables are buffered and are shared by all threads. key_buffer_size is the size of the buffer used for index blocks.\\\\\\n***Adaptive Hash Index Size***: When InnoDB notices that some index values are being accessed very frequently, it builds a hash index for them in memory on top of B-Tree indexes.\\\\\\n ***Query Cache Size***: The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. The query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time.\\\\\\n***InnoDB Dictionary Size***: The data dictionary is InnoDB ‘s internal catalog of tables. InnoDB stores the data dictionary on disk, and loads entries into memory while the server is running.\\\\\\n***InnoDB Log Buffer Size***: The MySQL InnoDB log buffer allows transactions to run without having to write the log to disk before the transactions commit.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 54\n      },\n      \"hiddenSeries\": false,\n      \"id\": 50,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Detailed descriptions about metrics\",\n          \"url\": \"https://www.percona.com/doc/percona-monitoring-and-management/dashboard.mysql-overview.html#mysql-internal-memory-overview\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"System Memory\",\n          \"fill\": 0,\n          \"stack\": false\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"node_memory_MemTotal{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"System Memory {{ instance_name }}\",\n          \"refId\": \"G\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_page_size{cluster=\\\"$cluster\\\"} * on (instance) mysql_global_status_buffer_pool_pages{cluster=\\\"$cluster\\\",state=\\\"data\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Buffer Pool Data {{ instance_name }}\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_innodb_log_buffer_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Log Buffer Size {{ instance_name }}\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_innodb_additional_mem_pool_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"InnoDB Additional Memory Pool Size {{ instance_name }}\",\n          \"refId\": \"H\",\n          \"step\": 40\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_mem_dictionary{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Dictionary Size {{ instance_name }}\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_key_buffer_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Key Buffer Size {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_query_cache_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Query Cache Size {{ instance_name }}\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_mem_adaptive_hash{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Adaptive Hash Index Size {{ instance_name }}\",\n          \"refId\": \"E\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_variables_tokudb_cache_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"TokuDB Cache Size {{ instance_name }}\",\n          \"refId\": \"I\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Internal Memory Overview\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 61\n      },\n      \"id\": 390,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Command, Handlers, Processes\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Top Command Counters**\\n\\nThe Com_{{xxx}} statement counter variables indicate the number of times each xxx statement has been executed. There is one status variable for each type of statement. For example, Com_delete and Com_update count [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements, respectively. Com_delete_multi and Com_update_multi are similar but apply to [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements that use multiple-table syntax.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 62\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"hideZero\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (Com_xxx)\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Com_xxx\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, rate(mysql_global_status_commands_total{cluster=\\\"$cluster\\\"}[5m])>0) or topk(5, irate(mysql_global_status_commands_total{cluster=\\\"$cluster\\\"}[5m])>0)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Com_{{ command }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Command Counters\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**Top Command Counters Hourly**\\n\\nThe Com_{{xxx}} statement counter variables indicate the number of times each xxx statement has been executed. There is one status variable for each type of statement. For example, Com_delete and Com_update count [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements, respectively. Com_delete_multi and Com_update_multi are similar but apply to [``DELETE``](https://dev.mysql.com/doc/refman/5.7/en/delete.html) and [``UPDATE``](https://dev.mysql.com/doc/refman/5.7/en/update.html) statements that use multiple-table syntax.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 69\n      },\n      \"hiddenSeries\": false,\n      \"id\": 39,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (Com_xxx)\",\n          \"url\": \"https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Com_xxx\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, increase(mysql_global_status_commands_total{cluster=\\\"$cluster\\\"}[1h])>0)\",\n          \"format\": \"time_series\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Com_{{ command }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Command Counters Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Handlers**\\n\\nHandler statistics are internal statistics on how MySQL is selecting, updating, inserting, and modifying rows, tables, and indexes.\\n\\nThis is in fact the layer between the Storage Engine and MySQL.\\n\\n* `read_rnd_next` is incremented when the server performs a full table scan and this is a counter you don't really want to see with a high value.\\n* `read_key` is incremented when a read is done with an index.\\n* `read_next` is incremented when the storage engine is asked to 'read the next index entry'. A high value means a lot of index scans are being done.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 76\n      },\n      \"hiddenSeries\": false,\n      \"id\": 8,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_handlers_total{cluster=\\\"$cluster\\\", handler!~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m]) or irate(mysql_global_status_handlers_total{cluster=\\\"$cluster\\\", handler!~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ handler }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"J\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Handlers\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 83\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_handlers_total{cluster=\\\"$cluster\\\", handler=~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m]) or irate(mysql_global_status_handlers_total{cluster=\\\"$cluster\\\", handler=~\\\"commit|rollback|savepoint.*|prepare\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ handler }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Transaction Handlers\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 0,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 90\n      },\n      \"hiddenSeries\": false,\n      \"id\": 40,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_info_schema_threads{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ state }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Process States\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 97\n      },\n      \"hiddenSeries\": false,\n      \"id\": 49,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"hideZero\": true,\n        \"max\": true,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"topk(5, avg_over_time(mysql_info_schema_threads{cluster=\\\"$cluster\\\"}[1h]))\",\n          \"interval\": \"1h\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ state }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 3600\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": \"24h\",\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Top Process States Hourly\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 104\n      },\n      \"id\": 391,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Query Cache\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Query Cache Memory**\\n\\nThe query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time. This serialization is true not only for SELECTs, but also for INSERT/UPDATE/DELETE.\\n\\nThis also means that the larger the `query_cache_size` is set to, the slower those operations become. In concurrent environments, the MySQL Query Cache quickly becomes a contention point, decreasing performance. MariaDB and AWS Aurora have done work to try and eliminate the query cache contention in their flavors of MySQL, while MySQL 8.0 has eliminated the query cache feature.\\n\\nThe recommended settings for most environments is to set:\\n  ``query_cache_type=0``\\n  ``query_cache_size=0``\\n\\nNote that while you can dynamically change these values, to completely remove the contention point you have to restart the database.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 105\n      },\n      \"hiddenSeries\": false,\n      \"id\": 46,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_qcache_free_memory{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Free Memory {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_query_cache_size{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Query Cache Size {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Query Cache Memory\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Query Cache Activity**\\n\\nThe query cache has huge scalability problems in that only one thread can do an operation in the query cache at the same time. This serialization is true not only for SELECTs, but also for INSERT/UPDATE/DELETE.\\n\\nThis also means that the larger the `query_cache_size` is set to, the slower those operations become. In concurrent environments, the MySQL Query Cache quickly becomes a contention point, decreasing performance. MariaDB and AWS Aurora have done work to try and eliminate the query cache contention in their flavors of MySQL, while MySQL 8.0 has eliminated the query cache feature.\\n\\nThe recommended settings for most environments is to set:\\n``query_cache_type=0``\\n``query_cache_size=0``\\n\\nNote that while you can dynamically change these values, to completely remove the contention point you have to restart the database.\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 105\n      },\n      \"height\": \"\",\n      \"hiddenSeries\": false,\n      \"id\": 45,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_hits{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_qcache_hits{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Hits {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_inserts{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_qcache_inserts{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inserts {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_not_cached{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_qcache_not_cached{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Not Cached {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_qcache_lowmem_prunes{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_qcache_lowmem_prunes{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Prunes {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"F\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_qcache_queries_in_cache{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Queries in Cache {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Query Cache Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 112\n      },\n      \"id\": 392,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Files and Tables\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 113\n      },\n      \"hiddenSeries\": false,\n      \"id\": 43,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_opened_files{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_opened_files{cluster=\\\"$cluster\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Openings {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL File Openings\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 113\n      },\n      \"hiddenSeries\": false,\n      \"id\": 41,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_files{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Files {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_open_files_limit{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Files Limit {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"mysql_global_status_innodb_num_open_files{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"InnoDB Open Files {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Open Files\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 120\n      },\n      \"id\": 393,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"Table Openings\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Table Open Cache Status**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 121\n      },\n      \"hiddenSeries\": false,\n      \"id\": 44,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Table Open Cache Hit Ratio\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(mysql_global_status_opened_tables{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_opened_tables{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Openings {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Hits {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_misses{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_misses{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Misses {{ instance_name }}\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_table_open_cache_overflows{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_overflows{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Misses due to Overflows {{ instance_name }}\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"(rate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m]))/((rate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_hits{cluster=\\\"$cluster\\\"}[5m]))+(rate(mysql_global_status_table_open_cache_misses{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_table_open_cache_misses{cluster=\\\"$cluster\\\"}[5m])))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Open Cache Hit Ratio {{ instance_name }}\",\n          \"refId\": \"E\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Open Cache Status\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"percentunit\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Open Tables**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 121\n      },\n      \"hiddenSeries\": false,\n      \"id\": 42,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_tables{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Tables {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_table_open_cache{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Open Cache {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Open Tables\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 128\n      },\n      \"id\": 394,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"MySQL Table Definition Cache\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"description\": \"**MySQL Table Definition Cache**\\n\\nThe recommendation is to set the `table_open_cache_instances` to a loose correlation to virtual CPUs, keeping in mind that more instances means the cache is split more times. If you have a cache set to 500 but it has 10 instances, each cache will only have 50 cached.\\n\\nThe `table_definition_cache` and `table_open_cache` can be left as default as they are auto-sized MySQL 5.6 and above (ie: do not set them to any value).\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 129\n      },\n      \"hiddenSeries\": false,\n      \"id\": 54,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [\n        {\n          \"title\": \"Server Status Variables (table_open_cache)\",\n          \"url\": \"http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_table_open_cache\"\n        }\n      ],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Opened Table Definitions\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_status_open_table_definitions{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open Table Definitions {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"mysql_global_variables_table_definition_cache{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Table Definitions Cache Size {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(mysql_global_status_opened_table_definitions{cluster=\\\"$cluster\\\"}[5m]) or irate(mysql_global_status_opened_table_definitions{cluster=\\\"$cluster\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Opened Table Definitions {{ instance_name }}\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MySQL Table Definition Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 136\n      },\n      \"id\": 395,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"System Charts\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 137\n      },\n      \"hiddenSeries\": false,\n      \"id\": 31,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pgpgin{cluster=\\\"$cluster\\\"}[5m]) * 1024 or irate(node_vmstat_pgpgin{cluster=\\\"$cluster\\\"}[5m]) * 1024\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Page In {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pgpgout{cluster=\\\"$cluster\\\"}[5m]) * 1024 or irate(node_vmstat_pgpgout{cluster=\\\"$cluster\\\"}[5m]) * 1024\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Page Out {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"I/O Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 137\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 37,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_MemTotal{cluster=\\\"$cluster\\\"} - (node_memory_MemFree{cluster=\\\"$cluster\\\"} + node_memory_Buffers{cluster=\\\"$cluster\\\"} + node_memory_Cached{cluster=\\\"$cluster\\\"})\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Used {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_MemFree{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Free {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_Buffers{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Buffers {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"D\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"node_memory_Cached{cluster=\\\"$cluster\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Cached {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"E\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory Distribution\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Load 1m\": \"#58140C\",\n        \"Max Core Utilization\": \"#bf1b00\",\n        \"iowait\": \"#e24d42\",\n        \"nice\": \"#1f78c1\",\n        \"softirq\": \"#806eb7\",\n        \"system\": \"#eab839\",\n        \"user\": \"#508642\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 6,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 137\n      },\n      \"height\": \"\",\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Max Core Utilization\",\n          \"lines\": false,\n          \"pointradius\": 1,\n          \"points\": true,\n          \"stack\": false\n        },\n        {\n          \"alias\": \"Load 1m\",\n          \"color\": \"#58140C\",\n          \"fill\": 2,\n          \"stack\": false,\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"clamp_max(((avg by (mode) ( (clamp_max(rate(node_cpu{cluster=\\\"$cluster\\\",mode!=\\\"idle\\\"}[5m]),1)) or (clamp_max(irate(node_cpu{cluster=\\\"$cluster\\\",mode!=\\\"idle\\\"}[5m]),1)) ))*100 or (avg_over_time(node_cpu_average{env=\\\"$env\\\",instance=~\\\"$host\\\", mode!=\\\"total\\\", mode!=\\\"idle\\\"}[5m]) or avg_over_time(node_cpu_average{env=\\\"$env\\\",instance=~\\\"$host\\\", mode!=\\\"total\\\", mode!=\\\"idle\\\"}[5m]))),100)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ mode }} {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clamp_max(max by () (sum  by (cpu) ( (clamp_max(rate(node_cpu{cluster=\\\"$cluster\\\",mode!=\\\"idle\\\",mode!=\\\"iowait\\\"}[5m]),1)) or (clamp_max(irate(node_cpu{cluster=\\\"$cluster\\\",mode!=\\\"idle\\\",mode!=\\\"iowait\\\"}[5m]),1)) ))*100,100)\",\n          \"format\": \"time_series\",\n          \"hide\": true,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max Core Utilization {{ instance_name }}\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"node_load1{cluster=\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Load 1m {{ instance_name }}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CPU Usage / Load\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 1,\n          \"format\": \"percent\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": 100,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"none\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 144\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 36,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": false,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 1,\n      \"points\": true,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum((rate(node_disk_read_time_ms{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m]) / rate(node_disk_reads_completed{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m])) or (irate(node_disk_read_time_ms{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m]) / irate(node_disk_reads_completed{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m]))\\nor avg_over_time(aws_rds_read_latency_average{cluster=\\\"$cluster\\\"}[5m]) or avg_over_time(aws_rds_read_latency_average{cluster=\\\"$cluster\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Read {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2m\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum((rate(node_disk_write_time_ms{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m]) / rate(node_disk_writes_completed{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m])) or (irate(node_disk_write_time_ms{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m]) / irate(node_disk_writes_completed{device!~\\\"dm-.+\\\", cluster=\\\"$cluster\\\"}[5m])) or \\navg_over_time(aws_rds_write_latency_average{cluster=\\\"$cluster\\\"}[5m]) or avg_over_time(aws_rds_write_latency_average{cluster=\\\"$cluster\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Write {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Disk Latency\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 2,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 144\n      },\n      \"height\": \"250px\",\n      \"hiddenSeries\": false,\n      \"id\": 21,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Outbound\",\n          \"transform\": \"negative-Y\"\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum(rate(node_network_receive_bytes{cluster=\\\"$cluster\\\", device!=\\\"lo\\\"}[5m])) or sum(irate(node_network_receive_bytes{cluster=\\\"$cluster\\\", device!=\\\"lo\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_rx{cluster=\\\"$cluster\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_rx{cluster=\\\"$cluster\\\"}[5m])) \",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Inbound {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"sum(rate(node_network_transmit_bytes{cluster=\\\"$cluster\\\", device!=\\\"lo\\\"}[5m])) or sum(irate(node_network_transmit_bytes{cluster=\\\"$cluster\\\", device!=\\\"lo\\\"}[5m])) or\\nsum(max_over_time(rdsosmetrics_network_tx{cluster=\\\"$cluster\\\"}[5m])) or sum(max_over_time(rdsosmetrics_network_tx{cluster=\\\"$cluster\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Outbound {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Network Traffic\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 2,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 144\n      },\n      \"hiddenSeries\": false,\n      \"id\": 38,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"sort\": \"avg\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pswpin{cluster=\\\"$cluster\\\"}[5m]) * 4096 or irate(node_vmstat_pswpin{cluster=\\\"$cluster\\\"}[5m]) * 4096\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Swap In (Reads) {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20,\n          \"target\": \"\"\n        },\n        {\n          \"calculatedInterval\": \"2s\",\n          \"datasourceErrors\": {},\n          \"errors\": {},\n          \"expr\": \"rate(node_vmstat_pswpout{cluster=\\\"$cluster\\\"}[5m]) * 4096 or irate(node_vmstat_pswpout{cluster=\\\"$cluster\\\"}[5m]) * 4096\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Swap Out (Writes) {{ instance_name }}\",\n          \"metric\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20,\n          \"target\": \"\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Swap Activity\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"auto\": true,\n        \"auto_count\": 200,\n        \"auto_min\": \"1s\",\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"auto\",\n          \"value\": \"$__auto_interval_interval\"\n        },\n        \"datasource\": \"Prometheus\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"Interval\",\n        \"multi\": false,\n        \"multiFormat\": \"glob\",\n        \"name\": \"interval\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"auto\",\n            \"value\": \"$__auto_interval_interval\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1s\",\n            \"value\": \"1s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"5s\",\n            \"value\": \"5s\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1m\",\n            \"value\": \"1m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"5m\",\n            \"value\": \"5m\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1h\",\n            \"value\": \"1h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"6h\",\n            \"value\": \"6h\"\n          },\n          {\n            \"selected\": false,\n            \"text\": \"1d\",\n            \"value\": \"1d\"\n          }\n        ],\n        \"query\": \"1s,5s,1m,5m,1h,6h,1d\",\n        \"queryValue\": \"\",\n        \"refresh\": 2,\n        \"skipUrlSync\": false,\n        \"type\": \"interval\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(mysql_up, cluster)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"集群\",\n        \"multi\": false,\n        \"name\": \"cluster\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(mysql_up, cluster)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/23-tenginecluster-nginx-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 23,\n    \"uid\": \"9MOLXSbMz1\",\n    \"title\": \"TengineCluster (Nginx) 信息面板\",\n    \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"nginx_server_connections{cluster=~\\\"$cluster\\\",status=~\\\"active|writing|reading|waiting\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{status}} {{instance_name}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_cache{cluster=~\\\"$cluster\\\" }[5m])) by (status)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ status }} {{instance_name}}\",\n          \"metric\": \"nginx_server_cache\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Cache\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_requests{cluster=~\\\"$cluster\\\", code!=\\\"total\\\"}[5m])) by (code)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ code }} {{ instance_name }}\",\n          \"metric\": \"nginx_server_requests\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Requests\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_server_bytes{cluster=~\\\"$cluster\\\"}[5m])) by (direction)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ direction }} {{instance_name}}\",\n          \"metric\": \"nginx_server_bytes\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Bytes\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"This one is providing aggregated error codes, but it's still possible to graph these per upstream.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_upstream_requests{cluster=~\\\"$cluster\\\", upstream=~\\\"^$upstream$\\\",code!=\\\"total\\\"}[5m])) by (code)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ code }} {{ instance_name }}\",\n          \"metric\": \"nginx_upstream_requests\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Requests\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(nginx_upstream_bytes{cluster=~\\\"$cluster\\\", upstream=~\\\"^$upstream$\\\"}[5m])) by (direction)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ direction }} {{instance_name}}\",\n          \"metric\": \"nginx_upstream_bytes\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Bytes\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 7,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(nginx_upstream_responseMsec{cluster=~\\\"$cluster\\\", upstream=~\\\"^$upstream$\\\"}) by (backend)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ backend }} {{instance_name}}\",\n          \"metric\": \"nginx_upstream_response\",\n          \"refId\": \"A\",\n          \"step\": 120\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Upstream Backend Response\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(nginx_server_bytes,cluster)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"集群\",\n        \"multi\": false,\n        \"name\": \"cluster\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(nginx_server_bytes,cluster)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(nginx_upstream_bytes{cluster=\\\"$cluster\\\"}, upstream)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"upstream\",\n        \"multi\": false,\n        \"name\": \"upstream\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(nginx_upstream_bytes{cluster=\\\"$cluster\\\"}, upstream)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/24-victoriametrics-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 24,\n    \"uid\": \"wNf0q_kZk\",\n    \"title\": \"victoriametrics 信息面板\",\n    \"panels\": [\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Prometheus\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 6,\n        \"panels\": [],\n        \"title\": \"Stats\",\n        \"type\": \"row\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 4,\n          \"x\": 0,\n          \"y\": 1\n        },\n        \"id\": 85,\n        \"options\": {\n          \"content\": \"<div style=\\\"text-align: center;\\\">$version</div>\",\n          \"mode\": \"markdown\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"title\": \"Version\",\n        \"type\": \"text\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"How many datapoints are in storage\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"short\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 4,\n          \"y\": 1\n        },\n        \"id\": 26,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type!=\\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Total datapoints\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Total amount of used disk space\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"bytes\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 9,\n          \"y\": 1\n        },\n        \"id\": 81,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": false,\n            \"expr\": \"sum(vm_data_size_bytes{job=~\\\"$job\\\", type!=\\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Disk space usage\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Average disk usage per datapoint.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"bytes\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 14,\n          \"y\": 1\n        },\n        \"id\": 82,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": false,\n            \"expr\": \"sum(vm_data_size_bytes{job=~\\\"$job\\\", type!=\\\"indexdb\\\"}) / sum(vm_rows{job=~\\\"$job\\\", type!=\\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Bytes per point\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Total size of allowed memory via flag `-memory.allowedPercent`\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"bytes\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 19,\n          \"y\": 1\n        },\n        \"id\": 79,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_allowed_memory_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Allowed memory\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"red\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"green\",\n                  \"value\": 1800\n                }\n              ]\n            },\n            \"unit\": \"s\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 4,\n          \"x\": 0,\n          \"y\": 3\n        },\n        \"id\": 87,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"auto\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"vm_app_uptime_seconds{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Uptime\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"How many entries inverted index contains. This value is proportional to the number of unique timeseries in storage(cardinality).\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"short\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 4,\n          \"y\": 3\n        },\n        \"id\": 38,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Index size\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Total size of available memory for VM process\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"bytes\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 9,\n          \"y\": 3\n        },\n        \"id\": 78,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_available_memory_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Available memory\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The minimum free disk space left\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"percentage\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                }\n              ]\n            },\n            \"unit\": \"bytes\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 14,\n          \"y\": 3\n        },\n        \"id\": 80,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"min(vm_free_disk_space_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Min free disk space\",\n        \"type\": \"stat\"\n      },\n      {\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Total number of available CPUs for VM process\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"color\": {\n              \"mode\": \"thresholds\"\n            },\n            \"mappings\": [],\n            \"thresholds\": {\n              \"mode\": \"absolute\",\n              \"steps\": [\n                {\n                  \"color\": \"green\",\n                  \"value\": null\n                },\n                {\n                  \"color\": \"red\",\n                  \"value\": 80\n                }\n              ]\n            },\n            \"unit\": \"short\"\n          },\n          \"overrides\": []\n        },\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 5,\n          \"x\": 19,\n          \"y\": 3\n        },\n        \"id\": 77,\n        \"links\": [],\n        \"maxDataPoints\": 100,\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\",\n          \"reduceOptions\": {\n            \"calcs\": [\n              \"lastNotNull\"\n            ],\n            \"fields\": \"\",\n            \"values\": false\n          },\n          \"text\": {},\n          \"textMode\": \"auto\"\n        },\n        \"pluginVersion\": \"8.3.2\",\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_available_cpu_cores{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"instant\": true,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"title\": \"Available CPU\",\n        \"type\": \"stat\"\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Prometheus\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 5\n        },\n        \"id\": 24,\n        \"panels\": [],\n        \"title\": \"Performance\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"* `*` - unsupported query path\\n* `/write` - insert into VM\\n* `/metrics` - query VM system metrics\\n* `/query` - query instant values\\n* `/query_range` - query over a range of time\\n* `/series` - match a certain label set\\n* `/label/{}/values` - query a list of label values (variables mostly)\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 12,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null as zero\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_http_requests_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\", path!~\\\"/favicon.ico\\\"}[1m])) by (path) > 0\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{path}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Requests rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The less time it takes is better.\\n* `*` - unsupported query path\\n* `/write` - insert into VM\\n* `/metrics` - query VM system metrics\\n* `/query` - query instant values\\n* `/query_range` - query over a range of time\\n* `/series` - match a certain label set\\n* `/label/{}/values` - query a list of label values (variables mostly)\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 22,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"max\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null as zero\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"go_gc_duration_seconds_sum{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"gc_duration_seconds_sum ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"s\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the number of active time series with new data points inserted during the last hour. High value may result in ingestion slowdown. \\n\\nSee following link for details:\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 14\n        },\n        \"hiddenSeries\": false,\n        \"id\": 51,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [\n          {\n            \"targetBlank\": true,\n            \"title\": \"troubleshooting\",\n            \"url\": \"https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#troubleshooting\"\n          }\n        ],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"vm_cache_entries{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"storage/hour_metric_ids\\\"}\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"Active time series\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Active time series ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"VictoriaMetrics stores various caches in RAM. Memory size for these caches may be limited with -`memory.allowedPercent` flag. Line `max allowed` shows max allowed memory size for cache.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 14\n        },\n        \"hiddenSeries\": false,\n        \"id\": 33,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"max allowed\",\n            \"color\": \"#C4162A\"\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_cache_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"size\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"max(vm_allowed_memory_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"max allowed\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Cache size ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows how many ongoing insertions (not API /write calls) on disk are taking place, where:\\n* `max` - equal to number of CPUs;\\n* `current` - current number of goroutines busy with inserting rows into underlying storage.\\n\\nEvery successful API /write call results into flush on disk. However, these two actions are separated and controlled via different concurrency limiters. The `max` on this panel can't be changed and always equal to number of CPUs. \\n\\nWhen `current` hits `max` constantly, it means storage is overloaded and requires more CPU.\\n\\n\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 22\n        },\n        \"hiddenSeries\": false,\n        \"id\": 59,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"hideEmpty\": false,\n          \"hideZero\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"max\",\n            \"color\": \"#C4162A\"\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_concurrent_addrows_capacity{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"max\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(vm_concurrent_addrows_current{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"current\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Concurrent flushes on disk ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"* `*` - unsupported query path\\n* `/write` - insert into VM\\n* `/metrics` - query VM system metrics\\n* `/query` - query instant values\\n* `/query_range` - query over a range of time\\n* `/series` - match a certain label set\\n* `/label/{}/values` - query a list of label values (variables mostly)\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 22\n        },\n        \"hiddenSeries\": false,\n        \"id\": 35,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null as zero\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_http_request_errors_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[1m])) by (path) > 0\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{path}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Requests error rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Prometheus\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 30\n        },\n        \"id\": 14,\n        \"panels\": [],\n        \"title\": \"Storage\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows how many datapoints are in the storage and what is average disk usage per datapoint.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 31\n        },\n        \"hiddenSeries\": false,\n        \"id\": 30,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"bytes-per-datapoint\",\n            \"yaxis\": 2\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type != \\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"total datapoints\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(vm_data_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type!=\\\"indexdb\\\"}) / sum(vm_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type != \\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"bytes-per-datapoint\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Datapoints ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"decimals\": 2,\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the time needed to reach the 100% of disk capacity based on the following params:\\n* free disk space;\\n* row ingestion rate;\\n* dedup rate;\\n* compression.\\n\\nUse this panel for capacity planning in order to estimate the time remaining for running out of the disk space.\\n\\n\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 31\n        },\n        \"hiddenSeries\": false,\n        \"id\": 73,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"hideZero\": true,\n          \"max\": false,\n          \"min\": true,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null as zero\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"vm_free_disk_space_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"} / ignoring(path) ((rate(vm_rows_added_to_storage_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[1d]) - ignoring(type) rate(vm_deduplicated_samples_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"merge\\\"}[1d])) * scalar(sum(vm_data_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type!=\\\"indexdb\\\"}) / sum(vm_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type!=\\\"indexdb\\\"})))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Storage full ETA ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"s\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows amount of on-disk space occupied by data points and the remaining disk space at `-storageDataPath`\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 39\n        },\n        \"hiddenSeries\": false,\n        \"id\": 53,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"rightSide\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_data_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type!=\\\"indexdb\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"Used\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"vm_free_disk_space_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"Free\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Disk space usage - datapoints ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"How many datapoints are in RAM queue waiting to be written into storage. The number of pending data points should be in the range from 0 to `2*<ingestion_rate>`, since VictoriaMetrics pushes pending data to persistent storage every second.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 39\n        },\n        \"hiddenSeries\": false,\n        \"id\": 34,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"pending index entries\",\n            \"yaxis\": 2\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"vm_pending_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"storage\\\"}\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"pending datapoints\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"vm_pending_rows{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"indexdb\\\"}\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"pending index entries\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Pending datapoints ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"decimals\": 3,\n            \"format\": \"none\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows amount of on-disk space occupied by inverted index.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 47\n        },\n        \"hiddenSeries\": false,\n        \"id\": 55,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"vm_data_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\", type=\\\"indexdb\\\"}\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"disk space used\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Disk space usage - index ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Data parts of LSM tree.\\nHigh number of parts could be an evidence of slow merge performance - check the resource utilization.\\n* `indexdb` - inverted index\\n* `storage/small` - recently added parts of data ingested into storage(hot data)\\n* `storage/big` -  small parts gradually merged into big parts (cold data)\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 47\n        },\n        \"hiddenSeries\": false,\n        \"id\": 36,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_parts{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) by (type)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{type}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"LSM parts ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows how many rows were ignored on insertion due to corrupted or out of retention timestamps.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 55\n        },\n        \"hiddenSeries\": false,\n        \"id\": 58,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_rows_ignored_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) by (reason)\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{reason}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Rows ignored ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The number of on-going merges in storage nodes.  It is expected to have high numbers for `storage/small` metric.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 55\n        },\n        \"hiddenSeries\": false,\n        \"id\": 62,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(vm_active_merges{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) by(type)\",\n            \"legendFormat\": \"{{type}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Active merges ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the rate of logging the messages by their level. Unexpected spike in rate is a good reason to check logs.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 63\n        },\n        \"hiddenSeries\": false,\n        \"id\": 67,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(vm_log_messages_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m])) by (level) \",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{level}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Logging rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The number of rows merged per second by storage nodes.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 63\n        },\n        \"hiddenSeries\": false,\n        \"id\": 64,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(vm_rows_merged_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m])) by(type)\",\n            \"legendFormat\": \"{{type}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Merge speed ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:867\",\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:868\",\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Prometheus\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 71\n        },\n        \"id\": 71,\n        \"panels\": [],\n        \"title\": \"Troubleshooting\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The percentage of slow inserts comparing to total insertion rate during the last 5 minutes. \\n\\nThe less value is better. If percentage remains high (>50%) during extended periods of time, then it is likely more RAM is needed for optimal handling of the current number of active time series. \\n\\nIn general, VictoriaMetrics requires ~1KB or RAM per active time series, so it should be easy calculating the required amounts of RAM for the current workload according to capacity planning docs. But the resulting number may be far from the real number because the required amounts of memory depends on may other factors such as the number of labels per time series and the length of label values.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 72\n        },\n        \"hiddenSeries\": false,\n        \"id\": 10,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_slow_row_inserts_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m])) / sum(rate(vm_rows_added_to_storage_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"慢写入所占比例\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"慢写入 ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 2,\n            \"format\": \"percentunit\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Slow queries rate according to `search.logSlowQueryDuration` flag, which is `5s` by default.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 72\n        },\n        \"hiddenSeries\": false,\n        \"id\": 60,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_slow_queries_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"slow queries rate\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Slow queries rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the percentage of used cache size from the allowed size by type. \\nValues close to 100% show the maximum potential utilization.\\nValues close to 0% show that cache is underutilized.\",\n        \"fill\": 0,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 80\n        },\n        \"hiddenSeries\": false,\n        \"id\": 90,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"vm_cache_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"} / vm_cache_size_max_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"{{type}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Cache usage % ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:235\",\n            \"format\": \"percentunit\",\n            \"logBase\": 1,\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:236\",\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the rate and total number of new series created over last 24h.\\n\\nHigh churn rate tightly connected with database performance and may result in unexpected OOM's or slow queries. It is recommended to always keep an eye on this metric to avoid unexpected cardinality \\\"explosions\\\".\\n\\nThe higher churn rate is, the more resources required to handle it. Consider to keep the churn rate as low as possible.\\n\\nGood references to read:\\n* https://www.robustperception.io/cardinality-is-key\\n* https://www.robustperception.io/using-tsdb-analyze-to-investigate-churn-and-cardinality\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 81\n        },\n        \"hiddenSeries\": false,\n        \"id\": 66,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"new series over 24h\",\n            \"yaxis\": 2\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(vm_new_timeseries_created_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"interval\": \"\",\n            \"legendFormat\": \"churn rate\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(increase(vm_new_timeseries_created_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[24h]))\",\n            \"interval\": \"\",\n            \"legendFormat\": \"new series over 24h\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Churn rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"The percentage of slow rows read comparing to total slow blocks read rate during the last 5 minutes. \\n\\nThe less value is better. If percentage remains high (>50%) during extended periods of time, then it is likely more RAM is needed for optimal handling of the current number of active time series. \\n\\nIn general, VictoriaMetrics requires ~1KB or RAM per active time series, so it should be easy calculating the required amounts of RAM for the current workload according to capacity planning docs. But the resulting number may be far from the real number because the required amounts of memory depends on may other factors such as the number of labels per time series and the length of label values.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 89\n        },\n        \"hiddenSeries\": false,\n        \"id\": 74,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_vmselect_metric_rows_read_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m])) / sum(rate(vm_vmselect_metric_blocks_read_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"慢查询所占比例\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"慢查询 ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 2,\n            \"format\": \"percentunit\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows whether background merges were deferred because of not enough of disk space. \\n\\nBefore starting the data parts merge (improves compression and reduces number of files), VictoriaMetrics estimates if there is enough of disk space for the operation. If panel shows values higher than zero, it means no disk space left for merging operation.\\n\\nConsider extending the disk size or decreasing retention. \",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 89\n        },\n        \"hiddenSeries\": false,\n        \"id\": 88,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_merge_need_free_disk_space{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) by(type)\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"{{ type }} deferred\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Merges deferred ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 2,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Prometheus\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 98\n        },\n        \"id\": 46,\n        \"panels\": [],\n        \"title\": \"Resource usage\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 99\n        },\n        \"hiddenSeries\": false,\n        \"id\": 44,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(go_memstats_sys_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) + sum(vm_cache_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"requested from system\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(go_memstats_heap_inuse_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}) + sum(vm_cache_size_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"heap inuse\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"sum(go_memstats_stack_inuse_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"stack inuse\",\n            \"refId\": \"C\"\n          },\n          {\n            \"expr\": \"sum(process_resident_memory_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"resident\",\n            \"refId\": \"D\"\n          },\n          {\n            \"exemplar\": true,\n            \"expr\": \"sum(process_resident_memory_anon_bytes{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"resident anonymous\",\n            \"refId\": \"E\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Memory usage ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 99\n        },\n        \"hiddenSeries\": false,\n        \"id\": 57,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"rate(process_cpu_seconds_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m])\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"CPU cores used\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"CPU ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Panel shows the number of open file descriptors in the OS.\\nReaching the limit of open files can cause various issues and must be prevented.\\n\\nSee how to change limits here https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 107\n        },\n        \"hiddenSeries\": false,\n        \"id\": 75,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"max\",\n            \"color\": \"#C4162A\"\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(process_open_fds{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"open\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"min(process_max_fds{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"interval\": \"\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"max\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Open FDs ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 2,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows the number of bytes read/write from the storage layer.\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 107\n        },\n        \"hiddenSeries\": false,\n        \"id\": 76,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"read\",\n            \"transform\": \"negative-Y\"\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(process_io_storage_read_bytes_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"read\",\n            \"refId\": \"A\"\n          },\n          {\n            \"datasource\": \"Prometheus\",\n            \"expr\": \"sum(rate(process_io_storage_written_bytes_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"write\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Disk writes/reads ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"bytes\",\n            \"logBase\": 1,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 115\n        },\n        \"hiddenSeries\": false,\n        \"id\": 47,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(go_goroutines{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"gc duration\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Goroutines ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"Shows avg GC duration\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 115\n        },\n        \"hiddenSeries\": false,\n        \"id\": 42,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(go_gc_duration_seconds_sum{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\\n/\\nsum(rate(go_gc_duration_seconds_count{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[5m]))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"avg gc duration\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"GC duration ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"s\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 123\n        },\n        \"hiddenSeries\": false,\n        \"id\": 48,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(process_num_threads{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"threads\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"Threads ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 0,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 123\n        },\n        \"hiddenSeries\": false,\n        \"id\": 37,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(vm_tcplistener_conns{job=~\\\"$job\\\", instance=~\\\"$instance\\\"})\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"connections\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"TCP connections ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Prometheus\",\n        \"description\": \"\",\n        \"fieldConfig\": {\n          \"defaults\": {\n            \"links\": []\n          },\n          \"overrides\": []\n        },\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 131\n        },\n        \"hiddenSeries\": false,\n        \"id\": 49,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": true,\n          \"current\": true,\n          \"max\": true,\n          \"min\": false,\n          \"show\": true,\n          \"sort\": \"current\",\n          \"sortDesc\": true,\n          \"total\": false,\n          \"values\": true\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"alertThreshold\": true\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"8.3.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"datasource\": \"Prometheus\",\n            \"exemplar\": true,\n            \"expr\": \"sum(rate(vm_tcplistener_accepts_total{job=~\\\"$job\\\", instance=~\\\"$instance\\\"}[1m]))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"interval\": \"\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"connections\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeRegions\": [],\n        \"title\": \"TCP connections rate ($instance)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"mode\": \"time\",\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"min\": \"0\",\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false\n        }\n      }\n    ],\n    \"style\": \"dark\",\n    \"refresh\": false,\n    \"schemaVersion\": 33,\n    \"tags\": [\n      \"victoriametrics\",\n      \"vmsingle\"\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allFormat\": \"glob\",\n          \"current\": {\n            \"selected\": false,\n            \"text\": \"default\",\n            \"value\": \"default\"\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(vm_app_version, env)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"环境\",\n          \"multi\": false,\n          \"name\": \"env\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(vm_app_version, env)\",\n            \"refId\": \"VictoriaMetrics-job-Variable-Query\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"current\": {\n            \"selected\": false,\n            \"text\": \"victoriaMetricsExporter\",\n            \"value\": \"victoriaMetricsExporter\"\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(vm_app_version{version=~\\\"vmstorage.*\\\"}, job)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"multi\": false,\n          \"name\": \"job\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(vm_app_version{version=~\\\"vmstorage.*\\\"}, job)\",\n            \"refId\": \"VictoriaMetrics-job-Variable-Query\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"current\": {\n            \"selected\": false,\n            \"text\": \"v1.71.0\",\n            \"value\": \"v1.71.0\"\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(vm_app_version{job=~\\\"$job\\\", instance=~\\\"$instance\\\"},  version)\",\n          \"hide\": 2,\n          \"includeAll\": false,\n          \"multi\": false,\n          \"name\": \"version\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(vm_app_version{job=~\\\"$job\\\", instance=~\\\"$instance\\\"},  version)\",\n            \"refId\": \"VictoriaMetrics-version-Variable-Query\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"/.*-tags-(v\\\\d+\\\\.\\\\d+\\\\.\\\\d+)/\",\n          \"skipUrlSync\": false,\n          \"sort\": 2,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"current\": {\n            \"selected\": false,\n            \"text\": \"10.0.16.167\",\n            \"value\": \"10.0.16.167\"\n          },\n          \"datasource\": \"Prometheus\",\n          \"definition\": \"label_values(vm_app_version{job=~\\\"$job\\\"}, instance)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"instance\",\n          \"multi\": false,\n          \"name\": \"instance\",\n          \"options\": [],\n          \"query\": {\n            \"query\": \"label_values(vm_app_version{job=~\\\"$job\\\"}, instance)\",\n            \"refId\": \"VictoriaMetrics-instance-Variable-Query\"\n          },\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-30m\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ]\n    },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/25-rocketmq-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 25,\n    \"uid\": \"fzr_Fmd4k\",\n    \"title\": \"rocketmq 信息面板\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 7,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 12,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_broker_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_broker_qps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"broker tps & broker qps\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 9,\n        \"x\": 7,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_consumer_offset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_consumer_offset\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 0\n      },\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_consumer_message_size{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_consumer_message_size\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 7,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_producer_offset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_producer_offset\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"消费tps\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 9,\n        \"x\": 7,\n        \"y\": 6\n      },\n      \"id\": 4,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_consumer_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_consumer_tps\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 6\n      },\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_producer_message_size{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_producer_message_size\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 7,\n        \"x\": 0,\n        \"y\": 13\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_producer_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_producer_tps\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 9,\n        \"x\": 7,\n        \"y\": 13\n      },\n      \"id\": 10,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_group_get_latency_by_storetime{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_group_get_latency_by_storetime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 6,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 13\n      },\n      \"id\": 18,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rocketmq_producer_offset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}) by (topic) - on(topic)  group_right  sum(rocketmq_consumer_offset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}) by (group,topic)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"rocketmq_message_accumulation\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_0ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_0to10ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_10to50ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_50to100ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_100to200ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"E\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_200to500ms{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"F\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_500to1s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"G\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_1to2s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"H\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_2to3s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"I\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_3to4s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"J\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_4to5s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"K\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_5to10s{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"L\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_pmdt_10stomore{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"M\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"PutMessageDistributeTime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 19\n      },\n      \"id\": 28,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_brokeruntime_pull_threadpoolqueue_headwait_timemills{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_query_threadpoolqueue_headwait_timemills{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_send_threadpoolqueue_headwait_timemills{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"threadpoolqueue_headwait_timemills\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 19\n      },\n      \"id\": 30,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_client_consume_fail_msg_count{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_client_consume_fail_msg_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_client_consume_ok_msg_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rocketmq_client_consume_rt{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"rocketmq_client_consumer_pull_rt{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"E\"\n        },\n        {\n          \"expr\": \"rocketmq_client_consumer_pull_tps{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"F\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"consume client info\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 26,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_brokeruntime_getfound_tps10{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_gettotal_tps10{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_gettransfered_tps10{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_getmiss_tps10{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_put_tps10{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"E\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"runtime tps\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 26\n      },\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_brokeruntime_commitlog_disk_ratio{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_consumequeue_disk_ratio{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_commitlogdir_capacity_free{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_commitlogdir_capacity_total{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_commitlog_maxoffset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"E\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_commitlog_minoffset{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"F\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"disk space\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 26\n      },\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_put_total_today_now{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_gettotal_today_now{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_dispatch_behind_bytes{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_put_message_size_total{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"D\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_put_message_average_size{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"E\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_gettotal_yesterdaymorning{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"F\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_puttotal_yesterdaymorning{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"G\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_gettotal_todaymorning{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"H\"\n        },\n        {\n          \"expr\": \"rocketmq_brokeruntime_msg_puttotal_todaymorning{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"refId\": \"I\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"broker runtime info\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(rocketmq_topic_retry_offset,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(rocketmq_topic_retry_offset,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(rocketmq_topic_retry_offset{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(rocketmq_topic_retry_offset{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(rocketmq_topic_retry_offset,job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(rocketmq_topic_retry_offset,job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}\n"
  },
  {
    "path": "package_hub/grafana_dashboard_json/3-mian-ban-lie-biao.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 3,\n    \"uid\": \"XrwAXz_Mz\",\n    \"title\": \"面板列表\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 20,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"folderId\": null,\n        \"maxItems\": 100,\n        \"query\": \"\",\n        \"showHeadings\": false,\n        \"showRecentlyViewed\": false,\n        \"showSearch\": true,\n        \"showStarred\": false,\n        \"tags\": []\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Dashboard List\",\n      \"type\": \"dashlist\"\n    }\n  ],\n    \"templating\": {\n    \"list\": []\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/4-app-logs.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 4,\n    \"uid\": \"liz0yRCZz\",\n    \"title\": \"AppLogs\",\n    \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"cacheTimeout\": null,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Loki\",\n      \"decimals\": null,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"interval\": null,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": false,\n        \"hideZero\": false,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": true,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(count_over_time({job=\\\"$app\\\",filename=\\\"$filename\\\",env=\\\"$env\\\",instance=\\\"$instance\\\"}[$__interval]))\",\n          \"legendFormat\": \"{{ log }}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"datasource\": \"Loki\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 25,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"maxDataPoints\": \"\",\n      \"options\": {\n        \"showLabels\": false,\n        \"showTime\": false,\n        \"sortOrder\": \"Descending\",\n        \"wrapLogMessage\": false\n      },\n      \"pluginVersion\": \"7.2.2\",\n      \"targets\": [\n        {\n          \"expr\": \"{job=\\\"$app\\\",filename=\\\"$filename\\\",env=\\\"$env\\\",instance=\\\"$instance\\\"} |= \\\"$search\\\"\",\n          \"hide\": false,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"logs\"\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Loki\",\n        \"definition\": \"label_values(env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": \"label_values(env)\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Loki\",\n        \"definition\": \"label_values({env=\\\"$env\\\"},job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"app\",\n        \"multi\": false,\n        \"name\": \"app\",\n        \"options\": [],\n        \"query\": \"label_values({env=\\\"$env\\\"},job)\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Loki\",\n        \"definition\": \"label_values({job=\\\"$app\\\",env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": \"label_values({job=\\\"$app\\\",env=\\\"$env\\\"},instance)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Loki\",\n        \"definition\": \"label_values({job=\\\"$app\\\",env=\\\"$env\\\",instance=\\\"$instance\\\"},filename)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"filename\",\n        \"multi\": false,\n        \"name\": \"filename\",\n        \"options\": [],\n        \"query\": \"label_values({job=\\\"$app\\\",env=\\\"$env\\\",instance=\\\"$instance\\\"},filename)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": \"\",\n          \"value\": \"\"\n        },\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"label\": \"String Match\",\n        \"name\": \"search\",\n        \"options\": [\n          {\n            \"selected\": true,\n            \"text\": \"\",\n            \"value\": \"\"\n          }\n        ],\n        \"query\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"textbox\"\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/5-arangodb-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 5,\n    \"uid\": \"jtXDTgPMk\",\n    \"title\": \"Arangodb 信息面板\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb base level\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 26,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_base_level{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"RocksdbBaseLevel\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"The number of client connections that are currently open.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_client_connection_statistics_client_connections{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ClientConnections\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Amount of time that this process has been scheduled in kernel mode, measured in seconds.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 6,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_process_statistics_system_time{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"UseSystemTime\",\n      \"type\": \"stat\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Amount of time that this process has been scheduled in user mode, measured in seconds.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"interval\": null,\n      \"links\": [],\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_process_statistics_user_time{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"UseUserTime\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb background errors\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 1\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"id\": 24,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_background_errors{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"RocksdbBackgroundErrors\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Transactions started\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"#6ED0E0\",\n                \"value\": 100\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 4\n      },\n      \"id\": 12,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_transactions_started{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"ArangodbTransactionsStarted\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"Number of threads in the arangod process.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 4\n      },\n      \"id\": 2,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_process_statistics_number_of_threads{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"ThreadNumbers\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb cache limit\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"#BA43A9\",\n                \"value\": 50\n              }\n            ]\n          },\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 4\n      },\n      \"id\": 20,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_cache_limit{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"RocksdbCacheLimit\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb size all mem tables\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          },\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"hiddenSeries\": false,\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_size_all_mem_tables{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RocksdbSizeAllMemTables\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb_cache_allocated\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 8\n      },\n      \"hiddenSeries\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_cache_allocated{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RocksdbCacheAllocated\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb  snapshots num\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 30,\n      \"interval\": null,\n      \"links\": [],\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_num_snapshots{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"RocksdbNumSnapshots\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"decimals\": null,\n      \"description\": \"Transactions committed\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 16\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_transactions_committed{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ArangodbTransactionsCommitted\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb estimate num keys\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_estimate_num_keys{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RocksdbEstimateNumKeys \",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb actual delayed write rate\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 24\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_actual_delayed_write_rate{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RocksdbActualDelayedWriteRate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percent\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"rocksdb cache hit rate recent\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"hiddenSeries\": false,\n      \"id\": 18,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rocksdb_cache_hit_rate_recent{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RocksdbCacheHitRateRecent\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percent\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"ransactions aborted\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 32\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"arangodb_transactions_aborted{env=~\\\"$env\\\",instance=~\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ArangodbTransactionsAborted\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(arangodb_client_connection_statistics_client_connections,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(arangodb_client_connection_statistics_client_connections,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(arangodb_client_connection_statistics_client_connections{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(arangodb_client_connection_statistics_client_connections{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"arangodbExporter\",\n          \"value\": \"arangodbExporter\"\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(arangodb_client_connection_statistics_client_connections,job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(arangodb_client_connection_statistics_client_connections,job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/6-beanstalkdxin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 6,\n    \"uid\": \"TBAkOmyMz\",\n    \"title\": \"Beanstalkd 信息面板\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"up time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"id\": 2,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"uptime{job=\\\"beanstalkExporter\\\",env=~\\\"$env\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"UpTime\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"sys cpu time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rusage_stime{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"SysCpuTime\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"description\": \"user cpu time\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 10,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"rusage_utime{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"UserCpuTime\",\n      \"type\": \"stat\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"total connections\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"options\": {\n        \"displayMode\": \"gradient\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"mean\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showUnfilled\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"total_connections{job=\\\"beanstalkExporter\\\",env=~\\\"$env\\\",instance=~\\\"$instance\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"TotalConnections\",\n      \"type\": \"bargauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"total jobs\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 3\n      },\n      \"hiddenSeries\": false,\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"total_jobs{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"TotalJobs\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"buried jobs\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 3\n      },\n      \"hiddenSeries\": false,\n      \"id\": 34,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_jobs_buried{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"BuriedJobs\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"delayed jobs\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 11\n      },\n      \"hiddenSeries\": false,\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"current_jobs_delayed{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"DelayedJobs\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of timeout job\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 11\n      },\n      \"hiddenSeries\": false,\n      \"id\": 30,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"job_timeouts{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"TimeoutJobNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of stats cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_stats{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"StatsCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of reverse cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 19\n      },\n      \"hiddenSeries\": false,\n      \"id\": 26,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_reserve{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ReverseCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of release cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 27\n      },\n      \"hiddenSeries\": false,\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_release{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"ReleaseCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of put cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 27\n      },\n      \"hiddenSeries\": false,\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_put{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"PutCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of peek cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"hiddenSeries\": false,\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_peek{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"PeekCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of kick cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 35\n      },\n      \"hiddenSeries\": false,\n      \"id\": 18,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_kick{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"KickCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of ignore cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 43\n      },\n      \"hiddenSeries\": false,\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_ignore{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"IgnoreCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of delete cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 43\n      },\n      \"hiddenSeries\": false,\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_delete{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"DeleteCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": true,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"description\": \"num of bury cmd \",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 51\n      },\n      \"hiddenSeries\": false,\n      \"id\": 12,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"cmd_bury{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"beanstalkExporter\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"BuryCmdNum\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(total_jobs,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(total_jobs,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"beanstalkExporter\\\",env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"beanstalkExporter\\\",env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/7-clickhouse-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 7,\n    \"uid\": \"VqK1fIBMk\",\n    \"title\": \"Clickhouse 信息面板\",\n    \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(clickhouse_query{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}[1m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Query\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/merge.*/\",\n          \"bars\": false,\n          \"lines\": true\n        },\n        {\n          \"alias\": \"/rate.*/\",\n          \"bars\": true,\n          \"lines\": false,\n          \"yaxis\": 2,\n          \"zindex\": -1\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": true,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_merge{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"merge {{job}}\",\n          \"refId\": \"A\",\n          \"step\": 4\n        },\n        {\n          \"expr\": \"rate(clickhouse_merged_rows_total{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}[1m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"rate {{job}}\",\n          \"refId\": \"B\",\n          \"step\": 4\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Merge\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"id\": 6,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(clickhouse_readonly_replica{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"})\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 60\n        }\n      ],\n      \"thresholds\": \"1,1\",\n      \"title\": \"ReadOnly replica\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 7\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_replicated_checks{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clickhouse_replicated_fetch{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"clickhouse_replicated_send{instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Replication\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_read{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_write{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Read/Write\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 9,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_background_pool_task{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{job}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Pool Tasks\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_tcp_connection{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"tcp {{instance}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_http_connection{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"http {{instance}}\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"clickhouse_interserver_connection{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"interserver\",\n          \"refId\": \"C\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Connections\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"unit\": \"decbytes\"\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 21\n      },\n      \"hiddenSeries\": false,\n      \"id\": 10,\n      \"isNew\": true,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": true,\n        \"hideEmpty\": true,\n        \"hideZero\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"clickhouse_memory_tracking{env=~\\\"$env\\\",instance=\\\"$instance\\\",job=\\\"$job\\\"}\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(clickhouse_query,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(clickhouse_query,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(clickhouse_query{env=\\\"$env\\\"},instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"instance\",\n        \"multi\": false,\n        \"name\": \"instance\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(clickhouse_query{env=\\\"$env\\\"},instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(clickhouse_query,job)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(clickhouse_query,job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/8-elasticsearch-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 8,\n    \"uid\": \"qXG-qEFMk\",\n    \"title\": \"ElasticSearch 信息面板\",\n    \"panels\": [\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 90,\n      \"panels\": [],\n      \"title\": \"Cluster\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            },\n            {\n              \"id\": 1,\n              \"op\": \"=\",\n              \"text\": \"green\",\n              \"type\": 1,\n              \"value\": \"1\"\n            },\n            {\n              \"id\": 2,\n              \"op\": \"=\",\n              \"text\": \"yellow\",\n              \"type\": 1,\n              \"value\": \"2\"\n            },\n            {\n              \"id\": 3,\n              \"op\": \"=\",\n              \"text\": \"red\",\n              \"type\": 1,\n              \"value\": \"3\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 2\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 3\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 92,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"scalar(elasticsearch_cluster_health_status{color=\\\"green\\\",env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}) + scalar(elasticsearch_cluster_health_status{color=\\\"yellow\\\",env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}) * 2 + scalar(elasticsearch_cluster_health_status{color=\\\"red\\\",env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}) * 3\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Cluster Status\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 12,\n        \"y\": 1\n      },\n      \"id\": 8,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(elasticsearch_cluster_health_number_of_nodes{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"})/count(elasticsearch_cluster_health_number_of_nodes{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{instance}}\",\n          \"metric\": \"elasticsearch_cluster_health_number_of_nodes\",\n          \"refId\": \"A\",\n          \"step\": 1800\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Running Nodes\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 1\n      },\n      \"id\": 94,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_number_of_data_nodes{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Data Nodes\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 0.5\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 1\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 1\n      },\n      \"id\": 96,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_number_of_pending_tasks{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Pending Tasks\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 4\n      },\n      \"id\": 76,\n      \"panels\": [],\n      \"title\": \"Shards\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 0,\n        \"y\": 5\n      },\n      \"id\": 78,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_active_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 4,\n        \"y\": 5\n      },\n      \"id\": 80,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_active_primary_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Active Primary Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 0.5\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 1\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 8,\n        \"y\": 5\n      },\n      \"id\": 82,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_initializing_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Initializing Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 0.5\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 1\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 12,\n        \"y\": 5\n      },\n      \"id\": 84,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_relocating_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Relocating Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 0.5\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 1\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 5\n      },\n      \"id\": 86,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_unassigned_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unassigned Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [\n            {\n              \"id\": 0,\n              \"op\": \"=\",\n              \"text\": \"N/A\",\n              \"type\": 1,\n              \"value\": \"null\"\n            }\n          ],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"#299c46\",\n                \"value\": null\n              },\n              {\n                \"color\": \"rgba(237, 129, 40, 0.89)\",\n                \"value\": 0.5\n              },\n              {\n                \"color\": \"#d44a3a\",\n                \"value\": 1\n              }\n            ]\n          },\n          \"unit\": \"none\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 5\n      },\n      \"id\": 88,\n      \"interval\": null,\n      \"links\": [],\n      \"maxDataPoints\": 100,\n      \"options\": {\n        \"orientation\": \"horizontal\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"elasticsearch_cluster_health_delayed_unassigned_shards{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Delayed Unassigned Shards\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 70,\n      \"panels\": [],\n      \"title\": \"Documents\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(elasticsearch_indices_docs{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Documents\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Documents indexed\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 9\n      },\n      \"hiddenSeries\": false,\n      \"id\": 4,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(elasticsearch_indices_store_size_bytes{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Index Size\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Index Size\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"hiddenSeries\": false,\n      \"id\": 72,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(elasticsearch_indices_indexing_index_total{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{name}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Documents Indexed Rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"Documents/s\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 18\n      },\n      \"hiddenSeries\": false,\n      \"id\": 74,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(elasticsearch_indices_search_fetch_total{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[1h])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{name}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Query Rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"Queris/s\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 27\n      },\n      \"height\": \"\",\n      \"hiddenSeries\": false,\n      \"id\": 64,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(elasticsearch_thread_pool_queue_count{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\", type!=\\\"management\\\"}) by (type)\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Type: {{type}}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Queue Count\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"datasource\": \"Prometheus\",\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"id\": 68,\n      \"panels\": [],\n      \"title\": \"System\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 65,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(elasticsearch_jvm_gc_collection_seconds_sum{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[1m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ name }} {{ gc }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"GC seconds\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 36\n      },\n      \"hiddenSeries\": false,\n      \"id\": 66,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(elasticsearch_thread_pool_rejected_count{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\", type!=\\\"management\\\"}[5m])\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ name }} {{ type }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Thread pool rejections\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 44\n      },\n      \"hiddenSeries\": false,\n      \"id\": 1,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(elasticsearch_thread_pool_active_count{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\", type!=\\\"management\\\"}) by (type)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Type: {{ type }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Thread Pools\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 44\n      },\n      \"hiddenSeries\": false,\n      \"id\": 28,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"avg_over_time(elasticsearch_jvm_memory_used_bytes{area=\\\"heap\\\",env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[15m]) / elasticsearch_jvm_memory_max_bytes{area=\\\"heap\\\",env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ name }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Avg Heap in 15min\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": 1,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"editable\": true,\n      \"error\": false,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {},\n          \"links\": []\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 52\n      },\n      \"hiddenSeries\": false,\n      \"id\": 5,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(elasticsearch_transport_rx_packets_total{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"RX\",\n          \"refId\": \"A\",\n          \"step\": 240\n        },\n        {\n          \"expr\": \"sum(rate(elasticsearch_transport_tx_packets_total{env=~\\\"$env\\\",cluster=~\\\"$cluster\\\"}[5m])) * -1\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"TX\",\n          \"refId\": \"B\",\n          \"step\": 240\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"RX/TX Rate 5m\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(elasticsearch_cluster_health_status,env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(elasticsearch_cluster_health_status,env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": null,\n        \"current\": {},\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values(elasticsearch_cluster_health_status{env=\\\"$env\\\"},cluster)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"cluster\",\n        \"multi\": false,\n        \"name\": \"cluster\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(elasticsearch_cluster_health_status{env=\\\"$env\\\"},cluster)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/grafana_dashboard_json/9-httpd-xin-xi-mian-ban.json",
    "content": "{\n  \"dashboard\": {\n    \"id\": 9,\n    \"uid\": \"TOVJmRPMz\",\n    \"title\": \"Httpd 信息面板\",\n    \"panels\": [\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 0\n              },\n              {\n                \"color\": \"green\",\n                \"value\": 1\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 5,\n      \"links\": [],\n      \"options\": {\n        \"colorMode\": \"background\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"count(apache_up{env=~\\\"$env\\\",instance=~\\\"$host\\\"} == 1)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Apache Up\",\n          \"refId\": \"A\",\n          \"step\": 120\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Apache Up / Down\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 3\n      },\n      \"id\": 7,\n      \"links\": [],\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"process_max_fds{env=~\\\"$env\\\",instance=~\\\"$host\\\",job=\\\"httpdExporter\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{ state }}\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"process_max_fds\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 3\n      },\n      \"id\": 4,\n      \"links\": [],\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"process_cpu_seconds_total{env=~\\\"$env\\\",instance=~\\\"$host\\\",job=\\\"httpdExporter\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Load\",\n          \"refId\": \"A\",\n          \"step\": 240\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"process_cpu_seconds_total\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 9,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"apache_accesses_total{env=~\\\"$env\\\",instance=~\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"apache_accesses_total\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 6\n      },\n      \"id\": 11,\n      \"options\": {\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true,\n        \"text\": {}\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"apache_cpuload{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"apache_cpuload\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"aliasColors\": {},\n      \"breakPoint\": \"25%\",\n      \"cacheTimeout\": null,\n      \"combine\": {\n        \"label\": \"Others\",\n        \"threshold\": 0\n      },\n      \"datasource\": \"Prometheus\",\n      \"decimals\": -2,\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {},\n          \"custom\": {},\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": []\n          }\n        },\n        \"overrides\": []\n      },\n      \"fontSize\": \"70%\",\n      \"format\": \"short\",\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 6\n      },\n      \"id\": 23,\n      \"interval\": null,\n      \"legend\": {\n        \"header\": \"\",\n        \"percentage\": false,\n        \"show\": false,\n        \"sideWidth\": null,\n        \"values\": true\n      },\n      \"legendType\": \"Under graph\",\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"pieType\": \"pie\",\n      \"pluginVersion\": \"7.4.3\",\n      \"strokeWidth\": 1,\n      \"targets\": [\n        {\n          \"expr\": \"apache_workers{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"apache_workers\",\n      \"transformations\": [],\n      \"type\": \"grafana-piechart-panel\",\n      \"valueName\": \"current\"\n    },\n    {\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"custom\": {},\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 6\n      },\n      \"id\": 15,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"none\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"text\": {},\n        \"textMode\": \"value\"\n      },\n      \"pluginVersion\": \"7.4.3\",\n      \"targets\": [\n        {\n          \"expr\": \"apache_uptime_seconds_total{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"apache_uptime_seconds_total\",\n      \"type\": \"stat\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 19,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"http_request_size_bytes{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeRegions\": [],\n      \"title\": \"http_request_size_bytes\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"$$hashKey\": \"object:896\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"$$hashKey\": \"object:897\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 14\n      },\n      \"hiddenSeries\": false,\n      \"id\": 21,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"http_response_size_bytes{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeRegions\": [],\n      \"title\": \"http_request_size_bytes\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"$$hashKey\": \"object:985\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"$$hashKey\": \"object:986\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"hiddenSeries\": false,\n      \"id\": 13,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"apache_scoreboard{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeRegions\": [],\n      \"title\": \"apache_scoreboard\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"$$hashKey\": \"object:293\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"$$hashKey\": \"object:294\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"Prometheus\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {}\n        },\n        \"overrides\": []\n      },\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"hiddenSeries\": false,\n      \"id\": 17,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"alertThreshold\": true\n      },\n      \"percentage\": false,\n      \"pluginVersion\": \"7.4.3\",\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"http_request_duration_microseconds{env=\\\"$env\\\",instance=\\\"$host\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"queryType\": \"randomWalk\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeRegions\": [],\n      \"title\": \"http_request_duration_microseconds\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"$$hashKey\": \"object:649\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"$$hashKey\": \"object:650\",\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n    \"templating\": {\n    \"list\": [\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"httpdExporter\\\"}, env)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"环境\",\n        \"multi\": false,\n        \"name\": \"env\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"httpdExporter\\\"}, env)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"httpdExporter\\\",env=\\\"$env\\\"}, instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"host\",\n        \"multi\": false,\n        \"name\": \"host\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"httpdExporter\\\",env=\\\"$env\\\"}, instance)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allFormat\": \"glob\",\n        \"allValue\": null,\n        \"current\": {\n        },\n        \"datasource\": \"Prometheus\",\n        \"definition\": \"label_values({job=\\\"httpdExporter\\\"}, instance)\",\n        \"description\": null,\n        \"error\": null,\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"port\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values({job=\\\"httpdExporter\\\"}, instance)\",\n          \"refId\": \"Prometheus-port-Variable-Query\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n    \"version\": 1\n  }\n}"
  },
  {
    "path": "package_hub/openssl_upgrade/upgrade_ssl.sh",
    "content": "#!/bin/bash\n\n#解压新版本的openssl\ntar -xf '/tmp/upgrade_openssl/openssl-1.0.2k.tar.gz' -C '/usr/local/'\nopenssl_dir=\"/usr/local/openssl-1.0.2k\"\nopenssl_rpm=\"$(ls ${openssl_dir} | grep -E 'openssl(.*?)\\.rpm$')\"\nopenssl_rpm_lib=\"$(ls ${openssl_dir} | grep -E 'openssl-libs-(.*?)\\.rpm$')\"\ncd ${openssl_dir} && yum clean all &&\n    for rpm in $openssl_rpm;do\n        if [ $rpm != $openssl_rpm_lib ];then\n            openssl_rpm_=$rpm\n        fi\n    done\nyum localinstall --disablerepo='*' -y $openssl_rpm_ $openssl_rpm_lib &>/dev/null\n#再次查看openssl版本\nopenssl version\n\n#创建文件作为升级后标志（留痕）\necho 'upgrade openssl success' >> '/tmp/upgrade_openssl/is_upgrade_openssl.txt'\n"
  },
  {
    "path": "package_hub/prometheus_rules_template/exporter_status_rule.yml",
    "content": "groups:\n- name: Exporter Alert\n  rules:\n  - alert: exporter 异常\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 中的 {{ $labels.app }}_exporter 已经down掉超过一分钟.\n      summary: exporter status(instance {{ $labels.instance }})\n    expr: exporter_status{env=\"${ENV}\"} == 0\n    for: 1m\n    labels:\n      severity: critical\n"
  },
  {
    "path": "package_hub/prometheus_rules_template/node_data_rule.yml",
    "content": "groups:\n- name: node data disk alert\n  rules:\n  - alert: 主机数据分区磁盘使用率过高\n    annotations:\n      disk_data_path: ${DISK_DATA_PATH}\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 数据分区使用率当前值为{{ $value | humanize }}%,高于阈值 90%\n      summary: disk_data_used (instance {{ $labels.instance }})\n    expr: max((node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"})\n      *100/(node_filesystem_avail_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}+(node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"})))by(instance,env)\n      >= 90\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: critical\n  - alert: 主机数据分区磁盘使用率过高\n    annotations:\n      disk_data_path: ${DISK_DATA_PATH}\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 数据分区使用率当前值为{{ $value | humanize }}%,高于阈值 80%\n      summary: disk_data_used (instance {{ $labels.instance }})\n    expr: max((node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"})\n      *100/(node_filesystem_avail_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}+(node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"${DISK_DATA_PATH}\"})))by(instance,env)\n      >= 80\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: warning\n"
  },
  {
    "path": "package_hub/prometheus_rules_template/node_rule.yml",
    "content": "groups:\n- name: node alert\n  rules:\n  - alert: 实例宕机\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 实例 {{ $labels.instance }} monitor_agent进程丢失或主机发生宕机已超过1分钟\n      summary: 实例宕机({{ $labels.instance }})\n    expr: sum(up{job=\"nodeExporter\", env=\"${ENV}\"}) by (instance,env) < 1\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: critical\n  - alert: 主机CPU使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} CPU使用率当前值为{{ $value | humanize }}%,高于阈值 90%\n      summary: cpu_used (instance {{ $labels.instance }})\n    expr: (100 - sum(avg without (cpu)(irate(node_cpu_seconds_total{mode='idle', env=\"${ENV}\"}[2m])))\n      by (instance,env) * 100) >= 90\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: critical\n  - alert: 主机CPU使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} CPU使用率当前值为{{ $value | humanize }}%,高于阈值 80%\n      summary: cpu_used (instance {{ $labels.instance }})\n    expr: (100 - sum(avg without (cpu)(irate(node_cpu_seconds_total{mode='idle', env=\"${ENV}\"}[2m])))\n      by (instance,env) * 100) >= 80\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: warning\n  - alert: 主机内存使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 内存使用率当前值为{{ $value | humanize }}%,高于阈值 90%\n      summary: memory_used (instance {{ $labels.instance }})\n    expr: (1 - (node_memory_MemAvailable_bytes{env=\"${ENV}\"} / (node_memory_MemTotal_bytes{env=\"${ENV}\"})))\n      * 100 >= 90\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: critical\n  - alert: 主机内存使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 内存使用率当前值为{{ $value | humanize }}%,高于阈值 80%\n      summary: memory_used (instance {{ $labels.instance }})\n    expr: (1 - (node_memory_MemAvailable_bytes{env=\"${ENV}\"} / (node_memory_MemTotal_bytes{env=\"${ENV}\"})))\n      * 100 >= 80\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: warning\n  - alert: 主机根分区磁盘使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 根分区使用率当前值为{{ $value | humanize }}%,高于阈值 90%\n      summary: disk_root_used (instance {{ $labels.instance }})\n    expr: max((node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"/\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"/\"})\n      *100/(node_filesystem_avail_bytes{env=\"${ENV}\",mountpoint=\"/\"}+(node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"/\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"/\"})))by(instance,env)\n      >= 90\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: critical\n  - alert: 主机根分区磁盘使用率过高\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 根分区使用率当前值为{{ $value | humanize }}%,高于阈值 80%\n      summary: disk_root_used (instance {{ $labels.instance }})\n    expr: max((node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"/\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"/\"})\n      *100/(node_filesystem_avail_bytes{env=\"${ENV}\",mountpoint=\"/\"}+(node_filesystem_size_bytes{env=\"${ENV}\",mountpoint=\"/\"}-node_filesystem_free_bytes{env=\"${ENV}\",mountpoint=\"/\"})))by(instance,env)\n      >= 80\n    for: 1m\n    labels:\n      job: nodeExporter\n      severity: warning\n"
  },
  {
    "path": "package_hub/prometheus_rules_template/service_status_rule.yml",
    "content": "groups:\n- name: App state\n  rules:\n  - alert: app state\n    annotations:\n      consignee: ${EMAIL_ADDRESS}\n      description: 主机 {{ $labels.instance }} 中的 服务 {{ $labels.app }} 已经down掉超过一分钟.\n      summary: app state(instance {{ $labels.instance }})\n    expr: probe_success{env=\"${ENV}\"} == 0\n    for: 1m\n    labels:\n      severity: critical\n  - alert: kafka kafka_consumergroup_lag alert\n    annotations:\n      consignee: 987654321@qq.com\n      description:  Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多  {{ humanize $value }}\n      summary: kafka_consumergroup_lag (instance {{ $labels.instance }})\n    expr: sum(kafka_consumergroup_lag{env=\"${ENV}\"}) by (consumergroup,instance,job,env) > 3000\n    for: 1m\n    labels:\n      severity: warning\n  - alert: kafka kafka_consumergroup_lag alert\n    annotations:\n      consignee: 987654321@qq.com\n      description: Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多  {{ humanize $value }}\n      summary: kafka_consumergroup_lag (instance {{ $labels.instance }})\n    expr: sum(kafka_consumergroup_lag{env=\"${ENV}\"}) by (consumergroup,instance,job,env) > 5000\n    for: 1m\n    labels:\n      severity: critical"
  },
  {
    "path": "package_hub/reactor/auth.sls",
    "content": "{% if 'act' in data and data['act'] == 'denied' %}\nminion_delete:\n  wheel.key.delete:\n    - args:\n      - match: {{ data['id'] }}\n{% endif %}\n"
  },
  {
    "path": "package_hub/reactor/start.sls",
    "content": "agent_start:\n  runner.agent_start.update:\n    - agent_id: {{ data['id'] }}\n"
  },
  {
    "path": "package_hub/reactor/stop.sls",
    "content": "agent_stop:\n  runner.agent_stop.update:\n    - agent_id_lst: {{ data['present'] }}\n"
  },
  {
    "path": "package_hub/runners/agent_start.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: agent_start\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-24 11:48\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n在agent启动的时候自动获取到Agent的信息，并入库更新处理\n\"\"\"\n\nimport os\nimport sys\nimport logging\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nsys.path.append(os.path.join(os.path.dirname(\n    os.path.dirname(CURRENT_DIR)), \"omp_server\"))\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import Host\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger(\"server\")\n\n\ndef get_agent_detail(target):\n    \"\"\"\n    获取agent详情的方法\n    :param target: agent的id\n    :return:\n    \"\"\"\n    try:\n        salt_obj = SaltClient()\n        _flag, res = salt_obj.fun(target=target, fun=\"saltutil.sync_modules\")\n        logger.info(f\"同步模块返回标志: {_flag}; 返回值: {target} {res}\")\n        for i in range(5):\n            _flag, res = salt_obj.fun(target, \"get_agent_info.get_agent_info\")\n            if _flag:\n                break\n        if not _flag:\n            logger.error(f\"获取{target} get_agent_info详情失败: {res}\")\n            return\n        logger.info(f\"获取{target} get_agent_info详情成功: {res}\")\n        obj_list = Host.objects.filter(ip=target)\n        if obj_list:\n            obj_list.update(\n                memory=res.get(\"memory\", {}).get(\"memory_total\", 0),\n                cpu=res.get(\"cpu\", 0),\n                disk=res.get(\"disk\", {}),\n                host_agent=0,\n                host_name=res.get(\"hostname\")\n            )\n            logger.info(f\"更新{target}状态成功!\")\n        # TODO 暂时屏蔽自动入库逻辑，待设计完善后再进行补充\n        # else:\n        #     Host(\n        #         ip=target,\n        #         memory=res.get(\"memory\", {}).get(\"memory_total\", 0),\n        #         cpu=res.get(\"cpu\", 0),\n        #         disk=res.get(\"disk\", {}),\n        #         host_agent=0,\n        #         host_name=res.get(\"hostname\")\n        #     ).save()\n        #     logger.info(f\"插入{target}状态成功!\")\n    except Exception as e:\n        logger.error(f\"{target}状态更新失败: {str(e)}\")\n\n\ndef update(agent_id):\n    \"\"\"\n    更新agent的代码\n    :param agent_id:\n    :return:\n    \"\"\"\n    get_agent_detail(target=agent_id)\n"
  },
  {
    "path": "package_hub/runners/agent_stop.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: agent_stop\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-24 11:48\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n在agent启动的时候自动获取到Agent的信息，并入库更新处理\n\"\"\"\n\nimport os\nimport sys\nimport logging\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nsys.path.append(os.path.join(os.path.dirname(\n    os.path.dirname(CURRENT_DIR)), \"omp_server\"))\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import Host\n\nlogger = logging.getLogger(\"server\")\n\n\ndef update_agent_detail(agent_id_lst):\n    \"\"\"\n    获取agent详情的方法\n    :param agent_id_lst: agent的id lst\n    :return:\n    \"\"\"\n    try:\n        if not isinstance(agent_id_lst, list):\n            return\n        # 数据库中的agent是正常的，如果其id不再present列表内，则将其状态更新为'2-启动失败'\n        # 如果当前在线id列表有值，那么排除此部分值后刷新Agent状态\n        # 如果当前在线id列表无值，说明所有 原正常的Agent 均处于离线状态\n        if len(agent_id_lst) != 0:\n            Host.objects.filter(\n                host_agent=0).exclude(\n                ip__in=agent_id_lst).all().update(host_agent=2)\n        else:\n            Host.objects.filter(host_agent=0).update(host_agent=2)\n        Host.objects.filter(\n            ip__in=agent_id_lst).all().update(host_agent=0)\n        logger.info(f\"更新当前在线Agent: {agent_id_lst}状态更新成功!\")\n    except Exception as e:\n        logger.error(f\"{agent_id_lst}状态更新失败: {str(e)}\")\n\n\ndef update(agent_id_lst):\n    \"\"\"\n    更新agent的代码\n    :param agent_id_lst:\n    :return:\n    \"\"\"\n    update_agent_detail(agent_id_lst=agent_id_lst)\n"
  },
  {
    "path": "package_hub/template/app_publish_readme.md",
    "content": "# OMP 社区版-应用商店发布说明文档\n\n[TOC]\n\n## 1. 说明\n\n用户可以在应用商店发布“基础组件”与“应用服务”两个维度的产品，在区分上，应用服务可以理解为完整的提供某一类服务的产品，产品内部可由一个或多个“服务”组成 ，比如gitlab、jenkins等。基础组件的角色更多是作为其他完成产品的一部分存在，以完成产品的某些功能需求，如mysql、redis等。\n\n## 2. 基础组件打包规范\n\n注：用户在发布基础组件安装包时，需按照以下规范打包上传才可以正常发布\n\n### 2.1. 目录规范\n\n以MySQL服务为例，需将涉及到的文件统一放在 mysql目录下，目录名称与该服务名称保持一致，目录中需要提供与该目录名称一致的配置文件(如：mysql.yaml)、产品图标（如：mysql.svg)  和其他所需文件（如安装脚本等）\n\n**示例：**\n\n```shell\n$ tree ./mysql -L 2\n./mysql\t\t\t\t\t\t\t\t\t# 目录名称，请与组件名称一致\n├── mysql.svg\t\t\t\t\t\t# 平台展示组件图标，请使用 “组件名称.svg ” 命名，与目录名称保持一致\n├── mysql.yaml\t\t\t\t\t# 组件配置文件, 记录该组件安装、升级等所需信息, 请使用 “组件名称.yaml” 命名，与目录名称保持一致\n└── scripts\t\t\t\t\t\t\t# 组件的安装、启动等控制脚本，该脚本在安装时会调用\n│   ├── init.py\t\t\t\t\t# 初始化脚本\n│   ├── install.py\t\t\t# 组件安装脚本\n│   ├── mysql           # 组件启动、停止控制脚本，建议与服务名称一致\n│   ├── mysql_backup.py\t# 其他动作脚本，如备份等\n```\n\n**备注:**\n\n1. 组件图标请使用svg格式图片，如不添加会显示平台缺省图标；\n2. 确保目录名称(mysql)、配置文件(mysql.yaml) 、图标(mysql.svg) 名称统一, 上传安装包时，平台将根据名称校验对应文件合法性，如名称不一致，可能会导致校验不通过等问题；\n3. 确保安装包解压后是一个整体目录\n\n### 2.2. 压缩包命名规范\n\n请使用 `{name}-{version}-{others}-{package_md5}.tar.gz`  格式进行打包命名\n\n1. name:   安装包名称，建议字符:  `英文`  `数字`   `_`\n2. version: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n3. others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n4. package_md5:  安装包MD5 值\n\n例如：`mysql-5.7.31-beta-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz`\n\n```shell\n$ tar czf mysql-5.7.31.tar.gz mysql\n$ md5sum mysql-5.7.31.tar.gz\n8e955b24fefe7061eb79cfc61a9a02a1\n$ mv mysql-5.7.31.tar.gz mysql-5.7.31-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz\n```\n\n### 2.3.  配置文件(yaml)说明\n\n平台预留KEY值（该KEY值存在指定定义，请准确使用）：\n\n| KEY          | 说明         | 备注                                   |\n| ------------ | ------------ | -------------------------------------- |\n| service_port | 服务端口     | 供其他程序连接的端口号                 |\n| base_dir     | 应用安装目录 |                                        |\n| log_dir      | 应用日志目录 | 服务的日志采集会采集该目录下*.log 文件 |\n| data_dir     | 应用数据目录 |                                        |\n| username     | 用户名       |                                        |\n| password     | 密码         |                                        |\n\n```yaml\n# 类型定义，发布基础组件时 ,指定类型为 component （类型：string）\nkind: component\n# 组件在平台显示的名称，请与组件目录名称保持一致，建议字符：英文、数字、_ （类型：string）\nname: mysql\n# 上传后显示的组件版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion: 5.7.31\n# 组件描述信息，建议长度256字符之内，请针对组件书写贴切的描述文字 （类型：string）\ndescription: \"MySQL是一个关系型数据库管理系统，由瑞典MySQL AB 公司开发，属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一，在 WEB 应用方面，MySQL是最好的 RDBMS (Relational Database Management System，关系数据库管理系统) 应用软件之一。\"\n# 组件所属标签，请针对组件功能设置准确标签，平台会针对该标签对组件进行分类，（类型：list[string,string...]）\nlabels:\n  - 数据库\n# 指定该服务安装后是否需要启动 （类型：boolean)\nauto_launch: false\n# 指定组件是否为基础环境组件，如 jdk, 该类组件以基础环境方式安装 （类型：boolean)\nbase_env: flase\n# 定义组件所需端口号，如不启用端口，可留空 （类型：list[map,map...])\nports:\n    # 端口描述名称，用户在安装时会以该名称显示表单内容（类型：string)\n  - name: 服务端口\n    # 端口协议，支持 TCP/UDP\n    protocol: TCP\n    # 端口英文描述名称，该key会传入到安装脚本中 （类型： string）支持（英文、数字、_)\n    key: service_port  # 注：service_port 为保留关键词，表示 为 提供服务的端口\n    # 组件的默认端口号，在安装时，会以该值填入表单中（类型： int）\n    default: 3306\n# 组件监控相关配置，定义该组件在安装后如何监控 ，如果不需要监控可留空 （类型： map）\nmonitor:\n  # 监控进程名称，如“mysqld”，平台在发现mysqld进程不存在后，会发送告警提醒 ，不需要监控可留空（类型：string）\n  process_name:  \"mysqld\"\n  # 监控端口号，请根据 ports 中的变量设置，不需要监控可留空 （类型： {string}）\n  metric_port: {service_port}\n---\n# 设置集群模式方式，如果组件需要支持多种方式安装，可以在该字段中定义，如只支持单个实例安装，可留空（类型：map[list[map,map...]])\ndeploy:\n\t# 定义单实例模式安装 （类型：list[map,map...])\n  single:\n  \t\t# 部署方式的中文描述名称，该值会在表单中选择集群模式时显示 （类型：string）\n    - name: 单实例\n    \t# 该模式的key值 （类型：string）\n      key: single\n  # 定义多种集群模式安装 （类型：list[map,map...])\n  complex:\n  \t\t# 部署方式的中文描述名称，该值会在表单中选择集群模式时显示 （类型：string）\n    - name: 主从模式\n    \t# 该模式的key值 （类型：string）\n      key: master_slave\n      # 集群节点设置 （类型： map）\n      nodes:\n        # 初始节点数量 （类型：int）\n        start: 2\n        # 增加节点步长 （类型：int）\n        step: 1\n# 定义该组件安装所需依赖组件名称与版本,如不需其他组件依赖，可留空 （类型： list[map,map..])\n#例：\n#dependencies:\n#  - name: jdk\n#    version: 8u223\ndependencies:\n# 该组件所需最小资源需求 （类型：map)\nresources:\n\t# cpu最小需求 ，1000m 表示 1核  （类型：string）\n  cpu: 1000m\n  # 内存最小需求， 500m 表示 500兆内存 （类型：string）\n  memory: 500m\n\n---\n# 定义安装组件时所需参数，该参数会传入到 安装脚本中 （类型：list[map,map...]）\ninstall:\n\t\t# 传入参数中文描述名称，该名称会在用户安装组件时显示到表单中 （类型： string）\n  - name: \"安装目录\"\n  \t# 传入参数key值，会将该key与值 传入到安装脚本中 （类型：string）\n    key: base_dir\n    # 上面key默认值 （类型： stirng）\n    default: \"{data_path}/mysql\"  # 注： {data_path} 为主机数据目录占位符，请勿使用其他代替\n  - name: \"数据目录\"\n    key: data_dir\n    default: \"{data_path}/mysql/data\"\n  - name: \"日志目录\"\n    key: log_dir\n    default: \"{data_path}/mysql/log\"\n  - name: \"用户名\"\n    key: username\n    default: root\n  - name: \"密码\"\n    key: password\n    default: \"123456\"\n# 程序控制脚本与服务目录的相对路径 （类型：map）\ncontrol:\n\t# 启动脚本路径，如没有可留空 （类型：string）\n  start: \"./scripts/mysql start\"\n  # 停止脚本路径，如没有可留空 （类型：stirng）\n  stop: \"./scripts/mysql stop\"\n  # 重启脚本路径，如没有可留空 （类型：stirng）\n  restart: \"./scripts/mysql restart\"\n  # 重载脚本路径，如没有可留空 （类型：stirng）\n  reload:\n  # 安装脚本路径，必填 （类型：stirng）\n  install: \"./scripts/install.py\"\n  # 初始化脚本路径，必填 （类型：stirng）\n  init:  \"./scripts/init.py\"\n```\n\n### 2.4. 安装脚本编写说明\n\n在安装包成功发布后，可通过平台进行安装，平台会调用配置文件中指定的安装脚本进行程序安装，平台将会把安装脚本所需参数以如下形式进行传参，需要脚本在编写时对此进行支持。\n\n传参示例：\n\n```shell\n$ python ./scripts/install.py --local_ip 192.168.1.2 --data_json /data/LKJD82JDL.json\n```\n\n其中 local_ip 为安装主机的IP地址，data_json为安装所需数据文件路径\n\n安装脚本需要根据data_json内数据进行组件的安装、替换其他文件内的占位符\n\ndata.json示例:\n\n```json\n[\n    {\n        \"name\":\"nacos\",\n        \"ip\":\"1.1.1.1\",\n        \"version\":\"2.0.1\",\n        \"ports\":[\n            {\n                \"key\":\"service_port\",\n                \"name\":\"xxx端口\",\n                \"default\":8001\n            }\n        ],\n        \"install_arg\":[\n            {\n                \"key\":\"base_dir\",\n                \"name\":\"服务目录\",\n                \"default\":\"/data/app/nacos\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"数据目录\",\n                \"default\":\"/data/appData/nacos\"\n            },\n            {\n                \"key\":\"username\",\n                \"name\":\"用户名\",\n                \"default\":\"admin\"\n            },\n            {\n                \"key\":\"password\",\n                \"name\":\"密码\",\n                \"default\":\"admin123\"\n            }\n        ],\n        \"deploy_mode\":{\n\n        },\n        \"cluster_name\":\"\",\n        \"instance_name\":\"nacos-1\",\n        \"dependence\":[\n            {\n                \"name\":\"mysql\",\n                \"instance_name\":\"mysql-100\",\n                \"cluster_name\":\"mysql-JDLK3KA\"\n            }\n        ]\n    },\n    {\n        \"name\":\"mysql\",\n        \"ip\":\"192.1.2.3\",\n        \"version\":\"5.0.1\",\n        \"ports\":[\n            {\n                \"key\":\"service_port\",\n                \"name\":\"服务端口\",\n                \"default\":10601\n            }\n        ],\n        \"install_arg\":[\n            {\n                \"key\":\"base_dir\",\n                \"name\":\"服务目录\",\n                \"default\":\"/data/app/mysql\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"数据目录\",\n                \"default\":\"/data/appData/mysql\"\n            },\n            {\n                \"key\":\"data_dir\",\n                \"name\":\"日志目录\",\n                \"default\":\"/data/appData/log\"\n            },\n            {\n                \"key\":\"username\",\n                \"name\":\"用户名\",\n                \"default\":\"root\"\n            },\n            {\n                \"key\":\"password\",\n                \"name\":\"密码\",\n                \"default\":\"root123\"\n            }\n        ],\n        \"deploy_mode\":{\n\n        },\n        \"cluster_name\":\"\",\n        \"instance_name\":\"mysql-100\",\n        \"dependence\":[\n\n        ]\n    }\n]\n```\n\n\n\n## 3. 应用服务打包规范\n\n### 3.1. 目录规范\n\n在发布类别为应用服务的产品时，需要将产品名称、所属产品的服务名称、版本号做到全局统一\n\n**目录示例：**\n\n发布产品名称为“omp\",其中包含 3个服务为“omp_server\",\"omp_web\",\"omp_component\" 的目录结构如下\n\n```shell\n$ tree omp\nomp\n├── omp.svg # 定义产品图标，会在平台中展示，如果不创建则平台会展示缺省图标\n├── omp     # 定义产品下服务配置文件目录，将所需服务的配置文件存在该目录\n│   ├── omp_server.yaml  # 服务 omp_server 配置文件，文件名需要与服务名称一致\n│   ├── omp_web.yaml  # 服务 omp_web 配置文件，文件名需要与服务名称一致\n│   └── omp_component.yaml  # 服务 omp_agent 配置文件，文件名需要与服务名称一致\n├── omp_server-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_server 压缩包，以“-” 为分隔符，第一个为服务名称，需要与服务名称一致，格式为 {service_name}-{service_version}-{others}-{package_md5}.tar.gz\n├── omp_web-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_web 压缩包\n├── omp_component-0.1.0-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务 omp_agent 压缩包\n└── omp.yaml        # 定义产品配置文件，文件名需要与产品名称一致\n```\n\n其中服务目录以omp_server为例：\n\n```shell\n$ tree omp_server\nomp_server    # 服务包解压后目录名称，与服务名一致\n├── bin     # 服务控制脚本目录，启动、停止等\n│   └── omp_server    # 服务控制脚本，与服务名称一致\n├── omp_server.yaml    # 服务配置文件，与产品包中保持一致\n└── scripts            # 安装、升级脚本目录\n    ├── init.py        # 初始化脚本\n    ├── install.py     # 安装脚本\n    └── update.py      # 升级脚本\n```\n\n### 3.2. 压缩包命名规范\n\n请使用 `{name}-{version}-{others}-{package_md5}.tar.gz`  格式进行打包命名\n\n1. name:   安装包名称，建议字符:  `英文`  `数字`   `_`\n2. version: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n3. others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n4. package_md5:  安装包MD5 值\n\n例如： omp-0.1.0-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz\n\n### 3.3. 配置文件yaml说明\n\n发布类别为应用服务的产品时，需分别对 产品配置文件和产品下服务配置文件进行配置\n\n#### 3.3.1. 产品配置文件（yaml）格式说明\n\n```yaml\n# 类型定义，发布应用服务时,产品指定类型为 product （类型：string）\nkind: product\n# 定义产品名称，此名称需要与产品目录名称、产品配置文件名称保持一致，建议字符：英文、数字、_  （类型: string)\nname: omp\n# 上传后显示的产品版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion:\n# 产品描述信息，建议长度256字符之内，请针对产品书写贴切的描述文字 （类型：string）\ndescription: \"运维管理平台（OperationManagementPlatform，以下简称OMP）以管理服务为中心，为服务的安装、管理提供便捷可靠的方式。\"\n# 组件所属标签，请针对组件功能设置准确标签，平台会针对该标签对组件进行分类，（类型：list[string,string...]）\nlabels:\n  - omp\n# 定义该产品安装所需依赖产品名称与版本,如不需其他产品依赖，可留空 （类型： list[map,map..])\ndependencies:\n\n# 定义该产品下包含的服务信息，请确保列表中的服务包都包含在目录中，并且名称保持一致 （类型： list[map,map...])\nservice:\n    # 包含服务名称，请与服务包名保持一致 （类型： string）\n  - name: omp_server\n    # 服务版本，请与服务包版本一致 （类型：string）\n    version: 0.1.0\n  - name: omp_web\n    version: 0.1.0\n  - name: omp_component\n    version: 0.1.0\n```\n\n#### 3.3.2. 服务配置文件（yaml）格式说明\n\n```yaml\n# 类型定义，发布应用服务时,产品包含的服务指定类型为 service （类型：string）\nkind: service\n# 服务在平台显示的名称，请与服务目录名称保持一致，建议字符：英文、数字、_ （类型：string）\nname: omp_server\n# 上传后显示的服务版本，建议字符： 数字、字母、_ 、. （类型：string）\nversion: 0.1.0\n# 服务描述信息，建议长度256字符之内，请针对组件书写贴切的描述文字 （类型：string）\ndescription: \"服务描述内容...\"\n# 指定该服务安装后是否需要启动 （类型：boolean)\nauto_launch: true\n# 指定服务是否为基础环境组件，如 jdk, 该类组件以基础环境方式安装 （类型：boolean)\nbase_env: flase\n# 定义服务所需端口号，如不启用端口，可留空 （类型：list[map,map...])\nports:\n    # 端口描述名称，用户在安装时会以该名称显示表单内容（类型：string)\n  - name: 服务端口\n    # 端口协议，支持 TCP/UDP\n    protocol: TCP\n    # 端口英文描述名称，该key会传入到安装脚本中 （类型： string）支持（英文、数字、_)\n    key: service_port  # 注：service_port 为保留关键词，表示 为 提供服务的端口\n    # 组件的默认端口号，在安装时，会以该值填入表单中（类型： int）\n    default: 19001\n# 服务监控相关配置，定义该服务在安装后如何监控 ，如果不需要监控可留空 （类型： map）\nmonitor:\n  # 监控进程名称，如“service_a”，平台在发现service_a进程不存在后，会发送告警提醒,不需要监控可留空（类型：string）\n  process_name:  \"\"\n  # 监控端口号，请根据 ports 中的变量设置，不需要监控可留空 （类型： {string}）\n  metric_port: {service_port}\n---\n# 定义该组件安装所需依赖组件名称与版本,如不需其他组件依赖，可留空 （类型： list[map,map..])\ndependencies:\n  - name: mysql\n    version: 5.7.31\n  - name: redis\n    version: 5.0.1\n  - name: python\n    version: 3.8.3\n# 该组件所需最小资源需求 （类型：map)\nresources:\n  # cpu最小需求 ，1000m 表示 1核  （类型：string）\n  cpu: 1000m\n  # 内存最小需求， 500m 表示 500兆内存 （类型：string）\n  memory: 500m\n---\n# 定义安装组件时所需参数，该参数会传入到 安装脚本中 （类型：list[map,map...]）\ninstall:\n    # 传入参数中文描述名称，该名称会在用户安装组件时显示到表单中 （类型： string）\n  - name: \"安装目录\"\n    # 传入参数key值，会将该key与值 传入到安装脚本中 （类型：string）\n    key: base_dir\n    # 上面key默认值 （类型： stirng）\n    default: \"{data_path}/omp_server\"  # 注： {data_path} 为主机数据目录占位符，请勿使用其他代替\n#  - name: \"JVM设置\"\n#    key: jvm\n#    default: \"-XX:MaxPermSize=512m -Djava.awt.headless=true\"\n# 程序控制脚本与服务目录的相对路径 （类型：map）\ncontrol:\n        # 启动脚本路径，如没有可留空,脚本名称建议与服务名称一致  （类型：string）\n  start: \"./bin/omp_server start\"\n  # 停止脚本路径，如没有可留空，脚本名称建议与服务名称一致 （类型：stirng）\n  stop: \"./bin/omp_server stop\"\n  # 重启脚本路径，如没有可留空，脚本名称建议与服务名称一致 （类型：stirng）\n  restart: \"./bin/omp_server restart\"\n  # 重载脚本路径，如没有可留空 （类型：stirng）\n  reload:\n  # 安装脚本路径，必填 （类型：stirng）\n  install: \"./scripts/install.py\"\n  # 初始化脚本路径，必填 （类型：stirng）\n  init:  \"./scripts/init.py\"\n```"
  },
  {
    "path": "package_hub/template/inspection_html/asset-manifest.json",
    "content": "{\n  \"files\": {\n    \"main.css\": \"/static/css/main.041ca26a.chunk.css\",\n    \"main.js\": \"/static/js/main.e4ade54a.chunk.js\",\n    \"main.js.map\": \"/static/js/main.e4ade54a.chunk.js.map\",\n    \"runtime-main.js\": \"/static/js/runtime-main.da7bcbe2.js\",\n    \"runtime-main.js.map\": \"/static/js/runtime-main.da7bcbe2.js.map\",\n    \"static/css/2.8ca66de9.chunk.css\": \"/static/css/2.8ca66de9.chunk.css\",\n    \"static/js/2.0ca9bd94.chunk.js\": \"/static/js/2.0ca9bd94.chunk.js\",\n    \"static/js/2.0ca9bd94.chunk.js.map\": \"/static/js/2.0ca9bd94.chunk.js.map\",\n    \"index.html\": \"/index.html\",\n    \"static/css/2.8ca66de9.chunk.css.map\": \"/static/css/2.8ca66de9.chunk.css.map\",\n    \"static/css/main.041ca26a.chunk.css.map\": \"/static/css/main.041ca26a.chunk.css.map\",\n    \"static/js/2.0ca9bd94.chunk.js.LICENSE.txt\": \"/static/js/2.0ca9bd94.chunk.js.LICENSE.txt\",\n    \"static/media/index.02867153.less\": \"/static/media/index.02867153.less\",\n    \"static/media/index.2041a1d4.less\": \"/static/media/index.2041a1d4.less\",\n    \"static/media/index.2f186d27.less\": \"/static/media/index.2f186d27.less\",\n    \"static/media/index.32dc937e.less\": \"/static/media/index.32dc937e.less\",\n    \"static/media/index.383af9c4.less\": \"/static/media/index.383af9c4.less\",\n    \"static/media/index.51825487.less\": \"/static/media/index.51825487.less\",\n    \"static/media/index.60c6e3ea.less\": \"/static/media/index.60c6e3ea.less\",\n    \"static/media/index.67101e84.less\": \"/static/media/index.67101e84.less\",\n    \"static/media/index.68b48da1.less\": \"/static/media/index.68b48da1.less\",\n    \"static/media/index.73987a8f.less\": \"/static/media/index.73987a8f.less\",\n    \"static/media/index.8372475c.less\": \"/static/media/index.8372475c.less\",\n    \"static/media/index.85c775e4.less\": \"/static/media/index.85c775e4.less\",\n    \"static/media/index.8c12967b.less\": \"/static/media/index.8c12967b.less\",\n    \"static/media/index.976fe83e.less\": \"/static/media/index.976fe83e.less\",\n    \"static/media/index.cae8fdaf.less\": \"/static/media/index.cae8fdaf.less\",\n    \"static/media/index.d15ddbc9.less\": \"/static/media/index.d15ddbc9.less\",\n    \"static/media/index.d61ddb9a.less\": \"/static/media/index.d61ddb9a.less\",\n    \"static/media/index.e1e14bcc.less\": \"/static/media/index.e1e14bcc.less\",\n    \"static/media/index.less\": \"/static/media/index.e90871b5.less\",\n    \"static/media/index.module.b57695f6.less\": \"/static/media/index.module.b57695f6.less\"\n  },\n  \"entrypoints\": [\n    \"static/js/runtime-main.da7bcbe2.js\",\n    \"static/css/2.8ca66de9.chunk.css\",\n    \"static/js/2.0ca9bd94.chunk.js\",\n    \"static/css/main.041ca26a.chunk.css\",\n    \"static/js/main.e4ade54a.chunk.js\"\n  ]\n}"
  },
  {
    "path": "package_hub/template/inspection_html/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n    <title>云智慧</title>\n    <meta name=\"keywords\" content=\"cloudwise,front-end\" />\n    <meta name=\"description\" content=\"front-end\" />\n    <meta name=\"author\" content=\"cloudwiser\" />\n    <link href=\"./static/css/2.8ca66de9.chunk.css\" rel=\"stylesheet\" />\n    <link href=\"./static/css/main.041ca26a.chunk.css\" rel=\"stylesheet\" />\n  </head>\n  <body>\n    <div id=\"root\" style=\"height: 100%\"></div>\n    <script>\n      !(function (e) {\n        function t(t) {\n          for (\n            var n, f, l = t[0], p = t[1], i = t[2], c = 0, s = [];\n            c < l.length;\n            c++\n          )\n            (f = l[c]),\n              Object.prototype.hasOwnProperty.call(o, f) &&\n                o[f] &&\n                s.push(o[f][0]),\n              (o[f] = 0);\n          for (n in p)\n            Object.prototype.hasOwnProperty.call(p, n) && (e[n] = p[n]);\n          for (a && a(t); s.length; ) s.shift()();\n          return u.push.apply(u, i || []), r();\n        }\n        function r() {\n          for (var e, t = 0; t < u.length; t++) {\n            for (var r = u[t], n = !0, l = 1; l < r.length; l++) {\n              var p = r[l];\n              0 !== o[p] && (n = !1);\n            }\n            n && (u.splice(t--, 1), (e = f((f.s = r[0]))));\n          }\n          return e;\n        }\n        var n = {},\n          o = { 1: 0 },\n          u = [];\n        function f(t) {\n          if (n[t]) return n[t].exports;\n          var r = (n[t] = { i: t, l: !1, exports: {} });\n          return e[t].call(r.exports, r, r.exports, f), (r.l = !0), r.exports;\n        }\n        (f.m = e),\n          (f.c = n),\n          (f.d = function (e, t, r) {\n            f.o(e, t) ||\n              Object.defineProperty(e, t, { enumerable: !0, get: r });\n          }),\n          (f.r = function (e) {\n            \"undefined\" != typeof Symbol &&\n              Symbol.toStringTag &&\n              Object.defineProperty(e, Symbol.toStringTag, { value: \"Module\" }),\n              Object.defineProperty(e, \"__esModule\", { value: !0 });\n          }),\n          (f.t = function (e, t) {\n            if ((1 & t && (e = f(e)), 8 & t)) return e;\n            if (4 & t && \"object\" == typeof e && e && e.__esModule) return e;\n            var r = Object.create(null);\n            if (\n              (f.r(r),\n              Object.defineProperty(r, \"default\", { enumerable: !0, value: e }),\n              2 & t && \"string\" != typeof e)\n            )\n              for (var n in e)\n                f.d(\n                  r,\n                  n,\n                  function (t) {\n                    return e[t];\n                  }.bind(null, n)\n                );\n            return r;\n          }),\n          (f.n = function (e) {\n            var t =\n              e && e.__esModule\n                ? function () {\n                    return e.default;\n                  }\n                : function () {\n                    return e;\n                  };\n            return f.d(t, \"a\", t), t;\n          }),\n          (f.o = function (e, t) {\n            return Object.prototype.hasOwnProperty.call(e, t);\n          }),\n          (f.p = \"/\");\n        var l = (this[\"webpackJsonpomp-fontend\"] =\n            this[\"webpackJsonpomp-fontend\"] || []),\n          p = l.push.bind(l);\n        (l.push = t), (l = l.slice());\n        for (var i = 0; i < l.length; i++) t(l[i]);\n        var a = p;\n        r();\n      })([]);\n    </script>\n    <script src=\"./static/js/2.0ca9bd94.chunk.js\"></script>\n    <script src=\"./static/js/main.e4ade54a.chunk.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/css/2.8ca66de9.chunk.css",
    "content": "/*!\n * \n * antd v3.26.18\n * \n * Copyright 2015-present, Alipay, Inc.\n * All rights reserved.\n *       \n */body,html{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;color:rgba(0,0,0,.65);font-size:14px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\";font-variant:tabular-nums;line-height:1.5;background-color:#fff;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}[tabindex=\"-1\"]:focus{outline:none!important}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=number],input[type=password],input[type=text],textarea{-webkit-appearance:none}dl,ol,ul{margin-top:0;margin-bottom:1em}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#1890ff;text-decoration:none;background-color:transparent;outline:none;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;-webkit-text-decoration-skip:objects}a:hover{color:#40a9ff}a:active{color:#096dd9}a:active,a:hover{text-decoration:none;outline:0}a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}code,kbd,pre,samp{font-size:1em;font-family:\"SFMono-Regular\",Consolas,\"Liberation Mono\",Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;color:rgba(0,0,0,.45);text-align:left;caption-side:bottom}th{text-align:inherit}button,input,optgroup,select,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}::-moz-selection{color:#fff;background:#1890ff}::selection{color:#fff;background:#1890ff}.clearfix{zoom:1}.clearfix:after,.clearfix:before{display:table;content:\"\"}.clearfix:after{clear:both}.anticon{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.anticon>*{line-height:1}.anticon svg{display:inline-block}.anticon:before{display:none}.anticon .anticon-icon{display:block}.anticon[tabindex]{cursor:pointer}.anticon-spin,.anticon-spin:before{display:inline-block;-webkit-animation:loadingCircle 1s linear infinite;animation:loadingCircle 1s linear infinite}.fade-appear,.fade-enter,.fade-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.fade-appear.fade-appear-active,.fade-enter.fade-enter-active{-webkit-animation-name:antFadeIn;animation-name:antFadeIn;-webkit-animation-play-state:running;animation-play-state:running}.fade-leave.fade-leave-active{-webkit-animation-name:antFadeOut;animation-name:antFadeOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.fade-appear,.fade-enter{opacity:0}.fade-appear,.fade-enter,.fade-leave{-webkit-animation-timing-function:linear;animation-timing-function:linear}@-webkit-keyframes antFadeIn{0%{opacity:0}to{opacity:1}}@keyframes antFadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes antFadeOut{0%{opacity:1}to{opacity:0}}@keyframes antFadeOut{0%{opacity:1}to{opacity:0}}.move-up-appear,.move-up-enter,.move-up-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.move-up-appear.move-up-appear-active,.move-up-enter.move-up-enter-active{-webkit-animation-name:antMoveUpIn;animation-name:antMoveUpIn;-webkit-animation-play-state:running;animation-play-state:running}.move-up-leave.move-up-leave-active{-webkit-animation-name:antMoveUpOut;animation-name:antMoveUpOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.move-up-appear,.move-up-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-up-leave{-webkit-animation-timing-function:cubic-bezier(.6,.04,.98,.34);animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-down-appear,.move-down-enter,.move-down-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.move-down-appear.move-down-appear-active,.move-down-enter.move-down-enter-active{-webkit-animation-name:antMoveDownIn;animation-name:antMoveDownIn;-webkit-animation-play-state:running;animation-play-state:running}.move-down-leave.move-down-leave-active{-webkit-animation-name:antMoveDownOut;animation-name:antMoveDownOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.move-down-appear,.move-down-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-down-leave{-webkit-animation-timing-function:cubic-bezier(.6,.04,.98,.34);animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-left-appear,.move-left-enter,.move-left-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.move-left-appear.move-left-appear-active,.move-left-enter.move-left-enter-active{-webkit-animation-name:antMoveLeftIn;animation-name:antMoveLeftIn;-webkit-animation-play-state:running;animation-play-state:running}.move-left-leave.move-left-leave-active{-webkit-animation-name:antMoveLeftOut;animation-name:antMoveLeftOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.move-left-appear,.move-left-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-left-leave{-webkit-animation-timing-function:cubic-bezier(.6,.04,.98,.34);animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-right-appear,.move-right-enter,.move-right-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.move-right-appear.move-right-appear-active,.move-right-enter.move-right-enter-active{-webkit-animation-name:antMoveRightIn;animation-name:antMoveRightIn;-webkit-animation-play-state:running;animation-play-state:running}.move-right-leave.move-right-leave-active{-webkit-animation-name:antMoveRightOut;animation-name:antMoveRightOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.move-right-appear,.move-right-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-right-leave{-webkit-animation-timing-function:cubic-bezier(.6,.04,.98,.34);animation-timing-function:cubic-bezier(.6,.04,.98,.34)}@-webkit-keyframes antMoveDownIn{0%{-webkit-transform:translateY(100%);transform:translateY(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antMoveDownIn{0%{-webkit-transform:translateY(100%);transform:translateY(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antMoveDownOut{0%{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateY(100%);transform:translateY(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antMoveDownOut{0%{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateY(100%);transform:translateY(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes antMoveLeftIn{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antMoveLeftIn{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antMoveLeftOut{0%{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antMoveLeftOut{0%{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateX(-100%);transform:translateX(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes antMoveRightIn{0%{-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antMoveRightIn{0%{-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antMoveRightOut{0%{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antMoveRightOut{0%{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes antMoveUpIn{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antMoveUpIn{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antMoveUpOut{0%{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateY(-100%);transform:translateY(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antMoveUpOut{0%{-webkit-transform:translateY(0);transform:translateY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:translateY(-100%);transform:translateY(-100%);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes loadingCircle{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes loadingCircle{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}[ant-click-animating-without-extra-node=true],[ant-click-animating=true]{position:relative}html{--antd-wave-shadow-color:#1890ff}.ant-click-animating-node,[ant-click-animating-without-extra-node=true]:after{position:absolute;top:0;right:0;bottom:0;left:0;display:block;border-radius:inherit;-webkit-box-shadow:0 0 0 0 #1890ff;-webkit-box-shadow:0 0 0 0 var(--antd-wave-shadow-color);box-shadow:0 0 0 0 #1890ff;box-shadow:0 0 0 0 var(--antd-wave-shadow-color);opacity:.2;-webkit-animation:fadeEffect 2s cubic-bezier(.08,.82,.17,1),waveEffect .4s cubic-bezier(.08,.82,.17,1);animation:fadeEffect 2s cubic-bezier(.08,.82,.17,1),waveEffect .4s cubic-bezier(.08,.82,.17,1);-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;content:\"\";pointer-events:none}@-webkit-keyframes waveEffect{to{-webkit-box-shadow:0 0 0 #1890ff;box-shadow:0 0 0 #1890ff;-webkit-box-shadow:0 0 0 6px #1890ff;-webkit-box-shadow:0 0 0 6px var(--antd-wave-shadow-color);box-shadow:0 0 0 6px #1890ff;box-shadow:0 0 0 6px var(--antd-wave-shadow-color)}}@keyframes waveEffect{to{-webkit-box-shadow:0 0 0 #1890ff;box-shadow:0 0 0 #1890ff;-webkit-box-shadow:0 0 0 6px #1890ff;-webkit-box-shadow:0 0 0 6px var(--antd-wave-shadow-color);box-shadow:0 0 0 6px #1890ff;box-shadow:0 0 0 6px var(--antd-wave-shadow-color)}}@-webkit-keyframes fadeEffect{to{opacity:0}}@keyframes fadeEffect{to{opacity:0}}.slide-up-appear,.slide-up-enter,.slide-up-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.slide-up-appear.slide-up-appear-active,.slide-up-enter.slide-up-enter-active{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn;-webkit-animation-play-state:running;animation-play-state:running}.slide-up-leave.slide-up-leave-active{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.slide-up-appear,.slide-up-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.23,1,.32,1);animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-up-leave{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-down-appear,.slide-down-enter,.slide-down-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.slide-down-appear.slide-down-appear-active,.slide-down-enter.slide-down-enter-active{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn;-webkit-animation-play-state:running;animation-play-state:running}.slide-down-leave.slide-down-leave-active{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.slide-down-appear,.slide-down-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.23,1,.32,1);animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-down-leave{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-left-appear,.slide-left-enter,.slide-left-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.slide-left-appear.slide-left-appear-active,.slide-left-enter.slide-left-enter-active{-webkit-animation-name:antSlideLeftIn;animation-name:antSlideLeftIn;-webkit-animation-play-state:running;animation-play-state:running}.slide-left-leave.slide-left-leave-active{-webkit-animation-name:antSlideLeftOut;animation-name:antSlideLeftOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.slide-left-appear,.slide-left-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.23,1,.32,1);animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-left-leave{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-right-appear,.slide-right-enter,.slide-right-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.slide-right-appear.slide-right-appear-active,.slide-right-enter.slide-right-enter-active{-webkit-animation-name:antSlideRightIn;animation-name:antSlideRightIn;-webkit-animation-play-state:running;animation-play-state:running}.slide-right-leave.slide-right-leave-active{-webkit-animation-name:antSlideRightOut;animation-name:antSlideRightOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.slide-right-appear,.slide-right-enter{opacity:0;-webkit-animation-timing-function:cubic-bezier(.23,1,.32,1);animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-right-leave{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}@-webkit-keyframes antSlideUpIn{0%{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antSlideUpIn{0%{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antSlideUpOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antSlideUpOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes antSlideDownIn{0%{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:1}}@keyframes antSlideDownIn{0%{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:1}}@-webkit-keyframes antSlideDownOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:1}to{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:0}}@keyframes antSlideDownOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:1}to{-webkit-transform:scaleY(.8);transform:scaleY(.8);-webkit-transform-origin:100% 100%;transform-origin:100% 100%;opacity:0}}@-webkit-keyframes antSlideLeftIn{0%{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antSlideLeftIn{0%{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antSlideLeftOut{0%{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antSlideLeftOut{0%{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@-webkit-keyframes antSlideRightIn{0%{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:0}to{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:1}}@keyframes antSlideRightIn{0%{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:0}to{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:1}}@-webkit-keyframes antSlideRightOut{0%{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:1}to{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:0}}@keyframes antSlideRightOut{0%{-webkit-transform:scaleX(1);transform:scaleX(1);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:1}to{-webkit-transform:scaleX(.8);transform:scaleX(.8);-webkit-transform-origin:100% 0;transform-origin:100% 0;opacity:0}}.swing-appear,.swing-enter{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.swing-appear.swing-appear-active,.swing-enter.swing-enter-active{-webkit-animation-name:antSwingIn;animation-name:antSwingIn;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes antSwingIn{0%,to{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}40%{-webkit-transform:translateX(10px);transform:translateX(10px)}60%{-webkit-transform:translateX(-5px);transform:translateX(-5px)}80%{-webkit-transform:translateX(5px);transform:translateX(5px)}}@keyframes antSwingIn{0%,to{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}40%{-webkit-transform:translateX(10px);transform:translateX(10px)}60%{-webkit-transform:translateX(-5px);transform:translateX(-5px)}80%{-webkit-transform:translateX(5px);transform:translateX(5px)}}.zoom-appear,.zoom-enter,.zoom-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-appear.zoom-appear-active,.zoom-enter.zoom-enter-active{-webkit-animation-name:antZoomIn;animation-name:antZoomIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-leave.zoom-leave-active{-webkit-animation-name:antZoomOut;animation-name:antZoomOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-appear,.zoom-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-appear,.zoom-big-enter,.zoom-big-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-big-appear.zoom-big-appear-active,.zoom-big-enter.zoom-big-enter-active{-webkit-animation-name:antZoomBigIn;animation-name:antZoomBigIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-big-leave.zoom-big-leave-active{-webkit-animation-name:antZoomBigOut;animation-name:antZoomBigOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-big-appear,.zoom-big-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-fast-appear,.zoom-big-fast-enter,.zoom-big-fast-leave{-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-big-fast-appear.zoom-big-fast-appear-active,.zoom-big-fast-enter.zoom-big-fast-enter-active{-webkit-animation-name:antZoomBigIn;animation-name:antZoomBigIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-big-fast-leave.zoom-big-fast-leave-active{-webkit-animation-name:antZoomBigOut;animation-name:antZoomBigOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-big-fast-appear,.zoom-big-fast-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-fast-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-up-appear,.zoom-up-enter,.zoom-up-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-up-appear.zoom-up-appear-active,.zoom-up-enter.zoom-up-enter-active{-webkit-animation-name:antZoomUpIn;animation-name:antZoomUpIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-up-leave.zoom-up-leave-active{-webkit-animation-name:antZoomUpOut;animation-name:antZoomUpOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-up-appear,.zoom-up-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-up-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-down-appear,.zoom-down-enter,.zoom-down-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-down-appear.zoom-down-appear-active,.zoom-down-enter.zoom-down-enter-active{-webkit-animation-name:antZoomDownIn;animation-name:antZoomDownIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-down-leave.zoom-down-leave-active{-webkit-animation-name:antZoomDownOut;animation-name:antZoomDownOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-down-appear,.zoom-down-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-down-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-left-appear,.zoom-left-enter,.zoom-left-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-left-appear.zoom-left-appear-active,.zoom-left-enter.zoom-left-enter-active{-webkit-animation-name:antZoomLeftIn;animation-name:antZoomLeftIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-left-leave.zoom-left-leave-active{-webkit-animation-name:antZoomLeftOut;animation-name:antZoomLeftOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-left-appear,.zoom-left-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-left-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-right-appear,.zoom-right-enter,.zoom-right-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.zoom-right-appear.zoom-right-appear-active,.zoom-right-enter.zoom-right-enter-active{-webkit-animation-name:antZoomRightIn;animation-name:antZoomRightIn;-webkit-animation-play-state:running;animation-play-state:running}.zoom-right-leave.zoom-right-leave-active{-webkit-animation-name:antZoomRightOut;animation-name:antZoomRightOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.zoom-right-appear,.zoom-right-enter{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-animation-timing-function:cubic-bezier(.08,.82,.17,1);animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-right-leave{-webkit-animation-timing-function:cubic-bezier(.78,.14,.15,.86);animation-timing-function:cubic-bezier(.78,.14,.15,.86)}@-webkit-keyframes antZoomIn{0%{-webkit-transform:scale(.2);transform:scale(.2);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes antZoomIn{0%{-webkit-transform:scale(.2);transform:scale(.2);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@-webkit-keyframes antZoomOut{0%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.2);transform:scale(.2);opacity:0}}@keyframes antZoomOut{0%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.2);transform:scale(.2);opacity:0}}@-webkit-keyframes antZoomBigIn{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes antZoomBigIn{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@-webkit-keyframes antZoomBigOut{0%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}}@keyframes antZoomBigOut{0%{-webkit-transform:scale(1);transform:scale(1)}to{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}}@-webkit-keyframes antZoomUpIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 0;transform-origin:50% 0;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 0;transform-origin:50% 0}}@keyframes antZoomUpIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 0;transform-origin:50% 0;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 0;transform-origin:50% 0}}@-webkit-keyframes antZoomUpOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 0;transform-origin:50% 0}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 0;transform-origin:50% 0;opacity:0}}@keyframes antZoomUpOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 0;transform-origin:50% 0}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 0;transform-origin:50% 0;opacity:0}}@-webkit-keyframes antZoomLeftIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 50%;transform-origin:0 50%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:0 50%;transform-origin:0 50%}}@keyframes antZoomLeftIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 50%;transform-origin:0 50%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:0 50%;transform-origin:0 50%}}@-webkit-keyframes antZoomLeftOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:0 50%;transform-origin:0 50%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 50%;transform-origin:0 50%;opacity:0}}@keyframes antZoomLeftOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:0 50%;transform-origin:0 50%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 50%;transform-origin:0 50%;opacity:0}}@-webkit-keyframes antZoomRightIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}}@keyframes antZoomRightIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}}@-webkit-keyframes antZoomRightOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;opacity:0}}@keyframes antZoomRightOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;opacity:0}}@-webkit-keyframes antZoomDownIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 100%;transform-origin:50% 100%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 100%;transform-origin:50% 100%}}@keyframes antZoomDownIn{0%{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 100%;transform-origin:50% 100%;opacity:0}to{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 100%;transform-origin:50% 100%}}@-webkit-keyframes antZoomDownOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 100%;transform-origin:50% 100%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 100%;transform-origin:50% 100%;opacity:0}}@keyframes antZoomDownOut{0%{-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:50% 100%;transform-origin:50% 100%}to{-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:50% 100%;transform-origin:50% 100%;opacity:0}}.ant-motion-collapse-legacy{overflow:hidden}.ant-motion-collapse,.ant-motion-collapse-legacy-active{-webkit-transition:height .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)!important;transition:height .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)!important}.ant-motion-collapse{overflow:hidden}.ant-affix{position:fixed;z-index:10}.ant-alert{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;padding:8px 15px 8px 37px;word-wrap:break-word;border-radius:4px}.ant-alert.ant-alert-no-icon{padding:8px 15px}.ant-alert.ant-alert-closable{padding-right:30px}.ant-alert-icon{position:absolute;top:11.5px;left:16px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#e6f7ff;border:1px solid #91d5ff}.ant-alert-info .ant-alert-icon{color:#1890ff}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff1f0;border:1px solid #ffa39e}.ant-alert-error .ant-alert-icon{color:#f5222d}.ant-alert-close-icon{position:absolute;top:8px;right:16px;padding:0;overflow:hidden;font-size:12px;line-height:22px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:rgba(0,0,0,.45);-webkit-transition:color .3s;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:rgba(0,0,0,.75)}.ant-alert-close-text{color:rgba(0,0,0,.45);-webkit-transition:color .3s;transition:color .3s}.ant-alert-close-text:hover{color:rgba(0,0,0,.75)}.ant-alert-with-description{position:relative;padding:15px 15px 15px 64px;color:rgba(0,0,0,.65);line-height:1.5;border-radius:4px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{position:absolute;top:16px;left:24px;font-size:24px}.ant-alert-with-description .ant-alert-close-icon{position:absolute;top:16px;right:16px;font-size:14px;cursor:pointer}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:rgba(0,0,0,.85);font-size:16px}.ant-alert-message{color:rgba(0,0,0,.85)}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-closing{height:0!important;margin:0;padding-top:0;padding-bottom:0;-webkit-transform-origin:50% 0;-ms-transform-origin:50% 0;transform-origin:50% 0;-webkit-transition:all .3s cubic-bezier(.78,.14,.15,.86);transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert-slide-up-leave{-webkit-animation:antAlertSlideUpOut .3s cubic-bezier(.78,.14,.15,.86);animation:antAlertSlideUpOut .3s cubic-bezier(.78,.14,.15,.86);-webkit-animation-fill-mode:both;animation-fill-mode:both}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}@-webkit-keyframes antAlertSlideUpIn{0%{-webkit-transform:scaleY(0);transform:scaleY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@keyframes antAlertSlideUpIn{0%{-webkit-transform:scaleY(0);transform:scaleY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}to{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}}@-webkit-keyframes antAlertSlideUpOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleY(0);transform:scaleY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}@keyframes antAlertSlideUpOut{0%{-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:1}to{-webkit-transform:scaleY(0);transform:scaleY(0);-webkit-transform-origin:0 0;transform-origin:0 0;opacity:0}}.ant-anchor{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;padding:0 0 0 2px}.ant-anchor-wrapper{margin-left:-4px;padding-left:4px;overflow:auto;background-color:#fff}.ant-anchor-ink{position:absolute;top:0;left:0;height:100%}.ant-anchor-ink:before{position:relative;display:block;width:2px;height:100%;margin:0 auto;background-color:#e8e8e8;content:\" \"}.ant-anchor-ink-ball{position:absolute;left:50%;display:none;width:8px;height:8px;background-color:#fff;border:2px solid #1890ff;border-radius:8px;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);-webkit-transition:top .3s ease-in-out;transition:top .3s ease-in-out}.ant-anchor-ink-ball.visible{display:inline-block}.ant-anchor.fixed .ant-anchor-ink .ant-anchor-ink-ball{display:none}.ant-anchor-link{padding:7px 0 7px 16px;line-height:1.143}.ant-anchor-link-title{position:relative;display:block;margin-bottom:6px;overflow:hidden;color:rgba(0,0,0,.65);white-space:nowrap;text-overflow:ellipsis;-webkit-transition:all .3s;transition:all .3s}.ant-anchor-link-title:only-child{margin-bottom:0}.ant-anchor-link-active>.ant-anchor-link-title{color:#1890ff}.ant-anchor-link .ant-anchor-link{padding-top:5px;padding-bottom:5px}.ant-select-auto-complete{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-select-auto-complete.ant-select .ant-select-selection{border:0;-webkit-box-shadow:none;box-shadow:none}.ant-select-auto-complete.ant-select .ant-select-selection__rendered{height:100%;margin-right:0;margin-left:0;line-height:32px}.ant-select-auto-complete.ant-select .ant-select-selection__placeholder{margin-right:12px;margin-left:12px}.ant-select-auto-complete.ant-select .ant-select-selection--single{height:auto}.ant-select-auto-complete.ant-select .ant-select-search--inline{position:static;float:left}.ant-select-auto-complete.ant-select-allow-clear .ant-select-selection:hover .ant-select-selection__rendered{margin-right:0!important}.ant-select-auto-complete.ant-select .ant-input{height:32px;line-height:1.5;background:transparent;border-width:1px}.ant-select-auto-complete.ant-select .ant-input:focus,.ant-select-auto-complete.ant-select .ant-input:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-select-auto-complete.ant-select .ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1;background-color:transparent}.ant-select-auto-complete.ant-select .ant-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-select-auto-complete.ant-select-lg .ant-select-selection__rendered{line-height:40px}.ant-select-auto-complete.ant-select-lg .ant-input{height:40px;padding-top:6px;padding-bottom:6px}.ant-select-auto-complete.ant-select-sm .ant-select-selection__rendered{line-height:24px}.ant-select-auto-complete.ant-select-sm .ant-input{height:24px;padding-top:1px;padding-bottom:1px}.ant-input-group>.ant-select-auto-complete .ant-select-search__field.ant-input-affix-wrapper{display:inline;float:none}.ant-select{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;outline:0}.ant-select,.ant-select ol,.ant-select ul{margin:0;padding:0;list-style:none}.ant-select>ul>li>a{padding:0;background-color:#fff}.ant-select-arrow{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;right:11px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:1;-webkit-transform-origin:50% 50%;-ms-transform-origin:50% 50%;transform-origin:50% 50%}.ant-select-arrow>*{line-height:1}.ant-select-arrow svg{display:inline-block}.ant-select-arrow:before{display:none}.ant-select-arrow .ant-select-arrow-icon{display:block}.ant-select-arrow .ant-select-arrow-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-select-selection{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-radius:4px;outline:none;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-select-selection:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-select-selection__clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;font-style:normal;line-height:12px;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;-webkit-transition:color .3s ease,opacity .15s ease;transition:color .3s ease,opacity .15s ease;text-rendering:auto}.ant-select-selection__clear:before{display:block}.ant-select-selection__clear:hover{color:rgba(0,0,0,.45)}.ant-select-selection:hover .ant-select-selection__clear{opacity:1}.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}.ant-select-disabled{color:rgba(0,0,0,.25)}.ant-select-disabled .ant-select-selection{background:#f5f5f5;cursor:not-allowed}.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;-webkit-box-shadow:none;box-shadow:none}.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice__remove{display:none}.ant-select-selection--single{position:relative;height:32px;cursor:pointer}.ant-select-selection--single .ant-select-selection__rendered{margin-right:24px}.ant-select-no-arrow .ant-select-selection__rendered{margin-right:11px}.ant-select-selection__rendered{position:relative;display:block;margin-right:11px;margin-left:11px;line-height:30px}.ant-select-selection__rendered:after{display:inline-block;width:0;visibility:hidden;content:\".\";pointer-events:none}.ant-select-lg{font-size:16px}.ant-select-lg .ant-select-selection--single{height:40px}.ant-select-lg .ant-select-selection__rendered{line-height:38px}.ant-select-lg .ant-select-selection--multiple{min-height:40px}.ant-select-lg .ant-select-selection--multiple .ant-select-selection__rendered li{height:32px;line-height:32px}.ant-select-lg .ant-select-selection--multiple .ant-select-arrow,.ant-select-lg .ant-select-selection--multiple .ant-select-selection__clear{top:20px}.ant-select-sm .ant-select-selection--single{height:24px}.ant-select-sm .ant-select-selection__rendered{margin-left:7px;line-height:22px}.ant-select-sm .ant-select-selection--multiple{min-height:24px}.ant-select-sm .ant-select-selection--multiple .ant-select-selection__rendered li{height:16px;line-height:14px}.ant-select-sm .ant-select-selection--multiple .ant-select-arrow,.ant-select-sm .ant-select-selection--multiple .ant-select-selection__clear{top:12px}.ant-select-sm .ant-select-arrow,.ant-select-sm .ant-select-selection__clear{right:8px}.ant-select-disabled .ant-select-selection__choice__remove{color:rgba(0,0,0,.25);cursor:default}.ant-select-disabled .ant-select-selection__choice__remove:hover{color:rgba(0,0,0,.25)}.ant-select-search__field__wrap{position:relative;display:inline-block}.ant-select-search__field__placeholder,.ant-select-selection__placeholder{position:absolute;top:50%;right:9px;left:0;max-width:100%;height:20px;margin-top:-10px;overflow:hidden;color:#bfbfbf;line-height:20px;white-space:nowrap;text-align:left;text-overflow:ellipsis}.ant-select-search__field__placeholder{left:12px}.ant-select-search__field__mirror{position:absolute;top:0;left:0;white-space:pre;opacity:0;pointer-events:none}.ant-select-search--inline{position:absolute;width:100%;height:100%}.ant-select-search--inline .ant-select-search__field__wrap{width:100%;height:100%}.ant-select-search--inline .ant-select-search__field{width:100%;height:100%;font-size:100%;line-height:1;background:transparent;border-width:0;border-radius:4px;outline:0}.ant-select-search--inline>i{float:right}.ant-select-selection--multiple{min-height:32px;padding-bottom:3px;cursor:text;zoom:1}.ant-select-selection--multiple:after,.ant-select-selection--multiple:before{display:table;content:\"\"}.ant-select-selection--multiple:after{clear:both}.ant-select-selection--multiple .ant-select-search--inline{position:static;float:left;width:auto;max-width:100%;padding:0}.ant-select-selection--multiple .ant-select-search--inline .ant-select-search__field{width:.75em;max-width:100%;padding:1px}.ant-select-selection--multiple .ant-select-selection__rendered{height:auto;margin-bottom:-3px;margin-left:5px}.ant-select-selection--multiple .ant-select-selection__placeholder{margin-left:6px}.ant-select-selection--multiple .ant-select-selection__rendered>ul>li,.ant-select-selection--multiple>ul>li{height:24px;margin-top:3px;line-height:22px}.ant-select-selection--multiple .ant-select-selection__choice{position:relative;float:left;max-width:99%;margin-right:4px;padding:0 20px 0 10px;overflow:hidden;color:rgba(0,0,0,.65);background-color:#fafafa;border:1px solid #e8e8e8;border-radius:2px;cursor:default;-webkit-transition:padding .3s cubic-bezier(.645,.045,.355,1);transition:padding .3s cubic-bezier(.645,.045,.355,1)}.ant-select-selection--multiple .ant-select-selection__choice__disabled{padding:0 10px}.ant-select-selection--multiple .ant-select-selection__choice__content{display:inline-block;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-transition:margin .3s cubic-bezier(.645,.045,.355,1);transition:margin .3s cubic-bezier(.645,.045,.355,1)}.ant-select-selection--multiple .ant-select-selection__choice__remove{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;color:rgba(0,0,0,.45);font-weight:700;line-height:inherit;cursor:pointer;-webkit-transition:all .3s;transition:all .3s;display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}.ant-select-selection--multiple .ant-select-selection__choice__remove>*{line-height:1}.ant-select-selection--multiple .ant-select-selection__choice__remove svg{display:inline-block}.ant-select-selection--multiple .ant-select-selection__choice__remove:before{display:none}.ant-select-selection--multiple .ant-select-selection__choice__remove .ant-select-selection--multiple .ant-select-selection__choice__remove-icon{display:block}:root .ant-select-selection--multiple .ant-select-selection__choice__remove{font-size:12px}.ant-select-selection--multiple .ant-select-selection__choice__remove:hover{color:rgba(0,0,0,.75)}.ant-select-selection--multiple .ant-select-arrow,.ant-select-selection--multiple .ant-select-selection__clear{top:16px}.ant-select-allow-clear .ant-select-selection--multiple .ant-select-selection__rendered,.ant-select-show-arrow .ant-select-selection--multiple .ant-select-selection__rendered{margin-right:20px}.ant-select-open .ant-select-arrow-icon svg{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.ant-select-open .ant-select-selection{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-select-combobox .ant-select-arrow{display:none}.ant-select-combobox .ant-select-search--inline{float:none;width:100%;height:100%}.ant-select-combobox .ant-select-search__field__wrap{width:100%;height:100%}.ant-select-combobox .ant-select-search__field{position:relative;z-index:1;width:100%;height:100%;-webkit-box-shadow:none;box-shadow:none;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1),height 0s;transition:all .3s cubic-bezier(.645,.045,.355,1),height 0s}.ant-select-combobox.ant-select-allow-clear .ant-select-selection:hover .ant-select-selection__rendered,.ant-select-combobox.ant-select-show-arrow .ant-select-selection:hover .ant-select-selection__rendered{margin-right:20px}.ant-select-dropdown{margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\",;position:absolute;top:-9999px;left:-9999px;z-index:1050;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;font-variant:normal;background-color:#fff;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-select-dropdown.slide-up-appear.slide-up-appear-active.ant-select-dropdown-placement-bottomLeft,.ant-select-dropdown.slide-up-enter.slide-up-enter-active.ant-select-dropdown-placement-bottomLeft{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn}.ant-select-dropdown.slide-up-appear.slide-up-appear-active.ant-select-dropdown-placement-topLeft,.ant-select-dropdown.slide-up-enter.slide-up-enter-active.ant-select-dropdown-placement-topLeft{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn}.ant-select-dropdown.slide-up-leave.slide-up-leave-active.ant-select-dropdown-placement-bottomLeft{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut}.ant-select-dropdown.slide-up-leave.slide-up-leave-active.ant-select-dropdown-placement-topLeft{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut}.ant-select-dropdown-hidden{display:none}.ant-select-dropdown-menu{max-height:250px;margin-bottom:0;padding:4px 0;overflow:auto;list-style:none;outline:none}.ant-select-dropdown-menu-item-group-list{margin:0;padding:0}.ant-select-dropdown-menu-item-group-list>.ant-select-dropdown-menu-item{padding-left:20px}.ant-select-dropdown-menu-item-group-title{height:32px;padding:0 12px;color:rgba(0,0,0,.45);font-size:12px;line-height:32px}.ant-select-dropdown-menu-item-group-list .ant-select-dropdown-menu-item:first-child:not(:last-child),.ant-select-dropdown-menu-item-group:not(:last-child) .ant-select-dropdown-menu-item-group-list .ant-select-dropdown-menu-item:last-child{border-radius:0}.ant-select-dropdown-menu-item{position:relative;display:block;padding:5px 12px;overflow:hidden;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled){background-color:#e6f7ff}.ant-select-dropdown-menu-item-selected{color:rgba(0,0,0,.65);font-weight:600;background-color:#fafafa}.ant-select-dropdown-menu-item-disabled,.ant-select-dropdown-menu-item-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled){background-color:#e6f7ff}.ant-select-dropdown-menu-item-divider{height:1px;margin:1px 0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item{padding-right:32px}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item .ant-select-selected-icon{position:absolute;top:50%;right:12px;color:transparent;font-weight:700;font-size:12px;text-shadow:0 .1px 0,.1px 0 0,0 -.1px 0,-.1px 0;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);-webkit-transition:all .2s;transition:all .2s}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon{color:rgba(0,0,0,.87)}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-disabled .ant-select-selected-icon{display:none}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected .ant-select-selected-icon,.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected:hover .ant-select-selected-icon{display:inline-block;color:#1890ff}.ant-select-dropdown--empty.ant-select-dropdown--multiple .ant-select-dropdown-menu-item{padding-right:12px}.ant-select-dropdown-container-open .ant-select-dropdown,.ant-select-dropdown-open .ant-select-dropdown{display:block}.ant-empty{margin:0 8px;font-size:14px;line-height:22px;text-align:center}.ant-empty-image{height:100px;margin-bottom:8px}.ant-empty-image img{height:100%}.ant-empty-image svg{height:100%;margin:auto}.ant-empty-description{margin:0}.ant-empty-footer{margin-top:16px}.ant-empty-normal{margin:32px 0;color:rgba(0,0,0,.25)}.ant-empty-normal .ant-empty-image{height:40px}.ant-empty-small{margin:8px 0;color:rgba(0,0,0,.25)}.ant-empty-small .ant-empty-image{height:35px}.ant-input{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;width:100%;height:32px;padding:4px 11px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s}.ant-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input:-ms-input-placeholder{color:#bfbfbf}.ant-input::-webkit-input-placeholder{color:#bfbfbf}.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-input:placeholder-shown{text-overflow:ellipsis}.ant-input:focus,.ant-input:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-input:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-input-sm{height:24px;padding:1px 7px}.ant-input-group{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-group>[class*=col-]{padding-right:8px}.ant-input-group>[class*=col-]:last-child{padding-right:0}.ant-input-group-addon,.ant-input-group-wrap,.ant-input-group>.ant-input{display:table-cell}.ant-input-group-addon:not(:first-child):not(:last-child),.ant-input-group-wrap:not(:first-child):not(:last-child),.ant-input-group>.ant-input:not(:first-child):not(:last-child){border-radius:0}.ant-input-group-addon,.ant-input-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-group-wrap>*{display:block!important}.ant-input-group .ant-input{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-group .ant-input:focus,.ant-input-group .ant-input:hover{z-index:1;border-right-width:1px}.ant-input-group-addon{position:relative;padding:0 11px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;text-align:center;background-color:#fafafa;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s}.ant-input-group-addon .ant-select{margin:-5px -11px}.ant-input-group-addon .ant-select .ant-select-selection{margin:-1px;background-color:inherit;border:1px solid transparent;-webkit-box-shadow:none;box-shadow:none}.ant-input-group-addon .ant-select-focused .ant-select-selection,.ant-input-group-addon .ant-select-open .ant-select-selection{color:#1890ff}.ant-input-group-addon>i:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;content:\"\"}.ant-input-group-addon:first-child,.ant-input-group-addon:first-child .ant-select .ant-select-selection,.ant-input-group>.ant-input:first-child,.ant-input-group>.ant-input:first-child .ant-select .ant-select-selection{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:first-child) .ant-input{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:last-child) .ant-input{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group-addon:first-child{border-right:0}.ant-input-group-addon:last-child{border-left:0}.ant-input-group-addon:last-child,.ant-input-group-addon:last-child .ant-select .ant-select-selection,.ant-input-group>.ant-input:last-child,.ant-input-group>.ant-input:last-child .ant-select .ant-select-selection{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group-lg .ant-input,.ant-input-group-lg>.ant-input-group-addon{height:40px;padding:6px 11px;font-size:16px}.ant-input-group-sm .ant-input,.ant-input-group-sm>.ant-input-group-addon{height:24px;padding:1px 7px}.ant-input-group-lg .ant-select-selection--single{height:40px}.ant-input-group-sm .ant-select-selection--single{height:24px}.ant-input-group .ant-input-affix-wrapper{display:table-cell;float:left;width:100%}.ant-input-group.ant-input-group-compact{display:block;zoom:1}.ant-input-group.ant-input-group-compact:after,.ant-input-group.ant-input-group-compact:before{display:table;content:\"\"}.ant-input-group.ant-input-group-compact:after{clear:both}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-group.ant-input-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-group.ant-input-group-compact>:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-group.ant-input-group-compact .ant-input{float:none}.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input{border-right-width:1px;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor:focus,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor:hover,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection:focus,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection:hover,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input:focus,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input:hover{z-index:1}.ant-input-group.ant-input-group-compact>.ant-calendar-picker:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper:first-child .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker:first-child .ant-time-picker-input,.ant-input-group.ant-input-group-compact>:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-input-group.ant-input-group-compact>.ant-calendar-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper:last-child .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker:last-child .ant-time-picker-input,.ant-input-group.ant-input-group-compact>:last-child{border-right-width:1px;border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-group-wrapper{display:inline-block;width:100%;text-align:start;vertical-align:top}.ant-input-affix-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;width:100%;text-align:start}.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#40a9ff;border-right-width:1px!important}.ant-input-affix-wrapper .ant-input{position:relative;text-align:inherit}.ant-input-affix-wrapper .ant-input-prefix,.ant-input-affix-wrapper .ant-input-suffix{position:absolute;top:50%;z-index:2;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;color:rgba(0,0,0,.65);line-height:0;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-input-affix-wrapper .ant-input-prefix :not(.anticon),.ant-input-affix-wrapper .ant-input-suffix :not(.anticon){line-height:1.5}.ant-input-affix-wrapper .ant-input-disabled~.ant-input-suffix .anticon{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-input-affix-wrapper .ant-input-prefix{left:12px}.ant-input-affix-wrapper .ant-input-suffix{right:12px}.ant-input-affix-wrapper .ant-input:not(:first-child){padding-left:30px}.ant-input-affix-wrapper .ant-input:not(:last-child){padding-right:30px}.ant-input-affix-wrapper.ant-input-affix-wrapper-input-with-clear-btn .ant-input:not(:last-child){padding-right:49px}.ant-input-affix-wrapper.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input{padding-right:22px}.ant-input-affix-wrapper .ant-input{min-height:100%}.ant-input-password-icon{color:rgba(0,0,0,.45);cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-input-password-icon:hover{color:#333}.ant-input-clear-icon{color:rgba(0,0,0,.25);font-size:12px;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;vertical-align:0}.ant-input-clear-icon:hover{color:rgba(0,0,0,.45)}.ant-input-clear-icon:active{color:rgba(0,0,0,.65)}.ant-input-clear-icon+i{margin-left:6px}.ant-input-textarea-clear-icon{color:rgba(0,0,0,.25);font-size:12px;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;position:absolute;top:0;right:0;margin:8px 8px 0 0}.ant-input-textarea-clear-icon:hover{color:rgba(0,0,0,.45)}.ant-input-textarea-clear-icon:active{color:rgba(0,0,0,.65)}.ant-input-textarea-clear-icon+i{margin-left:6px}.ant-input-search-icon{color:rgba(0,0,0,.45);cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-input-search-icon:hover{color:rgba(0,0,0,.8)}.ant-input-search-enter-button input{border-right:0}.ant-input-search-enter-button+.ant-input-group-addon,.ant-input-search-enter-button input+.ant-input-group-addon{padding:0;border:0}.ant-input-search-enter-button+.ant-input-group-addon .ant-input-search-button,.ant-input-search-enter-button input+.ant-input-group-addon .ant-input-search-button{border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn{line-height:1.499;position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;-webkit-box-shadow:0 2px 0 rgba(0,0,0,.015);box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-ms-touch-action:manipulation;touch-action:manipulation;height:32px;padding:0 15px;font-size:14px;border-radius:4px;color:rgba(0,0,0,.65);background-color:#fff;border:1px solid #d9d9d9}.ant-btn>.anticon{line-height:1}.ant-btn,.ant-btn:active,.ant-btn:focus{outline:0}.ant-btn:not([disabled]):hover{text-decoration:none}.ant-btn:not([disabled]):active{outline:0;-webkit-box-shadow:none;box-shadow:none}.ant-btn.disabled,.ant-btn[disabled]{cursor:not-allowed}.ant-btn.disabled>*,.ant-btn[disabled]>*{pointer-events:none}.ant-btn-lg{height:40px;padding:0 15px;font-size:16px;border-radius:4px}.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}.ant-btn>a:only-child{color:currentColor}.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn:focus,.ant-btn:hover{color:#40a9ff;background-color:#fff;border-color:#40a9ff}.ant-btn:focus>a:only-child,.ant-btn:hover>a:only-child{color:currentColor}.ant-btn:focus>a:only-child:after,.ant-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn.active,.ant-btn:active{color:#096dd9;background-color:#fff;border-color:#096dd9}.ant-btn.active>a:only-child,.ant-btn:active>a:only-child{color:currentColor}.ant-btn.active>a:only-child:after,.ant-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-disabled,.ant-btn-disabled.active,.ant-btn-disabled:active,.ant-btn-disabled:focus,.ant-btn-disabled:hover,.ant-btn.disabled,.ant-btn.disabled.active,.ant-btn.disabled:active,.ant-btn.disabled:focus,.ant-btn.disabled:hover,.ant-btn[disabled],.ant-btn[disabled].active,.ant-btn[disabled]:active,.ant-btn[disabled]:focus,.ant-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-disabled.active>a:only-child,.ant-btn-disabled:active>a:only-child,.ant-btn-disabled:focus>a:only-child,.ant-btn-disabled:hover>a:only-child,.ant-btn-disabled>a:only-child,.ant-btn.disabled.active>a:only-child,.ant-btn.disabled:active>a:only-child,.ant-btn.disabled:focus>a:only-child,.ant-btn.disabled:hover>a:only-child,.ant-btn.disabled>a:only-child,.ant-btn[disabled].active>a:only-child,.ant-btn[disabled]:active>a:only-child,.ant-btn[disabled]:focus>a:only-child,.ant-btn[disabled]:hover>a:only-child,.ant-btn[disabled]>a:only-child{color:currentColor}.ant-btn-disabled.active>a:only-child:after,.ant-btn-disabled:active>a:only-child:after,.ant-btn-disabled:focus>a:only-child:after,.ant-btn-disabled:hover>a:only-child:after,.ant-btn-disabled>a:only-child:after,.ant-btn.disabled.active>a:only-child:after,.ant-btn.disabled:active>a:only-child:after,.ant-btn.disabled:focus>a:only-child:after,.ant-btn.disabled:hover>a:only-child:after,.ant-btn.disabled>a:only-child:after,.ant-btn[disabled].active>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn.active,.ant-btn:active,.ant-btn:focus,.ant-btn:hover{text-decoration:none;background:#fff}.ant-btn>i,.ant-btn>span{display:inline-block;-webkit-transition:margin-left .3s cubic-bezier(.645,.045,.355,1);transition:margin-left .3s cubic-bezier(.645,.045,.355,1);pointer-events:none}.ant-btn-primary{color:#fff;background-color:#1890ff;border-color:#1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);-webkit-box-shadow:0 2px 0 rgba(0,0,0,.045);box-shadow:0 2px 0 rgba(0,0,0,.045)}.ant-btn-primary>a:only-child{color:currentColor}.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:#40a9ff;border-color:#40a9ff}.ant-btn-primary:focus>a:only-child,.ant-btn-primary:hover>a:only-child{color:currentColor}.ant-btn-primary:focus>a:only-child:after,.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:#096dd9;border-color:#096dd9}.ant-btn-primary.active>a:only-child,.ant-btn-primary:active>a:only-child{color:currentColor}.ant-btn-primary.active>a:only-child:after,.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-primary-disabled.active>a:only-child,.ant-btn-primary-disabled:active>a:only-child,.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-primary-disabled>a:only-child,.ant-btn-primary.disabled.active>a:only-child,.ant-btn-primary.disabled:active>a:only-child,.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-primary.disabled>a:only-child,.ant-btn-primary[disabled].active>a:only-child,.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]>a:only-child{color:currentColor}.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-primary-disabled>a:only-child:after,.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-primary.disabled>a:only-child:after,.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#40a9ff;border-left-color:#40a9ff}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled{border-color:#d9d9d9}.ant-btn-group .ant-btn-primary:first-child:not(:last-child){border-right-color:#40a9ff}.ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled]{border-right-color:#d9d9d9}.ant-btn-group .ant-btn-primary+.ant-btn-primary,.ant-btn-group .ant-btn-primary:last-child:not(:first-child){border-left-color:#40a9ff}.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled],.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled]{border-left-color:#d9d9d9}.ant-btn-ghost{color:rgba(0,0,0,.65);background-color:transparent;border-color:#d9d9d9}.ant-btn-ghost>a:only-child{color:currentColor}.ant-btn-ghost>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-ghost:focus,.ant-btn-ghost:hover{color:#40a9ff;background-color:transparent;border-color:#40a9ff}.ant-btn-ghost:focus>a:only-child,.ant-btn-ghost:hover>a:only-child{color:currentColor}.ant-btn-ghost:focus>a:only-child:after,.ant-btn-ghost:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-ghost.active,.ant-btn-ghost:active{color:#096dd9;background-color:transparent;border-color:#096dd9}.ant-btn-ghost.active>a:only-child,.ant-btn-ghost:active>a:only-child{color:currentColor}.ant-btn-ghost.active>a:only-child:after,.ant-btn-ghost:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-ghost-disabled,.ant-btn-ghost-disabled.active,.ant-btn-ghost-disabled:active,.ant-btn-ghost-disabled:focus,.ant-btn-ghost-disabled:hover,.ant-btn-ghost.disabled,.ant-btn-ghost.disabled.active,.ant-btn-ghost.disabled:active,.ant-btn-ghost.disabled:focus,.ant-btn-ghost.disabled:hover,.ant-btn-ghost[disabled],.ant-btn-ghost[disabled].active,.ant-btn-ghost[disabled]:active,.ant-btn-ghost[disabled]:focus,.ant-btn-ghost[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-ghost-disabled.active>a:only-child,.ant-btn-ghost-disabled:active>a:only-child,.ant-btn-ghost-disabled:focus>a:only-child,.ant-btn-ghost-disabled:hover>a:only-child,.ant-btn-ghost-disabled>a:only-child,.ant-btn-ghost.disabled.active>a:only-child,.ant-btn-ghost.disabled:active>a:only-child,.ant-btn-ghost.disabled:focus>a:only-child,.ant-btn-ghost.disabled:hover>a:only-child,.ant-btn-ghost.disabled>a:only-child,.ant-btn-ghost[disabled].active>a:only-child,.ant-btn-ghost[disabled]:active>a:only-child,.ant-btn-ghost[disabled]:focus>a:only-child,.ant-btn-ghost[disabled]:hover>a:only-child,.ant-btn-ghost[disabled]>a:only-child{color:currentColor}.ant-btn-ghost-disabled.active>a:only-child:after,.ant-btn-ghost-disabled:active>a:only-child:after,.ant-btn-ghost-disabled:focus>a:only-child:after,.ant-btn-ghost-disabled:hover>a:only-child:after,.ant-btn-ghost-disabled>a:only-child:after,.ant-btn-ghost.disabled.active>a:only-child:after,.ant-btn-ghost.disabled:active>a:only-child:after,.ant-btn-ghost.disabled:focus>a:only-child:after,.ant-btn-ghost.disabled:hover>a:only-child:after,.ant-btn-ghost.disabled>a:only-child:after,.ant-btn-ghost[disabled].active>a:only-child:after,.ant-btn-ghost[disabled]:active>a:only-child:after,.ant-btn-ghost[disabled]:focus>a:only-child:after,.ant-btn-ghost[disabled]:hover>a:only-child:after,.ant-btn-ghost[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-dashed{color:rgba(0,0,0,.65);background-color:#fff;border-color:#d9d9d9;border-style:dashed}.ant-btn-dashed>a:only-child{color:currentColor}.ant-btn-dashed>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-dashed:focus,.ant-btn-dashed:hover{color:#40a9ff;background-color:#fff;border-color:#40a9ff}.ant-btn-dashed:focus>a:only-child,.ant-btn-dashed:hover>a:only-child{color:currentColor}.ant-btn-dashed:focus>a:only-child:after,.ant-btn-dashed:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-dashed.active,.ant-btn-dashed:active{color:#096dd9;background-color:#fff;border-color:#096dd9}.ant-btn-dashed.active>a:only-child,.ant-btn-dashed:active>a:only-child{color:currentColor}.ant-btn-dashed.active>a:only-child:after,.ant-btn-dashed:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-dashed-disabled,.ant-btn-dashed-disabled.active,.ant-btn-dashed-disabled:active,.ant-btn-dashed-disabled:focus,.ant-btn-dashed-disabled:hover,.ant-btn-dashed.disabled,.ant-btn-dashed.disabled.active,.ant-btn-dashed.disabled:active,.ant-btn-dashed.disabled:focus,.ant-btn-dashed.disabled:hover,.ant-btn-dashed[disabled],.ant-btn-dashed[disabled].active,.ant-btn-dashed[disabled]:active,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-dashed-disabled.active>a:only-child,.ant-btn-dashed-disabled:active>a:only-child,.ant-btn-dashed-disabled:focus>a:only-child,.ant-btn-dashed-disabled:hover>a:only-child,.ant-btn-dashed-disabled>a:only-child,.ant-btn-dashed.disabled.active>a:only-child,.ant-btn-dashed.disabled:active>a:only-child,.ant-btn-dashed.disabled:focus>a:only-child,.ant-btn-dashed.disabled:hover>a:only-child,.ant-btn-dashed.disabled>a:only-child,.ant-btn-dashed[disabled].active>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]>a:only-child{color:currentColor}.ant-btn-dashed-disabled.active>a:only-child:after,.ant-btn-dashed-disabled:active>a:only-child:after,.ant-btn-dashed-disabled:focus>a:only-child:after,.ant-btn-dashed-disabled:hover>a:only-child:after,.ant-btn-dashed-disabled>a:only-child:after,.ant-btn-dashed.disabled.active>a:only-child:after,.ant-btn-dashed.disabled:active>a:only-child:after,.ant-btn-dashed.disabled:focus>a:only-child:after,.ant-btn-dashed.disabled:hover>a:only-child:after,.ant-btn-dashed.disabled>a:only-child:after,.ant-btn-dashed[disabled].active>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-danger{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);-webkit-box-shadow:0 2px 0 rgba(0,0,0,.045);box-shadow:0 2px 0 rgba(0,0,0,.045)}.ant-btn-danger>a:only-child{color:currentColor}.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff7875;border-color:#ff7875}.ant-btn-danger:focus>a:only-child,.ant-btn-danger:hover>a:only-child{color:currentColor}.ant-btn-danger:focus>a:only-child:after,.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-danger.active,.ant-btn-danger:active{color:#fff;background-color:#d9363e;border-color:#d9363e}.ant-btn-danger.active>a:only-child,.ant-btn-danger:active>a:only-child{color:currentColor}.ant-btn-danger.active>a:only-child:after,.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-danger-disabled.active>a:only-child,.ant-btn-danger-disabled:active>a:only-child,.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-danger-disabled>a:only-child,.ant-btn-danger.disabled.active>a:only-child,.ant-btn-danger.disabled:active>a:only-child,.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-danger.disabled>a:only-child,.ant-btn-danger[disabled].active>a:only-child,.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]>a:only-child{color:currentColor}.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-danger-disabled>a:only-child:after,.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-danger.disabled>a:only-child:after,.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-link{color:#1890ff;background-color:transparent;border-color:transparent;-webkit-box-shadow:none;box-shadow:none}.ant-btn-link>a:only-child{color:currentColor}.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-link:focus,.ant-btn-link:hover{color:#40a9ff;background-color:transparent;border-color:#40a9ff}.ant-btn-link:focus>a:only-child,.ant-btn-link:hover>a:only-child{color:currentColor}.ant-btn-link:focus>a:only-child:after,.ant-btn-link:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-link.active,.ant-btn-link:active{color:#096dd9;background-color:transparent;border-color:#096dd9}.ant-btn-link.active>a:only-child,.ant-btn-link:active>a:only-child{color:currentColor}.ant-btn-link.active>a:only-child:after,.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-link-disabled,.ant-btn-link-disabled.active,.ant-btn-link-disabled:active,.ant-btn-link-disabled:focus,.ant-btn-link-disabled:hover,.ant-btn-link.disabled,.ant-btn-link.disabled.active,.ant-btn-link.disabled:active,.ant-btn-link.disabled:focus,.ant-btn-link.disabled:hover,.ant-btn-link[disabled],.ant-btn-link[disabled].active,.ant-btn-link[disabled]:active,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:hover{background-color:#f5f5f5;border-color:#d9d9d9}.ant-btn-link:active,.ant-btn-link:focus,.ant-btn-link:hover{border-color:transparent}.ant-btn-link-disabled,.ant-btn-link-disabled.active,.ant-btn-link-disabled:active,.ant-btn-link-disabled:focus,.ant-btn-link-disabled:hover,.ant-btn-link.disabled,.ant-btn-link.disabled.active,.ant-btn-link.disabled:active,.ant-btn-link.disabled:focus,.ant-btn-link.disabled:hover,.ant-btn-link[disabled],.ant-btn-link[disabled].active,.ant-btn-link[disabled]:active,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:hover{color:rgba(0,0,0,.25);background-color:transparent;border-color:transparent;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-link-disabled.active>a:only-child,.ant-btn-link-disabled:active>a:only-child,.ant-btn-link-disabled:focus>a:only-child,.ant-btn-link-disabled:hover>a:only-child,.ant-btn-link-disabled>a:only-child,.ant-btn-link.disabled.active>a:only-child,.ant-btn-link.disabled:active>a:only-child,.ant-btn-link.disabled:focus>a:only-child,.ant-btn-link.disabled:hover>a:only-child,.ant-btn-link.disabled>a:only-child,.ant-btn-link[disabled].active>a:only-child,.ant-btn-link[disabled]:active>a:only-child,.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-link[disabled]>a:only-child{color:currentColor}.ant-btn-link-disabled.active>a:only-child:after,.ant-btn-link-disabled:active>a:only-child:after,.ant-btn-link-disabled:focus>a:only-child:after,.ant-btn-link-disabled:hover>a:only-child:after,.ant-btn-link-disabled>a:only-child:after,.ant-btn-link.disabled.active>a:only-child:after,.ant-btn-link.disabled:active>a:only-child:after,.ant-btn-link.disabled:focus>a:only-child:after,.ant-btn-link.disabled:hover>a:only-child:after,.ant-btn-link.disabled>a:only-child:after,.ant-btn-link[disabled].active>a:only-child:after,.ant-btn-link[disabled]:active>a:only-child:after,.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-link[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-icon-only{width:32px;height:32px;padding:0;font-size:16px;border-radius:4px}.ant-btn-icon-only.ant-btn-lg{width:40px;height:40px;padding:0;font-size:18px;border-radius:4px}.ant-btn-icon-only.ant-btn-sm{width:24px;height:24px;padding:0;font-size:14px;border-radius:4px}.ant-btn-icon-only>i{vertical-align:middle}.ant-btn-round{height:32px;padding:0 16px;font-size:14px;border-radius:32px}.ant-btn-round.ant-btn-lg{height:40px;padding:0 20px;font-size:16px;border-radius:40px}.ant-btn-round.ant-btn-sm{height:24px;padding:0 12px;font-size:14px;border-radius:24px}.ant-btn-round.ant-btn-icon-only{width:auto}.ant-btn-circle,.ant-btn-circle-outline{min-width:32px;padding-right:0;padding-left:0;text-align:center;border-radius:50%}.ant-btn-circle-outline.ant-btn-lg,.ant-btn-circle.ant-btn-lg{min-width:40px;border-radius:50%}.ant-btn-circle-outline.ant-btn-sm,.ant-btn-circle.ant-btn-sm{min-width:24px;border-radius:50%}.ant-btn:before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:1;display:none;background:#fff;border-radius:inherit;opacity:.35;-webkit-transition:opacity .2s;transition:opacity .2s;content:\"\";pointer-events:none}.ant-btn .anticon{-webkit-transition:margin-left .3s cubic-bezier(.645,.045,.355,1);transition:margin-left .3s cubic-bezier(.645,.045,.355,1)}.ant-btn .anticon.anticon-minus>svg,.ant-btn .anticon.anticon-plus>svg{shape-rendering:optimizeSpeed}.ant-btn.ant-btn-loading{position:relative}.ant-btn.ant-btn-loading:not([disabled]){pointer-events:none}.ant-btn.ant-btn-loading:before{display:block}.ant-btn.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only){padding-left:29px}.ant-btn.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) .anticon:not(:last-child){margin-left:-14px}.ant-btn-sm.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only){padding-left:24px}.ant-btn-sm.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) .anticon{margin-left:-17px}.ant-btn-group{display:inline-block}.ant-btn-group,.ant-btn-group>.ant-btn,.ant-btn-group>span>.ant-btn{position:relative}.ant-btn-group>.ant-btn.active,.ant-btn-group>.ant-btn:active,.ant-btn-group>.ant-btn:focus,.ant-btn-group>.ant-btn:hover,.ant-btn-group>span>.ant-btn.active,.ant-btn-group>span>.ant-btn:active,.ant-btn-group>span>.ant-btn:focus,.ant-btn-group>span>.ant-btn:hover{z-index:2}.ant-btn-group>.ant-btn:disabled,.ant-btn-group>span>.ant-btn:disabled{z-index:0}.ant-btn-group>.ant-btn-icon-only{font-size:14px}.ant-btn-group-lg>.ant-btn,.ant-btn-group-lg>span>.ant-btn{height:40px;padding:0 15px;font-size:16px;border-radius:0;line-height:38px}.ant-btn-group-lg>.ant-btn.ant-btn-icon-only{width:40px;height:40px;padding-right:0;padding-left:0}.ant-btn-group-sm>.ant-btn,.ant-btn-group-sm>span>.ant-btn{height:24px;padding:0 7px;font-size:14px;border-radius:0;line-height:22px}.ant-btn-group-sm>.ant-btn>.anticon,.ant-btn-group-sm>span>.ant-btn>.anticon{font-size:14px}.ant-btn-group-sm>.ant-btn.ant-btn-icon-only{width:24px;height:24px;padding-right:0;padding-left:0}.ant-btn+.ant-btn-group,.ant-btn-group+.ant-btn,.ant-btn-group+.ant-btn-group,.ant-btn-group .ant-btn+.ant-btn,.ant-btn-group .ant-btn+span,.ant-btn-group>span+span,.ant-btn-group span+.ant-btn{margin-left:-1px}.ant-btn-group .ant-btn-primary+.ant-btn:not(.ant-btn-primary):not([disabled]){border-left-color:transparent}.ant-btn-group .ant-btn{border-radius:0}.ant-btn-group>.ant-btn:first-child,.ant-btn-group>span:first-child>.ant-btn{margin-left:0}.ant-btn-group>.ant-btn:only-child,.ant-btn-group>span:only-child>.ant-btn{border-radius:4px}.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-btn-group-sm>.ant-btn:only-child,.ant-btn-group-sm>span:only-child>.ant-btn{border-radius:4px}.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-btn-group>.ant-btn-group{float:left}.ant-btn-group>.ant-btn-group:not(:first-child):not(:last-child)>.ant-btn{border-radius:0}.ant-btn-group>.ant-btn-group:first-child:not(:last-child)>.ant-btn:last-child{padding-right:8px;border-top-right-radius:0;border-bottom-right-radius:0}.ant-btn-group>.ant-btn-group:last-child:not(:first-child)>.ant-btn:first-child{padding-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn:active>span,.ant-btn:focus>span{position:relative}.ant-btn>.anticon+span,.ant-btn>span+.anticon{margin-left:8px}.ant-btn-background-ghost{color:#fff;background:transparent!important;border-color:#fff}.ant-btn-background-ghost.ant-btn-primary{color:#1890ff;background-color:transparent;border-color:#1890ff;text-shadow:none}.ant-btn-background-ghost.ant-btn-primary>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-primary:focus,.ant-btn-background-ghost.ant-btn-primary:hover{color:#40a9ff;background-color:transparent;border-color:#40a9ff}.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-primary.active,.ant-btn-background-ghost.ant-btn-primary:active{color:#096dd9;background-color:transparent;border-color:#096dd9}.ant-btn-background-ghost.ant-btn-primary.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-primary-disabled,.ant-btn-background-ghost.ant-btn-primary-disabled.active,.ant-btn-background-ghost.ant-btn-primary-disabled:active,.ant-btn-background-ghost.ant-btn-primary-disabled:focus,.ant-btn-background-ghost.ant-btn-primary-disabled:hover,.ant-btn-background-ghost.ant-btn-primary.disabled,.ant-btn-background-ghost.ant-btn-primary.disabled.active,.ant-btn-background-ghost.ant-btn-primary.disabled:active,.ant-btn-background-ghost.ant-btn-primary.disabled:focus,.ant-btn-background-ghost.ant-btn-primary.disabled:hover,.ant-btn-background-ghost.ant-btn-primary[disabled],.ant-btn-background-ghost.ant-btn-primary[disabled].active,.ant-btn-background-ghost.ant-btn-primary[disabled]:active,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-primary-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-danger{color:#ff4d4f;background-color:transparent;border-color:#ff4d4f;text-shadow:none}.ant-btn-background-ghost.ant-btn-danger>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-danger:focus,.ant-btn-background-ghost.ant-btn-danger:hover{color:#ff7875;background-color:transparent;border-color:#ff7875}.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-danger.active,.ant-btn-background-ghost.ant-btn-danger:active{color:#d9363e;background-color:transparent;border-color:#d9363e}.ant-btn-background-ghost.ant-btn-danger.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-danger-disabled,.ant-btn-background-ghost.ant-btn-danger-disabled.active,.ant-btn-background-ghost.ant-btn-danger-disabled:active,.ant-btn-background-ghost.ant-btn-danger-disabled:focus,.ant-btn-background-ghost.ant-btn-danger-disabled:hover,.ant-btn-background-ghost.ant-btn-danger.disabled,.ant-btn-background-ghost.ant-btn-danger.disabled.active,.ant-btn-background-ghost.ant-btn-danger.disabled:active,.ant-btn-background-ghost.ant-btn-danger.disabled:focus,.ant-btn-background-ghost.ant-btn-danger.disabled:hover,.ant-btn-background-ghost.ant-btn-danger[disabled],.ant-btn-background-ghost.ant-btn-danger[disabled].active,.ant-btn-background-ghost.ant-btn-danger[disabled]:active,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-danger-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-link{color:#1890ff;background-color:transparent;border-color:transparent;text-shadow:none;color:#fff}.ant-btn-background-ghost.ant-btn-link>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-link:focus,.ant-btn-background-ghost.ant-btn-link:hover{color:#40a9ff;background-color:transparent;border-color:transparent}.ant-btn-background-ghost.ant-btn-link:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-link.active,.ant-btn-background-ghost.ant-btn-link:active{color:#096dd9;background-color:transparent;border-color:transparent}.ant-btn-background-ghost.ant-btn-link.active>a:only-child,.ant-btn-background-ghost.ant-btn-link:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-background-ghost.ant-btn-link-disabled,.ant-btn-background-ghost.ant-btn-link-disabled.active,.ant-btn-background-ghost.ant-btn-link-disabled:active,.ant-btn-background-ghost.ant-btn-link-disabled:focus,.ant-btn-background-ghost.ant-btn-link-disabled:hover,.ant-btn-background-ghost.ant-btn-link.disabled,.ant-btn-background-ghost.ant-btn-link.disabled.active,.ant-btn-background-ghost.ant-btn-link.disabled:active,.ant-btn-background-ghost.ant-btn-link.disabled:focus,.ant-btn-background-ghost.ant-btn-link.disabled:hover,.ant-btn-background-ghost.ant-btn-link[disabled],.ant-btn-background-ghost.ant-btn-link[disabled].active,.ant-btn-background-ghost.ant-btn-link[disabled]:active,.ant-btn-background-ghost.ant-btn-link[disabled]:focus,.ant-btn-background-ghost.ant-btn-link[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-link-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-btn-two-chinese-chars:first-letter{letter-spacing:.34em}.ant-btn-two-chinese-chars>:not(.anticon){margin-right:-.34em;letter-spacing:.34em}.ant-btn-block{width:100%}.ant-btn:empty{vertical-align:top}a.ant-btn{padding-top:.1px;line-height:30px}a.ant-btn-lg{line-height:38px}a.ant-btn-sm{line-height:22px}.ant-avatar{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;overflow:hidden;color:#fff;white-space:nowrap;text-align:center;vertical-align:middle;background:#ccc;width:32px;height:32px;line-height:32px;border-radius:50%}.ant-avatar-image{background:transparent}.ant-avatar-string{position:absolute;left:50%;-webkit-transform-origin:0 center;-ms-transform-origin:0 center;transform-origin:0 center}.ant-avatar.ant-avatar-icon{font-size:18px}.ant-avatar-lg{width:40px;height:40px;line-height:40px;border-radius:50%}.ant-avatar-lg-string{position:absolute;left:50%;-webkit-transform-origin:0 center;-ms-transform-origin:0 center;transform-origin:0 center}.ant-avatar-lg.ant-avatar-icon{font-size:24px}.ant-avatar-sm{width:24px;height:24px;line-height:24px;border-radius:50%}.ant-avatar-sm-string{position:absolute;left:50%;-webkit-transform-origin:0 center;-ms-transform-origin:0 center;transform-origin:0 center}.ant-avatar-sm.ant-avatar-icon{font-size:14px}.ant-avatar-square{border-radius:4px}.ant-avatar>img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ant-back-top{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:fixed;right:100px;bottom:50px;z-index:10;width:40px;height:40px;cursor:pointer}.ant-back-top-content{width:40px;height:40px;overflow:hidden;color:#fff;text-align:center;background-color:rgba(0,0,0,.45);border-radius:20px}.ant-back-top-content,.ant-back-top-content:hover{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-back-top-content:hover{background-color:rgba(0,0,0,.65)}.ant-back-top-icon{width:14px;height:16px;margin:12px auto;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) 100%/100% no-repeat}@media screen and (max-width:768px){.ant-back-top{right:60px}}@media screen and (max-width:480px){.ant-back-top{right:20px}}.ant-badge{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;color:unset;line-height:1}.ant-badge-count{min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:12px;line-height:20px;white-space:nowrap;text-align:center;background:#f5222d;border-radius:10px;-webkit-box-shadow:0 0 0 1px #fff;box-shadow:0 0 0 1px #fff}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{width:6px;height:6px;background:#f5222d;border-radius:100%;-webkit-box-shadow:0 0 0 1px #fff;box-shadow:0 0 0 1px #fff}.ant-badge-count,.ant-badge-dot,.ant-badge .ant-scroll-number-custom-component{position:absolute;top:0;right:0;z-index:1;-webkit-transform:translate(50%,-50%);-ms-transform:translate(50%,-50%);transform:translate(50%,-50%);-webkit-transform-origin:100% 0;-ms-transform-origin:100% 0;transform-origin:100% 0}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#52c41a}.ant-badge-status-processing{position:relative;background-color:#1890ff}.ant-badge-status-processing:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;-webkit-animation:antStatusProcessing 1.2s ease-in-out infinite;animation:antStatusProcessing 1.2s ease-in-out infinite;content:\"\"}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#f5222d}.ant-badge-status-warning{background-color:#faad14}.ant-badge-status-magenta,.ant-badge-status-pink{background:#eb2f96}.ant-badge-status-red{background:#f5222d}.ant-badge-status-volcano{background:#fa541c}.ant-badge-status-orange{background:#fa8c16}.ant-badge-status-yellow{background:#fadb14}.ant-badge-status-gold{background:#faad14}.ant-badge-status-cyan{background:#13c2c2}.ant-badge-status-lime{background:#a0d911}.ant-badge-status-green{background:#52c41a}.ant-badge-status-blue{background:#1890ff}.ant-badge-status-geekblue{background:#2f54eb}.ant-badge-status-purple{background:#722ed1}.ant-badge-status-text{margin-left:8px;color:rgba(0,0,0,.65);font-size:14px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{-webkit-animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);-webkit-animation-fill-mode:both;animation-fill-mode:both}.ant-badge-zoom-leave{-webkit-animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);-webkit-animation-fill-mode:both;animation-fill-mode:both}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-scroll-number{position:relative;top:auto;display:block}.ant-badge-not-a-wrapper .ant-badge-count{-webkit-transform:none;-ms-transform:none;transform:none}@-webkit-keyframes antStatusProcessing{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:.5}to{-webkit-transform:scale(2.4);transform:scale(2.4);opacity:0}}@keyframes antStatusProcessing{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:.5}to{-webkit-transform:scale(2.4);transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden}.ant-scroll-number-only{display:inline-block;height:20px;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0}.ant-scroll-number-symbol{vertical-align:top}@-webkit-keyframes antZoomBadgeIn{0%{-webkit-transform:scale(0) translate(50%,-50%);transform:scale(0) translate(50%,-50%);opacity:0}to{-webkit-transform:scale(1) translate(50%,-50%);transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeIn{0%{-webkit-transform:scale(0) translate(50%,-50%);transform:scale(0) translate(50%,-50%);opacity:0}to{-webkit-transform:scale(1) translate(50%,-50%);transform:scale(1) translate(50%,-50%)}}@-webkit-keyframes antZoomBadgeOut{0%{-webkit-transform:scale(1) translate(50%,-50%);transform:scale(1) translate(50%,-50%)}to{-webkit-transform:scale(0) translate(50%,-50%);transform:scale(0) translate(50%,-50%);opacity:0}}@keyframes antZoomBadgeOut{0%{-webkit-transform:scale(1) translate(50%,-50%);transform:scale(1) translate(50%,-50%)}to{-webkit-transform:scale(0) translate(50%,-50%);transform:scale(0) translate(50%,-50%);opacity:0}}.ant-breadcrumb{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";color:rgba(0,0,0,.45);font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:rgba(0,0,0,.45);-webkit-transition:color .3s;transition:color .3s}.ant-breadcrumb a:hover{color:#40a9ff}.ant-breadcrumb>span:last-child,.ant-breadcrumb>span:last-child a{color:rgba(0,0,0,.65)}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:rgba(0,0,0,.45)}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-menu{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;font-variant:tabular-nums;line-height:1.5;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";margin:0;padding:0;color:rgba(0,0,0,.65);line-height:0;list-style:none;background:#fff;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15);-webkit-transition:background .3s,width .2s;transition:background .3s,width .2s;zoom:1}.ant-menu:after,.ant-menu:before{display:table;content:\"\"}.ant-menu:after{clear:both}.ant-menu ol,.ant-menu ul{margin:0;padding:0;list-style:none}.ant-menu-hidden{display:none}.ant-menu-item-group-title{padding:8px 16px;color:rgba(0,0,0,.45);font-size:14px;line-height:1.5;-webkit-transition:all .3s;transition:all .3s}.ant-menu-submenu,.ant-menu-submenu-inline{-webkit-transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1);transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-selected{color:#1890ff}.ant-menu-item:active,.ant-menu-submenu-title:active{background:#e6f7ff}.ant-menu-submenu .ant-menu-sub{cursor:auto;-webkit-transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item>a{display:block;color:rgba(0,0,0,.65)}.ant-menu-item>a:hover{color:#1890ff}.ant-menu-item>a:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:\"\"}.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}.ant-menu-item>.ant-badge>a:hover{color:#1890ff}.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#1890ff}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#e6f7ff}.ant-menu-inline,.ant-menu-vertical,.ant-menu-vertical-left{border-right:1px solid #e8e8e8}.ant-menu-vertical-right{border-left:1px solid #e8e8e8}.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-vertical-left.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical.ant-menu-sub .ant-menu-item:after{border-right:0}.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.ant-menu-horizontal.ant-menu-sub{min-width:114px}.ant-menu-item,.ant-menu-submenu-title{position:relative;display:block;margin:0;padding:0 20px;white-space:nowrap;cursor:pointer;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .anticon,.ant-menu-submenu-title .anticon{min-width:14px;margin-right:10px;font-size:14px;-webkit-transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1);transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .anticon+span,.ant-menu-submenu-title .anticon+span{opacity:1;-webkit-transition:opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1);transition:opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1)}.ant-menu>.ant-menu-item-divider{height:1px;margin:1px 0;padding:0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-menu-submenu-popup{position:absolute;z-index:1050;background:#fff;border-radius:4px}.ant-menu-submenu-popup .submenu-title-wrapper{padding-right:20px}.ant-menu-submenu-popup:before{position:absolute;top:-7px;right:0;bottom:0;left:0;opacity:.0001;content:\" \"}.ant-menu-submenu>.ant-menu{background-color:#fff;border-radius:4px}.ant-menu-submenu>.ant-menu-submenu-title:after{-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow{position:absolute;top:50%;right:16px;width:10px;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{position:absolute;width:6px;height:1.5px;background:#fff;background:rgba(0,0,0,.65)\\9;background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.65)),to(rgba(0,0,0,.65)));background-image:linear-gradient(90deg,rgba(0,0,0,.65),rgba(0,0,0,.65));background-image:none\\9;border-radius:2px;-webkit-transition:background .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:background .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1);transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);content:\"\"}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{-webkit-transform:rotate(45deg) translateY(-2px);-ms-transform:rotate(45deg) translateY(-2px);transform:rotate(45deg) translateY(-2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{-webkit-transform:rotate(-45deg) translateY(2px);-ms-transform:rotate(-45deg) translateY(2px);transform:rotate(-45deg) translateY(2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before{background:-webkit-gradient(linear,left top,right top,from(#1890ff),to(#1890ff));background:linear-gradient(90deg,#1890ff,#1890ff)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{-webkit-transform:rotate(-45deg) translateX(2px);-ms-transform:rotate(-45deg) translateX(2px);transform:rotate(-45deg) translateX(2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{-webkit-transform:rotate(45deg) translateX(-2px);-ms-transform:rotate(45deg) translateX(-2px);transform:rotate(45deg) translateX(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow{-webkit-transform:translateY(-2px);-ms-transform:translateY(-2px);transform:translateY(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{-webkit-transform:rotate(-45deg) translateX(-2px);-ms-transform:rotate(-45deg) translateX(-2px);transform:rotate(-45deg) translateX(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{-webkit-transform:rotate(45deg) translateX(2px);-ms-transform:rotate(45deg) translateX(2px);transform:rotate(45deg) translateX(2px)}.ant-menu-vertical-left .ant-menu-submenu-selected,.ant-menu-vertical-left .ant-menu-submenu-selected>a,.ant-menu-vertical-right .ant-menu-submenu-selected,.ant-menu-vertical-right .ant-menu-submenu-selected>a,.ant-menu-vertical .ant-menu-submenu-selected,.ant-menu-vertical .ant-menu-submenu-selected>a{color:#1890ff}.ant-menu-horizontal{line-height:46px;white-space:nowrap;border:0;border-bottom:1px solid #e8e8e8;-webkit-box-shadow:none;box-shadow:none}.ant-menu-horizontal>.ant-menu-item,.ant-menu-horizontal>.ant-menu-submenu{position:relative;top:1px;display:inline-block;vertical-align:bottom;border-bottom:2px solid transparent}.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover{color:#1890ff;border-bottom:2px solid #1890ff}.ant-menu-horizontal>.ant-menu-item>a{display:block;color:rgba(0,0,0,.65)}.ant-menu-horizontal>.ant-menu-item>a:hover{color:#1890ff}.ant-menu-horizontal>.ant-menu-item>a:before{bottom:-2px}.ant-menu-horizontal>.ant-menu-item-selected>a{color:#1890ff}.ant-menu-horizontal:after{display:block;clear:both;height:0;content:\"\\20\"}.ant-menu-inline .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical .ant-menu-item{position:relative}.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #1890ff;-webkit-transform:scaleY(.0001);-ms-transform:scaleY(.0001);transform:scaleY(.0001);opacity:0;-webkit-transition:opacity .15s cubic-bezier(.215,.61,.355,1),-webkit-transform .15s cubic-bezier(.215,.61,.355,1);transition:opacity .15s cubic-bezier(.215,.61,.355,1),-webkit-transform .15s cubic-bezier(.215,.61,.355,1);transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1),-webkit-transform .15s cubic-bezier(.215,.61,.355,1);content:\"\"}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu{padding-bottom:.02px}.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child){margin-bottom:8px}.ant-menu-inline>.ant-menu-item,.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-left>.ant-menu-item,.ant-menu-vertical-left>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-right>.ant-menu-item,.ant-menu-vertical-right>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical>.ant-menu-item,.ant-menu-vertical>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px}.ant-menu-inline{width:100%}.ant-menu-inline .ant-menu-item-selected:after,.ant-menu-inline .ant-menu-selected:after{-webkit-transform:scaleY(1);-ms-transform:scaleY(1);transform:scaleY(1);opacity:1;-webkit-transition:opacity .15s cubic-bezier(.645,.045,.355,1),-webkit-transform .15s cubic-bezier(.645,.045,.355,1);transition:opacity .15s cubic-bezier(.645,.045,.355,1),-webkit-transform .15s cubic-bezier(.645,.045,.355,1);transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1);transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1),-webkit-transform .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{width:calc(100% + 1px)}.ant-menu-inline .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline-collapsed{width:80px}.ant-menu-inline-collapsed>.ant-menu-item,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title{left:0;padding:0 32px!important;text-overflow:clip}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow{display:none}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon,.ant-menu-inline-collapsed>.ant-menu-item .anticon,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon{margin:0;font-size:16px;line-height:40px}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon+span,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span,.ant-menu-inline-collapsed>.ant-menu-item .anticon+span,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span{display:inline-block;max-width:0;opacity:0}.ant-menu-inline-collapsed-tooltip{pointer-events:none}.ant-menu-inline-collapsed-tooltip .anticon{display:none}.ant-menu-inline-collapsed-tooltip a{color:hsla(0,0%,100%,.85)}.ant-menu-inline-collapsed .ant-menu-item-group-title{padding-right:4px;padding-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-menu-item-group-list{margin:0;padding:0}.ant-menu-item-group-list .ant-menu-item,.ant-menu-item-group-list .ant-menu-submenu-title{padding:0 16px 0 28px}.ant-menu-root.ant-menu-inline,.ant-menu-root.ant-menu-vertical,.ant-menu-root.ant-menu-vertical-left,.ant-menu-root.ant-menu-vertical-right,.ant-menu-sub.ant-menu-inline{-webkit-box-shadow:none;box-shadow:none}.ant-menu-sub.ant-menu-inline{padding:0;border:0;border-radius:0}.ant-menu-sub.ant-menu-inline>.ant-menu-item,.ant-menu-sub.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px;list-style-position:inside;list-style-type:disc}.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-left:32px}.ant-menu-item-disabled,.ant-menu-submenu-disabled{color:rgba(0,0,0,.25)!important;background:none;border-color:transparent!important;cursor:not-allowed}.ant-menu-item-disabled>a,.ant-menu-submenu-disabled>a{color:rgba(0,0,0,.25)!important;pointer-events:none}.ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-submenu-disabled>.ant-menu-submenu-title{color:rgba(0,0,0,.25)!important;cursor:not-allowed}.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:rgba(0,0,0,.25)!important}.ant-menu-dark,.ant-menu-dark .ant-menu-sub{color:hsla(0,0%,100%,.65);background:#001529}.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:.45;-webkit-transition:all .3s;transition:all .3s}.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark.ant-menu-submenu-popup{background:transparent}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:#000c17;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.45) inset;box-shadow:inset 0 2px 8px rgba(0,0,0,.45)}.ant-menu-dark.ant-menu-horizontal{border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu{top:0;margin-top:0;border-color:#001529;border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item>a:before{bottom:0}.ant-menu-dark .ant-menu-item,.ant-menu-dark .ant-menu-item-group-title,.ant-menu-dark .ant-menu-item>a{color:hsla(0,0%,100%,.65)}.ant-menu-dark.ant-menu-inline,.ant-menu-dark.ant-menu-vertical,.ant-menu-dark.ant-menu-vertical-left,.ant-menu-dark.ant-menu-vertical-right{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item,.ant-menu-dark.ant-menu-vertical .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical .ant-menu-item:after{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title{width:100%}.ant-menu-dark .ant-menu-item-active,.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-submenu-active,.ant-menu-dark .ant-menu-submenu-open,.ant-menu-dark .ant-menu-submenu-selected,.ant-menu-dark .ant-menu-submenu-title:hover{color:#fff;background-color:transparent}.ant-menu-dark .ant-menu-item-active>a,.ant-menu-dark .ant-menu-item:hover>a,.ant-menu-dark .ant-menu-submenu-active>a,.ant-menu-dark .ant-menu-submenu-open>a,.ant-menu-dark .ant-menu-submenu-selected>a,.ant-menu-dark .ant-menu-submenu-title:hover>a{color:#fff}.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{opacity:1}.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark .ant-menu-item:hover{background-color:transparent}.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}.ant-menu-dark .ant-menu-item-selected:after{border-right:0}.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#fff}.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#1890ff}.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}.ant-tooltip{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;z-index:1060;display:block;max-width:250px;visibility:visible}.ant-tooltip-hidden{display:none}.ant-tooltip-placement-top,.ant-tooltip-placement-topLeft,.ant-tooltip-placement-topRight{padding-bottom:8px}.ant-tooltip-placement-right,.ant-tooltip-placement-rightBottom,.ant-tooltip-placement-rightTop{padding-left:8px}.ant-tooltip-placement-bottom,.ant-tooltip-placement-bottomLeft,.ant-tooltip-placement-bottomRight{padding-top:8px}.ant-tooltip-placement-left,.ant-tooltip-placement-leftBottom,.ant-tooltip-placement-leftTop{padding-right:8px}.ant-tooltip-inner{min-width:30px;min-height:32px;padding:6px 8px;color:#fff;text-align:left;text-decoration:none;word-wrap:break-word;background-color:rgba(0,0,0,.75);border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-tooltip-arrow{position:absolute;display:block;width:13.07106781px;height:13.07106781px;overflow:hidden;background:transparent;pointer-events:none}.ant-tooltip-arrow:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:5px;height:5px;margin:auto;background-color:rgba(0,0,0,.75);content:\"\";pointer-events:auto}.ant-tooltip-placement-top .ant-tooltip-arrow,.ant-tooltip-placement-topLeft .ant-tooltip-arrow,.ant-tooltip-placement-topRight .ant-tooltip-arrow{bottom:-5.07106781px}.ant-tooltip-placement-top .ant-tooltip-arrow:before,.ant-tooltip-placement-topLeft .ant-tooltip-arrow:before,.ant-tooltip-placement-topRight .ant-tooltip-arrow:before{-webkit-box-shadow:3px 3px 7px rgba(0,0,0,.07);box-shadow:3px 3px 7px rgba(0,0,0,.07);-webkit-transform:translateY(-6.53553391px) rotate(45deg);-ms-transform:translateY(-6.53553391px) rotate(45deg);transform:translateY(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-top .ant-tooltip-arrow{left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.ant-tooltip-placement-topLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-topRight .ant-tooltip-arrow{right:13px}.ant-tooltip-placement-right .ant-tooltip-arrow,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow,.ant-tooltip-placement-rightTop .ant-tooltip-arrow{left:-5.07106781px}.ant-tooltip-placement-right .ant-tooltip-arrow:before,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow:before,.ant-tooltip-placement-rightTop .ant-tooltip-arrow:before{-webkit-box-shadow:-3px 3px 7px rgba(0,0,0,.07);box-shadow:-3px 3px 7px rgba(0,0,0,.07);-webkit-transform:translateX(6.53553391px) rotate(45deg);-ms-transform:translateX(6.53553391px) rotate(45deg);transform:translateX(6.53553391px) rotate(45deg)}.ant-tooltip-placement-right .ant-tooltip-arrow{top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-tooltip-placement-rightTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-left .ant-tooltip-arrow,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow,.ant-tooltip-placement-leftTop .ant-tooltip-arrow{right:-5.07106781px}.ant-tooltip-placement-left .ant-tooltip-arrow:before,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow:before,.ant-tooltip-placement-leftTop .ant-tooltip-arrow:before{-webkit-box-shadow:3px -3px 7px rgba(0,0,0,.07);box-shadow:3px -3px 7px rgba(0,0,0,.07);-webkit-transform:translateX(-6.53553391px) rotate(45deg);-ms-transform:translateX(-6.53553391px) rotate(45deg);transform:translateX(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-left .ant-tooltip-arrow{top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-tooltip-placement-leftTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-bottom .ant-tooltip-arrow,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{top:-5.07106781px}.ant-tooltip-placement-bottom .ant-tooltip-arrow:before,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow:before,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow:before{-webkit-box-shadow:-3px -3px 7px rgba(0,0,0,.07);box-shadow:-3px -3px 7px rgba(0,0,0,.07);-webkit-transform:translateY(6.53553391px) rotate(45deg);-ms-transform:translateY(6.53553391px) rotate(45deg);transform:translateY(6.53553391px) rotate(45deg)}.ant-tooltip-placement-bottom .ant-tooltip-arrow{left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{right:13px}.ant-dropdown{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-dropdown:before{position:absolute;top:-7px;right:0;bottom:-7px;left:-7px;z-index:-9999;opacity:.0001;content:\" \"}.ant-dropdown-wrap{position:relative}.ant-dropdown-wrap .ant-btn>.anticon-down{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-wrap .ant-btn>.anticon-down{font-size:12px}.ant-dropdown-wrap .anticon-down:before{-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s}.ant-dropdown-wrap-open .anticon-down:before{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.ant-dropdown-hidden,.ant-dropdown-menu-hidden{display:none}.ant-dropdown-menu{position:relative;margin:0;padding:4px 0;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15);-webkit-transform:translateZ(0)}.ant-dropdown-menu-item-group-title{padding:5px 12px;color:rgba(0,0,0,.45);-webkit-transition:all .3s;transition:all .3s}.ant-dropdown-menu-submenu-popup{position:absolute;z-index:1050}.ant-dropdown-menu-submenu-popup>.ant-dropdown-menu{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.ant-dropdown-menu-submenu-popup li,.ant-dropdown-menu-submenu-popup ul{list-style:none}.ant-dropdown-menu-submenu-popup ul{margin-right:.3em;margin-left:.3em;padding:0}.ant-dropdown-menu-item,.ant-dropdown-menu-submenu-title{clear:both;margin:0;padding:5px 12px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-dropdown-menu-item>.anticon:first-child,.ant-dropdown-menu-item>span>.anticon:first-child,.ant-dropdown-menu-submenu-title>.anticon:first-child,.ant-dropdown-menu-submenu-title>span>.anticon:first-child{min-width:12px;margin-right:8px;font-size:12px}.ant-dropdown-menu-item>a,.ant-dropdown-menu-submenu-title>a{display:block;margin:-5px -12px;padding:5px 12px;color:rgba(0,0,0,.65);-webkit-transition:all .3s;transition:all .3s}.ant-dropdown-menu-item-selected,.ant-dropdown-menu-item-selected>a,.ant-dropdown-menu-submenu-title-selected,.ant-dropdown-menu-submenu-title-selected>a{color:#1890ff;background-color:#e6f7ff}.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover{background-color:#e6f7ff}.ant-dropdown-menu-item-disabled,.ant-dropdown-menu-submenu-title-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-dropdown-menu-item-disabled:hover,.ant-dropdown-menu-submenu-title-disabled:hover{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-item-divider,.ant-dropdown-menu-submenu-title-divider{height:1px;margin:4px 0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow{position:absolute;right:8px}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:rgba(0,0,0,.45);font-style:normal;display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow-icon,:root .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{font-size:12px}.ant-dropdown-menu-item-group-list{margin:0 8px;padding:0;list-style:none}.ant-dropdown-menu-submenu-title{padding-right:26px}.ant-dropdown-menu-submenu-vertical{position:relative}.ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{position:absolute;top:0;left:100%;min-width:100%;margin-left:4px;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title{color:#1890ff}.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomRight,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomRight{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn}.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topRight,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topRight{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn}.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomRight{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut}.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topRight{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut}.ant-dropdown-link>.anticon.anticon-down,.ant-dropdown-trigger>.anticon.anticon-down{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-link>.anticon.anticon-down,:root .ant-dropdown-trigger>.anticon.anticon-down{font-size:12px}.ant-dropdown-button{white-space:nowrap}.ant-dropdown-button.ant-btn-group>.ant-btn:last-child:not(:first-child){padding-right:8px;padding-left:8px}.ant-dropdown-button .anticon.anticon-down{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-button .anticon.anticon-down{font-size:12px}.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu{background:#001529}.ant-dropdown-menu-dark .ant-dropdown-menu-item,.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow:after{color:hsla(0,0%,100%,.65)}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover{color:#fff;background:transparent}.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected>a{color:#fff;background:#1890ff}.ant-fullcalendar{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";border-top:1px solid #d9d9d9;outline:none}.ant-select.ant-fullcalendar-year-select{min-width:90px}.ant-select.ant-fullcalendar-year-select.ant-select-sm{min-width:70px}.ant-select.ant-fullcalendar-month-select{min-width:80px;margin-left:8px}.ant-select.ant-fullcalendar-month-select.ant-select-sm{min-width:70px}.ant-fullcalendar-header{padding:11px 16px 11px 0;text-align:right}.ant-fullcalendar-header .ant-select-dropdown{text-align:left}.ant-fullcalendar-header .ant-radio-group{margin-left:8px;text-align:left}.ant-fullcalendar-header label.ant-radio-button{height:22px;padding:0 10px;line-height:20px}.ant-fullcalendar-date-panel{position:relative;outline:none}.ant-fullcalendar-calendar-body{padding:8px 12px}.ant-fullcalendar table{width:100%;max-width:100%;height:256px;background-color:transparent;border-collapse:collapse}.ant-fullcalendar table,.ant-fullcalendar td,.ant-fullcalendar th{border:0}.ant-fullcalendar td{position:relative}.ant-fullcalendar-calendar-table{margin-bottom:0;border-spacing:0}.ant-fullcalendar-column-header{width:33px;padding:0;line-height:18px;text-align:center}.ant-fullcalendar-column-header .ant-fullcalendar-column-header-inner{display:block;font-weight:400}.ant-fullcalendar-week-number-header .ant-fullcalendar-column-header-inner{display:none}.ant-fullcalendar-date,.ant-fullcalendar-month{text-align:center;-webkit-transition:all .3s;transition:all .3s}.ant-fullcalendar-value{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:24px;background:transparent;border-radius:2px;-webkit-transition:all .3s;transition:all .3s}.ant-fullcalendar-value:hover{background:#e6f7ff;cursor:pointer}.ant-fullcalendar-value:active{color:#fff;background:#1890ff}.ant-fullcalendar-month-panel-cell .ant-fullcalendar-value{width:48px}.ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-value,.ant-fullcalendar-today .ant-fullcalendar-value{-webkit-box-shadow:0 0 0 1px #1890ff inset;box-shadow:inset 0 0 0 1px #1890ff}.ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-value,.ant-fullcalendar-selected-day .ant-fullcalendar-value{color:#fff;background:#1890ff}.ant-fullcalendar-disabled-cell-first-of-row .ant-fullcalendar-value{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-fullcalendar-disabled-cell-last-of-row .ant-fullcalendar-value{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-fullcalendar-last-month-cell .ant-fullcalendar-value,.ant-fullcalendar-next-month-btn-day .ant-fullcalendar-value{color:rgba(0,0,0,.25)}.ant-fullcalendar-month-panel-table{width:100%;table-layout:fixed;border-collapse:separate}.ant-fullcalendar-content{position:absolute;bottom:-9px;left:0;width:100%}.ant-fullcalendar-fullscreen{border-top:0}.ant-fullcalendar-fullscreen .ant-fullcalendar-table{table-layout:fixed}.ant-fullcalendar-fullscreen .ant-fullcalendar-header .ant-radio-group{margin-left:16px}.ant-fullcalendar-fullscreen .ant-fullcalendar-header label.ant-radio-button{height:32px;line-height:30px}.ant-fullcalendar-fullscreen .ant-fullcalendar-date,.ant-fullcalendar-fullscreen .ant-fullcalendar-month{display:block;height:116px;margin:0 4px;padding:4px 8px;color:rgba(0,0,0,.65);text-align:left;border-top:2px solid #e8e8e8;-webkit-transition:background .3s;transition:background .3s}.ant-fullcalendar-fullscreen .ant-fullcalendar-date:hover,.ant-fullcalendar-fullscreen .ant-fullcalendar-month:hover{background:#e6f7ff;cursor:pointer}.ant-fullcalendar-fullscreen .ant-fullcalendar-date:active,.ant-fullcalendar-fullscreen .ant-fullcalendar-month:active{background:#bae7ff}.ant-fullcalendar-fullscreen .ant-fullcalendar-column-header{padding-right:12px;padding-bottom:5px;text-align:right}.ant-fullcalendar-fullscreen .ant-fullcalendar-value{width:auto;text-align:right;background:transparent}.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-value{color:rgba(0,0,0,.65)}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-month,.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-date{background:transparent;border-top-color:#1890ff}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-value,.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-value{-webkit-box-shadow:none;box-shadow:none}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-month,.ant-fullcalendar-fullscreen .ant-fullcalendar-selected-day .ant-fullcalendar-date{background:#e6f7ff}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-value,.ant-fullcalendar-fullscreen .ant-fullcalendar-selected-day .ant-fullcalendar-value{color:#1890ff}.ant-fullcalendar-fullscreen .ant-fullcalendar-last-month-cell .ant-fullcalendar-date,.ant-fullcalendar-fullscreen .ant-fullcalendar-next-month-btn-day .ant-fullcalendar-date{color:rgba(0,0,0,.25)}.ant-fullcalendar-fullscreen .ant-fullcalendar-content{position:static;width:auto;height:88px;overflow-y:auto}.ant-fullcalendar-disabled-cell .ant-fullcalendar-date,.ant-fullcalendar-disabled-cell .ant-fullcalendar-date:hover{cursor:not-allowed}.ant-fullcalendar-disabled-cell:not(.ant-fullcalendar-today) .ant-fullcalendar-date,.ant-fullcalendar-disabled-cell:not(.ant-fullcalendar-today) .ant-fullcalendar-date:hover{background:transparent}.ant-fullcalendar-disabled-cell .ant-fullcalendar-value{width:auto;color:rgba(0,0,0,.25);border-radius:0;cursor:not-allowed}.ant-radio-group{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block}.ant-radio-wrapper{margin:0 8px 0 0}.ant-radio,.ant-radio-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;white-space:nowrap;cursor:pointer}.ant-radio{margin:0;line-height:1;vertical-align:sub;outline:none}.ant-radio-input:focus+.ant-radio-inner,.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner{border-color:#1890ff}.ant-radio-input:focus+.ant-radio-inner{-webkit-box-shadow:0 0 0 3px rgba(24,144,255,.08);box-shadow:0 0 0 3px rgba(24,144,255,.08)}.ant-radio-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;visibility:hidden;-webkit-animation:antRadioEffect .36s ease-in-out;animation:antRadioEffect .36s ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both;content:\"\"}.ant-radio-wrapper:hover .ant-radio:after,.ant-radio:hover:after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:100px;-webkit-transition:all .3s;transition:all .3s}.ant-radio-inner:after{position:absolute;top:3px;left:3px;display:table;width:8px;height:8px;background-color:#1890ff;border-top:0;border-left:0;border-radius:8px;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-transition:all .3s cubic-bezier(.78,.14,.15,.86);transition:all .3s cubic-bezier(.78,.14,.15,.86);content:\" \"}.ant-radio-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;cursor:pointer;opacity:0}.ant-radio-checked .ant-radio-inner{border-color:#1890ff}.ant-radio-checked .ant-radio-inner:after{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1);opacity:1;-webkit-transition:all .3s cubic-bezier(.78,.14,.15,.86);transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-radio-disabled .ant-radio-inner{background-color:#f5f5f5;border-color:#d9d9d9!important;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner:after{background-color:rgba(0,0,0,.2)}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:rgba(0,0,0,.65);line-height:30px;background:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-left:0;cursor:pointer;-webkit-transition:color .3s,background .3s,border-color .3s;transition:color .3s,background .3s,border-color .3s}.ant-radio-button-wrapper a{color:rgba(0,0,0,.65)}.ant-radio-button-wrapper>.ant-radio-button{display:block;width:0;height:0;margin-left:0}.ant-radio-group-large .ant-radio-button-wrapper{height:40px;font-size:16px;line-height:38px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child):before{position:absolute;top:0;left:-1px;display:block;width:1px;height:100%;background-color:#d9d9d9;content:\"\"}.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:4px 0 0 4px}.ant-radio-button-wrapper:last-child{border-radius:0 4px 4px 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:4px}.ant-radio-button-wrapper:hover{position:relative;color:#1890ff}.ant-radio-button-wrapper:focus-within{outline:3px solid rgba(24,144,255,.06)}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#1890ff;background:#fff;border-color:#1890ff;-webkit-box-shadow:-1px 0 0 0 #1890ff;box-shadow:-1px 0 0 0 #1890ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#1890ff!important;opacity:.1}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#1890ff;-webkit-box-shadow:none!important;box-shadow:none!important}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#40a9ff;border-color:#40a9ff;-webkit-box-shadow:-1px 0 0 0 #40a9ff;box-shadow:-1px 0 0 0 #40a9ff}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#096dd9;border-color:#096dd9;-webkit-box-shadow:-1px 0 0 0 #096dd9;box-shadow:-1px 0 0 0 #096dd9}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#1890ff;border-color:#1890ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#40a9ff;border-color:#40a9ff}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#096dd9;border-color:#096dd9}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}.ant-radio-button-wrapper-disabled{cursor:not-allowed}.ant-radio-button-wrapper-disabled,.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#d9d9d9}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#fff;background-color:#e6e6e6;border-color:#d9d9d9;-webkit-box-shadow:none;box-shadow:none}@-webkit-keyframes antRadioEffect{0%{-webkit-transform:scale(1);transform:scale(1);opacity:.5}to{-webkit-transform:scale(1.6);transform:scale(1.6);opacity:0}}@keyframes antRadioEffect{0%{-webkit-transform:scale(1);transform:scale(1);opacity:.5}to{-webkit-transform:scale(1.6);transform:scale(1.6);opacity:0}}@supports (-moz-appearance:meterbar) and (background-blend-mode:difference,normal){.ant-radio{vertical-align:text-bottom}}.ant-card{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;background:#fff;border-radius:2px;-webkit-transition:all .3s;transition:all .3s}.ant-card-hoverable{cursor:pointer}.ant-card-hoverable:hover{border-color:rgba(0,0,0,.09);-webkit-box-shadow:0 2px 8px rgba(0,0,0,.09);box-shadow:0 2px 8px rgba(0,0,0,.09)}.ant-card-bordered{border:1px solid #e8e8e8}.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;background:transparent;border-bottom:1px solid #e8e8e8;border-radius:2px 2px 0 0;zoom:1}.ant-card-head:after,.ant-card-head:before{display:table;content:\"\"}.ant-card-head:after{clear:both}.ant-card-head-wrapper{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.ant-card-head-title{display:inline-block;-ms-flex:1;flex:1 1;padding:16px 0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-card-head .ant-tabs{clear:both;margin-bottom:-17px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px}.ant-card-head .ant-tabs-bar{border-bottom:1px solid #e8e8e8}.ant-card-extra{float:right;margin-left:auto;padding:16px 0;color:rgba(0,0,0,.65);font-weight:400;font-size:14px}.ant-card-body{padding:24px;zoom:1}.ant-card-body:after,.ant-card-body:before{display:table;content:\"\"}.ant-card-body:after{clear:both}.ant-card-contain-grid:not(.ant-card-loading) .ant-card-body{margin:-1px 0 0 -1px;padding:0}.ant-card-grid{float:left;width:33.33%;padding:24px;border:0;border-radius:0;-webkit-box-shadow:1px 0 0 0 #e8e8e8,0 1px 0 0 #e8e8e8,1px 1px 0 0 #e8e8e8,1px 0 0 0 #e8e8e8 inset,0 1px 0 0 #e8e8e8 inset;box-shadow:1px 0 0 0 #e8e8e8,0 1px 0 0 #e8e8e8,1px 1px 0 0 #e8e8e8,inset 1px 0 0 0 #e8e8e8,inset 0 1px 0 0 #e8e8e8;-webkit-transition:all .3s;transition:all .3s}.ant-card-grid-hoverable:hover{position:relative;z-index:1;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-card-contain-tabs>.ant-card-head .ant-card-head-title{min-height:32px;padding-bottom:0}.ant-card-contain-tabs>.ant-card-head .ant-card-extra{padding-bottom:0}.ant-card-cover>*{display:block;width:100%}.ant-card-cover img{border-radius:2px 2px 0 0}.ant-card-actions{margin:0;padding:0;list-style:none;background:#fafafa;border-top:1px solid #e8e8e8;zoom:1}.ant-card-actions:after,.ant-card-actions:before{display:table;content:\"\"}.ant-card-actions:after{clear:both}.ant-card-actions>li{float:left;margin:12px 0;color:rgba(0,0,0,.45);text-align:center}.ant-card-actions>li>span{position:relative;display:block;min-width:32px;font-size:14px;line-height:22px;cursor:pointer}.ant-card-actions>li>span:hover{color:#1890ff;-webkit-transition:color .3s;transition:color .3s}.ant-card-actions>li>span>.anticon,.ant-card-actions>li>span a:not(.ant-btn){display:inline-block;width:100%;color:rgba(0,0,0,.45);line-height:22px;-webkit-transition:color .3s;transition:color .3s}.ant-card-actions>li>span>.anticon:hover,.ant-card-actions>li>span a:not(.ant-btn):hover{color:#1890ff}.ant-card-actions>li>span>.anticon{font-size:16px;line-height:22px}.ant-card-actions>li:not(:last-child){border-right:1px solid #e8e8e8}.ant-card-type-inner .ant-card-head{padding:0 24px;background:#fafafa}.ant-card-type-inner .ant-card-head-title{padding:12px 0;font-size:14px}.ant-card-type-inner .ant-card-body{padding:16px 24px}.ant-card-type-inner .ant-card-extra{padding:13.5px 0}.ant-card-meta{margin:-4px 0;zoom:1}.ant-card-meta:after,.ant-card-meta:before{display:table;content:\"\"}.ant-card-meta:after{clear:both}.ant-card-meta-avatar{float:left;padding-right:16px}.ant-card-meta-detail{overflow:hidden}.ant-card-meta-detail>div:not(:last-child){margin-bottom:8px}.ant-card-meta-title{overflow:hidden;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.ant-card-meta-description{color:rgba(0,0,0,.45)}.ant-card-loading{overflow:hidden}.ant-card-loading .ant-card-body{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-card-loading-content p{margin:0}.ant-card-loading-block{height:14px;margin:4px 0;background:-webkit-gradient(linear,left top,right top,from(rgba(207,216,220,.2)),color-stop(rgba(207,216,220,.4)),to(rgba(207,216,220,.2)));background:linear-gradient(90deg,rgba(207,216,220,.2),rgba(207,216,220,.4),rgba(207,216,220,.2));background-size:600% 600%;border-radius:2px;-webkit-animation:card-loading 1.4s ease infinite;animation:card-loading 1.4s ease infinite}@-webkit-keyframes card-loading{0%,to{background-position:0 50%}50%{background-position:100% 50%}}@keyframes card-loading{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.ant-card-small>.ant-card-head{min-height:36px;padding:0 12px;font-size:14px}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-head-title{padding:8px 0}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-extra{padding:8px 0;font-size:14px}.ant-card-small>.ant-card-body{padding:12px}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-container{height:40px}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-ink-bar{visibility:hidden}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab{height:40px;margin:0 2px 0 0;padding:0 16px;line-height:38px;background:#fafafa;border:1px solid #e8e8e8;border-radius:4px 4px 0 0;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active{height:40px;color:#1890ff;background:#fff;border-color:#e8e8e8;border-bottom:1px solid #fff}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active:before{border-top:2px solid transparent}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-disabled{color:#1890ff;color:rgba(0,0,0,.25)}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-inactive{padding:0}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-wrap{margin-bottom:0}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab .ant-tabs-close-x{width:16px;height:16px;height:14px;margin-right:-5px;margin-left:3px;overflow:hidden;color:rgba(0,0,0,.45);font-size:12px;vertical-align:middle;-webkit-transition:all .3s;transition:all .3s}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab .ant-tabs-close-x:hover{color:rgba(0,0,0,.85)}.ant-tabs.ant-tabs-card .ant-tabs-card-content>.ant-tabs-tabpane,.ant-tabs.ant-tabs-editable-card .ant-tabs-card-content>.ant-tabs-tabpane{-webkit-transition:none!important;transition:none!important}.ant-tabs.ant-tabs-card .ant-tabs-card-content>.ant-tabs-tabpane-inactive,.ant-tabs.ant-tabs-editable-card .ant-tabs-card-content>.ant-tabs-tabpane-inactive{overflow:hidden}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab:hover .anticon-close{opacity:1}.ant-tabs-extra-content{line-height:45px}.ant-tabs-extra-content .ant-tabs-new-tab{position:relative;width:20px;height:20px;color:rgba(0,0,0,.65);font-size:12px;line-height:20px;text-align:center;border:1px solid #e8e8e8;border-radius:2px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-tabs-extra-content .ant-tabs-new-tab:hover{color:#1890ff;border-color:#1890ff}.ant-tabs-extra-content .ant-tabs-new-tab svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-tabs.ant-tabs-large .ant-tabs-extra-content{line-height:56px}.ant-tabs.ant-tabs-small .ant-tabs-extra-content{line-height:37px}.ant-tabs.ant-tabs-card .ant-tabs-extra-content{line-height:40px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-nav-container{height:100%}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab{margin-bottom:8px;border-bottom:1px solid #e8e8e8}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab-active,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{padding-bottom:4px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab:last-child,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab:last-child{margin-bottom:8px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-new-tab,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-new-tab{width:90%}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-nav-wrap{margin-right:0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab{margin-right:1px;border-right:0;border-radius:4px 0 0 4px}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab-active{margin-right:-1px;padding-right:18px}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-nav-wrap{margin-left:0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab{margin-left:1px;border-left:0;border-radius:0 4px 4px 0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#1890ff}.ant-tabs{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;overflow:hidden;zoom:1}.ant-tabs:after,.ant-tabs:before{display:table;content:\"\"}.ant-tabs:after{clear:both}.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:2px;background-color:#1890ff;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.ant-tabs-bar{margin:0 0 16px;border-bottom:1px solid #e8e8e8;outline:none}.ant-tabs-bar,.ant-tabs-nav-container{-webkit-transition:padding .3s cubic-bezier(.645,.045,.355,1);transition:padding .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav-container{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;margin-bottom:-1px;overflow:hidden;font-size:14px;line-height:1.5;white-space:nowrap;zoom:1}.ant-tabs-nav-container:after,.ant-tabs-nav-container:before{display:table;content:\"\"}.ant-tabs-nav-container:after{clear:both}.ant-tabs-nav-container-scrolling{padding-right:32px;padding-left:32px}.ant-tabs-bottom .ant-tabs-bottom-bar{margin-top:16px;margin-bottom:0;border-top:1px solid #e8e8e8;border-bottom:none}.ant-tabs-bottom .ant-tabs-bottom-bar .ant-tabs-ink-bar{top:1px;bottom:auto}.ant-tabs-bottom .ant-tabs-bottom-bar .ant-tabs-nav-container{margin-top:-1px;margin-bottom:0}.ant-tabs-tab-next,.ant-tabs-tab-prev{position:absolute;z-index:2;width:0;height:100%;color:rgba(0,0,0,.45);text-align:center;background-color:transparent;border:0;cursor:pointer;opacity:0;-webkit-transition:width .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);transition:width .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}.ant-tabs-tab-next.ant-tabs-tab-arrow-show,.ant-tabs-tab-prev.ant-tabs-tab-arrow-show{width:32px;height:100%;opacity:1;pointer-events:auto}.ant-tabs-tab-next:hover,.ant-tabs-tab-prev:hover{color:rgba(0,0,0,.65)}.ant-tabs-tab-next-icon,.ant-tabs-tab-prev-icon{position:absolute;top:50%;left:50%;font-weight:700;font-style:normal;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;line-height:inherit;text-align:center;text-transform:none;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.ant-tabs-tab-next-icon-target,.ant-tabs-tab-prev-icon-target{display:block;display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg)}:root .ant-tabs-tab-next-icon-target,:root .ant-tabs-tab-prev-icon-target{font-size:12px}.ant-tabs-tab-btn-disabled{cursor:not-allowed}.ant-tabs-tab-btn-disabled,.ant-tabs-tab-btn-disabled:hover{color:rgba(0,0,0,.25)}.ant-tabs-tab-next{right:2px}.ant-tabs-tab-prev{left:0}:root .ant-tabs-tab-prev{-webkit-filter:none;filter:none}.ant-tabs-nav-wrap{margin-bottom:-1px;overflow:hidden}.ant-tabs-nav-scroll{overflow:hidden;white-space:nowrap}.ant-tabs-nav{position:relative;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding-left:0;list-style:none;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav:after,.ant-tabs-nav:before{display:table;content:\" \"}.ant-tabs-nav:after{clear:both}.ant-tabs-nav .ant-tabs-tab{position:relative;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;height:100%;margin:0 32px 0 0;padding:12px 16px;text-decoration:none;cursor:pointer;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav .ant-tabs-tab:before{position:absolute;top:-1px;left:0;width:100%;border-top:2px solid transparent;border-radius:4px 4px 0 0;-webkit-transition:all .3s;transition:all .3s;content:\"\";pointer-events:none}.ant-tabs-nav .ant-tabs-tab:last-child{margin-right:0}.ant-tabs-nav .ant-tabs-tab:hover{color:#40a9ff}.ant-tabs-nav .ant-tabs-tab:active{color:#096dd9}.ant-tabs-nav .ant-tabs-tab .anticon{margin-right:8px}.ant-tabs-nav .ant-tabs-tab-active{color:#1890ff;font-weight:500}.ant-tabs-nav .ant-tabs-tab-disabled,.ant-tabs-nav .ant-tabs-tab-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-tabs .ant-tabs-large-bar .ant-tabs-nav-container{font-size:16px}.ant-tabs .ant-tabs-large-bar .ant-tabs-tab{padding:16px}.ant-tabs .ant-tabs-small-bar .ant-tabs-nav-container{font-size:14px}.ant-tabs .ant-tabs-small-bar .ant-tabs-tab{padding:8px 16px}.ant-tabs-content:before{display:block;overflow:hidden;content:\"\"}.ant-tabs .ant-tabs-bottom-content,.ant-tabs .ant-tabs-top-content{width:100%}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane{-ms-flex-negative:0;flex-shrink:0;width:100%;-webkit-backface-visibility:hidden;opacity:1;-webkit-transition:opacity .45s;transition:opacity .45s}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane-inactive,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane-inactive input,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-tabs .ant-tabs-bottom-content.ant-tabs-content-animated,.ant-tabs .ant-tabs-top-content.ant-tabs-content-animated{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-webkit-transition:margin-left .3s cubic-bezier(.645,.045,.355,1);transition:margin-left .3s cubic-bezier(.645,.045,.355,1);will-change:margin-left}.ant-tabs .ant-tabs-left-bar,.ant-tabs .ant-tabs-right-bar{height:100%;border-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-arrow-show,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-arrow-show{width:100%;height:32px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab{display:block;float:none;margin:0 0 16px;padding:8px 24px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab:last-child,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab:last-child{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-extra-content,.ant-tabs .ant-tabs-right-bar .ant-tabs-extra-content{text-align:center}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-scroll,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-scroll{width:auto}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{height:100%}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container.ant-tabs-nav-container-scrolling,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container.ant-tabs-nav-container-scrolling{padding:32px 0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav{width:100%}.ant-tabs .ant-tabs-left-bar .ant-tabs-ink-bar,.ant-tabs .ant-tabs-right-bar .ant-tabs-ink-bar{top:0;bottom:auto;left:auto;width:2px;height:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-next,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-next{right:0;bottom:0;width:100%;height:32px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-prev,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-prev{top:0;width:100%;height:32px}.ant-tabs .ant-tabs-left-content,.ant-tabs .ant-tabs-right-content{width:auto;margin-top:0!important;overflow:hidden}.ant-tabs .ant-tabs-left-bar{float:left;margin-right:-1px;margin-bottom:0;border-right:1px solid #e8e8e8}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab{text-align:right}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap{margin-right:-1px}.ant-tabs .ant-tabs-left-bar .ant-tabs-ink-bar{right:1px}.ant-tabs .ant-tabs-left-content{padding-left:24px;border-left:1px solid #e8e8e8}.ant-tabs .ant-tabs-right-bar{float:right;margin-bottom:0;margin-left:-1px;border-left:1px solid #e8e8e8}.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{margin-left:-1px}.ant-tabs .ant-tabs-right-bar .ant-tabs-ink-bar{left:1px}.ant-tabs .ant-tabs-right-content{padding-right:24px;border-right:1px solid #e8e8e8}.ant-tabs-bottom .ant-tabs-ink-bar-animated,.ant-tabs-top .ant-tabs-ink-bar-animated{-webkit-transition:width .2s cubic-bezier(.645,.045,.355,1),left .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:width .2s cubic-bezier(.645,.045,.355,1),left .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),width .2s cubic-bezier(.645,.045,.355,1),left .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),width .2s cubic-bezier(.645,.045,.355,1),left .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-left .ant-tabs-ink-bar-animated,.ant-tabs-right .ant-tabs-ink-bar-animated{-webkit-transition:height .2s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:height .2s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),height .2s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),height .2s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-content-animated,.no-flex>.ant-tabs-content>.ant-tabs-content-animated{margin-left:0!important;-webkit-transform:none!important;-ms-transform:none!important;transform:none!important}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-tabpane-inactive,.no-flex>.ant-tabs-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-tabpane-inactive input,.no-flex>.ant-tabs-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-tabs-left-content>.ant-tabs-content-animated,.ant-tabs-right-content>.ant-tabs-content-animated{margin-left:0!important;-webkit-transform:none!important;-ms-transform:none!important;transform:none!important}.ant-tabs-left-content>.ant-tabs-tabpane-inactive,.ant-tabs-right-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs-left-content>.ant-tabs-tabpane-inactive input,.ant-tabs-right-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-row{position:relative;height:auto;margin-right:0;margin-left:0;zoom:1;display:block;-webkit-box-sizing:border-box;box-sizing:border-box}.ant-row:after,.ant-row:before{display:table;content:\"\"}.ant-row+.ant-row:before,.ant-row:after{clear:both}.ant-row-flex{-ms-flex-flow:row wrap;flex-flow:row wrap}.ant-row-flex,.ant-row-flex:after,.ant-row-flex:before{display:-ms-flexbox;display:flex}.ant-row-flex-start{-ms-flex-pack:start;justify-content:flex-start}.ant-row-flex-center{-ms-flex-pack:center;justify-content:center}.ant-row-flex-end{-ms-flex-pack:end;justify-content:flex-end}.ant-row-flex-space-between{-ms-flex-pack:justify;justify-content:space-between}.ant-row-flex-space-around{-ms-flex-pack:distribute;justify-content:space-around}.ant-row-flex-top{-ms-flex-align:start;align-items:flex-start}.ant-row-flex-middle{-ms-flex-align:center;align-items:center}.ant-row-flex-bottom{-ms-flex-align:end;align-items:flex-end}.ant-col{position:relative;min-height:1px}.ant-col-1,.ant-col-2,.ant-col-3,.ant-col-4,.ant-col-5,.ant-col-6,.ant-col-7,.ant-col-8,.ant-col-9,.ant-col-10,.ant-col-11,.ant-col-12,.ant-col-13,.ant-col-14,.ant-col-15,.ant-col-16,.ant-col-17,.ant-col-18,.ant-col-19,.ant-col-20,.ant-col-21,.ant-col-22,.ant-col-23,.ant-col-24,.ant-col-lg-1,.ant-col-lg-2,.ant-col-lg-3,.ant-col-lg-4,.ant-col-lg-5,.ant-col-lg-6,.ant-col-lg-7,.ant-col-lg-8,.ant-col-lg-9,.ant-col-lg-10,.ant-col-lg-11,.ant-col-lg-12,.ant-col-lg-13,.ant-col-lg-14,.ant-col-lg-15,.ant-col-lg-16,.ant-col-lg-17,.ant-col-lg-18,.ant-col-lg-19,.ant-col-lg-20,.ant-col-lg-21,.ant-col-lg-22,.ant-col-lg-23,.ant-col-lg-24,.ant-col-md-1,.ant-col-md-2,.ant-col-md-3,.ant-col-md-4,.ant-col-md-5,.ant-col-md-6,.ant-col-md-7,.ant-col-md-8,.ant-col-md-9,.ant-col-md-10,.ant-col-md-11,.ant-col-md-12,.ant-col-md-13,.ant-col-md-14,.ant-col-md-15,.ant-col-md-16,.ant-col-md-17,.ant-col-md-18,.ant-col-md-19,.ant-col-md-20,.ant-col-md-21,.ant-col-md-22,.ant-col-md-23,.ant-col-md-24,.ant-col-sm-1,.ant-col-sm-2,.ant-col-sm-3,.ant-col-sm-4,.ant-col-sm-5,.ant-col-sm-6,.ant-col-sm-7,.ant-col-sm-8,.ant-col-sm-9,.ant-col-sm-10,.ant-col-sm-11,.ant-col-sm-12,.ant-col-sm-13,.ant-col-sm-14,.ant-col-sm-15,.ant-col-sm-16,.ant-col-sm-17,.ant-col-sm-18,.ant-col-sm-19,.ant-col-sm-20,.ant-col-sm-21,.ant-col-sm-22,.ant-col-sm-23,.ant-col-sm-24,.ant-col-xs-1,.ant-col-xs-2,.ant-col-xs-3,.ant-col-xs-4,.ant-col-xs-5,.ant-col-xs-6,.ant-col-xs-7,.ant-col-xs-8,.ant-col-xs-9,.ant-col-xs-10,.ant-col-xs-11,.ant-col-xs-12,.ant-col-xs-13,.ant-col-xs-14,.ant-col-xs-15,.ant-col-xs-16,.ant-col-xs-17,.ant-col-xs-18,.ant-col-xs-19,.ant-col-xs-20,.ant-col-xs-21,.ant-col-xs-22,.ant-col-xs-23,.ant-col-xs-24{position:relative;padding-right:0;padding-left:0}.ant-col-1,.ant-col-2,.ant-col-3,.ant-col-4,.ant-col-5,.ant-col-6,.ant-col-7,.ant-col-8,.ant-col-9,.ant-col-10,.ant-col-11,.ant-col-12,.ant-col-13,.ant-col-14,.ant-col-15,.ant-col-16,.ant-col-17,.ant-col-18,.ant-col-19,.ant-col-20,.ant-col-21,.ant-col-22,.ant-col-23,.ant-col-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-push-24{left:100%}.ant-col-pull-24{right:100%}.ant-col-offset-24{margin-left:100%}.ant-col-order-24{-ms-flex-order:24;order:24}.ant-col-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-push-23{left:95.83333333%}.ant-col-pull-23{right:95.83333333%}.ant-col-offset-23{margin-left:95.83333333%}.ant-col-order-23{-ms-flex-order:23;order:23}.ant-col-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-push-22{left:91.66666667%}.ant-col-pull-22{right:91.66666667%}.ant-col-offset-22{margin-left:91.66666667%}.ant-col-order-22{-ms-flex-order:22;order:22}.ant-col-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-push-21{left:87.5%}.ant-col-pull-21{right:87.5%}.ant-col-offset-21{margin-left:87.5%}.ant-col-order-21{-ms-flex-order:21;order:21}.ant-col-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-push-20{left:83.33333333%}.ant-col-pull-20{right:83.33333333%}.ant-col-offset-20{margin-left:83.33333333%}.ant-col-order-20{-ms-flex-order:20;order:20}.ant-col-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-push-19{left:79.16666667%}.ant-col-pull-19{right:79.16666667%}.ant-col-offset-19{margin-left:79.16666667%}.ant-col-order-19{-ms-flex-order:19;order:19}.ant-col-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-push-18{left:75%}.ant-col-pull-18{right:75%}.ant-col-offset-18{margin-left:75%}.ant-col-order-18{-ms-flex-order:18;order:18}.ant-col-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-push-17{left:70.83333333%}.ant-col-pull-17{right:70.83333333%}.ant-col-offset-17{margin-left:70.83333333%}.ant-col-order-17{-ms-flex-order:17;order:17}.ant-col-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-push-16{left:66.66666667%}.ant-col-pull-16{right:66.66666667%}.ant-col-offset-16{margin-left:66.66666667%}.ant-col-order-16{-ms-flex-order:16;order:16}.ant-col-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-push-15{left:62.5%}.ant-col-pull-15{right:62.5%}.ant-col-offset-15{margin-left:62.5%}.ant-col-order-15{-ms-flex-order:15;order:15}.ant-col-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-push-14{left:58.33333333%}.ant-col-pull-14{right:58.33333333%}.ant-col-offset-14{margin-left:58.33333333%}.ant-col-order-14{-ms-flex-order:14;order:14}.ant-col-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-push-13{left:54.16666667%}.ant-col-pull-13{right:54.16666667%}.ant-col-offset-13{margin-left:54.16666667%}.ant-col-order-13{-ms-flex-order:13;order:13}.ant-col-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-push-12{left:50%}.ant-col-pull-12{right:50%}.ant-col-offset-12{margin-left:50%}.ant-col-order-12{-ms-flex-order:12;order:12}.ant-col-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-push-11{left:45.83333333%}.ant-col-pull-11{right:45.83333333%}.ant-col-offset-11{margin-left:45.83333333%}.ant-col-order-11{-ms-flex-order:11;order:11}.ant-col-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-push-10{left:41.66666667%}.ant-col-pull-10{right:41.66666667%}.ant-col-offset-10{margin-left:41.66666667%}.ant-col-order-10{-ms-flex-order:10;order:10}.ant-col-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-push-9{left:37.5%}.ant-col-pull-9{right:37.5%}.ant-col-offset-9{margin-left:37.5%}.ant-col-order-9{-ms-flex-order:9;order:9}.ant-col-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-push-8{left:33.33333333%}.ant-col-pull-8{right:33.33333333%}.ant-col-offset-8{margin-left:33.33333333%}.ant-col-order-8{-ms-flex-order:8;order:8}.ant-col-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-push-7{left:29.16666667%}.ant-col-pull-7{right:29.16666667%}.ant-col-offset-7{margin-left:29.16666667%}.ant-col-order-7{-ms-flex-order:7;order:7}.ant-col-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-push-6{left:25%}.ant-col-pull-6{right:25%}.ant-col-offset-6{margin-left:25%}.ant-col-order-6{-ms-flex-order:6;order:6}.ant-col-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-push-5{left:20.83333333%}.ant-col-pull-5{right:20.83333333%}.ant-col-offset-5{margin-left:20.83333333%}.ant-col-order-5{-ms-flex-order:5;order:5}.ant-col-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-push-4{left:16.66666667%}.ant-col-pull-4{right:16.66666667%}.ant-col-offset-4{margin-left:16.66666667%}.ant-col-order-4{-ms-flex-order:4;order:4}.ant-col-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-push-3{left:12.5%}.ant-col-pull-3{right:12.5%}.ant-col-offset-3{margin-left:12.5%}.ant-col-order-3{-ms-flex-order:3;order:3}.ant-col-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-push-2{left:8.33333333%}.ant-col-pull-2{right:8.33333333%}.ant-col-offset-2{margin-left:8.33333333%}.ant-col-order-2{-ms-flex-order:2;order:2}.ant-col-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-push-1{left:4.16666667%}.ant-col-pull-1{right:4.16666667%}.ant-col-offset-1{margin-left:4.16666667%}.ant-col-order-1{-ms-flex-order:1;order:1}.ant-col-0{display:none}.ant-col-offset-0{margin-left:0}.ant-col-order-0{-ms-flex-order:0;order:0}.ant-col-xs-1,.ant-col-xs-2,.ant-col-xs-3,.ant-col-xs-4,.ant-col-xs-5,.ant-col-xs-6,.ant-col-xs-7,.ant-col-xs-8,.ant-col-xs-9,.ant-col-xs-10,.ant-col-xs-11,.ant-col-xs-12,.ant-col-xs-13,.ant-col-xs-14,.ant-col-xs-15,.ant-col-xs-16,.ant-col-xs-17,.ant-col-xs-18,.ant-col-xs-19,.ant-col-xs-20,.ant-col-xs-21,.ant-col-xs-22,.ant-col-xs-23,.ant-col-xs-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-xs-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-xs-push-24{left:100%}.ant-col-xs-pull-24{right:100%}.ant-col-xs-offset-24{margin-left:100%}.ant-col-xs-order-24{-ms-flex-order:24;order:24}.ant-col-xs-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-xs-push-23{left:95.83333333%}.ant-col-xs-pull-23{right:95.83333333%}.ant-col-xs-offset-23{margin-left:95.83333333%}.ant-col-xs-order-23{-ms-flex-order:23;order:23}.ant-col-xs-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-xs-push-22{left:91.66666667%}.ant-col-xs-pull-22{right:91.66666667%}.ant-col-xs-offset-22{margin-left:91.66666667%}.ant-col-xs-order-22{-ms-flex-order:22;order:22}.ant-col-xs-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-xs-push-21{left:87.5%}.ant-col-xs-pull-21{right:87.5%}.ant-col-xs-offset-21{margin-left:87.5%}.ant-col-xs-order-21{-ms-flex-order:21;order:21}.ant-col-xs-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-xs-push-20{left:83.33333333%}.ant-col-xs-pull-20{right:83.33333333%}.ant-col-xs-offset-20{margin-left:83.33333333%}.ant-col-xs-order-20{-ms-flex-order:20;order:20}.ant-col-xs-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-xs-push-19{left:79.16666667%}.ant-col-xs-pull-19{right:79.16666667%}.ant-col-xs-offset-19{margin-left:79.16666667%}.ant-col-xs-order-19{-ms-flex-order:19;order:19}.ant-col-xs-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-xs-push-18{left:75%}.ant-col-xs-pull-18{right:75%}.ant-col-xs-offset-18{margin-left:75%}.ant-col-xs-order-18{-ms-flex-order:18;order:18}.ant-col-xs-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-xs-push-17{left:70.83333333%}.ant-col-xs-pull-17{right:70.83333333%}.ant-col-xs-offset-17{margin-left:70.83333333%}.ant-col-xs-order-17{-ms-flex-order:17;order:17}.ant-col-xs-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-xs-push-16{left:66.66666667%}.ant-col-xs-pull-16{right:66.66666667%}.ant-col-xs-offset-16{margin-left:66.66666667%}.ant-col-xs-order-16{-ms-flex-order:16;order:16}.ant-col-xs-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-xs-push-15{left:62.5%}.ant-col-xs-pull-15{right:62.5%}.ant-col-xs-offset-15{margin-left:62.5%}.ant-col-xs-order-15{-ms-flex-order:15;order:15}.ant-col-xs-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-xs-push-14{left:58.33333333%}.ant-col-xs-pull-14{right:58.33333333%}.ant-col-xs-offset-14{margin-left:58.33333333%}.ant-col-xs-order-14{-ms-flex-order:14;order:14}.ant-col-xs-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-xs-push-13{left:54.16666667%}.ant-col-xs-pull-13{right:54.16666667%}.ant-col-xs-offset-13{margin-left:54.16666667%}.ant-col-xs-order-13{-ms-flex-order:13;order:13}.ant-col-xs-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-xs-push-12{left:50%}.ant-col-xs-pull-12{right:50%}.ant-col-xs-offset-12{margin-left:50%}.ant-col-xs-order-12{-ms-flex-order:12;order:12}.ant-col-xs-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-xs-push-11{left:45.83333333%}.ant-col-xs-pull-11{right:45.83333333%}.ant-col-xs-offset-11{margin-left:45.83333333%}.ant-col-xs-order-11{-ms-flex-order:11;order:11}.ant-col-xs-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-xs-push-10{left:41.66666667%}.ant-col-xs-pull-10{right:41.66666667%}.ant-col-xs-offset-10{margin-left:41.66666667%}.ant-col-xs-order-10{-ms-flex-order:10;order:10}.ant-col-xs-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-xs-push-9{left:37.5%}.ant-col-xs-pull-9{right:37.5%}.ant-col-xs-offset-9{margin-left:37.5%}.ant-col-xs-order-9{-ms-flex-order:9;order:9}.ant-col-xs-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-xs-push-8{left:33.33333333%}.ant-col-xs-pull-8{right:33.33333333%}.ant-col-xs-offset-8{margin-left:33.33333333%}.ant-col-xs-order-8{-ms-flex-order:8;order:8}.ant-col-xs-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-xs-push-7{left:29.16666667%}.ant-col-xs-pull-7{right:29.16666667%}.ant-col-xs-offset-7{margin-left:29.16666667%}.ant-col-xs-order-7{-ms-flex-order:7;order:7}.ant-col-xs-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-xs-push-6{left:25%}.ant-col-xs-pull-6{right:25%}.ant-col-xs-offset-6{margin-left:25%}.ant-col-xs-order-6{-ms-flex-order:6;order:6}.ant-col-xs-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-xs-push-5{left:20.83333333%}.ant-col-xs-pull-5{right:20.83333333%}.ant-col-xs-offset-5{margin-left:20.83333333%}.ant-col-xs-order-5{-ms-flex-order:5;order:5}.ant-col-xs-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-xs-push-4{left:16.66666667%}.ant-col-xs-pull-4{right:16.66666667%}.ant-col-xs-offset-4{margin-left:16.66666667%}.ant-col-xs-order-4{-ms-flex-order:4;order:4}.ant-col-xs-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-xs-push-3{left:12.5%}.ant-col-xs-pull-3{right:12.5%}.ant-col-xs-offset-3{margin-left:12.5%}.ant-col-xs-order-3{-ms-flex-order:3;order:3}.ant-col-xs-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-xs-push-2{left:8.33333333%}.ant-col-xs-pull-2{right:8.33333333%}.ant-col-xs-offset-2{margin-left:8.33333333%}.ant-col-xs-order-2{-ms-flex-order:2;order:2}.ant-col-xs-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-xs-push-1{left:4.16666667%}.ant-col-xs-pull-1{right:4.16666667%}.ant-col-xs-offset-1{margin-left:4.16666667%}.ant-col-xs-order-1{-ms-flex-order:1;order:1}.ant-col-xs-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xs-push-0{left:auto}.ant-col-xs-pull-0{right:auto}.ant-col-xs-offset-0{margin-left:0}.ant-col-xs-order-0{-ms-flex-order:0;order:0}@media (min-width:576px){.ant-col-sm-1,.ant-col-sm-2,.ant-col-sm-3,.ant-col-sm-4,.ant-col-sm-5,.ant-col-sm-6,.ant-col-sm-7,.ant-col-sm-8,.ant-col-sm-9,.ant-col-sm-10,.ant-col-sm-11,.ant-col-sm-12,.ant-col-sm-13,.ant-col-sm-14,.ant-col-sm-15,.ant-col-sm-16,.ant-col-sm-17,.ant-col-sm-18,.ant-col-sm-19,.ant-col-sm-20,.ant-col-sm-21,.ant-col-sm-22,.ant-col-sm-23,.ant-col-sm-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-sm-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-sm-push-24{left:100%}.ant-col-sm-pull-24{right:100%}.ant-col-sm-offset-24{margin-left:100%}.ant-col-sm-order-24{-ms-flex-order:24;order:24}.ant-col-sm-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-sm-push-23{left:95.83333333%}.ant-col-sm-pull-23{right:95.83333333%}.ant-col-sm-offset-23{margin-left:95.83333333%}.ant-col-sm-order-23{-ms-flex-order:23;order:23}.ant-col-sm-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-sm-push-22{left:91.66666667%}.ant-col-sm-pull-22{right:91.66666667%}.ant-col-sm-offset-22{margin-left:91.66666667%}.ant-col-sm-order-22{-ms-flex-order:22;order:22}.ant-col-sm-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-sm-push-21{left:87.5%}.ant-col-sm-pull-21{right:87.5%}.ant-col-sm-offset-21{margin-left:87.5%}.ant-col-sm-order-21{-ms-flex-order:21;order:21}.ant-col-sm-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-sm-push-20{left:83.33333333%}.ant-col-sm-pull-20{right:83.33333333%}.ant-col-sm-offset-20{margin-left:83.33333333%}.ant-col-sm-order-20{-ms-flex-order:20;order:20}.ant-col-sm-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-sm-push-19{left:79.16666667%}.ant-col-sm-pull-19{right:79.16666667%}.ant-col-sm-offset-19{margin-left:79.16666667%}.ant-col-sm-order-19{-ms-flex-order:19;order:19}.ant-col-sm-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-sm-push-18{left:75%}.ant-col-sm-pull-18{right:75%}.ant-col-sm-offset-18{margin-left:75%}.ant-col-sm-order-18{-ms-flex-order:18;order:18}.ant-col-sm-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-sm-push-17{left:70.83333333%}.ant-col-sm-pull-17{right:70.83333333%}.ant-col-sm-offset-17{margin-left:70.83333333%}.ant-col-sm-order-17{-ms-flex-order:17;order:17}.ant-col-sm-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-sm-push-16{left:66.66666667%}.ant-col-sm-pull-16{right:66.66666667%}.ant-col-sm-offset-16{margin-left:66.66666667%}.ant-col-sm-order-16{-ms-flex-order:16;order:16}.ant-col-sm-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-sm-push-15{left:62.5%}.ant-col-sm-pull-15{right:62.5%}.ant-col-sm-offset-15{margin-left:62.5%}.ant-col-sm-order-15{-ms-flex-order:15;order:15}.ant-col-sm-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-sm-push-14{left:58.33333333%}.ant-col-sm-pull-14{right:58.33333333%}.ant-col-sm-offset-14{margin-left:58.33333333%}.ant-col-sm-order-14{-ms-flex-order:14;order:14}.ant-col-sm-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-sm-push-13{left:54.16666667%}.ant-col-sm-pull-13{right:54.16666667%}.ant-col-sm-offset-13{margin-left:54.16666667%}.ant-col-sm-order-13{-ms-flex-order:13;order:13}.ant-col-sm-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-sm-push-12{left:50%}.ant-col-sm-pull-12{right:50%}.ant-col-sm-offset-12{margin-left:50%}.ant-col-sm-order-12{-ms-flex-order:12;order:12}.ant-col-sm-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-sm-push-11{left:45.83333333%}.ant-col-sm-pull-11{right:45.83333333%}.ant-col-sm-offset-11{margin-left:45.83333333%}.ant-col-sm-order-11{-ms-flex-order:11;order:11}.ant-col-sm-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-sm-push-10{left:41.66666667%}.ant-col-sm-pull-10{right:41.66666667%}.ant-col-sm-offset-10{margin-left:41.66666667%}.ant-col-sm-order-10{-ms-flex-order:10;order:10}.ant-col-sm-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-sm-push-9{left:37.5%}.ant-col-sm-pull-9{right:37.5%}.ant-col-sm-offset-9{margin-left:37.5%}.ant-col-sm-order-9{-ms-flex-order:9;order:9}.ant-col-sm-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-sm-push-8{left:33.33333333%}.ant-col-sm-pull-8{right:33.33333333%}.ant-col-sm-offset-8{margin-left:33.33333333%}.ant-col-sm-order-8{-ms-flex-order:8;order:8}.ant-col-sm-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-sm-push-7{left:29.16666667%}.ant-col-sm-pull-7{right:29.16666667%}.ant-col-sm-offset-7{margin-left:29.16666667%}.ant-col-sm-order-7{-ms-flex-order:7;order:7}.ant-col-sm-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-sm-push-6{left:25%}.ant-col-sm-pull-6{right:25%}.ant-col-sm-offset-6{margin-left:25%}.ant-col-sm-order-6{-ms-flex-order:6;order:6}.ant-col-sm-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-sm-push-5{left:20.83333333%}.ant-col-sm-pull-5{right:20.83333333%}.ant-col-sm-offset-5{margin-left:20.83333333%}.ant-col-sm-order-5{-ms-flex-order:5;order:5}.ant-col-sm-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-sm-push-4{left:16.66666667%}.ant-col-sm-pull-4{right:16.66666667%}.ant-col-sm-offset-4{margin-left:16.66666667%}.ant-col-sm-order-4{-ms-flex-order:4;order:4}.ant-col-sm-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-sm-push-3{left:12.5%}.ant-col-sm-pull-3{right:12.5%}.ant-col-sm-offset-3{margin-left:12.5%}.ant-col-sm-order-3{-ms-flex-order:3;order:3}.ant-col-sm-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-sm-push-2{left:8.33333333%}.ant-col-sm-pull-2{right:8.33333333%}.ant-col-sm-offset-2{margin-left:8.33333333%}.ant-col-sm-order-2{-ms-flex-order:2;order:2}.ant-col-sm-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-sm-push-1{left:4.16666667%}.ant-col-sm-pull-1{right:4.16666667%}.ant-col-sm-offset-1{margin-left:4.16666667%}.ant-col-sm-order-1{-ms-flex-order:1;order:1}.ant-col-sm-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-sm-push-0{left:auto}.ant-col-sm-pull-0{right:auto}.ant-col-sm-offset-0{margin-left:0}.ant-col-sm-order-0{-ms-flex-order:0;order:0}}@media (min-width:768px){.ant-col-md-1,.ant-col-md-2,.ant-col-md-3,.ant-col-md-4,.ant-col-md-5,.ant-col-md-6,.ant-col-md-7,.ant-col-md-8,.ant-col-md-9,.ant-col-md-10,.ant-col-md-11,.ant-col-md-12,.ant-col-md-13,.ant-col-md-14,.ant-col-md-15,.ant-col-md-16,.ant-col-md-17,.ant-col-md-18,.ant-col-md-19,.ant-col-md-20,.ant-col-md-21,.ant-col-md-22,.ant-col-md-23,.ant-col-md-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-md-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-md-push-24{left:100%}.ant-col-md-pull-24{right:100%}.ant-col-md-offset-24{margin-left:100%}.ant-col-md-order-24{-ms-flex-order:24;order:24}.ant-col-md-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-md-push-23{left:95.83333333%}.ant-col-md-pull-23{right:95.83333333%}.ant-col-md-offset-23{margin-left:95.83333333%}.ant-col-md-order-23{-ms-flex-order:23;order:23}.ant-col-md-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-md-push-22{left:91.66666667%}.ant-col-md-pull-22{right:91.66666667%}.ant-col-md-offset-22{margin-left:91.66666667%}.ant-col-md-order-22{-ms-flex-order:22;order:22}.ant-col-md-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-md-push-21{left:87.5%}.ant-col-md-pull-21{right:87.5%}.ant-col-md-offset-21{margin-left:87.5%}.ant-col-md-order-21{-ms-flex-order:21;order:21}.ant-col-md-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-md-push-20{left:83.33333333%}.ant-col-md-pull-20{right:83.33333333%}.ant-col-md-offset-20{margin-left:83.33333333%}.ant-col-md-order-20{-ms-flex-order:20;order:20}.ant-col-md-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-md-push-19{left:79.16666667%}.ant-col-md-pull-19{right:79.16666667%}.ant-col-md-offset-19{margin-left:79.16666667%}.ant-col-md-order-19{-ms-flex-order:19;order:19}.ant-col-md-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-md-push-18{left:75%}.ant-col-md-pull-18{right:75%}.ant-col-md-offset-18{margin-left:75%}.ant-col-md-order-18{-ms-flex-order:18;order:18}.ant-col-md-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-md-push-17{left:70.83333333%}.ant-col-md-pull-17{right:70.83333333%}.ant-col-md-offset-17{margin-left:70.83333333%}.ant-col-md-order-17{-ms-flex-order:17;order:17}.ant-col-md-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-md-push-16{left:66.66666667%}.ant-col-md-pull-16{right:66.66666667%}.ant-col-md-offset-16{margin-left:66.66666667%}.ant-col-md-order-16{-ms-flex-order:16;order:16}.ant-col-md-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-md-push-15{left:62.5%}.ant-col-md-pull-15{right:62.5%}.ant-col-md-offset-15{margin-left:62.5%}.ant-col-md-order-15{-ms-flex-order:15;order:15}.ant-col-md-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-md-push-14{left:58.33333333%}.ant-col-md-pull-14{right:58.33333333%}.ant-col-md-offset-14{margin-left:58.33333333%}.ant-col-md-order-14{-ms-flex-order:14;order:14}.ant-col-md-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-md-push-13{left:54.16666667%}.ant-col-md-pull-13{right:54.16666667%}.ant-col-md-offset-13{margin-left:54.16666667%}.ant-col-md-order-13{-ms-flex-order:13;order:13}.ant-col-md-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-md-push-12{left:50%}.ant-col-md-pull-12{right:50%}.ant-col-md-offset-12{margin-left:50%}.ant-col-md-order-12{-ms-flex-order:12;order:12}.ant-col-md-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-md-push-11{left:45.83333333%}.ant-col-md-pull-11{right:45.83333333%}.ant-col-md-offset-11{margin-left:45.83333333%}.ant-col-md-order-11{-ms-flex-order:11;order:11}.ant-col-md-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-md-push-10{left:41.66666667%}.ant-col-md-pull-10{right:41.66666667%}.ant-col-md-offset-10{margin-left:41.66666667%}.ant-col-md-order-10{-ms-flex-order:10;order:10}.ant-col-md-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-md-push-9{left:37.5%}.ant-col-md-pull-9{right:37.5%}.ant-col-md-offset-9{margin-left:37.5%}.ant-col-md-order-9{-ms-flex-order:9;order:9}.ant-col-md-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-md-push-8{left:33.33333333%}.ant-col-md-pull-8{right:33.33333333%}.ant-col-md-offset-8{margin-left:33.33333333%}.ant-col-md-order-8{-ms-flex-order:8;order:8}.ant-col-md-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-md-push-7{left:29.16666667%}.ant-col-md-pull-7{right:29.16666667%}.ant-col-md-offset-7{margin-left:29.16666667%}.ant-col-md-order-7{-ms-flex-order:7;order:7}.ant-col-md-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-md-push-6{left:25%}.ant-col-md-pull-6{right:25%}.ant-col-md-offset-6{margin-left:25%}.ant-col-md-order-6{-ms-flex-order:6;order:6}.ant-col-md-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-md-push-5{left:20.83333333%}.ant-col-md-pull-5{right:20.83333333%}.ant-col-md-offset-5{margin-left:20.83333333%}.ant-col-md-order-5{-ms-flex-order:5;order:5}.ant-col-md-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-md-push-4{left:16.66666667%}.ant-col-md-pull-4{right:16.66666667%}.ant-col-md-offset-4{margin-left:16.66666667%}.ant-col-md-order-4{-ms-flex-order:4;order:4}.ant-col-md-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-md-push-3{left:12.5%}.ant-col-md-pull-3{right:12.5%}.ant-col-md-offset-3{margin-left:12.5%}.ant-col-md-order-3{-ms-flex-order:3;order:3}.ant-col-md-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-md-push-2{left:8.33333333%}.ant-col-md-pull-2{right:8.33333333%}.ant-col-md-offset-2{margin-left:8.33333333%}.ant-col-md-order-2{-ms-flex-order:2;order:2}.ant-col-md-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-md-push-1{left:4.16666667%}.ant-col-md-pull-1{right:4.16666667%}.ant-col-md-offset-1{margin-left:4.16666667%}.ant-col-md-order-1{-ms-flex-order:1;order:1}.ant-col-md-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-md-push-0{left:auto}.ant-col-md-pull-0{right:auto}.ant-col-md-offset-0{margin-left:0}.ant-col-md-order-0{-ms-flex-order:0;order:0}}@media (min-width:992px){.ant-col-lg-1,.ant-col-lg-2,.ant-col-lg-3,.ant-col-lg-4,.ant-col-lg-5,.ant-col-lg-6,.ant-col-lg-7,.ant-col-lg-8,.ant-col-lg-9,.ant-col-lg-10,.ant-col-lg-11,.ant-col-lg-12,.ant-col-lg-13,.ant-col-lg-14,.ant-col-lg-15,.ant-col-lg-16,.ant-col-lg-17,.ant-col-lg-18,.ant-col-lg-19,.ant-col-lg-20,.ant-col-lg-21,.ant-col-lg-22,.ant-col-lg-23,.ant-col-lg-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-lg-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-lg-push-24{left:100%}.ant-col-lg-pull-24{right:100%}.ant-col-lg-offset-24{margin-left:100%}.ant-col-lg-order-24{-ms-flex-order:24;order:24}.ant-col-lg-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-lg-push-23{left:95.83333333%}.ant-col-lg-pull-23{right:95.83333333%}.ant-col-lg-offset-23{margin-left:95.83333333%}.ant-col-lg-order-23{-ms-flex-order:23;order:23}.ant-col-lg-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-lg-push-22{left:91.66666667%}.ant-col-lg-pull-22{right:91.66666667%}.ant-col-lg-offset-22{margin-left:91.66666667%}.ant-col-lg-order-22{-ms-flex-order:22;order:22}.ant-col-lg-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-lg-push-21{left:87.5%}.ant-col-lg-pull-21{right:87.5%}.ant-col-lg-offset-21{margin-left:87.5%}.ant-col-lg-order-21{-ms-flex-order:21;order:21}.ant-col-lg-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-lg-push-20{left:83.33333333%}.ant-col-lg-pull-20{right:83.33333333%}.ant-col-lg-offset-20{margin-left:83.33333333%}.ant-col-lg-order-20{-ms-flex-order:20;order:20}.ant-col-lg-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-lg-push-19{left:79.16666667%}.ant-col-lg-pull-19{right:79.16666667%}.ant-col-lg-offset-19{margin-left:79.16666667%}.ant-col-lg-order-19{-ms-flex-order:19;order:19}.ant-col-lg-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-lg-push-18{left:75%}.ant-col-lg-pull-18{right:75%}.ant-col-lg-offset-18{margin-left:75%}.ant-col-lg-order-18{-ms-flex-order:18;order:18}.ant-col-lg-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-lg-push-17{left:70.83333333%}.ant-col-lg-pull-17{right:70.83333333%}.ant-col-lg-offset-17{margin-left:70.83333333%}.ant-col-lg-order-17{-ms-flex-order:17;order:17}.ant-col-lg-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-lg-push-16{left:66.66666667%}.ant-col-lg-pull-16{right:66.66666667%}.ant-col-lg-offset-16{margin-left:66.66666667%}.ant-col-lg-order-16{-ms-flex-order:16;order:16}.ant-col-lg-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-lg-push-15{left:62.5%}.ant-col-lg-pull-15{right:62.5%}.ant-col-lg-offset-15{margin-left:62.5%}.ant-col-lg-order-15{-ms-flex-order:15;order:15}.ant-col-lg-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-lg-push-14{left:58.33333333%}.ant-col-lg-pull-14{right:58.33333333%}.ant-col-lg-offset-14{margin-left:58.33333333%}.ant-col-lg-order-14{-ms-flex-order:14;order:14}.ant-col-lg-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-lg-push-13{left:54.16666667%}.ant-col-lg-pull-13{right:54.16666667%}.ant-col-lg-offset-13{margin-left:54.16666667%}.ant-col-lg-order-13{-ms-flex-order:13;order:13}.ant-col-lg-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-lg-push-12{left:50%}.ant-col-lg-pull-12{right:50%}.ant-col-lg-offset-12{margin-left:50%}.ant-col-lg-order-12{-ms-flex-order:12;order:12}.ant-col-lg-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-lg-push-11{left:45.83333333%}.ant-col-lg-pull-11{right:45.83333333%}.ant-col-lg-offset-11{margin-left:45.83333333%}.ant-col-lg-order-11{-ms-flex-order:11;order:11}.ant-col-lg-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-lg-push-10{left:41.66666667%}.ant-col-lg-pull-10{right:41.66666667%}.ant-col-lg-offset-10{margin-left:41.66666667%}.ant-col-lg-order-10{-ms-flex-order:10;order:10}.ant-col-lg-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-lg-push-9{left:37.5%}.ant-col-lg-pull-9{right:37.5%}.ant-col-lg-offset-9{margin-left:37.5%}.ant-col-lg-order-9{-ms-flex-order:9;order:9}.ant-col-lg-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-lg-push-8{left:33.33333333%}.ant-col-lg-pull-8{right:33.33333333%}.ant-col-lg-offset-8{margin-left:33.33333333%}.ant-col-lg-order-8{-ms-flex-order:8;order:8}.ant-col-lg-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-lg-push-7{left:29.16666667%}.ant-col-lg-pull-7{right:29.16666667%}.ant-col-lg-offset-7{margin-left:29.16666667%}.ant-col-lg-order-7{-ms-flex-order:7;order:7}.ant-col-lg-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-lg-push-6{left:25%}.ant-col-lg-pull-6{right:25%}.ant-col-lg-offset-6{margin-left:25%}.ant-col-lg-order-6{-ms-flex-order:6;order:6}.ant-col-lg-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-lg-push-5{left:20.83333333%}.ant-col-lg-pull-5{right:20.83333333%}.ant-col-lg-offset-5{margin-left:20.83333333%}.ant-col-lg-order-5{-ms-flex-order:5;order:5}.ant-col-lg-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-lg-push-4{left:16.66666667%}.ant-col-lg-pull-4{right:16.66666667%}.ant-col-lg-offset-4{margin-left:16.66666667%}.ant-col-lg-order-4{-ms-flex-order:4;order:4}.ant-col-lg-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-lg-push-3{left:12.5%}.ant-col-lg-pull-3{right:12.5%}.ant-col-lg-offset-3{margin-left:12.5%}.ant-col-lg-order-3{-ms-flex-order:3;order:3}.ant-col-lg-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-lg-push-2{left:8.33333333%}.ant-col-lg-pull-2{right:8.33333333%}.ant-col-lg-offset-2{margin-left:8.33333333%}.ant-col-lg-order-2{-ms-flex-order:2;order:2}.ant-col-lg-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-lg-push-1{left:4.16666667%}.ant-col-lg-pull-1{right:4.16666667%}.ant-col-lg-offset-1{margin-left:4.16666667%}.ant-col-lg-order-1{-ms-flex-order:1;order:1}.ant-col-lg-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-lg-push-0{left:auto}.ant-col-lg-pull-0{right:auto}.ant-col-lg-offset-0{margin-left:0}.ant-col-lg-order-0{-ms-flex-order:0;order:0}}@media (min-width:1200px){.ant-col-xl-1,.ant-col-xl-2,.ant-col-xl-3,.ant-col-xl-4,.ant-col-xl-5,.ant-col-xl-6,.ant-col-xl-7,.ant-col-xl-8,.ant-col-xl-9,.ant-col-xl-10,.ant-col-xl-11,.ant-col-xl-12,.ant-col-xl-13,.ant-col-xl-14,.ant-col-xl-15,.ant-col-xl-16,.ant-col-xl-17,.ant-col-xl-18,.ant-col-xl-19,.ant-col-xl-20,.ant-col-xl-21,.ant-col-xl-22,.ant-col-xl-23,.ant-col-xl-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-xl-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-xl-push-24{left:100%}.ant-col-xl-pull-24{right:100%}.ant-col-xl-offset-24{margin-left:100%}.ant-col-xl-order-24{-ms-flex-order:24;order:24}.ant-col-xl-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-xl-push-23{left:95.83333333%}.ant-col-xl-pull-23{right:95.83333333%}.ant-col-xl-offset-23{margin-left:95.83333333%}.ant-col-xl-order-23{-ms-flex-order:23;order:23}.ant-col-xl-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-xl-push-22{left:91.66666667%}.ant-col-xl-pull-22{right:91.66666667%}.ant-col-xl-offset-22{margin-left:91.66666667%}.ant-col-xl-order-22{-ms-flex-order:22;order:22}.ant-col-xl-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-xl-push-21{left:87.5%}.ant-col-xl-pull-21{right:87.5%}.ant-col-xl-offset-21{margin-left:87.5%}.ant-col-xl-order-21{-ms-flex-order:21;order:21}.ant-col-xl-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-xl-push-20{left:83.33333333%}.ant-col-xl-pull-20{right:83.33333333%}.ant-col-xl-offset-20{margin-left:83.33333333%}.ant-col-xl-order-20{-ms-flex-order:20;order:20}.ant-col-xl-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-xl-push-19{left:79.16666667%}.ant-col-xl-pull-19{right:79.16666667%}.ant-col-xl-offset-19{margin-left:79.16666667%}.ant-col-xl-order-19{-ms-flex-order:19;order:19}.ant-col-xl-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-xl-push-18{left:75%}.ant-col-xl-pull-18{right:75%}.ant-col-xl-offset-18{margin-left:75%}.ant-col-xl-order-18{-ms-flex-order:18;order:18}.ant-col-xl-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-xl-push-17{left:70.83333333%}.ant-col-xl-pull-17{right:70.83333333%}.ant-col-xl-offset-17{margin-left:70.83333333%}.ant-col-xl-order-17{-ms-flex-order:17;order:17}.ant-col-xl-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-xl-push-16{left:66.66666667%}.ant-col-xl-pull-16{right:66.66666667%}.ant-col-xl-offset-16{margin-left:66.66666667%}.ant-col-xl-order-16{-ms-flex-order:16;order:16}.ant-col-xl-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-xl-push-15{left:62.5%}.ant-col-xl-pull-15{right:62.5%}.ant-col-xl-offset-15{margin-left:62.5%}.ant-col-xl-order-15{-ms-flex-order:15;order:15}.ant-col-xl-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-xl-push-14{left:58.33333333%}.ant-col-xl-pull-14{right:58.33333333%}.ant-col-xl-offset-14{margin-left:58.33333333%}.ant-col-xl-order-14{-ms-flex-order:14;order:14}.ant-col-xl-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-xl-push-13{left:54.16666667%}.ant-col-xl-pull-13{right:54.16666667%}.ant-col-xl-offset-13{margin-left:54.16666667%}.ant-col-xl-order-13{-ms-flex-order:13;order:13}.ant-col-xl-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-xl-push-12{left:50%}.ant-col-xl-pull-12{right:50%}.ant-col-xl-offset-12{margin-left:50%}.ant-col-xl-order-12{-ms-flex-order:12;order:12}.ant-col-xl-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-xl-push-11{left:45.83333333%}.ant-col-xl-pull-11{right:45.83333333%}.ant-col-xl-offset-11{margin-left:45.83333333%}.ant-col-xl-order-11{-ms-flex-order:11;order:11}.ant-col-xl-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-xl-push-10{left:41.66666667%}.ant-col-xl-pull-10{right:41.66666667%}.ant-col-xl-offset-10{margin-left:41.66666667%}.ant-col-xl-order-10{-ms-flex-order:10;order:10}.ant-col-xl-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-xl-push-9{left:37.5%}.ant-col-xl-pull-9{right:37.5%}.ant-col-xl-offset-9{margin-left:37.5%}.ant-col-xl-order-9{-ms-flex-order:9;order:9}.ant-col-xl-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-xl-push-8{left:33.33333333%}.ant-col-xl-pull-8{right:33.33333333%}.ant-col-xl-offset-8{margin-left:33.33333333%}.ant-col-xl-order-8{-ms-flex-order:8;order:8}.ant-col-xl-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-xl-push-7{left:29.16666667%}.ant-col-xl-pull-7{right:29.16666667%}.ant-col-xl-offset-7{margin-left:29.16666667%}.ant-col-xl-order-7{-ms-flex-order:7;order:7}.ant-col-xl-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-xl-push-6{left:25%}.ant-col-xl-pull-6{right:25%}.ant-col-xl-offset-6{margin-left:25%}.ant-col-xl-order-6{-ms-flex-order:6;order:6}.ant-col-xl-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-xl-push-5{left:20.83333333%}.ant-col-xl-pull-5{right:20.83333333%}.ant-col-xl-offset-5{margin-left:20.83333333%}.ant-col-xl-order-5{-ms-flex-order:5;order:5}.ant-col-xl-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-xl-push-4{left:16.66666667%}.ant-col-xl-pull-4{right:16.66666667%}.ant-col-xl-offset-4{margin-left:16.66666667%}.ant-col-xl-order-4{-ms-flex-order:4;order:4}.ant-col-xl-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-xl-push-3{left:12.5%}.ant-col-xl-pull-3{right:12.5%}.ant-col-xl-offset-3{margin-left:12.5%}.ant-col-xl-order-3{-ms-flex-order:3;order:3}.ant-col-xl-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-xl-push-2{left:8.33333333%}.ant-col-xl-pull-2{right:8.33333333%}.ant-col-xl-offset-2{margin-left:8.33333333%}.ant-col-xl-order-2{-ms-flex-order:2;order:2}.ant-col-xl-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-xl-push-1{left:4.16666667%}.ant-col-xl-pull-1{right:4.16666667%}.ant-col-xl-offset-1{margin-left:4.16666667%}.ant-col-xl-order-1{-ms-flex-order:1;order:1}.ant-col-xl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xl-push-0{left:auto}.ant-col-xl-pull-0{right:auto}.ant-col-xl-offset-0{margin-left:0}.ant-col-xl-order-0{-ms-flex-order:0;order:0}}@media (min-width:1600px){.ant-col-xxl-1,.ant-col-xxl-2,.ant-col-xxl-3,.ant-col-xxl-4,.ant-col-xxl-5,.ant-col-xxl-6,.ant-col-xxl-7,.ant-col-xxl-8,.ant-col-xxl-9,.ant-col-xxl-10,.ant-col-xxl-11,.ant-col-xxl-12,.ant-col-xxl-13,.ant-col-xxl-14,.ant-col-xxl-15,.ant-col-xxl-16,.ant-col-xxl-17,.ant-col-xxl-18,.ant-col-xxl-19,.ant-col-xxl-20,.ant-col-xxl-21,.ant-col-xxl-22,.ant-col-xxl-23,.ant-col-xxl-24{-ms-flex:0 0 auto;flex:0 0 auto;float:left}.ant-col-xxl-24{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%}.ant-col-xxl-push-24{left:100%}.ant-col-xxl-pull-24{right:100%}.ant-col-xxl-offset-24{margin-left:100%}.ant-col-xxl-order-24{-ms-flex-order:24;order:24}.ant-col-xxl-23{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:95.83333333%}.ant-col-xxl-push-23{left:95.83333333%}.ant-col-xxl-pull-23{right:95.83333333%}.ant-col-xxl-offset-23{margin-left:95.83333333%}.ant-col-xxl-order-23{-ms-flex-order:23;order:23}.ant-col-xxl-22{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:91.66666667%}.ant-col-xxl-push-22{left:91.66666667%}.ant-col-xxl-pull-22{right:91.66666667%}.ant-col-xxl-offset-22{margin-left:91.66666667%}.ant-col-xxl-order-22{-ms-flex-order:22;order:22}.ant-col-xxl-21{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:87.5%}.ant-col-xxl-push-21{left:87.5%}.ant-col-xxl-pull-21{right:87.5%}.ant-col-xxl-offset-21{margin-left:87.5%}.ant-col-xxl-order-21{-ms-flex-order:21;order:21}.ant-col-xxl-20{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:83.33333333%}.ant-col-xxl-push-20{left:83.33333333%}.ant-col-xxl-pull-20{right:83.33333333%}.ant-col-xxl-offset-20{margin-left:83.33333333%}.ant-col-xxl-order-20{-ms-flex-order:20;order:20}.ant-col-xxl-19{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:79.16666667%}.ant-col-xxl-push-19{left:79.16666667%}.ant-col-xxl-pull-19{right:79.16666667%}.ant-col-xxl-offset-19{margin-left:79.16666667%}.ant-col-xxl-order-19{-ms-flex-order:19;order:19}.ant-col-xxl-18{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:75%}.ant-col-xxl-push-18{left:75%}.ant-col-xxl-pull-18{right:75%}.ant-col-xxl-offset-18{margin-left:75%}.ant-col-xxl-order-18{-ms-flex-order:18;order:18}.ant-col-xxl-17{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:70.83333333%}.ant-col-xxl-push-17{left:70.83333333%}.ant-col-xxl-pull-17{right:70.83333333%}.ant-col-xxl-offset-17{margin-left:70.83333333%}.ant-col-xxl-order-17{-ms-flex-order:17;order:17}.ant-col-xxl-16{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:66.66666667%}.ant-col-xxl-push-16{left:66.66666667%}.ant-col-xxl-pull-16{right:66.66666667%}.ant-col-xxl-offset-16{margin-left:66.66666667%}.ant-col-xxl-order-16{-ms-flex-order:16;order:16}.ant-col-xxl-15{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:62.5%}.ant-col-xxl-push-15{left:62.5%}.ant-col-xxl-pull-15{right:62.5%}.ant-col-xxl-offset-15{margin-left:62.5%}.ant-col-xxl-order-15{-ms-flex-order:15;order:15}.ant-col-xxl-14{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:58.33333333%}.ant-col-xxl-push-14{left:58.33333333%}.ant-col-xxl-pull-14{right:58.33333333%}.ant-col-xxl-offset-14{margin-left:58.33333333%}.ant-col-xxl-order-14{-ms-flex-order:14;order:14}.ant-col-xxl-13{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:54.16666667%}.ant-col-xxl-push-13{left:54.16666667%}.ant-col-xxl-pull-13{right:54.16666667%}.ant-col-xxl-offset-13{margin-left:54.16666667%}.ant-col-xxl-order-13{-ms-flex-order:13;order:13}.ant-col-xxl-12{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.ant-col-xxl-push-12{left:50%}.ant-col-xxl-pull-12{right:50%}.ant-col-xxl-offset-12{margin-left:50%}.ant-col-xxl-order-12{-ms-flex-order:12;order:12}.ant-col-xxl-11{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:45.83333333%}.ant-col-xxl-push-11{left:45.83333333%}.ant-col-xxl-pull-11{right:45.83333333%}.ant-col-xxl-offset-11{margin-left:45.83333333%}.ant-col-xxl-order-11{-ms-flex-order:11;order:11}.ant-col-xxl-10{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:41.66666667%}.ant-col-xxl-push-10{left:41.66666667%}.ant-col-xxl-pull-10{right:41.66666667%}.ant-col-xxl-offset-10{margin-left:41.66666667%}.ant-col-xxl-order-10{-ms-flex-order:10;order:10}.ant-col-xxl-9{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:37.5%}.ant-col-xxl-push-9{left:37.5%}.ant-col-xxl-pull-9{right:37.5%}.ant-col-xxl-offset-9{margin-left:37.5%}.ant-col-xxl-order-9{-ms-flex-order:9;order:9}.ant-col-xxl-8{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:33.33333333%}.ant-col-xxl-push-8{left:33.33333333%}.ant-col-xxl-pull-8{right:33.33333333%}.ant-col-xxl-offset-8{margin-left:33.33333333%}.ant-col-xxl-order-8{-ms-flex-order:8;order:8}.ant-col-xxl-7{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:29.16666667%}.ant-col-xxl-push-7{left:29.16666667%}.ant-col-xxl-pull-7{right:29.16666667%}.ant-col-xxl-offset-7{margin-left:29.16666667%}.ant-col-xxl-order-7{-ms-flex-order:7;order:7}.ant-col-xxl-6{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:25%}.ant-col-xxl-push-6{left:25%}.ant-col-xxl-pull-6{right:25%}.ant-col-xxl-offset-6{margin-left:25%}.ant-col-xxl-order-6{-ms-flex-order:6;order:6}.ant-col-xxl-5{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:20.83333333%}.ant-col-xxl-push-5{left:20.83333333%}.ant-col-xxl-pull-5{right:20.83333333%}.ant-col-xxl-offset-5{margin-left:20.83333333%}.ant-col-xxl-order-5{-ms-flex-order:5;order:5}.ant-col-xxl-4{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:16.66666667%}.ant-col-xxl-push-4{left:16.66666667%}.ant-col-xxl-pull-4{right:16.66666667%}.ant-col-xxl-offset-4{margin-left:16.66666667%}.ant-col-xxl-order-4{-ms-flex-order:4;order:4}.ant-col-xxl-3{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:12.5%}.ant-col-xxl-push-3{left:12.5%}.ant-col-xxl-pull-3{right:12.5%}.ant-col-xxl-offset-3{margin-left:12.5%}.ant-col-xxl-order-3{-ms-flex-order:3;order:3}.ant-col-xxl-2{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:8.33333333%}.ant-col-xxl-push-2{left:8.33333333%}.ant-col-xxl-pull-2{right:8.33333333%}.ant-col-xxl-offset-2{margin-left:8.33333333%}.ant-col-xxl-order-2{-ms-flex-order:2;order:2}.ant-col-xxl-1{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:4.16666667%}.ant-col-xxl-push-1{left:4.16666667%}.ant-col-xxl-pull-1{right:4.16666667%}.ant-col-xxl-offset-1{margin-left:4.16666667%}.ant-col-xxl-order-1{-ms-flex-order:1;order:1}.ant-col-xxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxl-push-0{left:auto}.ant-col-xxl-pull-0{right:auto}.ant-col-xxl-offset-0{margin-left:0}.ant-col-xxl-order-0{-ms-flex-order:0;order:0}}.ant-carousel{margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-carousel,.ant-carousel .slick-slider{-webkit-box-sizing:border-box;box-sizing:border-box}.ant-carousel .slick-slider{position:relative;display:block;-webkit-touch-callout:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.ant-carousel .slick-list{position:relative;display:block;margin:0;padding:0;overflow:hidden}.ant-carousel .slick-list:focus{outline:none}.ant-carousel .slick-list.dragging{cursor:pointer}.ant-carousel .slick-list .slick-slide{pointer-events:none}.ant-carousel .slick-list .slick-slide input.ant-checkbox-input,.ant-carousel .slick-list .slick-slide input.ant-radio-input{visibility:hidden}.ant-carousel .slick-list .slick-slide.slick-active{pointer-events:auto}.ant-carousel .slick-list .slick-slide.slick-active input.ant-checkbox-input,.ant-carousel .slick-list .slick-slide.slick-active input.ant-radio-input{visibility:visible}.ant-carousel .slick-slider .slick-list,.ant-carousel .slick-slider .slick-track{-webkit-transform:translateZ(0);transform:translateZ(0)}.ant-carousel .slick-track{position:relative;top:0;left:0;display:block}.ant-carousel .slick-track:after,.ant-carousel .slick-track:before{display:table;content:\"\"}.ant-carousel .slick-track:after{clear:both}.slick-loading .ant-carousel .slick-track{visibility:hidden}.ant-carousel .slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .ant-carousel .slick-slide{float:right}.ant-carousel .slick-slide img{display:block}.ant-carousel .slick-slide.slick-loading img{display:none}.ant-carousel .slick-slide.dragging img{pointer-events:none}.ant-carousel .slick-initialized .slick-slide{display:block}.ant-carousel .slick-loading .slick-slide{visibility:hidden}.ant-carousel .slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.ant-carousel .slick-arrow.slick-hidden{display:none}.ant-carousel .slick-next,.ant-carousel .slick-prev{position:absolute;top:50%;display:block;width:20px;height:20px;margin-top:-10px;padding:0;font-size:0;line-height:0;border:0;cursor:pointer}.ant-carousel .slick-next,.ant-carousel .slick-next:focus,.ant-carousel .slick-next:hover,.ant-carousel .slick-prev,.ant-carousel .slick-prev:focus,.ant-carousel .slick-prev:hover{color:transparent;background:transparent;outline:none}.ant-carousel .slick-next:focus:before,.ant-carousel .slick-next:hover:before,.ant-carousel .slick-prev:focus:before,.ant-carousel .slick-prev:hover:before{opacity:1}.ant-carousel .slick-next.slick-disabled:before,.ant-carousel .slick-prev.slick-disabled:before{opacity:.25}.ant-carousel .slick-prev{left:-25px}.ant-carousel .slick-prev:before{content:\"←\"}.ant-carousel .slick-next{right:-25px}.ant-carousel .slick-next:before{content:\"→\"}.ant-carousel .slick-dots{position:absolute;display:block;width:100%;height:3px;margin:0;padding:0;text-align:center;list-style:none}.ant-carousel .slick-dots-bottom{bottom:12px}.ant-carousel .slick-dots-top{top:12px}.ant-carousel .slick-dots li{position:relative;display:inline-block;margin:0 2px;padding:0;text-align:center;vertical-align:top}.ant-carousel .slick-dots li button{display:block;width:16px;height:3px;padding:0;color:transparent;font-size:0;background:#fff;border:0;border-radius:1px;outline:none;cursor:pointer;opacity:.3;-webkit-transition:all .5s;transition:all .5s}.ant-carousel .slick-dots li button:focus,.ant-carousel .slick-dots li button:hover{opacity:.75}.ant-carousel .slick-dots li.slick-active button{width:24px;background:#fff;opacity:1}.ant-carousel .slick-dots li.slick-active button:focus,.ant-carousel .slick-dots li.slick-active button:hover{opacity:1}.ant-carousel-vertical .slick-dots{top:50%;bottom:auto;width:3px;height:auto;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-carousel-vertical .slick-dots-left{left:12px}.ant-carousel-vertical .slick-dots-right{right:12px}.ant-carousel-vertical .slick-dots li{margin:0 2px;vertical-align:baseline}.ant-carousel-vertical .slick-dots li button{width:3px;height:16px}.ant-carousel-vertical .slick-dots li.slick-active button{width:3px;height:24px}.ant-cascader{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-cascader-input.ant-input{position:static;width:100%;padding-right:24px;background-color:transparent!important;cursor:pointer}.ant-cascader-picker-show-search .ant-cascader-input.ant-input{position:relative}.ant-cascader-picker{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;background-color:#fff;border-radius:4px;outline:0;cursor:pointer;-webkit-transition:color .3s;transition:color .3s}.ant-cascader-picker-with-value .ant-cascader-picker-label{color:transparent}.ant-cascader-picker-disabled{color:rgba(0,0,0,.25);background:#f5f5f5;cursor:not-allowed}.ant-cascader-picker-disabled .ant-cascader-input{cursor:not-allowed}.ant-cascader-picker:focus .ant-cascader-input{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-cascader-picker-show-search.ant-cascader-picker-focused{color:rgba(0,0,0,.25)}.ant-cascader-picker-label{position:absolute;top:50%;left:0;width:100%;height:20px;margin-top:-10px;padding:0 20px 0 12px;overflow:hidden;line-height:20px;white-space:nowrap;text-overflow:ellipsis}.ant-cascader-picker-clear{position:absolute;top:50%;right:12px;z-index:2;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:12px;background:#fff;cursor:pointer;opacity:0;-webkit-transition:color .3s ease,opacity .15s ease;transition:color .3s ease,opacity .15s ease}.ant-cascader-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-cascader-picker:hover .ant-cascader-picker-clear{opacity:1}.ant-cascader-picker-arrow{position:absolute;top:50%;right:12px;z-index:1;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:12px;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s}.ant-cascader-picker-arrow.ant-cascader-picker-arrow-expand{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.ant-cascader-picker-label:hover+.ant-cascader-input{border-color:#40a9ff;border-right-width:1px!important}.ant-cascader-picker-small .ant-cascader-picker-arrow,.ant-cascader-picker-small .ant-cascader-picker-clear{right:8px}.ant-cascader-menus{position:absolute;z-index:1050;font-size:14px;white-space:nowrap;background:#fff;border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-cascader-menus ol,.ant-cascader-menus ul{margin:0;list-style:none}.ant-cascader-menus-empty,.ant-cascader-menus-hidden{display:none}.ant-cascader-menus.slide-up-appear.slide-up-appear-active.ant-cascader-menus-placement-bottomLeft,.ant-cascader-menus.slide-up-enter.slide-up-enter-active.ant-cascader-menus-placement-bottomLeft{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn}.ant-cascader-menus.slide-up-appear.slide-up-appear-active.ant-cascader-menus-placement-topLeft,.ant-cascader-menus.slide-up-enter.slide-up-enter-active.ant-cascader-menus-placement-topLeft{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn}.ant-cascader-menus.slide-up-leave.slide-up-leave-active.ant-cascader-menus-placement-bottomLeft{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut}.ant-cascader-menus.slide-up-leave.slide-up-leave-active.ant-cascader-menus-placement-topLeft{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut}.ant-cascader-menu{display:inline-block;min-width:111px;height:180px;margin:0;padding:4px 0;overflow:auto;vertical-align:top;list-style:none;border-right:1px solid #e8e8e8;-ms-overflow-style:-ms-autohiding-scrollbar}.ant-cascader-menu:first-child{border-radius:4px 0 0 4px}.ant-cascader-menu:last-child{margin-right:-1px;border-right-color:transparent;border-radius:0 4px 4px 0}.ant-cascader-menu:only-child{border-radius:4px}.ant-cascader-menu-item{padding:5px 12px;line-height:22px;white-space:nowrap;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-cascader-menu-item:hover{background:#e6f7ff}.ant-cascader-menu-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-cascader-menu-item-disabled:hover{background:transparent}.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled),.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover{font-weight:600;background-color:#fafafa}.ant-cascader-menu-item-expand{position:relative;padding-right:24px}.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-loading-icon{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);position:absolute;right:12px;color:rgba(0,0,0,.45)}:root .ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,:root .ant-cascader-menu-item-loading-icon{font-size:12px}.ant-cascader-menu-item .ant-cascader-menu-item-keyword{color:#f5222d}.ant-checkbox{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:#1890ff}.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:\"\"}.ant-checkbox-wrapper:hover .ant-checkbox:after,.ant-checkbox:hover:after{visibility:visible}.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;-webkit-transition:all .3s;transition:all .3s}.ant-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(0) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(0) translate(-50%,-50%);transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;-webkit-transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:\" \"}.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(1) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(1) translate(-50%,-50%);transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;-webkit-transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:\" \"}.ant-checkbox-checked .ant-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-checkbox-disabled{cursor:not-allowed}.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed}.ant-checkbox-disabled .ant-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-checkbox-disabled .ant-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;-webkit-animation-name:none;animation-name:none}.ant-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-checkbox-disabled:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox-disabled:after{visibility:hidden}.ant-checkbox-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;line-height:unset;cursor:pointer}.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled{cursor:not-allowed}.ant-checkbox-wrapper+.ant-checkbox-wrapper{margin-left:8px}.ant-checkbox+span{padding-right:8px;padding-left:8px}.ant-checkbox-group{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block}.ant-checkbox-group-item{display:inline-block;margin-right:8px}.ant-checkbox-group-item:last-child{margin-right:0}.ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:0}.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;-webkit-transform:translate(-50%,-50%) scale(1);-ms-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1);opacity:1;content:\" \"}.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-collapse{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:4px}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 4px 4px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 16px 12px 40px;color:rgba(0,0,0,.85);line-height:22px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;left:16px;display:inline-block;font-size:12px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow>*{line-height:1}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{display:inline-block}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow:before{display:none}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow .ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow-icon{display:block}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{-webkit-transition:-webkit-transform .24s;transition:-webkit-transform .24s;transition:transform .24s;transition:transform .24s,-webkit-transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{float:right}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{right:16px;left:auto}.ant-collapse-anim-active{-webkit-transition:height .2s cubic-bezier(.215,.61,.355,1);transition:height .2s cubic-bezier(.215,.61,.355,1)}.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid #d9d9d9}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-inactive{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 4px 4px}.ant-collapse-borderless{background-color:#fafafa;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-comment{position:relative}.ant-comment-inner{display:-ms-flexbox;display:flex;padding:16px 0}.ant-comment-avatar{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-right:12px;cursor:pointer}.ant-comment-avatar img{width:32px;height:32px;border-radius:50%}.ant-comment-content{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;min-width:1px;font-size:14px;word-wrap:break-word}.ant-comment-content-author{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start;margin-bottom:4px;font-size:14px}.ant-comment-content-author>a,.ant-comment-content-author>span{padding-right:8px;font-size:12px;line-height:18px}.ant-comment-content-author-name{color:rgba(0,0,0,.45);font-size:14px;-webkit-transition:color .3s;transition:color .3s}.ant-comment-content-author-name>*,.ant-comment-content-author-name>:hover{color:rgba(0,0,0,.45)}.ant-comment-content-author-time{color:#ccc;white-space:nowrap;cursor:auto}.ant-comment-content-detail p{white-space:pre-wrap}.ant-comment-actions{margin-top:12px;padding-left:0}.ant-comment-actions>li{display:inline-block;color:rgba(0,0,0,.45)}.ant-comment-actions>li>span{padding-right:10px;color:rgba(0,0,0,.45);font-size:12px;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-comment-actions>li>span:hover{color:#595959}.ant-comment-nested{margin-left:44px}.ant-calendar-picker-container{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;z-index:1050;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"}.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-topRight,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-topRight{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn}.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-bottomRight,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-bottomRight{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn}.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-topRight{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut}.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-bottomRight{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut}.ant-calendar-picker{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;outline:none;cursor:text;-webkit-transition:opacity .3s;transition:opacity .3s}.ant-calendar-picker-input{outline:none}.ant-calendar-picker-input.ant-input{line-height:1.5}.ant-calendar-picker-input.ant-input-sm{padding-top:0;padding-bottom:0}.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff}.ant-calendar-picker:focus .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-calendar-picker-clear,.ant-calendar-picker-icon{position:absolute;top:50%;right:12px;z-index:1;width:14px;height:14px;margin-top:-7px;font-size:12px;line-height:14px;-webkit-transition:all .3s;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-calendar-picker-clear{z-index:2;color:rgba(0,0,0,.25);font-size:14px;background:#fff;cursor:pointer;opacity:0;pointer-events:none}.ant-calendar-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-calendar-picker:hover .ant-calendar-picker-clear{opacity:1;pointer-events:auto}.ant-calendar-picker-icon{display:inline-block;color:rgba(0,0,0,.25);font-size:14px;line-height:1}.ant-input-disabled+.ant-calendar-picker-icon{cursor:not-allowed}.ant-calendar-picker-small .ant-calendar-picker-clear,.ant-calendar-picker-small .ant-calendar-picker-icon{right:8px}.ant-calendar{position:relative;width:280px;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #fff;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid #e8e8e8}.ant-calendar-input{width:100%;height:22px;color:rgba(0,0,0,.65);background:#fff;border:0;outline:0;cursor:auto}.ant-calendar-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-calendar-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-week-number{width:286px}.ant-calendar-week-number-cell{text-align:center}.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-calendar-header a:hover{color:#40a9ff}.ant-calendar-header .ant-calendar-century-select,.ant-calendar-header .ant-calendar-decade-select,.ant-calendar-header .ant-calendar-month-select,.ant-calendar-header .ant-calendar-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-header .ant-calendar-century-select-arrow,.ant-calendar-header .ant-calendar-decade-select-arrow,.ant-calendar-header .ant-calendar-month-select-arrow,.ant-calendar-header .ant-calendar-year-select-arrow{display:none}.ant-calendar-header .ant-calendar-next-century-btn,.ant-calendar-header .ant-calendar-next-decade-btn,.ant-calendar-header .ant-calendar-next-month-btn,.ant-calendar-header .ant-calendar-next-year-btn,.ant-calendar-header .ant-calendar-prev-century-btn,.ant-calendar-header .ant-calendar-prev-decade-btn,.ant-calendar-header .ant-calendar-prev-month-btn,.ant-calendar-header .ant-calendar-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,\"Hiragino Sans GB\",\"Microsoft Yahei\",\"Microsoft Sans Serif\",sans-serif;line-height:40px}.ant-calendar-header .ant-calendar-prev-century-btn,.ant-calendar-header .ant-calendar-prev-decade-btn,.ant-calendar-header .ant-calendar-prev-year-btn{left:7px;height:100%}.ant-calendar-header .ant-calendar-prev-century-btn:after,.ant-calendar-header .ant-calendar-prev-century-btn:before,.ant-calendar-header .ant-calendar-prev-decade-btn:after,.ant-calendar-header .ant-calendar-prev-decade-btn:before,.ant-calendar-header .ant-calendar-prev-year-btn:after,.ant-calendar-header .ant-calendar-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-header .ant-calendar-prev-century-btn:hover:after,.ant-calendar-header .ant-calendar-prev-century-btn:hover:before,.ant-calendar-header .ant-calendar-prev-decade-btn:hover:after,.ant-calendar-header .ant-calendar-prev-decade-btn:hover:before,.ant-calendar-header .ant-calendar-prev-year-btn:hover:after,.ant-calendar-header .ant-calendar-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-prev-century-btn:after,.ant-calendar-header .ant-calendar-prev-decade-btn:after,.ant-calendar-header .ant-calendar-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-header .ant-calendar-next-century-btn,.ant-calendar-header .ant-calendar-next-decade-btn,.ant-calendar-header .ant-calendar-next-year-btn{right:7px;height:100%}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-header .ant-calendar-next-century-btn:hover:after,.ant-calendar-header .ant-calendar-next-century-btn:hover:before,.ant-calendar-header .ant-calendar-next-decade-btn:hover:after,.ant-calendar-header .ant-calendar-next-decade-btn:hover:before,.ant-calendar-header .ant-calendar-next-year-btn:hover:after,.ant-calendar-header .ant-calendar-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:after{display:none}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:before{position:relative;left:3px}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:after{display:inline-block}.ant-calendar-header .ant-calendar-prev-month-btn{left:29px;height:100%}.ant-calendar-header .ant-calendar-prev-month-btn:after,.ant-calendar-header .ant-calendar-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-header .ant-calendar-prev-month-btn:hover:after,.ant-calendar-header .ant-calendar-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-prev-month-btn:after{display:none}.ant-calendar-header .ant-calendar-next-month-btn{right:29px;height:100%}.ant-calendar-header .ant-calendar-next-month-btn:after,.ant-calendar-header .ant-calendar-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-header .ant-calendar-next-month-btn:hover:after,.ant-calendar-header .ant-calendar-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-next-month-btn:after{display:none}.ant-calendar-header .ant-calendar-next-month-btn:after,.ant-calendar-header .ant-calendar-next-month-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-body{padding:8px 12px}.ant-calendar table{width:100%;max-width:100%;background-color:transparent;border-collapse:collapse}.ant-calendar table,.ant-calendar td,.ant-calendar th{text-align:center;border:0}.ant-calendar-calendar-table{margin-bottom:0;border-spacing:0}.ant-calendar-column-header{width:33px;padding:6px 0;line-height:18px;text-align:center}.ant-calendar-column-header .ant-calendar-column-header-inner{display:block;font-weight:400}.ant-calendar-week-number-header .ant-calendar-column-header-inner{display:none}.ant-calendar-cell{height:30px;padding:3px 0}.ant-calendar-date{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:22px;text-align:center;background:transparent;border:1px solid transparent;border-radius:2px;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-calendar-date-panel{position:relative;outline:none}.ant-calendar-date:hover{background:#e6f7ff;cursor:pointer}.ant-calendar-date:active{color:#fff;background:#40a9ff}.ant-calendar-today .ant-calendar-date{color:#1890ff;font-weight:700;border-color:#1890ff}.ant-calendar-selected-day .ant-calendar-date{background:#bae7ff}.ant-calendar-last-month-cell .ant-calendar-date,.ant-calendar-last-month-cell .ant-calendar-date:hover,.ant-calendar-next-month-btn-day .ant-calendar-date,.ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgba(0,0,0,.25);background:transparent;border-color:transparent}.ant-calendar-disabled-cell .ant-calendar-date{position:relative;width:auto;color:rgba(0,0,0,.25);background:#f5f5f5;border:1px solid transparent;border-radius:0;cursor:not-allowed}.ant-calendar-disabled-cell .ant-calendar-date:hover{background:#f5f5f5}.ant-calendar-disabled-cell.ant-calendar-selected-day .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;background:rgba(0,0,0,.1);border-radius:2px;content:\"\"}.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date{position:relative;padding-right:5px;padding-left:5px}.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;border:1px solid rgba(0,0,0,.25);border-radius:2px;content:\" \"}.ant-calendar-disabled-cell-first-of-row .ant-calendar-date{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-calendar-disabled-cell-last-of-row .ant-calendar-date{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid #e8e8e8}.ant-calendar-footer:empty{border-top:0}.ant-calendar-footer-btn{display:block;text-align:center}.ant-calendar-footer-extra{text-align:left}.ant-calendar .ant-calendar-clear-btn,.ant-calendar .ant-calendar-today-btn{display:inline-block;margin:0 0 0 8px;text-align:center}.ant-calendar .ant-calendar-clear-btn-disabled,.ant-calendar .ant-calendar-today-btn-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-calendar .ant-calendar-clear-btn:only-child,.ant-calendar .ant-calendar-today-btn:only-child{margin:0}.ant-calendar .ant-calendar-clear-btn{position:absolute;top:7px;right:5px;display:none;width:20px;height:20px;margin:0;overflow:hidden;line-height:20px;text-align:center;text-indent:-76px}.ant-calendar .ant-calendar-clear-btn:after{display:inline-block;width:20px;color:rgba(0,0,0,.25);font-size:14px;line-height:1;text-indent:43px;-webkit-transition:color .3s ease;transition:color .3s ease}.ant-calendar .ant-calendar-clear-btn:hover:after{color:rgba(0,0,0,.45)}.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;-webkit-box-shadow:0 2px 0 rgba(0,0,0,.015);box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-ms-touch-action:manipulation;touch-action:manipulation;height:32px;color:#fff;background-color:#1890ff;border:1px solid #1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);-webkit-box-shadow:0 2px 0 rgba(0,0,0,.045);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:4px;line-height:22px}.ant-calendar .ant-calendar-ok-btn>.anticon{line-height:1}.ant-calendar .ant-calendar-ok-btn,.ant-calendar .ant-calendar-ok-btn:active,.ant-calendar .ant-calendar-ok-btn:focus{outline:0}.ant-calendar .ant-calendar-ok-btn:not([disabled]):hover{text-decoration:none}.ant-calendar .ant-calendar-ok-btn:not([disabled]):active{outline:0;-webkit-box-shadow:none;box-shadow:none}.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn[disabled]{cursor:not-allowed}.ant-calendar .ant-calendar-ok-btn.disabled>*,.ant-calendar .ant-calendar-ok-btn[disabled]>*{pointer-events:none}.ant-calendar .ant-calendar-ok-btn-lg{height:40px;padding:0 15px;font-size:16px;border-radius:4px}.ant-calendar .ant-calendar-ok-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}.ant-calendar .ant-calendar-ok-btn>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:#40a9ff;border-color:#40a9ff}.ant-calendar .ant-calendar-ok-btn:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-calendar .ant-calendar-ok-btn.active,.ant-calendar .ant-calendar-ok-btn:active{color:#fff;background-color:#096dd9;border-color:#096dd9}.ant-calendar .ant-calendar-ok-btn.active>a:only-child,.ant-calendar .ant-calendar-ok-btn:active>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;-webkit-box-shadow:none;box-shadow:none}.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:\"\"}.ant-calendar-range-picker-input{width:44%;height:99%;text-align:center;background-color:transparent;border:0;outline:0}.ant-calendar-range-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-range-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-range-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-range-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-range-picker-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-calendar-range-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-range-picker-input[disabled]{cursor:not-allowed}.ant-calendar-range-picker-separator{display:inline-block;min-width:10px;height:100%;color:rgba(0,0,0,.45);white-space:nowrap;text-align:center;vertical-align:top;pointer-events:none}.ant-calendar-range{width:552px;overflow:hidden}.ant-calendar-range .ant-calendar-date-panel:after{display:block;clear:both;height:0;visibility:hidden;content:\".\"}.ant-calendar-range-part{position:relative;width:50%}.ant-calendar-range-left{float:left}.ant-calendar-range-left .ant-calendar-time-picker-inner{border-right:1px solid #e8e8e8}.ant-calendar-range-right{float:right}.ant-calendar-range-right .ant-calendar-time-picker-inner{border-left:1px solid #e8e8e8}.ant-calendar-range-middle{position:absolute;left:50%;z-index:1;height:34px;margin:1px 0 0;padding:0 200px 0 0;color:rgba(0,0,0,.45);line-height:34px;text-align:center;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);pointer-events:none}.ant-calendar-range-right .ant-calendar-date-input-wrap{margin-left:-90px}.ant-calendar-range.ant-calendar-time .ant-calendar-range-middle{padding:0 10px 0 0;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.ant-calendar-range .ant-calendar-today :not(.ant-calendar-disabled-cell) :not(.ant-calendar-last-month-cell) :not(.ant-calendar-next-month-btn-day) .ant-calendar-date{color:#1890ff;background:#bae7ff;border-color:#1890ff}.ant-calendar-range .ant-calendar-selected-end-date .ant-calendar-date,.ant-calendar-range .ant-calendar-selected-start-date .ant-calendar-date{color:#fff;background:#1890ff;border:1px solid transparent}.ant-calendar-range .ant-calendar-selected-end-date .ant-calendar-date:hover,.ant-calendar-range .ant-calendar-selected-start-date .ant-calendar-date:hover{background:#1890ff}.ant-calendar-range.ant-calendar-time .ant-calendar-range-right .ant-calendar-date-input-wrap{margin-left:0}.ant-calendar-range .ant-calendar-input-wrap{position:relative;height:34px}.ant-calendar-range .ant-calendar-input,.ant-calendar-range .ant-calendar-time-picker-input{position:relative;display:inline-block;width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;height:24px;padding:4px 0;line-height:24px;border:0;-webkit-box-shadow:none;box-shadow:none}.ant-calendar-range .ant-calendar-input::-moz-placeholder,.ant-calendar-range .ant-calendar-time-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-range .ant-calendar-input:-ms-input-placeholder,.ant-calendar-range .ant-calendar-time-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-range .ant-calendar-input::-webkit-input-placeholder,.ant-calendar-range .ant-calendar-time-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-range .ant-calendar-input:-moz-placeholder-shown,.ant-calendar-range .ant-calendar-time-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-range .ant-calendar-input:-ms-input-placeholder,.ant-calendar-range .ant-calendar-time-picker-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-calendar-range .ant-calendar-input:placeholder-shown,.ant-calendar-range .ant-calendar-time-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-range .ant-calendar-input:hover,.ant-calendar-range .ant-calendar-time-picker-input:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-calendar-range .ant-calendar-input:focus,.ant-calendar-range .ant-calendar-time-picker-input:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-calendar-range .ant-calendar-input-disabled,.ant-calendar-range .ant-calendar-time-picker-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-calendar-range .ant-calendar-input-disabled:hover,.ant-calendar-range .ant-calendar-time-picker-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-calendar-range .ant-calendar-input[disabled],.ant-calendar-range .ant-calendar-time-picker-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-calendar-range .ant-calendar-input[disabled]:hover,.ant-calendar-range .ant-calendar-time-picker-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-calendar-range .ant-calendar-input,textarea.ant-calendar-range .ant-calendar-time-picker-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-calendar-range .ant-calendar-input-lg,.ant-calendar-range .ant-calendar-time-picker-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-calendar-range .ant-calendar-input-sm,.ant-calendar-range .ant-calendar-time-picker-input-sm{height:24px;padding:1px 7px}.ant-calendar-range .ant-calendar-input:focus,.ant-calendar-range .ant-calendar-time-picker-input:focus{-webkit-box-shadow:none;box-shadow:none}.ant-calendar-range .ant-calendar-time-picker-icon{display:none}.ant-calendar-range.ant-calendar-week-number{width:574px}.ant-calendar-range.ant-calendar-week-number .ant-calendar-range-part{width:286px}.ant-calendar-range .ant-calendar-decade-panel,.ant-calendar-range .ant-calendar-month-panel,.ant-calendar-range .ant-calendar-year-panel{top:34px}.ant-calendar-range .ant-calendar-month-panel .ant-calendar-year-panel{top:0}.ant-calendar-range .ant-calendar-decade-panel-table,.ant-calendar-range .ant-calendar-month-panel-table,.ant-calendar-range .ant-calendar-year-panel-table{height:208px}.ant-calendar-range .ant-calendar-in-range-cell{position:relative;border-radius:0}.ant-calendar-range .ant-calendar-in-range-cell>div{position:relative;z-index:1}.ant-calendar-range .ant-calendar-in-range-cell:before{position:absolute;top:4px;right:0;bottom:4px;left:0;display:block;background:#e6f7ff;border:0;border-radius:0;content:\"\"}.ant-calendar-range .ant-calendar-footer-extra{float:left}div.ant-calendar-range-quick-selector{text-align:left}div.ant-calendar-range-quick-selector>a{margin-right:8px}.ant-calendar-range .ant-calendar-decade-panel-header,.ant-calendar-range .ant-calendar-header,.ant-calendar-range .ant-calendar-month-panel-header,.ant-calendar-range .ant-calendar-year-panel-header{border-bottom:0}.ant-calendar-range .ant-calendar-body,.ant-calendar-range .ant-calendar-decade-panel-body,.ant-calendar-range .ant-calendar-month-panel-body,.ant-calendar-range .ant-calendar-year-panel-body{border-top:1px solid #e8e8e8}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker{top:68px;z-index:2;width:100%;height:207px}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-panel{height:267px;margin-top:-34px}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-inner{height:100%;padding-top:40px;background:none}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-combobox{display:inline-block;height:100%;background-color:#fff;border-top:1px solid #e8e8e8}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-select{height:100%}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-select ul{max-height:100%}.ant-calendar-range.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn{margin-right:8px}.ant-calendar-range.ant-calendar-time .ant-calendar-today-btn{height:22px;margin:8px 12px;line-height:22px}.ant-calendar-range-with-ranges.ant-calendar-time .ant-calendar-time-picker{height:233px}.ant-calendar-range.ant-calendar-show-time-picker .ant-calendar-body{border-top-color:transparent}.ant-calendar-time-picker{position:absolute;top:40px;width:100%;background-color:#fff}.ant-calendar-time-picker-panel{position:absolute;z-index:1050;width:100%}.ant-calendar-time-picker-inner{position:relative;display:inline-block;width:100%;overflow:hidden;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;outline:none}.ant-calendar-time-picker-column-1,.ant-calendar-time-picker-column-1 .ant-calendar-time-picker-select,.ant-calendar-time-picker-combobox{width:100%}.ant-calendar-time-picker-column-2 .ant-calendar-time-picker-select{width:50%}.ant-calendar-time-picker-column-3 .ant-calendar-time-picker-select{width:33.33%}.ant-calendar-time-picker-column-4 .ant-calendar-time-picker-select{width:25%}.ant-calendar-time-picker-input-wrap{display:none}.ant-calendar-time-picker-select{position:relative;float:left;height:226px;overflow:hidden;font-size:14px;border-right:1px solid #e8e8e8}.ant-calendar-time-picker-select:hover{overflow-y:auto}.ant-calendar-time-picker-select:first-child{margin-left:0;border-left:0}.ant-calendar-time-picker-select:last-child{border-right:0}.ant-calendar-time-picker-select ul{width:100%;max-height:206px;margin:0;padding:0;list-style:none}.ant-calendar-time-picker-select li{width:100%;height:24px;margin:0;line-height:24px;text-align:center;list-style:none;cursor:pointer;-webkit-transition:all .3s;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-calendar-time-picker-select li:last-child:after{display:block;height:202px;content:\"\"}.ant-calendar-time-picker-select li:hover{background:#e6f7ff}.ant-calendar-time-picker-select li:focus{color:#1890ff;font-weight:600;outline:none}li.ant-calendar-time-picker-select-option-selected{font-weight:600;background:#f5f5f5}li.ant-calendar-time-picker-select-option-disabled{color:rgba(0,0,0,.25)}li.ant-calendar-time-picker-select-option-disabled:hover{background:transparent;cursor:not-allowed}.ant-calendar-time .ant-calendar-day-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:34px}.ant-calendar-time .ant-calendar-footer{position:relative;height:auto}.ant-calendar-time .ant-calendar-footer-btn{text-align:right}.ant-calendar-time .ant-calendar-footer .ant-calendar-today-btn{float:left;margin:0}.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn{display:inline-block;margin-right:8px}.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn-disabled{color:rgba(0,0,0,.25)}.ant-calendar-month-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;background:#fff;border-radius:4px;outline:none}.ant-calendar-month-panel>div{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%}.ant-calendar-month-panel-hidden{display:none}.ant-calendar-month-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative}.ant-calendar-month-panel-header a:hover{color:#40a9ff}.ant-calendar-month-panel-header .ant-calendar-month-panel-century-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-month-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-month-panel-header .ant-calendar-month-panel-century-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-month-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-year-select-arrow{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,\"Hiragino Sans GB\",\"Microsoft Yahei\",\"Microsoft Sans Serif\",sans-serif;line-height:40px}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn{right:7px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after{display:inline-block}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn{right:29px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-month-panel-body{-ms-flex:1;flex:1 1}.ant-calendar-month-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-month-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-month-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month,.ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month:hover{color:#fff;background:#1890ff}.ant-calendar-month-panel-cell{text-align:center}.ant-calendar-month-panel-cell-disabled .ant-calendar-month-panel-month,.ant-calendar-month-panel-cell-disabled .ant-calendar-month-panel-month:hover{color:rgba(0,0,0,.25);background:#f5f5f5;cursor:not-allowed}.ant-calendar-month-panel-month{display:inline-block;height:24px;margin:0 auto;padding:0 8px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-calendar-month-panel-month:hover{background:#e6f7ff;cursor:pointer}.ant-calendar-year-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;background:#fff;border-radius:4px;outline:none}.ant-calendar-year-panel>div{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:100%}.ant-calendar-year-panel-hidden{display:none}.ant-calendar-year-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative}.ant-calendar-year-panel-header a:hover{color:#40a9ff}.ant-calendar-year-panel-header .ant-calendar-year-panel-century-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-month-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-year-panel-header .ant-calendar-year-panel-century-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-month-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-year-select-arrow{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,\"Hiragino Sans GB\",\"Microsoft Yahei\",\"Microsoft Sans Serif\",sans-serif;line-height:40px}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn{right:7px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after{display:inline-block}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn{right:29px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-year-panel-body{-ms-flex:1;flex:1 1}.ant-calendar-year-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-year-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-year-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-year-panel-cell{text-align:center}.ant-calendar-year-panel-year{display:inline-block;height:24px;margin:0 auto;padding:0 8px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-calendar-year-panel-year:hover{background:#e6f7ff;cursor:pointer}.ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,.ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year:hover{color:#fff;background:#1890ff}.ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,.ant-calendar-year-panel-next-decade-cell .ant-calendar-year-panel-year{color:rgba(0,0,0,.25);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-calendar-decade-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;background:#fff;border-radius:4px;outline:none}.ant-calendar-decade-panel-hidden{display:none}.ant-calendar-decade-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative}.ant-calendar-decade-panel-header a:hover{color:#40a9ff}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-century-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-decade-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-month-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-century-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-decade-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-month-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-year-select-arrow{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,\"Hiragino Sans GB\",\"Microsoft Yahei\",\"Microsoft Sans Serif\",sans-serif;line-height:40px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn{right:7px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after{display:inline-block}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn{right:29px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;-webkit-transform:rotate(-45deg) scale(.8);-ms-transform:rotate(-45deg) scale(.8);transform:rotate(-45deg) scale(.8);-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:before{-webkit-transform:rotate(135deg) scale(.8);-ms-transform:rotate(135deg) scale(.8);transform:rotate(135deg) scale(.8)}.ant-calendar-decade-panel-body{-ms-flex:1;flex:1 1}.ant-calendar-decade-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-decade-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-decade-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-decade-panel-cell{white-space:nowrap;text-align:center}.ant-calendar-decade-panel-decade{display:inline-block;height:24px;margin:0 auto;padding:0 6px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-calendar-decade-panel-decade:hover{background:#e6f7ff;cursor:pointer}.ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade,.ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade:hover{color:#fff;background:#1890ff}.ant-calendar-decade-panel-last-century-cell .ant-calendar-decade-panel-decade,.ant-calendar-decade-panel-next-century-cell .ant-calendar-decade-panel-decade{color:rgba(0,0,0,.25);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-calendar-month .ant-calendar-month-header-wrap{position:relative;height:288px}.ant-calendar-month .ant-calendar-month-panel,.ant-calendar-month .ant-calendar-year-panel{top:0;height:100%}.ant-calendar-week-number-cell{opacity:.5}.ant-calendar-week-number .ant-calendar-body tr{cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-calendar-week-number .ant-calendar-body tr:hover{background:#e6f7ff}.ant-calendar-week-number .ant-calendar-body tr.ant-calendar-active-week{font-weight:700;background:#bae7ff}.ant-calendar-week-number .ant-calendar-body tr .ant-calendar-selected-day .ant-calendar-date,.ant-calendar-week-number .ant-calendar-body tr .ant-calendar-selected-day:hover .ant-calendar-date{color:rgba(0,0,0,.65);background:transparent}.ant-time-picker-panel{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;z-index:1050;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"}.ant-time-picker-panel-inner{position:relative;left:-2px;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-time-picker-panel-input{width:100%;max-width:154px;margin:0;padding:0;line-height:normal;border:0;outline:0;cursor:auto}.ant-time-picker-panel-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-time-picker-panel-input:-ms-input-placeholder{color:#bfbfbf}.ant-time-picker-panel-input::-webkit-input-placeholder{color:#bfbfbf}.ant-time-picker-panel-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-time-picker-panel-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-time-picker-panel-input:placeholder-shown{text-overflow:ellipsis}.ant-time-picker-panel-input-wrap{position:relative;padding:7px 2px 7px 12px;border-bottom:1px solid #e8e8e8}.ant-time-picker-panel-input-invalid{border-color:#f5222d}.ant-time-picker-panel-narrow .ant-time-picker-panel-input-wrap{max-width:112px}.ant-time-picker-panel-select{position:relative;float:left;width:56px;max-height:192px;overflow:hidden;font-size:14px;border-left:1px solid #e8e8e8}.ant-time-picker-panel-select:hover{overflow-y:auto}.ant-time-picker-panel-select:first-child{margin-left:0;border-left:0}.ant-time-picker-panel-select:last-child{border-right:0}.ant-time-picker-panel-select:only-child{width:100%}.ant-time-picker-panel-select ul{width:56px;margin:0;padding:0 0 160px;list-style:none}.ant-time-picker-panel-select li{width:100%;height:32px;margin:0;padding:0 0 0 12px;line-height:32px;text-align:left;list-style:none;cursor:pointer;-webkit-transition:all .3s;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-time-picker-panel-select li:focus{color:#1890ff;font-weight:600;outline:none}.ant-time-picker-panel-select li:hover{background:#e6f7ff}li.ant-time-picker-panel-select-option-selected{font-weight:600;background:#f5f5f5}li.ant-time-picker-panel-select-option-selected:hover{background:#f5f5f5}li.ant-time-picker-panel-select-option-disabled{color:rgba(0,0,0,.25)}li.ant-time-picker-panel-select-option-disabled:hover{background:transparent;cursor:not-allowed}li.ant-time-picker-panel-select-option-disabled:focus{color:rgba(0,0,0,.25);font-weight:inherit}.ant-time-picker-panel-combobox{zoom:1}.ant-time-picker-panel-combobox:after,.ant-time-picker-panel-combobox:before{display:table;content:\"\"}.ant-time-picker-panel-combobox:after{clear:both}.ant-time-picker-panel-addon{padding:8px;border-top:1px solid #e8e8e8}.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-topRight,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-topRight{-webkit-animation-name:antSlideDownIn;animation-name:antSlideDownIn}.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-bottomRight,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-bottomRight{-webkit-animation-name:antSlideUpIn;animation-name:antSlideUpIn}.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-topRight{-webkit-animation-name:antSlideDownOut;animation-name:antSlideDownOut}.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-bottomRight{-webkit-animation-name:antSlideUpOut;animation-name:antSlideUpOut}.ant-time-picker{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;font-size:14px;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";width:128px;outline:none;cursor:text;-webkit-transition:opacity .3s;transition:opacity .3s}.ant-time-picker,.ant-time-picker-input{color:rgba(0,0,0,.65);line-height:1.5;position:relative;display:inline-block}.ant-time-picker-input{width:100%;height:32px;padding:4px 11px;font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s}.ant-time-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-time-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-time-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-time-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-time-picker-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-time-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-time-picker-input:focus,.ant-time-picker-input:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-time-picker-input:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-time-picker-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-time-picker-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-time-picker-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-time-picker-input-sm{height:24px;padding:1px 7px}.ant-time-picker-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-time-picker-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-time-picker-open{opacity:0}.ant-time-picker-clear,.ant-time-picker-icon{position:absolute;top:50%;right:11px;z-index:1;width:14px;height:14px;margin-top:-7px;color:rgba(0,0,0,.25);line-height:14px;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-time-picker-clear .ant-time-picker-clock-icon,.ant-time-picker-icon .ant-time-picker-clock-icon{display:block;color:rgba(0,0,0,.25);line-height:1}.ant-time-picker-clear{z-index:2;background:#fff;opacity:0;pointer-events:none}.ant-time-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-time-picker:hover .ant-time-picker-clear{opacity:1;pointer-events:auto}.ant-time-picker-large .ant-time-picker-input{height:40px;padding:6px 11px;font-size:16px}.ant-time-picker-small .ant-time-picker-input{height:24px;padding:1px 7px}.ant-time-picker-small .ant-time-picker-clear,.ant-time-picker-small .ant-time-picker-icon{right:7px}@media not all and (min-resolution:0.001dpcm){@supports (-webkit-appearance:none) and (stroke-color:transparent){.ant-input{line-height:1.5}}}.ant-tag{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;height:auto;margin:0 8px 0 0;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:#fafafa;border:1px solid #d9d9d9;border-radius:4px;cursor:default;opacity:1;-webkit-transition:all .3s cubic-bezier(.78,.14,.15,.86);transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-tag:hover{opacity:.85}.ant-tag,.ant-tag a,.ant-tag a:hover{color:rgba(0,0,0,.65)}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag .anticon-close{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);margin-left:3px;color:rgba(0,0,0,.45);font-weight:700;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.78,.14,.15,.86);transition:all .3s cubic-bezier(.78,.14,.15,.86)}:root .ant-tag .anticon-close{font-size:12px}.ant-tag .anticon-close:hover{color:rgba(0,0,0,.85)}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover,.ant-tag-has-color a,.ant-tag-has-color a:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#1890ff}.ant-tag-checkable-checked,.ant-tag-checkable:active{color:#fff}.ant-tag-checkable-checked{background-color:#1890ff}.ant-tag-checkable:active{background-color:#096dd9}.ant-tag-hidden{display:none}.ant-tag-pink{color:#eb2f96;background:#fff0f6;border-color:#ffadd2}.ant-tag-pink-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-magenta{color:#eb2f96;background:#fff0f6;border-color:#ffadd2}.ant-tag-magenta-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-red{color:#f5222d;background:#fff1f0;border-color:#ffa39e}.ant-tag-red-inverse{color:#fff;background:#f5222d;border-color:#f5222d}.ant-tag-volcano{color:#fa541c;background:#fff2e8;border-color:#ffbb96}.ant-tag-volcano-inverse{color:#fff;background:#fa541c;border-color:#fa541c}.ant-tag-orange{color:#fa8c16;background:#fff7e6;border-color:#ffd591}.ant-tag-orange-inverse{color:#fff;background:#fa8c16;border-color:#fa8c16}.ant-tag-yellow{color:#fadb14;background:#feffe6;border-color:#fffb8f}.ant-tag-yellow-inverse{color:#fff;background:#fadb14;border-color:#fadb14}.ant-tag-gold{color:#faad14;background:#fffbe6;border-color:#ffe58f}.ant-tag-gold-inverse{color:#fff;background:#faad14;border-color:#faad14}.ant-tag-cyan{color:#13c2c2;background:#e6fffb;border-color:#87e8de}.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}.ant-tag-lime{color:#a0d911;background:#fcffe6;border-color:#eaff8f}.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}.ant-tag-green{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}.ant-tag-geekblue{color:#2f54eb;background:#f0f5ff;border-color:#adc6ff}.ant-tag-geekblue-inverse{color:#fff;background:#2f54eb;border-color:#2f54eb}.ant-tag-purple{color:#722ed1;background:#f9f0ff;border-color:#d3adf7}.ant-tag-purple-inverse{color:#fff;background:#722ed1;border-color:#722ed1}.ant-descriptions-title{margin-bottom:20px;color:rgba(0,0,0,.85);font-weight:700;font-size:16px;line-height:1.5}.ant-descriptions-view{width:100%;overflow:hidden;border-radius:4px}.ant-descriptions-view table{width:100%;table-layout:fixed}.ant-descriptions-row>td,.ant-descriptions-row>th{padding-bottom:16px}.ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-item-label{color:rgba(0,0,0,.85);font-weight:400;font-size:14px;line-height:1.5}.ant-descriptions-item-label:after{position:relative;top:-.5px;margin:0 8px 0 2px;content:\" \"}.ant-descriptions-item-colon:after{content:\":\"}.ant-descriptions-item-no-label:after{margin:0;content:\"\"}.ant-descriptions-item-content{display:table-cell;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5}.ant-descriptions-item{padding-bottom:0}.ant-descriptions-item>span{display:inline-block}.ant-descriptions-middle .ant-descriptions-row>td,.ant-descriptions-middle .ant-descriptions-row>th{padding-bottom:12px}.ant-descriptions-small .ant-descriptions-row>td,.ant-descriptions-small .ant-descriptions-row>th{padding-bottom:8px}.ant-descriptions-bordered .ant-descriptions-view{border:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-view>table{table-layout:auto}.ant-descriptions-bordered .ant-descriptions-item-content,.ant-descriptions-bordered .ant-descriptions-item-label{padding:16px 24px;border-right:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-item-content:last-child,.ant-descriptions-bordered .ant-descriptions-item-label:last-child{border-right:none}.ant-descriptions-bordered .ant-descriptions-item-label{background-color:#fafafa}.ant-descriptions-bordered .ant-descriptions-item-label:after{display:none}.ant-descriptions-bordered .ant-descriptions-row{border-bottom:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content,.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label{padding:12px 24px}.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content,.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label{padding:8px 16px}.ant-divider{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";background:#e8e8e8}.ant-divider,.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;width:1px;height:.9em;margin:0 8px;vertical-align:middle}.ant-divider-horizontal{display:block;clear:both;width:100%;min-width:100%;height:1px;margin:24px 0}.ant-divider-horizontal.ant-divider-with-text-center,.ant-divider-horizontal.ant-divider-with-text-left,.ant-divider-horizontal.ant-divider-with-text-right{display:table;margin:16px 0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-align:center;background:transparent}.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid #e8e8e8;-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%);content:\"\"}.ant-divider-horizontal.ant-divider-with-text-left .ant-divider-inner-text,.ant-divider-horizontal.ant-divider-with-text-right .ant-divider-inner-text{display:inline-block;padding:0 10px}.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:after{top:50%;width:5%}.ant-divider-inner-text{display:inline-block;padding:0 24px}.ant-divider-dashed{background:none;border:dashed #e8e8e8;border-width:1px 0 0}.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed{border-top:0}.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed:before{border-style:dashed none none}.ant-divider-vertical.ant-divider-dashed{border-width:0 0 0 1px}.ant-drawer{position:fixed;z-index:1000;width:0;height:100%;-webkit-transition:height 0s ease .3s,width 0s ease .3s,-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:height 0s ease .3s,width 0s ease .3s,-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1),height 0s ease .3s,width 0s ease .3s;transition:transform .3s cubic-bezier(.7,.3,.1,1),height 0s ease .3s,width 0s ease .3s,-webkit-transform .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer>*{-webkit-transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1),-webkit-box-shadow .3s cubic-bezier(.7,.3,.1,1);transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1),-webkit-box-shadow .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1),box-shadow .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1),box-shadow .3s cubic-bezier(.7,.3,.1,1),-webkit-transform .3s cubic-bezier(.7,.3,.1,1),-webkit-box-shadow .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-content-wrapper{position:absolute}.ant-drawer .ant-drawer-content{width:100%;height:100%}.ant-drawer-left,.ant-drawer-right{top:0;width:0;height:100%}.ant-drawer-left .ant-drawer-content-wrapper,.ant-drawer-right .ant-drawer-content-wrapper{height:100%}.ant-drawer-left.ant-drawer-open,.ant-drawer-right.ant-drawer-open{width:100%;-webkit-transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1),-webkit-transform .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-left.ant-drawer-open.no-mask,.ant-drawer-right.ant-drawer-open.no-mask{width:0}.ant-drawer-left.ant-drawer-open .ant-drawer-content-wrapper{-webkit-box-shadow:2px 0 8px rgba(0,0,0,.15);box-shadow:2px 0 8px rgba(0,0,0,.15)}.ant-drawer-right,.ant-drawer-right .ant-drawer-content-wrapper{right:0}.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper{-webkit-box-shadow:-2px 0 8px rgba(0,0,0,.15);box-shadow:-2px 0 8px rgba(0,0,0,.15)}.ant-drawer-right.ant-drawer-open.no-mask{right:1px;-webkit-transform:translateX(1px);-ms-transform:translateX(1px);transform:translateX(1px)}.ant-drawer-bottom,.ant-drawer-top{left:0;width:100%;height:0%}.ant-drawer-bottom .ant-drawer-content-wrapper,.ant-drawer-top .ant-drawer-content-wrapper{width:100%}.ant-drawer-bottom.ant-drawer-open,.ant-drawer-top.ant-drawer-open{height:100%;-webkit-transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:-webkit-transform .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1);transition:transform .3s cubic-bezier(.7,.3,.1,1),-webkit-transform .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-bottom.ant-drawer-open.no-mask,.ant-drawer-top.ant-drawer-open.no-mask{height:0%}.ant-drawer-top{top:0}.ant-drawer-top.ant-drawer-open .ant-drawer-content-wrapper{-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-drawer-bottom,.ant-drawer-bottom .ant-drawer-content-wrapper{bottom:0}.ant-drawer-bottom.ant-drawer-open .ant-drawer-content-wrapper{-webkit-box-shadow:0 -2px 8px rgba(0,0,0,.15);box-shadow:0 -2px 8px rgba(0,0,0,.15)}.ant-drawer-bottom.ant-drawer-open.no-mask{bottom:1px;-webkit-transform:translateY(1px);-ms-transform:translateY(1px);transform:translateY(1px)}.ant-drawer.ant-drawer-open .ant-drawer-mask{height:100%;opacity:1;-webkit-transition:none;transition:none;-webkit-animation:antdDrawerFadeIn .3s cubic-bezier(.7,.3,.1,1);animation:antdDrawerFadeIn .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-title{margin:0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:22px}.ant-drawer-content{position:relative;z-index:1;overflow:auto;background-color:#fff;background-clip:padding-box;border:0}.ant-drawer-close{position:absolute;top:0;right:0;z-index:10;display:block;width:56px;height:56px;padding:0;color:rgba(0,0,0,.45);font-weight:700;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;text-rendering:auto}.ant-drawer-close:focus,.ant-drawer-close:hover{color:rgba(0,0,0,.75);text-decoration:none}.ant-drawer-header{position:relative;padding:16px 24px;border-bottom:1px solid #e8e8e8;border-radius:4px 4px 0 0}.ant-drawer-header,.ant-drawer-header-no-title{color:rgba(0,0,0,.65);background:#fff}.ant-drawer-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}.ant-drawer-wrapper-body{height:100%;overflow:auto}.ant-drawer-mask{position:absolute;top:0;left:0;width:100%;height:0;background-color:rgba(0,0,0,.45);opacity:0;filter:alpha(opacity=45);-webkit-transition:opacity .3s linear,height 0s ease .3s;transition:opacity .3s linear,height 0s ease .3s}.ant-drawer-open-content{-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}@-webkit-keyframes antdDrawerFadeIn{0%{opacity:0}to{opacity:1}}@keyframes antdDrawerFadeIn{0%{opacity:0}to{opacity:1}}.ant-form{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-form legend{display:block;width:100%;margin-bottom:20px;padding:0;color:rgba(0,0,0,.45);font-size:16px;line-height:inherit;border:0;border-bottom:1px solid #d9d9d9}.ant-form label{font-size:14px}.ant-form input[type=search]{-webkit-box-sizing:border-box;box-sizing:border-box}.ant-form input[type=checkbox],.ant-form input[type=radio]{line-height:normal}.ant-form input[type=file]{display:block}.ant-form input[type=range]{display:block;width:100%}.ant-form select[multiple],.ant-form select[size]{height:auto}.ant-form input[type=checkbox]:focus,.ant-form input[type=file]:focus,.ant-form input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.ant-form output{display:block;padding-top:15px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5}.ant-form-item-required:before{display:inline-block;margin-right:4px;color:#f5222d;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:\"*\"}.ant-form-hide-required-mark .ant-form-item-required:before{display:none}.ant-form-item-label>label{color:rgba(0,0,0,.85)}.ant-form-item-label>label:after{content:\":\";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-form-item-label>label.ant-form-item-no-colon:after{content:\" \"}.ant-form-item{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";margin:0 0 24px;vertical-align:top}.ant-form-item label{position:relative}.ant-form-item label>.anticon{font-size:14px;vertical-align:top}.ant-form-item-control{position:relative;line-height:40px;zoom:1}.ant-form-item-control:after,.ant-form-item-control:before{display:table;content:\"\"}.ant-form-item-control:after{clear:both}.ant-form-item-children{position:relative}.ant-form-item-with-help{margin-bottom:5px}.ant-form-item-label{display:inline-block;overflow:hidden;line-height:39.9999px;white-space:nowrap;text-align:right;vertical-align:middle}.ant-form-item-label-left{text-align:left}.ant-form-item .ant-switch{margin:2px 0 4px}.ant-form-explain,.ant-form-extra{clear:both;min-height:22px;margin-top:-2px;color:rgba(0,0,0,.45);font-size:14px;line-height:1.5;-webkit-transition:color .3s cubic-bezier(.215,.61,.355,1);transition:color .3s cubic-bezier(.215,.61,.355,1)}.ant-form-explain{margin-bottom:-1px}.ant-form-extra{padding-top:4px}.ant-form-text{display:inline-block;padding-right:8px}.ant-form-split{display:block;text-align:center}form .has-feedback .ant-input{padding-right:30px}form .has-feedback .ant-input-affix-wrapper .ant-input-suffix{padding-right:18px}form .has-feedback .ant-input-affix-wrapper .ant-input{padding-right:49px}form .has-feedback .ant-input-affix-wrapper.ant-input-affix-wrapper-input-with-clear-btn .ant-input{padding-right:68px}form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-arrow,form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection__clear,form .has-feedback>.ant-select .ant-select-arrow,form .has-feedback>.ant-select .ant-select-selection__clear{right:28px}form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection-selected-value,form .has-feedback>.ant-select .ant-select-selection-selected-value{padding-right:42px}form .has-feedback .ant-cascader-picker-arrow{margin-right:17px}form .has-feedback .ant-calendar-picker-clear,form .has-feedback .ant-calendar-picker-icon,form .has-feedback .ant-cascader-picker-clear,form .has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix,form .has-feedback .ant-time-picker-clear,form .has-feedback .ant-time-picker-icon{right:28px}form .ant-mentions,form textarea.ant-input{height:auto;margin-bottom:4px}form .ant-upload{background:transparent}form input[type=checkbox],form input[type=radio]{width:14px;height:14px}form .ant-checkbox-inline,form .ant-radio-inline{display:inline-block;margin-left:8px;font-weight:400;vertical-align:middle;cursor:pointer}form .ant-checkbox-inline:first-child,form .ant-radio-inline:first-child{margin-left:0}form .ant-checkbox-vertical,form .ant-radio-vertical{display:block}form .ant-checkbox-vertical+.ant-checkbox-vertical,form .ant-radio-vertical+.ant-radio-vertical{margin-left:0}form .ant-input-number+.ant-form-text{margin-left:8px}form .ant-input-number-handler-wrap{z-index:2}form .ant-cascader-picker,form .ant-select{width:100%}form .ant-input-group .ant-cascader-picker,form .ant-input-group .ant-select{width:auto}form .ant-input-group-wrapper,form :not(.ant-input-group-wrapper)>.ant-input-group{display:inline-block;vertical-align:middle}form:not(.ant-form-vertical) .ant-input-group-wrapper,form:not(.ant-form-vertical) :not(.ant-input-group-wrapper)>.ant-input-group{position:relative;top:-1px}.ant-col-24.ant-form-item-label,.ant-col-xl-24.ant-form-item-label,.ant-form-vertical .ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-24.ant-form-item-label label:after,.ant-col-xl-24.ant-form-item-label label:after,.ant-form-vertical .ant-form-item-label label:after{display:none}.ant-form-vertical .ant-form-item{padding-bottom:8px}.ant-form-vertical .ant-form-item-control{line-height:1.5}.ant-form-vertical .ant-form-explain{margin-top:2px;margin-bottom:-5px}.ant-form-vertical .ant-form-extra{margin-top:2px;margin-bottom:-4px}@media (max-width:575px){.ant-form-item-control-wrapper,.ant-form-item-label{display:block;width:100%}.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-form-item-label label:after{display:none}.ant-col-xs-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-xs-24.ant-form-item-label label:after{display:none}}@media (max-width:767px){.ant-col-sm-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-sm-24.ant-form-item-label label:after{display:none}}@media (max-width:991px){.ant-col-md-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-md-24.ant-form-item-label label:after{display:none}}@media (max-width:1199px){.ant-col-lg-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-lg-24.ant-form-item-label label:after{display:none}}@media (max-width:1599px){.ant-col-xl-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-xl-24.ant-form-item-label label:after{display:none}}.ant-form-inline .ant-form-item{display:inline-block;margin-right:16px;margin-bottom:0}.ant-form-inline .ant-form-item-with-help{margin-bottom:24px}.ant-form-inline .ant-form-item>.ant-form-item-control-wrapper,.ant-form-inline .ant-form-item>.ant-form-item-label{display:inline-block;vertical-align:top}.ant-form-inline .ant-form-text,.ant-form-inline .has-feedback{display:inline-block}.has-error.has-feedback .ant-form-item-children-icon,.has-success.has-feedback .ant-form-item-children-icon,.has-warning.has-feedback .ant-form-item-children-icon,.is-validating.has-feedback .ant-form-item-children-icon{position:absolute;top:50%;right:0;z-index:1;width:32px;height:20px;margin-top:-10px;font-size:14px;line-height:20px;text-align:center;visibility:visible;-webkit-animation:zoomIn .3s cubic-bezier(.12,.4,.29,1.46);animation:zoomIn .3s cubic-bezier(.12,.4,.29,1.46);pointer-events:none}.has-error.has-feedback .ant-form-item-children-icon svg,.has-success.has-feedback .ant-form-item-children-icon svg,.has-warning.has-feedback .ant-form-item-children-icon svg,.is-validating.has-feedback .ant-form-item-children-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.has-success.has-feedback .ant-form-item-children-icon{color:#52c41a;-webkit-animation-name:diffZoomIn1!important;animation-name:diffZoomIn1!important}.has-warning .ant-form-explain,.has-warning .ant-form-split{color:#faad14}.has-warning .ant-input,.has-warning .ant-input:hover{background-color:#fff;border-color:#faad14}.has-warning .ant-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input:not([disabled]):hover{border-color:#faad14}.has-warning .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-affix-wrapper .ant-input,.has-warning .ant-input-affix-wrapper .ant-input:hover{background-color:#fff;border-color:#faad14}.has-warning .ant-input-affix-wrapper .ant-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#faad14}.has-warning .ant-input-prefix{color:#faad14}.has-warning .ant-input-group-addon{color:#faad14;background-color:#fff;border-color:#faad14}.has-warning .has-feedback{color:#faad14}.has-warning.has-feedback .ant-form-item-children-icon{color:#faad14;-webkit-animation-name:diffZoomIn3!important;animation-name:diffZoomIn3!important}.has-warning .ant-select-selection,.has-warning .ant-select-selection:hover{border-color:#faad14}.has-warning .ant-select-focused .ant-select-selection,.has-warning .ant-select-open .ant-select-selection{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-calendar-picker-icon:after,.has-warning .ant-cascader-picker-arrow,.has-warning .ant-picker-icon:after,.has-warning .ant-select-arrow,.has-warning .ant-time-picker-icon:after{color:#faad14}.has-warning .ant-input-number,.has-warning .ant-time-picker-input{border-color:#faad14}.has-warning .ant-input-number-focused,.has-warning .ant-input-number:focus,.has-warning .ant-time-picker-input-focused,.has-warning .ant-time-picker-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-number:not([disabled]):hover,.has-warning .ant-time-picker-input:not([disabled]):hover{border-color:#faad14}.has-warning .ant-cascader-picker:focus .ant-cascader-input{border-color:#ffc53d;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(250,173,20,.2);box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-cascader-picker:hover .ant-cascader-input{border-color:#faad14}.has-error .ant-form-explain,.has-error .ant-form-split{color:#f5222d}.has-error .ant-input,.has-error .ant-input:hover{background-color:#fff;border-color:#f5222d}.has-error .ant-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input:not([disabled]):hover{border-color:#f5222d}.has-error .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-affix-wrapper .ant-input,.has-error .ant-input-affix-wrapper .ant-input:hover{background-color:#fff;border-color:#f5222d}.has-error .ant-input-affix-wrapper .ant-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#f5222d}.has-error .ant-input-prefix{color:#f5222d}.has-error .ant-input-group-addon{color:#f5222d;background-color:#fff;border-color:#f5222d}.has-error .has-feedback{color:#f5222d}.has-error.has-feedback .ant-form-item-children-icon{color:#f5222d;-webkit-animation-name:diffZoomIn2!important;animation-name:diffZoomIn2!important}.has-error .ant-select-selection,.has-error .ant-select-selection:hover{border-color:#f5222d}.has-error .ant-select-focused .ant-select-selection,.has-error .ant-select-open .ant-select-selection{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-select.ant-select-auto-complete .ant-input:focus{border-color:#f5222d}.has-error .ant-input-group-addon .ant-select-selection{border-color:transparent;-webkit-box-shadow:none;box-shadow:none}.has-error .ant-calendar-picker-icon:after,.has-error .ant-cascader-picker-arrow,.has-error .ant-picker-icon:after,.has-error .ant-select-arrow,.has-error .ant-time-picker-icon:after{color:#f5222d}.has-error .ant-input-number,.has-error .ant-time-picker-input{border-color:#f5222d}.has-error .ant-input-number-focused,.has-error .ant-input-number:focus,.has-error .ant-time-picker-input-focused,.has-error .ant-time-picker-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-number:not([disabled]):hover,.has-error .ant-mention-wrapper .ant-mention-editor,.has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):hover,.has-error .ant-time-picker-input:not([disabled]):hover{border-color:#f5222d}.has-error .ant-cascader-picker:focus .ant-cascader-input,.has-error .ant-mention-wrapper.ant-mention-active:not([disabled]) .ant-mention-editor,.has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(245,34,45,.2);box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-cascader-picker:hover .ant-cascader-input,.has-error .ant-transfer-list{border-color:#f5222d}.has-error .ant-transfer-list-search:not([disabled]){border-color:#d9d9d9}.has-error .ant-transfer-list-search:not([disabled]):hover{border-color:#40a9ff;border-right-width:1px!important}.has-error .ant-transfer-list-search:not([disabled]):focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.is-validating.has-feedback .ant-form-item-children-icon{display:inline-block;color:#1890ff}.ant-advanced-search-form .ant-form-item{margin-bottom:24px}.ant-advanced-search-form .ant-form-item-with-help{margin-bottom:5px}.show-help-appear,.show-help-enter,.show-help-leave{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-play-state:paused;animation-play-state:paused}.show-help-appear.show-help-appear-active,.show-help-enter.show-help-enter-active{-webkit-animation-name:antShowHelpIn;animation-name:antShowHelpIn;-webkit-animation-play-state:running;animation-play-state:running}.show-help-leave.show-help-leave-active{-webkit-animation-name:antShowHelpOut;animation-name:antShowHelpOut;-webkit-animation-play-state:running;animation-play-state:running;pointer-events:none}.show-help-appear,.show-help-enter{opacity:0}.show-help-appear,.show-help-enter,.show-help-leave{-webkit-animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-timing-function:cubic-bezier(.645,.045,.355,1)}@-webkit-keyframes antShowHelpIn{0%{-webkit-transform:translateY(-5px);transform:translateY(-5px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes antShowHelpIn{0%{-webkit-transform:translateY(-5px);transform:translateY(-5px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@-webkit-keyframes antShowHelpOut{to{-webkit-transform:translateY(-5px);transform:translateY(-5px);opacity:0}}@keyframes antShowHelpOut{to{-webkit-transform:translateY(-5px);transform:translateY(-5px);opacity:0}}@-webkit-keyframes diffZoomIn1{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes diffZoomIn1{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes diffZoomIn2{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes diffZoomIn2{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes diffZoomIn3{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes diffZoomIn3{0%{-webkit-transform:scale(0);transform:scale(0)}to{-webkit-transform:scale(1);transform:scale(1)}}.ant-input-number{-webkit-box-sizing:border-box;box-sizing:border-box;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;-webkit-transition:all .3s;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #d9d9d9;border-radius:4px}.ant-input-number::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input-number:-ms-input-placeholder{color:#bfbfbf}.ant-input-number::-webkit-input-placeholder{color:#bfbfbf}.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number:-ms-input-placeholder{text-overflow:ellipsis}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-number:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-input-number[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-input-number-lg{height:40px;padding:6px 11px}.ant-input-number-sm{height:24px;padding:1px 7px}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:rgba(0,0,0,.45);font-weight:700;line-height:0;text-align:center;-webkit-transition:all .1s linear;transition:all .1s linear}.ant-input-number-handler:active{background:#f4f4f4}.ant-input-number-handler:hover .ant-input-number-handler-down-inner,.ant-input-number-handler:hover .ant-input-number-handler-up-inner{color:#40a9ff}.ant-input-number-handler-down-inner,.ant-input-number-handler-up-inner{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:rgba(0,0,0,.45);line-height:12px;-webkit-transition:all .1s linear;transition:all .1s linear;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-input-number-handler-down-inner>*,.ant-input-number-handler-up-inner>*{line-height:1}.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}.ant-input-number-focused,.ant-input-number:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-input-number-focused{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 11px;text-align:left;background-color:transparent;border:0;border-radius:4px;outline:0;-webkit-transition:all .3s linear;transition:all .3s linear;-moz-appearance:textfield!important}.ant-input-number-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input-number-input:-ms-input-placeholder{color:#bfbfbf}.ant-input-number-input::-webkit-input-placeholder{color:#bfbfbf}.ant-input-number-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number-input:-ms-input-placeholder{text-overflow:ellipsis}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none}.ant-input-number-lg{padding:0;font-size:16px}.ant-input-number-lg input{height:38px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#fff;border-left:1px solid #d9d9d9;border-radius:0 4px 4px 0;opacity:0;-webkit-transition:opacity .24s linear .1s;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner{display:inline-block;font-size:12px;font-size:7px\\9;-webkit-transform:scale(.58333333) rotate(0deg);-ms-transform:scale(.58333333) rotate(0deg);transform:scale(.58333333) rotate(0deg);min-width:auto;margin-right:0}:root .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner,:root .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner{font-size:12px}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number:hover .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:4px;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #d9d9d9;border-bottom-right-radius:4px;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;margin-top:-6px;text-align:center}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-handler-down-disabled,.ant-input-number-handler-up-disabled{cursor:not-allowed}.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner,.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner{color:rgba(0,0,0,.25)}.ant-layout{display:-ms-flexbox;display:flex;-ms-flex:auto;flex:auto;-ms-flex-direction:column;flex-direction:column;min-height:0;background:#f0f2f5}.ant-layout,.ant-layout *{-webkit-box-sizing:border-box;box-sizing:border-box}.ant-layout.ant-layout-has-sider{-ms-flex-direction:row;flex-direction:row}.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{overflow-x:hidden}.ant-layout-footer,.ant-layout-header{-ms-flex:0 0 auto;flex:0 0 auto}.ant-layout-header{height:64px;padding:0 50px;line-height:64px;background:#001529}.ant-layout-footer{padding:24px 50px;color:rgba(0,0,0,.65);font-size:14px;background:#f0f2f5}.ant-layout-content{-ms-flex:auto;flex:auto;min-height:0}.ant-layout-sider{position:relative;min-width:0;background:#001529;-webkit-transition:all .2s;transition:all .2s}.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}.ant-layout-sider-has-trigger{padding-bottom:48px}.ant-layout-sider-right{-ms-flex-order:1;order:1}.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;-webkit-transition:all .2s;transition:all .2s}.ant-layout-sider-zero-width>*{overflow:hidden}.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#001529;border-radius:0 4px 4px 0;cursor:pointer;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-layout-sider-zero-width-trigger:hover{background:#192c3e}.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:4px 0 0 4px}.ant-layout-sider-light{background:#fff}.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}.ant-list{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative}.ant-list *{outline:none}.ant-list-pagination{margin-top:24px;text-align:right}.ant-list-pagination .ant-pagination-options{text-align:left}.ant-list-more{margin-top:12px;text-align:center}.ant-list-more button{padding-right:32px;padding-left:32px}.ant-list-spin{min-height:40px;text-align:center}.ant-list-empty-text{padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center}.ant-list-items{margin:0;padding:0;list-style:none}.ant-list-item{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:12px 0}.ant-list-item-content{color:rgba(0,0,0,.65)}.ant-list-item-meta{display:-ms-flexbox;display:flex;-ms-flex:1;flex:1 1;-ms-flex-align:start;align-items:flex-start;font-size:0}.ant-list-item-meta-avatar{margin-right:16px}.ant-list-item-meta-content{-ms-flex:1 0;flex:1 0}.ant-list-item-meta-title{margin-bottom:4px;color:rgba(0,0,0,.65);font-size:14px;line-height:22px}.ant-list-item-meta-title>a{color:rgba(0,0,0,.65);-webkit-transition:all .3s;transition:all .3s}.ant-list-item-meta-title>a:hover{color:#1890ff}.ant-list-item-meta-description{color:rgba(0,0,0,.45);font-size:14px;line-height:22px}.ant-list-item-action{-ms-flex:0 0 auto;flex:0 0 auto;margin-left:48px;padding:0;font-size:0;list-style:none}.ant-list-item-action>li{position:relative;display:inline-block;padding:0 8px;color:rgba(0,0,0,.45);font-size:14px;line-height:22px;text-align:center;cursor:pointer}.ant-list-item-action>li:first-child{padding-left:0}.ant-list-item-action-split{position:absolute;top:50%;right:0;width:1px;height:14px;margin-top:-7px;background-color:#e8e8e8}.ant-list-footer,.ant-list-header{background:transparent}.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}.ant-list-split .ant-list-item{border-bottom:1px solid #e8e8e8}.ant-list-split .ant-list-item:last-child{border-bottom:none}.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}.ant-list-something-after-last-item .ant-spin-container>.ant-list-items>.ant-list-item:last-child{border-bottom:1px solid #e8e8e8}.ant-list-lg .ant-list-item{padding-top:16px;padding-bottom:16px}.ant-list-sm .ant-list-item{padding-top:8px;padding-bottom:8px}.ant-list-vertical .ant-list-item{-ms-flex-align:initial;align-items:normal}.ant-list-vertical .ant-list-item-main{display:block;-ms-flex:1;flex:1 1}.ant-list-vertical .ant-list-item-extra{margin-left:40px}.ant-list-vertical .ant-list-item-meta{margin-bottom:16px}.ant-list-vertical .ant-list-item-meta-title{margin-bottom:12px;color:rgba(0,0,0,.85);font-size:16px;line-height:24px}.ant-list-vertical .ant-list-item-action{margin-top:16px;margin-left:auto}.ant-list-vertical .ant-list-item-action>li{padding:0 16px}.ant-list-vertical .ant-list-item-action>li:first-child{padding-left:0}.ant-list-grid .ant-col>.ant-list-item{display:block;max-width:100%;margin-bottom:16px;padding-top:0;padding-bottom:0;border-bottom:none}.ant-list-item-no-flex{display:block}.ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:right}.ant-list-bordered{border:1px solid #d9d9d9;border-radius:4px}.ant-list-bordered .ant-list-footer,.ant-list-bordered .ant-list-header,.ant-list-bordered .ant-list-item{padding-right:24px;padding-left:24px}.ant-list-bordered .ant-list-item{border-bottom:1px solid #e8e8e8}.ant-list-bordered .ant-list-pagination{margin:16px 24px}.ant-list-bordered.ant-list-sm .ant-list-item{padding-right:16px;padding-left:16px}.ant-list-bordered.ant-list-sm .ant-list-footer,.ant-list-bordered.ant-list-sm .ant-list-header{padding:8px 16px}.ant-list-bordered.ant-list-lg .ant-list-footer,.ant-list-bordered.ant-list-lg .ant-list-header{padding:16px 24px}@media screen and (max-width:768px){.ant-list-item-action,.ant-list-vertical .ant-list-item-extra{margin-left:24px}}@media screen and (max-width:576px){.ant-list-item{-ms-flex-wrap:wrap;flex-wrap:wrap}.ant-list-item-action{margin-left:12px}.ant-list-vertical .ant-list-item{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.ant-list-vertical .ant-list-item-main{min-width:220px}.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}.ant-spin{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;display:none;color:#1890ff;text-align:center;vertical-align:middle;opacity:0;-webkit-transition:-webkit-transform .3s cubic-bezier(.78,.14,.15,.86);transition:-webkit-transform .3s cubic-bezier(.78,.14,.15,.86);transition:transform .3s cubic-bezier(.78,.14,.15,.86);transition:transform .3s cubic-bezier(.78,.14,.15,.86),-webkit-transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;-webkit-transition:opacity .3s;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none\\9;width:100%;height:100%;background:#fff;opacity:0;-webkit-transition:all .3s;transition:all .3s;content:\"\";pointer-events:none}.ant-spin-blur{clear:both;overflow:hidden;opacity:.5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:rgba(0,0,0,.45)}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#1890ff;border-radius:100%;-webkit-transform:scale(.75);-ms-transform:scale(.75);transform:scale(.75);-webkit-transform-origin:50% 50%;-ms-transform-origin:50% 50%;transform-origin:50% 50%;opacity:.3;-webkit-animation:antSpinMove 1s linear infinite alternate;animation:antSpinMove 1s linear infinite alternate}.ant-spin-dot-item:first-child{top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}.ant-spin-dot-spin{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);-webkit-animation:antRotate 1.2s linear infinite;animation:antRotate 1.2s linear infinite}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.ant-spin-blur{background:#fff;opacity:.5}}@-webkit-keyframes antSpinMove{to{opacity:1}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}.ant-pagination{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-pagination,.ant-pagination ol,.ant-pagination ul{margin:0;padding:0;list-style:none}.ant-pagination:after{display:block;clear:both;height:0;overflow:hidden;visibility:hidden;content:\" \"}.ant-pagination-item,.ant-pagination-total-text{display:inline-block;height:32px;margin-right:8px;line-height:30px;vertical-align:middle}.ant-pagination-item{min-width:32px;font-family:Arial;text-align:center;list-style:none;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-pagination-item a{display:block;padding:0 6px;color:rgba(0,0,0,.65);-webkit-transition:none;transition:none}.ant-pagination-item a:hover{text-decoration:none}.ant-pagination-item:focus,.ant-pagination-item:hover{border-color:#1890ff;-webkit-transition:all .3s;transition:all .3s}.ant-pagination-item:focus a,.ant-pagination-item:hover a{color:#1890ff}.ant-pagination-item-active{font-weight:500;background:#fff;border-color:#1890ff}.ant-pagination-item-active a{color:#1890ff}.ant-pagination-item-active:focus,.ant-pagination-item-active:hover{border-color:#40a9ff}.ant-pagination-item-active:focus a,.ant-pagination-item-active:hover a{color:#40a9ff}.ant-pagination-jump-next,.ant-pagination-jump-prev{outline:0}.ant-pagination-jump-next .ant-pagination-item-container,.ant-pagination-jump-prev .ant-pagination-item-container{position:relative}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon{display:inline-block;font-size:12px;font-size:12px\\9;-webkit-transform:scale(1) rotate(0deg);-ms-transform:scale(1) rotate(0deg);transform:scale(1) rotate(0deg);color:#1890ff;letter-spacing:-1px;opacity:0;-webkit-transition:all .2s;transition:all .2s}:root .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon,:root .ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon{font-size:12px}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg{top:0;right:0;bottom:0;left:0;margin:auto}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis{position:absolute;top:0;right:0;bottom:0;left:0;display:block;margin:auto;color:rgba(0,0,0,.25);letter-spacing:2px;text-align:center;text-indent:.13em;opacity:1;-webkit-transition:all .2s;transition:all .2s}.ant-pagination-jump-next:focus .ant-pagination-item-link-icon,.ant-pagination-jump-next:hover .ant-pagination-item-link-icon,.ant-pagination-jump-prev:focus .ant-pagination-item-link-icon,.ant-pagination-jump-prev:hover .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-next:focus .ant-pagination-item-ellipsis,.ant-pagination-jump-next:hover .ant-pagination-item-ellipsis,.ant-pagination-jump-prev:focus .ant-pagination-item-ellipsis,.ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-jump-next,.ant-pagination-jump-prev,.ant-pagination-prev{margin-right:8px}.ant-pagination-jump-next,.ant-pagination-jump-prev,.ant-pagination-next,.ant-pagination-prev{display:inline-block;min-width:32px;height:32px;color:rgba(0,0,0,.65);font-family:Arial;line-height:32px;text-align:center;vertical-align:middle;list-style:none;border-radius:4px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-pagination-next,.ant-pagination-prev{outline:0}.ant-pagination-next a,.ant-pagination-prev a{color:rgba(0,0,0,.65);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-pagination-next:hover a,.ant-pagination-prev:hover a{border-color:#40a9ff}.ant-pagination-next .ant-pagination-item-link,.ant-pagination-prev .ant-pagination-item-link{display:block;height:100%;font-size:12px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:none;-webkit-transition:all .3s;transition:all .3s}.ant-pagination-next:focus .ant-pagination-item-link,.ant-pagination-next:hover .ant-pagination-item-link,.ant-pagination-prev:focus .ant-pagination-item-link,.ant-pagination-prev:hover .ant-pagination-item-link{color:#1890ff;border-color:#1890ff}.ant-pagination-disabled,.ant-pagination-disabled:focus,.ant-pagination-disabled:hover{cursor:not-allowed}.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination-disabled:focus .ant-pagination-item-link,.ant-pagination-disabled:focus a,.ant-pagination-disabled:hover .ant-pagination-item-link,.ant-pagination-disabled:hover a,.ant-pagination-disabled a{color:rgba(0,0,0,.25);border-color:#d9d9d9;cursor:not-allowed}.ant-pagination-slash{margin:0 10px 0 5px}.ant-pagination-options{display:inline-block;margin-left:16px;vertical-align:middle}.ant-pagination-options-size-changer.ant-select{display:inline-block;width:auto;margin-right:8px}.ant-pagination-options-quick-jumper{display:inline-block;height:32px;line-height:32px;vertical-align:top}.ant-pagination-options-quick-jumper input{position:relative;display:inline-block;width:100%;height:32px;padding:4px 11px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;width:50px;margin:0 8px}.ant-pagination-options-quick-jumper input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-pagination-options-quick-jumper input:-ms-input-placeholder{color:#bfbfbf}.ant-pagination-options-quick-jumper input::-webkit-input-placeholder{color:#bfbfbf}.ant-pagination-options-quick-jumper input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:-ms-input-placeholder{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:focus,.ant-pagination-options-quick-jumper input:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-pagination-options-quick-jumper input:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-pagination-options-quick-jumper input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-pagination-options-quick-jumper input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-pagination-options-quick-jumper input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-pagination-options-quick-jumper input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-pagination-options-quick-jumper input-sm{height:24px;padding:1px 7px}.ant-pagination-simple .ant-pagination-next,.ant-pagination-simple .ant-pagination-prev{height:24px;line-height:24px;vertical-align:top}.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link,.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link{height:24px;border:0}.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link:after,.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination-simple .ant-pagination-simple-pager{display:inline-block;height:24px;margin-right:8px}.ant-pagination-simple .ant-pagination-simple-pager input{-webkit-box-sizing:border-box;box-sizing:border-box;height:100%;margin-right:8px;padding:0 6px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:4px;outline:none;-webkit-transition:border-color .3s;transition:border-color .3s}.ant-pagination-simple .ant-pagination-simple-pager input:hover{border-color:#1890ff}.ant-pagination.mini .ant-pagination-simple-pager,.ant-pagination.mini .ant-pagination-total-text{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-item{min-width:24px;height:24px;margin:0;line-height:22px}.ant-pagination.mini .ant-pagination-item:not(.ant-pagination-item-active){background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-next,.ant-pagination.mini .ant-pagination-prev{min-width:24px;height:24px;margin:0;line-height:24px}.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link,.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link{background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link:after,.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-jump-next,.ant-pagination.mini .ant-pagination-jump-prev{height:24px;margin-right:0;line-height:24px}.ant-pagination.mini .ant-pagination-options{margin-left:2px}.ant-pagination.mini .ant-pagination-options-quick-jumper{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-options-quick-jumper input{height:24px;padding:1px 7px;width:44px}.ant-pagination.ant-pagination-disabled{cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item{background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item a{color:rgba(0,0,0,.25);background:transparent;border:none;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active{background:#dbdbdb;border-color:transparent}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active a{color:#fff}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination.ant-pagination-disabled .ant-pagination-item-link:focus,.ant-pagination.ant-pagination-disabled .ant-pagination-item-link:hover{color:rgba(0,0,0,.45);background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:focus .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:hover .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:focus .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:hover .ant-pagination-item-link-icon{opacity:0}.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:focus .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:hover .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:focus .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis{opacity:1}@media only screen and (max-width:992px){.ant-pagination-item-after-jump-prev,.ant-pagination-item-before-jump-next{display:none}}@media only screen and (max-width:576px){.ant-pagination-options{display:none}}.ant-mention-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;font-size:14px;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;vertical-align:middle}.ant-mention-wrapper,.ant-mention-wrapper .ant-mention-editor{padding:0;color:rgba(0,0,0,.65);line-height:1.5;position:relative;width:100%}.ant-mention-wrapper .ant-mention-editor{display:inline-block;height:32px;font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;display:block;height:auto;min-height:32px}.ant-mention-wrapper .ant-mention-editor::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-mention-wrapper .ant-mention-editor:-ms-input-placeholder{color:#bfbfbf}.ant-mention-wrapper .ant-mention-editor::-webkit-input-placeholder{color:#bfbfbf}.ant-mention-wrapper .ant-mention-editor:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mention-wrapper .ant-mention-editor:-ms-input-placeholder{text-overflow:ellipsis}.ant-mention-wrapper .ant-mention-editor:placeholder-shown{text-overflow:ellipsis}.ant-mention-wrapper .ant-mention-editor:focus,.ant-mention-wrapper .ant-mention-editor:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-mention-wrapper .ant-mention-editor:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-mention-wrapper .ant-mention-editor-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mention-wrapper .ant-mention-editor-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mention-wrapper .ant-mention-editor[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mention-wrapper .ant-mention-editor[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-mention-wrapper .ant-mention-editor{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-mention-wrapper .ant-mention-editor-lg{height:40px;padding:6px 11px;font-size:16px}.ant-mention-wrapper .ant-mention-editor-sm{height:24px;padding:1px 7px}.ant-mention-wrapper .ant-mention-editor-wrapper{height:auto;overflow-y:auto}.ant-mention-wrapper.ant-mention-active:not(.disabled) .ant-mention-editor{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-mention-wrapper.disabled .ant-mention-editor{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mention-wrapper.disabled .ant-mention-editor:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mention-wrapper .public-DraftEditorPlaceholder-root{position:absolute;pointer-events:none}.ant-mention-wrapper .public-DraftEditorPlaceholder-root .public-DraftEditorPlaceholder-inner{height:auto;padding:5px 11px;color:#bfbfbf;white-space:pre-wrap;word-wrap:break-word;outline:none;opacity:1}.ant-mention-wrapper .DraftEditor-editorContainer .public-DraftEditor-content{height:auto;padding:5px 11px}.ant-mention-dropdown{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;top:-9999px;left:-9999px;z-index:1050;min-width:120px;max-height:250px;margin:1.5em 0 0;overflow-x:hidden;overflow-y:auto;background-color:#fff;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-mention-dropdown-placement-top{margin-top:-.1em}.ant-mention-dropdown-notfound.ant-mention-dropdown-item{color:rgba(0,0,0,.25)}.ant-mention-dropdown-notfound.ant-mention-dropdown-item .anticon-loading{display:block;color:#1890ff;text-align:center}.ant-mention-dropdown-item{position:relative;display:block;padding:5px 12px;overflow:hidden;color:rgba(0,0,0,.65);font-weight:400;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.ant-mention-dropdown-item-active,.ant-mention-dropdown-item.focus,.ant-mention-dropdown-item:hover{background-color:#e6f7ff}.ant-mention-dropdown-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-mention-dropdown-item-disabled:hover{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-mention-dropdown-item-selected,.ant-mention-dropdown-item-selected:hover{color:rgba(0,0,0,.65);font-weight:700;background-color:#f5f5f5}.ant-mention-dropdown-item-divider{height:1px;margin:1px 0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-mentions{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;position:relative;display:inline-block;height:auto;padding:0;overflow:hidden;line-height:1.5;white-space:pre-wrap;vertical-align:bottom}.ant-mentions::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-mentions:-ms-input-placeholder{color:#bfbfbf}.ant-mentions::-webkit-input-placeholder{color:#bfbfbf}.ant-mentions:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions:-ms-input-placeholder{text-overflow:ellipsis}.ant-mentions:placeholder-shown{text-overflow:ellipsis}.ant-mentions:focus,.ant-mentions:hover{border-color:#40a9ff;border-right-width:1px!important}.ant-mentions:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-mentions-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mentions[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-mentions{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-mentions-lg{height:40px;padding:6px 11px;font-size:16px}.ant-mentions-sm{height:24px;padding:1px 7px}.ant-mentions-disabled>textarea{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions-disabled>textarea:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mentions-focused{border-color:#40a9ff;border-right-width:1px!important;outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-mentions-measure,.ant-mentions>textarea{min-height:30px;margin:0;padding:4px 11px;overflow:inherit;overflow-x:hidden;overflow-y:auto;font-weight:inherit;font-size:inherit;font-family:inherit;font-style:inherit;-webkit-font-feature-settings:inherit;font-feature-settings:inherit;font-variant:inherit;font-size-adjust:inherit;font-stretch:inherit;line-height:inherit;direction:inherit;letter-spacing:inherit;white-space:inherit;text-align:inherit;vertical-align:top;word-wrap:break-word;word-break:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit}.ant-mentions>textarea{width:100%;border:none;outline:none;resize:none}.ant-mentions>textarea::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-mentions>textarea:-ms-input-placeholder{color:#bfbfbf}.ant-mentions>textarea::-webkit-input-placeholder{color:#bfbfbf}.ant-mentions>textarea:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions>textarea:-ms-input-placeholder{text-overflow:ellipsis}.ant-mentions>textarea:placeholder-shown{text-overflow:ellipsis}.ant-mentions>textarea:-moz-read-only{cursor:default}.ant-mentions>textarea:read-only{cursor:default}.ant-mentions-measure{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;color:transparent;pointer-events:none}.ant-mentions-measure>span{display:inline-block;min-height:1em}.ant-mentions-dropdown{margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\",;position:absolute;top:-9999px;left:-9999px;z-index:1050;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;font-variant:normal;background-color:#fff;border-radius:4px;outline:none;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-mentions-dropdown-hidden{display:none}.ant-mentions-dropdown-menu{max-height:250px;margin-bottom:0;padding-left:0;overflow:auto;list-style:none;outline:none}.ant-mentions-dropdown-menu-item{position:relative;display:block;min-width:100px;padding:5px 12px;overflow:hidden;color:rgba(0,0,0,.65);font-weight:400;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-mentions-dropdown-menu-item:hover{background-color:#e6f7ff}.ant-mentions-dropdown-menu-item:first-child{border-radius:4px 4px 0 0}.ant-mentions-dropdown-menu-item:last-child{border-radius:0 0 4px 4px}.ant-mentions-dropdown-menu-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-mentions-dropdown-menu-item-disabled:hover{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-mentions-dropdown-menu-item-selected{color:rgba(0,0,0,.65);font-weight:600;background-color:#fafafa}.ant-mentions-dropdown-menu-item-active{background-color:#e6f7ff}.ant-message{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:fixed;top:16px;left:0;z-index:1010;width:100%;pointer-events:none}.ant-message-notice{padding:8px;text-align:center}.ant-message-notice:first-child{margin-top:-8px}.ant-message-notice-content{display:inline-block;padding:10px 16px;background:#fff;border-radius:4px;-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15);pointer-events:all}.ant-message-success .anticon{color:#52c41a}.ant-message-error .anticon{color:#f5222d}.ant-message-warning .anticon{color:#faad14}.ant-message-info .anticon,.ant-message-loading .anticon{color:#1890ff}.ant-message .anticon{position:relative;top:1px;margin-right:8px;font-size:16px}.ant-message-notice.move-up-leave.move-up-leave-active{overflow:hidden;-webkit-animation-name:MessageMoveOut;animation-name:MessageMoveOut;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes MessageMoveOut{0%{max-height:150px;padding:8px;opacity:1}to{max-height:0;padding:0;opacity:0}}@keyframes MessageMoveOut{0%{max-height:150px;padding:8px;opacity:1}to{max-height:0;padding:0;opacity:0}}.ant-modal{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;top:100px;width:auto;margin:0 auto;padding:0 0 24px;pointer-events:none}.ant-modal-wrap{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-modal-title{margin:0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:22px;word-wrap:break-word}.ant-modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:0;border-radius:4px;-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15);pointer-events:auto}.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:rgba(0,0,0,.45);font-weight:700;line-height:1;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;-webkit-transition:color .3s;transition:color .3s}.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}.ant-modal-close:focus,.ant-modal-close:hover{color:rgba(0,0,0,.75);text-decoration:none}.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:4px 4px 0 0}.ant-modal-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}.ant-modal-footer{padding:10px 16px;text-align:right;background:transparent;border-top:1px solid #e8e8e8;border-radius:0 0 4px 4px}.ant-modal-footer button+button{margin-bottom:0;margin-left:8px}.ant-modal.zoom-appear,.ant-modal.zoom-enter{-webkit-transform:none;-ms-transform:none;transform:none;opacity:0;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:rgba(0,0,0,.45);filter:alpha(opacity=50)}.ant-modal-mask-hidden{display:none}.ant-modal-open{overflow:hidden}.ant-modal-centered{text-align:center}.ant-modal-centered:before{display:inline-block;width:0;height:100%;vertical-align:middle;content:\"\"}.ant-modal-centered .ant-modal{top:0;display:inline-block;text-align:left;vertical-align:middle}@media (max-width:767px){.ant-modal{max-width:calc(100vw - 16px);margin:8px auto}.ant-modal-centered .ant-modal{-ms-flex:1;flex:1 1}}.ant-modal-confirm .ant-modal-close,.ant-modal-confirm .ant-modal-header{display:none}.ant-modal-confirm .ant-modal-body{padding:32px 32px 24px}.ant-modal-confirm-body-wrapper{zoom:1}.ant-modal-confirm-body-wrapper:after,.ant-modal-confirm-body-wrapper:before{display:table;content:\"\"}.ant-modal-confirm-body-wrapper:after{clear:both}.ant-modal-confirm-body .ant-modal-confirm-title{display:block;overflow:hidden;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:1.4}.ant-modal-confirm-body .ant-modal-confirm-content{margin-top:8px;color:rgba(0,0,0,.65);font-size:14px}.ant-modal-confirm-body>.anticon{float:left;margin-right:16px;font-size:22px}.ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-left:38px}.ant-modal-confirm .ant-modal-confirm-btns{float:right;margin-top:24px}.ant-modal-confirm .ant-modal-confirm-btns button+button{margin-bottom:0;margin-left:8px}.ant-modal-confirm-error .ant-modal-confirm-body>.anticon{color:#f5222d}.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon,.ant-modal-confirm-warning .ant-modal-confirm-body>.anticon{color:#faad14}.ant-modal-confirm-info .ant-modal-confirm-body>.anticon{color:#1890ff}.ant-modal-confirm-success .ant-modal-confirm-body>.anticon{color:#52c41a}.ant-notification{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:fixed;z-index:1010;width:384px;max-width:calc(100vw - 32px);margin:0 24px 0 0}.ant-notification-bottomLeft,.ant-notification-topLeft{margin-right:0;margin-left:24px}.ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active{-webkit-animation-name:NotificationLeftFadeIn;animation-name:NotificationLeftFadeIn}.ant-notification-close-icon{font-size:14px;cursor:pointer}.ant-notification-notice{position:relative;margin-bottom:16px;padding:16px 24px;overflow:hidden;line-height:1.5;background:#fff;border-radius:4px;-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}.ant-notification-notice-message{display:inline-block;margin-bottom:8px;color:rgba(0,0,0,.85);font-size:16px;line-height:24px}.ant-notification-notice-message-single-line-auto-margin{display:block;width:calc(264px - 100%);max-width:4px;background-color:transparent;pointer-events:none}.ant-notification-notice-message-single-line-auto-margin:before{display:block;content:\"\"}.ant-notification-notice-description{font-size:14px}.ant-notification-notice-closable .ant-notification-notice-message{padding-right:24px}.ant-notification-notice-with-icon .ant-notification-notice-message{margin-bottom:4px;margin-left:48px;font-size:16px}.ant-notification-notice-with-icon .ant-notification-notice-description{margin-left:48px;font-size:14px}.ant-notification-notice-icon{position:absolute;margin-left:4px;font-size:24px;line-height:24px}.anticon.ant-notification-notice-icon-success{color:#52c41a}.anticon.ant-notification-notice-icon-info{color:#1890ff}.anticon.ant-notification-notice-icon-warning{color:#faad14}.anticon.ant-notification-notice-icon-error{color:#f5222d}.ant-notification-notice-close{position:absolute;top:16px;right:22px;color:rgba(0,0,0,.45);outline:none}.ant-notification-notice-close:hover{color:rgba(0,0,0,.67)}.ant-notification-notice-btn{float:right;margin-top:16px}.ant-notification .notification-fade-effect{-webkit-animation-duration:.24s;animation-duration:.24s;-webkit-animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-timing-function:cubic-bezier(.645,.045,.355,1);-webkit-animation-fill-mode:both;animation-fill-mode:both}.ant-notification-fade-appear,.ant-notification-fade-enter{opacity:0;-webkit-animation-play-state:paused;animation-play-state:paused}.ant-notification-fade-appear,.ant-notification-fade-enter,.ant-notification-fade-leave{-webkit-animation-duration:.24s;animation-duration:.24s;-webkit-animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-timing-function:cubic-bezier(.645,.045,.355,1);-webkit-animation-fill-mode:both;animation-fill-mode:both}.ant-notification-fade-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-play-state:paused;animation-play-state:paused}.ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-fade-enter.ant-notification-fade-enter-active{-webkit-animation-name:NotificationFadeIn;animation-name:NotificationFadeIn;-webkit-animation-play-state:running;animation-play-state:running}.ant-notification-fade-leave.ant-notification-fade-leave-active{-webkit-animation-name:NotificationFadeOut;animation-name:NotificationFadeOut;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes NotificationFadeIn{0%{left:384px;opacity:0}to{left:0;opacity:1}}@keyframes NotificationFadeIn{0%{left:384px;opacity:0}to{left:0;opacity:1}}@-webkit-keyframes NotificationLeftFadeIn{0%{right:384px;opacity:0}to{right:0;opacity:1}}@keyframes NotificationLeftFadeIn{0%{right:384px;opacity:0}to{right:0;opacity:1}}@-webkit-keyframes NotificationFadeOut{0%{max-height:150px;margin-bottom:16px;padding-top:16px 24px;padding-bottom:16px 24px;opacity:1}to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}}@keyframes NotificationFadeOut{0%{max-height:150px;margin-bottom:16px;padding-top:16px 24px;padding-bottom:16px 24px;opacity:1}to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}}.ant-page-header{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;padding:16px 24px;background-color:#fff}.ant-page-header-ghost{background-color:inherit}.ant-page-header.has-breadcrumb{padding-top:12px}.ant-page-header.has-footer{padding-bottom:0}.ant-page-header-back{float:left;margin:8px 16px 8px 0;font-size:16px;line-height:1}.ant-page-header-back-button{color:#1890ff;text-decoration:none;outline:none;-webkit-transition:color .3s;transition:color .3s;color:#000;cursor:pointer}.ant-page-header-back-button:focus,.ant-page-header-back-button:hover{color:#40a9ff}.ant-page-header-back-button:active{color:#096dd9}.ant-page-header .ant-divider-vertical{height:14px;margin:0 12px;vertical-align:middle}.ant-breadcrumb+.ant-page-header-heading{margin-top:8px}.ant-page-header-heading{width:100%;overflow:hidden}.ant-page-header-heading-title{display:block;float:left;margin-bottom:0;padding-right:12px;color:rgba(0,0,0,.85);font-weight:600;font-size:20px;line-height:32px}.ant-page-header-heading .ant-avatar{float:left;margin-right:12px}.ant-page-header-heading-sub-title{float:left;margin:5px 12px 5px 0;color:rgba(0,0,0,.45);font-size:14px;line-height:22px}.ant-page-header-heading-tags{float:left;margin:4px 0}.ant-page-header-heading-extra{float:right}.ant-page-header-heading-extra>*{margin-left:8px}.ant-page-header-heading-extra>:first-child{margin-left:0}.ant-page-header-content{padding-top:12px;overflow:hidden}.ant-page-header-footer{margin-top:16px}.ant-page-header-footer .ant-tabs-bar{margin-bottom:1px;border-bottom:0}.ant-page-header-footer .ant-tabs-bar .ant-tabs-nav .ant-tabs-tab{padding:8px;font-size:16px}@media (max-width:576px){.ant-page-header-heading-extra{display:block;float:unset;width:100%;padding-top:12px;overflow:hidden}}.ant-popover{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:absolute;top:0;left:0;z-index:1030;font-weight:400;white-space:normal;text-align:left;cursor:auto;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ant-popover:after{position:absolute;background:hsla(0,0%,100%,.01);content:\"\"}.ant-popover-hidden{display:none}.ant-popover-placement-top,.ant-popover-placement-topLeft,.ant-popover-placement-topRight{padding-bottom:10px}.ant-popover-placement-right,.ant-popover-placement-rightBottom,.ant-popover-placement-rightTop{padding-left:10px}.ant-popover-placement-bottom,.ant-popover-placement-bottomLeft,.ant-popover-placement-bottomRight{padding-top:10px}.ant-popover-placement-left,.ant-popover-placement-leftBottom,.ant-popover-placement-leftTop{padding-right:10px}.ant-popover-inner{background-color:#fff;background-clip:padding-box;border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15);-webkit-box-shadow:0 0 8px rgba(0,0,0,.15)\\9;box-shadow:0 0 8px rgba(0,0,0,.15)\\9}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.ant-popover-inner{-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}}.ant-popover-title{min-width:177px;min-height:32px;margin:0;padding:5px 16px 4px;color:rgba(0,0,0,.85);font-weight:500;border-bottom:1px solid #e8e8e8}.ant-popover-inner-content{padding:12px 16px;color:rgba(0,0,0,.65)}.ant-popover-message{position:relative;padding:4px 0 12px;color:rgba(0,0,0,.65);font-size:14px}.ant-popover-message>.anticon{position:absolute;top:8px;color:#faad14;font-size:14px}.ant-popover-message-title{padding-left:22px}.ant-popover-buttons{margin-bottom:4px;text-align:right}.ant-popover-buttons button{margin-left:8px}.ant-popover-arrow{position:absolute;display:block;width:8.48528137px;height:8.48528137px;background:transparent;border-style:solid;border-width:4.24264069px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-topLeft>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-topRight>.ant-popover-content>.ant-popover-arrow{bottom:6.2px;border-color:transparent #fff #fff transparent;-webkit-box-shadow:3px 3px 7px rgba(0,0,0,.07);box-shadow:3px 3px 7px rgba(0,0,0,.07)}.ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow{left:50%;-webkit-transform:translateX(-50%) rotate(45deg);-ms-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.ant-popover-placement-topLeft>.ant-popover-content>.ant-popover-arrow{left:16px}.ant-popover-placement-topRight>.ant-popover-content>.ant-popover-arrow{right:16px}.ant-popover-placement-right>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-rightBottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-rightTop>.ant-popover-content>.ant-popover-arrow{left:6px;border-color:transparent transparent #fff #fff;-webkit-box-shadow:-3px 3px 7px rgba(0,0,0,.07);box-shadow:-3px 3px 7px rgba(0,0,0,.07)}.ant-popover-placement-right>.ant-popover-content>.ant-popover-arrow{top:50%;-webkit-transform:translateY(-50%) rotate(45deg);-ms-transform:translateY(-50%) rotate(45deg);transform:translateY(-50%) rotate(45deg)}.ant-popover-placement-rightTop>.ant-popover-content>.ant-popover-arrow{top:12px}.ant-popover-placement-rightBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}.ant-popover-placement-bottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-bottomLeft>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-bottomRight>.ant-popover-content>.ant-popover-arrow{top:6px;border-color:#fff transparent transparent #fff;-webkit-box-shadow:-2px -2px 5px rgba(0,0,0,.06);box-shadow:-2px -2px 5px rgba(0,0,0,.06)}.ant-popover-placement-bottom>.ant-popover-content>.ant-popover-arrow{left:50%;-webkit-transform:translateX(-50%) rotate(45deg);-ms-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.ant-popover-placement-bottomLeft>.ant-popover-content>.ant-popover-arrow{left:16px}.ant-popover-placement-bottomRight>.ant-popover-content>.ant-popover-arrow{right:16px}.ant-popover-placement-left>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{right:6px;border-color:#fff #fff transparent transparent;-webkit-box-shadow:3px -3px 7px rgba(0,0,0,.07);box-shadow:3px -3px 7px rgba(0,0,0,.07)}.ant-popover-placement-left>.ant-popover-content>.ant-popover-arrow{top:50%;-webkit-transform:translateY(-50%) rotate(45deg);-ms-transform:translateY(-50%) rotate(45deg);transform:translateY(-50%) rotate(45deg)}.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{top:12px}.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}.ant-progress{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block}.ant-progress-line{position:relative;width:100%;font-size:14px}.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#f5f5f5;border-radius:100px}.ant-progress-circle-trail{stroke:#f5f5f5}.ant-progress-circle-path{-webkit-animation:ant-progress-appear .3s;animation:ant-progress-appear .3s}.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#1890ff}.ant-progress-bg,.ant-progress-success-bg{position:relative;background-color:#1890ff;border-radius:100px;-webkit-transition:all .4s cubic-bezier(.08,.82,.17,1) 0s;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#52c41a}.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:rgba(0,0,0,.45);font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}.ant-progress-text .anticon{font-size:14px}.ant-progress-status-active .ant-progress-bg:before{position:absolute;top:0;right:0;bottom:0;left:0;background:#fff;border-radius:10px;opacity:0;-webkit-animation:ant-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;animation:ant-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;content:\"\"}.ant-progress-status-exception .ant-progress-bg{background-color:#f5222d}.ant-progress-status-exception .ant-progress-text{color:#f5222d}.ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#f5222d}.ant-progress-status-success .ant-progress-bg{background-color:#52c41a}.ant-progress-status-success .ant-progress-text{color:#52c41a}.ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#52c41a}.ant-progress-circle .ant-progress-inner{position:relative;line-height:1;background-color:transparent}.ant-progress-circle .ant-progress-text{position:absolute;top:50%;left:50%;width:100%;margin:0;padding:0;color:rgba(0,0,0,.65);line-height:1;white-space:normal;text-align:center;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.ant-progress-circle .ant-progress-text .anticon{font-size:1.16666667em}.ant-progress-circle.ant-progress-status-exception .ant-progress-text{color:#f5222d}.ant-progress-circle.ant-progress-status-success .ant-progress-text{color:#52c41a}@-webkit-keyframes ant-progress-active{0%{width:0;opacity:.1}20%{width:0;opacity:.5}to{width:100%;opacity:0}}@keyframes ant-progress-active{0%{width:0;opacity:.1}20%{width:0;opacity:.5}to{width:100%;opacity:0}}.ant-rate{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;margin:0;padding:0;color:#fadb14;font-size:20px;line-height:unset;list-style:none;outline:none}.ant-rate-disabled .ant-rate-star{cursor:default}.ant-rate-disabled .ant-rate-star:hover{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.ant-rate-star{position:relative;display:inline-block;margin:0;padding:0;color:inherit;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-rate-star:not(:last-child){margin-right:8px}.ant-rate-star>div:focus{outline:0}.ant-rate-star>div:focus,.ant-rate-star>div:hover{-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.ant-rate-star-first,.ant-rate-star-second{color:#e8e8e8;-webkit-transition:all .3s;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-rate-star-first .anticon,.ant-rate-star-second .anticon{vertical-align:middle}.ant-rate-star-first{position:absolute;top:0;left:0;width:50%;height:100%;overflow:hidden;opacity:0}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-half .ant-rate-star-second{opacity:1}.ant-rate-star-full .ant-rate-star-second,.ant-rate-star-half .ant-rate-star-first{color:inherit}.ant-rate-text{display:inline-block;margin-left:8px;font-size:14px}.ant-result{padding:48px 32px}.ant-result-success .ant-result-icon>.anticon{color:#52c41a}.ant-result-error .ant-result-icon>.anticon{color:#f5222d}.ant-result-info .ant-result-icon>.anticon{color:#1890ff}.ant-result-warning .ant-result-icon>.anticon{color:#faad14}.ant-result-image{width:250px;height:295px;margin:auto}.ant-result-icon{margin-bottom:24px;text-align:center}.ant-result-icon>.anticon{font-size:72px}.ant-result-title{color:rgba(0,0,0,.85);font-size:24px;line-height:1.8;text-align:center}.ant-result-subtitle{color:rgba(0,0,0,.45);font-size:14px;line-height:1.6;text-align:center}.ant-result-extra{margin-top:32px;text-align:center}.ant-result-extra>*{margin-right:8px}.ant-result-extra>:last-child{margin-right:0}.ant-result-content{margin-top:24px;padding:24px 40px;background-color:#fafafa}.ant-skeleton{display:table;width:100%}.ant-skeleton-header{display:table-cell;padding-right:16px;vertical-align:top}.ant-skeleton-header .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:#f2f2f2;width:32px;height:32px;line-height:32px}.ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-content{display:table-cell;width:100%;vertical-align:top}.ant-skeleton-content .ant-skeleton-title{width:100%;height:16px;margin-top:16px;background:#f2f2f2}.ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:24px}.ant-skeleton-content .ant-skeleton-paragraph{padding:0}.ant-skeleton-content .ant-skeleton-paragraph>li{width:100%;height:16px;list-style:none;background:#f2f2f2}.ant-skeleton-content .ant-skeleton-paragraph>li:last-child:not(:first-child):not(:nth-child(2)){width:61%}.ant-skeleton-content .ant-skeleton-paragraph>li+li{margin-top:16px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title{margin-top:12px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:28px}.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title{background:-webkit-gradient(linear,left top,right top,color-stop(25%,#f2f2f2),color-stop(37%,#e6e6e6),color-stop(63%,#f2f2f2));background:linear-gradient(90deg,#f2f2f2 25%,#e6e6e6 37%,#f2f2f2 63%);background-size:400% 100%;-webkit-animation:ant-skeleton-loading 1.4s ease infinite;animation:ant-skeleton-loading 1.4s ease infinite}@-webkit-keyframes ant-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes ant-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.ant-slider{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;height:12px;margin:14px 6px 10px;padding:4px 0;cursor:pointer;-ms-touch-action:none;touch-action:none}.ant-slider-vertical{width:12px;height:100%;margin:6px 10px;padding:0 4px}.ant-slider-vertical .ant-slider-rail{width:4px;height:100%}.ant-slider-vertical .ant-slider-track{width:4px}.ant-slider-vertical .ant-slider-handle{margin-bottom:-7px;margin-left:-5px}.ant-slider-vertical .ant-slider-mark{top:0;left:12px;width:18px;height:100%}.ant-slider-vertical .ant-slider-mark-text{left:4px;white-space:nowrap}.ant-slider-vertical .ant-slider-step{width:4px;height:100%}.ant-slider-vertical .ant-slider-dot{top:auto;left:2px;margin-bottom:-4px}.ant-slider-tooltip .ant-tooltip-inner{min-width:unset}.ant-slider-with-marks{margin-bottom:28px}.ant-slider-rail{width:100%;background-color:#f5f5f5;border-radius:2px}.ant-slider-rail,.ant-slider-track{position:absolute;height:4px;-webkit-transition:background-color .3s;transition:background-color .3s}.ant-slider-track{background-color:#91d5ff;border-radius:4px}.ant-slider-handle{position:absolute;width:14px;height:14px;margin-top:-5px;background-color:#fff;border:2px solid #91d5ff;border-radius:50%;-webkit-box-shadow:0;box-shadow:0;cursor:pointer;-webkit-transition:border-color .3s,-webkit-box-shadow .6s,-webkit-transform .3s cubic-bezier(.18,.89,.32,1.28);transition:border-color .3s,-webkit-box-shadow .6s,-webkit-transform .3s cubic-bezier(.18,.89,.32,1.28);transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28);transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28),-webkit-box-shadow .6s,-webkit-transform .3s cubic-bezier(.18,.89,.32,1.28)}.ant-slider-handle:focus{border-color:#46a6ff;outline:none;-webkit-box-shadow:0 0 0 5px rgba(24,144,255,.2);box-shadow:0 0 0 5px rgba(24,144,255,.2)}.ant-slider-handle.ant-tooltip-open{border-color:#1890ff}.ant-slider:hover .ant-slider-rail{background-color:#e1e1e1}.ant-slider:hover .ant-slider-track{background-color:#69c0ff}.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open){border-color:#69c0ff}.ant-slider-mark{position:absolute;top:14px;left:0;width:100%;font-size:14px}.ant-slider-mark-text{position:absolute;display:inline-block;color:rgba(0,0,0,.45);text-align:center;word-break:keep-all;cursor:pointer}.ant-slider-mark-text-active{color:rgba(0,0,0,.65)}.ant-slider-step{position:absolute;width:100%;height:4px;background:transparent}.ant-slider-dot{position:absolute;top:-2px;width:8px;height:8px;background-color:#fff;border:2px solid #e8e8e8;border-radius:50%;cursor:pointer}.ant-slider-dot,.ant-slider-dot:first-child,.ant-slider-dot:last-child{margin-left:-4px}.ant-slider-dot-active{border-color:#8cc8ff}.ant-slider-disabled{cursor:not-allowed}.ant-slider-disabled .ant-slider-track{background-color:rgba(0,0,0,.25)!important}.ant-slider-disabled .ant-slider-dot,.ant-slider-disabled .ant-slider-handle{background-color:#fff;border-color:rgba(0,0,0,.25)!important;-webkit-box-shadow:none;box-shadow:none;cursor:not-allowed}.ant-slider-disabled .ant-slider-dot,.ant-slider-disabled .ant-slider-mark-text{cursor:not-allowed!important}.ant-statistic{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-statistic-title{margin-bottom:4px;color:rgba(0,0,0,.45);font-size:14px}.ant-statistic-content{color:rgba(0,0,0,.85);font-size:24px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"}.ant-statistic-content-value-decimal{font-size:16px}.ant-statistic-content-prefix,.ant-statistic-content-suffix{display:inline-block}.ant-statistic-content-prefix{margin-right:4px}.ant-statistic-content-suffix{margin-left:4px;font-size:16px}.ant-steps{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:-ms-flexbox;display:flex;width:100%;font-size:0}.ant-steps-item{position:relative;display:inline-block;-ms-flex:1;flex:1 1;overflow:hidden;vertical-align:top}.ant-steps-item-container{outline:none}.ant-steps-item:last-child{-ms-flex:none;flex:none}.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after,.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-tail{display:none}.ant-steps-item-content,.ant-steps-item-icon{display:inline-block;vertical-align:top}.ant-steps-item-icon{width:32px;height:32px;margin-right:8px;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"Helvetica Neue\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\";line-height:32px;text-align:center;border:1px solid rgba(0,0,0,.25);border-radius:32px;-webkit-transition:background-color .3s,border-color .3s;transition:background-color .3s,border-color .3s}.ant-steps-item-icon>.ant-steps-icon{position:relative;top:-1px;color:#1890ff;line-height:1}.ant-steps-item-tail{position:absolute;top:12px;left:0;width:100%;padding:0 10px}.ant-steps-item-tail:after{display:inline-block;width:100%;height:1px;background:#e8e8e8;border-radius:1px;-webkit-transition:background .3s;transition:background .3s;content:\"\"}.ant-steps-item-title{position:relative;display:inline-block;padding-right:16px;color:rgba(0,0,0,.65);font-size:16px;line-height:32px}.ant-steps-item-title:after{position:absolute;top:16px;left:100%;display:block;width:9999px;height:1px;background:#e8e8e8;content:\"\"}.ant-steps-item-subtitle{display:inline;margin-left:8px;font-weight:400}.ant-steps-item-description,.ant-steps-item-subtitle{color:rgba(0,0,0,.45);font-size:14px}.ant-steps-item-wait .ant-steps-item-icon{background-color:#fff;border-color:rgba(0,0,0,.25)}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon{color:rgba(0,0,0,.25)}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:rgba(0,0,0,.25)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.45)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.45)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item-process .ant-steps-item-icon{background-color:#fff;border-color:#1890ff}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#1890ff}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.85)}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.65)}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item-process .ant-steps-item-icon{background:#1890ff}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#fff}.ant-steps-item-process .ant-steps-item-title{font-weight:500}.ant-steps-item-finish .ant-steps-item-icon{background-color:#fff;border-color:#1890ff}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#1890ff}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.65)}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#1890ff}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.45)}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#1890ff}.ant-steps-item-error .ant-steps-item-icon{background-color:#fff;border-color:#f5222d}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon{color:#f5222d}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item.ant-steps-next-error .ant-steps-item-title:after{background:#f5222d}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]{cursor:pointer}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-icon .ant-steps-icon,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-title{-webkit-transition:color .3s;transition:color .3s}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-title{color:#1890ff}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon{border-color:#1890ff}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon .ant-steps-icon{color:#1890ff}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{margin-right:16px;white-space:nowrap}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child{margin-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail{display:none}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description{max-width:140px;white-space:normal}.ant-steps-item-custom .ant-steps-item-icon{height:auto;background:none;border:0}.ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{top:0;left:.5px;width:32px;height:32px;font-size:24px;line-height:32px}.ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#1890ff}.ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon{width:auto}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{margin-right:12px}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child{margin-right:0}.ant-steps-small .ant-steps-item-icon{width:24px;height:24px;font-size:12px;line-height:24px;text-align:center;border-radius:24px}.ant-steps-small .ant-steps-item-title{padding-right:12px;font-size:14px;line-height:24px}.ant-steps-small .ant-steps-item-title:after{top:12px}.ant-steps-small .ant-steps-item-description{color:rgba(0,0,0,.45);font-size:14px}.ant-steps-small .ant-steps-item-tail{top:8px}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon{width:inherit;height:inherit;line-height:inherit;background:none;border:0;border-radius:0}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{font-size:24px;line-height:24px;-webkit-transform:none;-ms-transform:none;transform:none}.ant-steps-vertical{display:block}.ant-steps-vertical .ant-steps-item{display:block;overflow:visible}.ant-steps-vertical .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-vertical .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-vertical .ant-steps-item-title{line-height:32px}.ant-steps-vertical .ant-steps-item-description{padding-bottom:12px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-vertical>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}@media (max-width:480px){.ant-steps-horizontal.ant-steps-label-horizontal{display:block}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item{display:block;overflow:visible}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-title{line-height:32px}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-description{padding-bottom:12px}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}}.ant-steps-label-vertical .ant-steps-item{overflow:visible}.ant-steps-label-vertical .ant-steps-item-tail{margin-left:58px;padding:3.5px 24px}.ant-steps-label-vertical .ant-steps-item-content{display:block;width:116px;margin-top:8px;text-align:center}.ant-steps-label-vertical .ant-steps-item-icon{display:inline-block;margin-left:42px}.ant-steps-label-vertical .ant-steps-item-title{padding-right:0}.ant-steps-label-vertical .ant-steps-item-title:after{display:none}.ant-steps-label-vertical .ant-steps-item-subtitle{display:block;margin-bottom:4px;margin-left:0;line-height:1.5}.ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon{margin-left:46px}.ant-steps-dot .ant-steps-item-title,.ant-steps-dot.ant-steps-small .ant-steps-item-title{line-height:1.5}.ant-steps-dot .ant-steps-item-tail,.ant-steps-dot.ant-steps-small .ant-steps-item-tail{top:2px;width:100%;margin:0 0 0 70px;padding:0}.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{width:calc(100% - 20px);height:3px;margin-left:12px}.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{left:2px}.ant-steps-dot .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-icon{width:8px;height:8px;margin-left:67px;padding-right:0;line-height:8px;background:transparent;border:0}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{position:relative;float:left;width:100%;height:100%;border-radius:100px;-webkit-transition:all .3s;transition:all .3s}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{position:absolute;top:-12px;left:-26px;width:60px;height:32px;background:rgba(0,0,0,.001);content:\"\"}.ant-steps-dot .ant-steps-item-content,.ant-steps-dot.ant-steps-small .ant-steps-item-content{width:140px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon{width:10px;height:10px;line-height:10px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon .ant-steps-icon-dot{top:-1px}.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-top:8px;margin-left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{top:2px;left:-9px;margin:0;padding:22px 0 4px}.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-icon-dot{left:-2px}.ant-steps-navigation{padding-top:12px}.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-left:-12px}.ant-steps-navigation .ant-steps-item{overflow:visible;text-align:center}.ant-steps-navigation .ant-steps-item-container{display:inline-block;height:100%;margin-left:-16px;padding-bottom:12px;text-align:left;-webkit-transition:opacity .3s;transition:opacity .3s}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-content{max-width:auto}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{max-width:100%;padding-right:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title:after{display:none}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]{cursor:pointer}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]:hover{opacity:.85}.ant-steps-navigation .ant-steps-item:last-child{-ms-flex:1;flex:1 1}.ant-steps-navigation .ant-steps-item:last-child:after{display:none}.ant-steps-navigation .ant-steps-item:after{position:absolute;top:50%;left:100%;display:inline-block;width:12px;height:12px;margin-top:-14px;margin-left:-2px;border:1px solid rgba(0,0,0,.25);border-bottom:none;border-left:none;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);content:\"\"}.ant-steps-navigation .ant-steps-item:before{position:absolute;bottom:0;left:50%;display:inline-block;width:0;height:3px;background-color:#1890ff;-webkit-transition:width .3s,left .3s;transition:width .3s,left .3s;-webkit-transition-timing-function:ease-out;transition-timing-function:ease-out;content:\"\"}.ant-steps-navigation .ant-steps-item.ant-steps-item-active:before{left:0;width:100%}@media (max-width:480px){.ant-steps-navigation>.ant-steps-item{margin-right:0!important}.ant-steps-navigation>.ant-steps-item:before{display:none}.ant-steps-navigation>.ant-steps-item.ant-steps-item-active:before{top:0;right:0;left:unset;display:block;width:3px;height:calc(100% - 24px)}.ant-steps-navigation>.ant-steps-item:after{position:relative;top:-2px;left:50%;display:block;width:8px;height:8px;margin-bottom:8px;text-align:center;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg)}.ant-steps-navigation>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}}.ant-steps-flex-not-supported.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item{margin-left:-16px;padding-left:16px;background:#fff}.ant-steps-flex-not-supported.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item{margin-left:-12px;padding-left:12px}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item:last-child{overflow:hidden}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item:last-child .ant-steps-icon-dot:after{right:-200px;width:200px}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:after,.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:before{position:absolute;top:0;left:-10px;width:10px;height:8px;background:#fff;content:\"\"}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:after{right:-10px;left:auto}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#ccc}.ant-switch{margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:44px;height:22px;line-height:20px;vertical-align:middle;background-color:rgba(0,0,0,.25);border:1px solid transparent;border-radius:100px;cursor:pointer;-webkit-transition:all .36s;transition:all .36s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-switch-inner{display:block;margin-right:6px;margin-left:24px;color:#fff;font-size:12px}.ant-switch-loading-icon,.ant-switch:after{position:absolute;top:1px;left:1px;width:18px;height:18px;background-color:#fff;border-radius:18px;cursor:pointer;-webkit-transition:all .36s cubic-bezier(.78,.14,.15,.86);transition:all .36s cubic-bezier(.78,.14,.15,.86);content:\" \"}.ant-switch:after{-webkit-box-shadow:0 2px 4px 0 rgba(0,35,11,.2);box-shadow:0 2px 4px 0 rgba(0,35,11,.2)}.ant-switch:not(.ant-switch-disabled):active:after,.ant-switch:not(.ant-switch-disabled):active:before{width:24px}.ant-switch-loading-icon{z-index:1;display:none;font-size:12px;background:transparent}.ant-switch-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-switch-loading .ant-switch-loading-icon{display:inline-block;color:rgba(0,0,0,.65)}.ant-switch-checked.ant-switch-loading .ant-switch-loading-icon{color:#1890ff}.ant-switch:focus{outline:0;-webkit-box-shadow:0 0 0 2px rgba(24,144,255,.2);box-shadow:0 0 0 2px rgba(24,144,255,.2)}.ant-switch:focus:hover{-webkit-box-shadow:none;box-shadow:none}.ant-switch-small{min-width:28px;height:16px;line-height:14px}.ant-switch-small .ant-switch-inner{margin-right:3px;margin-left:18px;font-size:12px}.ant-switch-small:after{width:12px;height:12px}.ant-switch-small:active:after,.ant-switch-small:active:before{width:16px}.ant-switch-small .ant-switch-loading-icon{width:12px;height:12px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin-right:18px;margin-left:3px}.ant-switch-small.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-13px}.ant-switch-small.ant-switch-loading .ant-switch-loading-icon{font-weight:700;-webkit-transform:scale(.66667);-ms-transform:scale(.66667);transform:scale(.66667)}.ant-switch-checked{background-color:#1890ff}.ant-switch-checked .ant-switch-inner{margin-right:24px;margin-left:6px}.ant-switch-checked:after{left:100%;margin-left:-1px;-webkit-transform:translateX(-100%);-ms-transform:translateX(-100%);transform:translateX(-100%)}.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-19px}.ant-switch-disabled,.ant-switch-loading{cursor:not-allowed;opacity:.4}.ant-switch-disabled *,.ant-switch-disabled:after,.ant-switch-disabled:before,.ant-switch-loading *,.ant-switch-loading:after,.ant-switch-loading:before{cursor:not-allowed}@-webkit-keyframes AntSwitchSmallLoadingCircle{0%{-webkit-transform:rotate(0deg) scale(.66667);transform:rotate(0deg) scale(.66667);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}to{-webkit-transform:rotate(1turn) scale(.66667);transform:rotate(1turn) scale(.66667);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}}@keyframes AntSwitchSmallLoadingCircle{0%{-webkit-transform:rotate(0deg) scale(.66667);transform:rotate(0deg) scale(.66667);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}to{-webkit-transform:rotate(1turn) scale(.66667);transform:rotate(1turn) scale(.66667);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}}.ant-table-wrapper{zoom:1}.ant-table-wrapper:after,.ant-table-wrapper:before{display:table;content:\"\"}.ant-table-wrapper:after{clear:both}.ant-table{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;clear:both}.ant-table-body{-webkit-transition:opacity .3s;transition:opacity .3s}.ant-table-empty .ant-table-body{overflow-x:auto!important;overflow-y:hidden!important}.ant-table table{width:100%;text-align:left;border-radius:4px 4px 0 0;border-collapse:separate;border-spacing:0}.ant-table-layout-fixed table{table-layout:fixed}.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:1px solid #e8e8e8;-webkit-transition:background .3s ease;transition:background .3s ease}.ant-table-thead>tr>th[colspan]:not([colspan=\"1\"]){text-align:center}.ant-table-thead>tr>th .ant-table-filter-icon,.ant-table-thead>tr>th .anticon-filter{position:absolute;top:0;right:0;width:28px;height:100%;color:#bfbfbf;font-size:12px;text-align:center;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-table-thead>tr>th .ant-table-filter-icon>svg,.ant-table-thead>tr>th .anticon-filter>svg{position:absolute;top:50%;left:50%;margin-top:-5px;margin-left:-6px}.ant-table-thead>tr>th .ant-table-filter-selected.anticon{color:#1890ff}.ant-table-thead>tr>th .ant-table-column-sorter{display:table-cell;vertical-align:middle}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner{height:1em;margin-top:.35em;margin-left:.57142857em;color:#bfbfbf;line-height:1em;text-align:center;-webkit-transition:all .3s;transition:all .3s}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up{display:inline-block;font-size:12px;font-size:11px\\9;-webkit-transform:scale(.91666667) rotate(0deg);-ms-transform:scale(.91666667) rotate(0deg);transform:scale(.91666667) rotate(0deg);display:block;height:1em;line-height:1em;-webkit-transition:all .3s;transition:all .3s}:root .ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down,:root .ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up{font-size:12px}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down.on,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up.on{color:#1890ff}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full{margin-top:-.15em}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-down,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-up{height:.5em;line-height:.5em}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-down{margin-top:.125em}.ant-table-thead>tr>th.ant-table-column-has-actions{position:relative;background-clip:padding-box;-webkit-background-clip:border-box}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters{padding-right:30px!important}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters .ant-table-filter-icon.ant-table-filter-open,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters .anticon-filter.ant-table-filter-open,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:hover,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:hover{color:rgba(0,0,0,.45);background:#e5e5e5}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:active,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:active{color:rgba(0,0,0,.65)}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters{cursor:pointer}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .ant-table-filter-icon,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .anticon-filter{background:#f2f2f2}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-down:not(.on),.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-up:not(.on){color:rgba(0,0,0,.45)}.ant-table-thead>tr>th .ant-table-header-column{display:inline-block;max-width:100%;vertical-align:top}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters{display:table}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters>.ant-table-column-title{display:table-cell;vertical-align:middle}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters>:not(.ant-table-column-sorter){position:relative}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters:before{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters:hover:before{background:rgba(0,0,0,.04)}.ant-table-thead>tr>th.ant-table-column-has-sorters{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-table-thead>tr:first-child>th:first-child{border-top-left-radius:4px}.ant-table-thead>tr:first-child>th:last-child{border-top-right-radius:4px}.ant-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:0}.ant-table-tbody>tr>td{border-bottom:1px solid #e8e8e8;-webkit-transition:all .3s,border 0s;transition:all .3s,border 0s}.ant-table-tbody>tr,.ant-table-thead>tr{-webkit-transition:all .3s,height 0s;transition:all .3s,height 0s}.ant-table-tbody>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td{background:#e6f7ff}.ant-table-tbody>tr.ant-table-row-selected>td.ant-table-column-sort,.ant-table-tbody>tr:hover.ant-table-row-selected>td,.ant-table-tbody>tr:hover.ant-table-row-selected>td.ant-table-column-sort,.ant-table-thead>tr.ant-table-row-selected>td.ant-table-column-sort,.ant-table-thead>tr:hover.ant-table-row-selected>td,.ant-table-thead>tr:hover.ant-table-row-selected>td.ant-table-column-sort{background:#fafafa}.ant-table-thead>tr:hover{background:none}.ant-table-footer{position:relative;padding:16px;color:rgba(0,0,0,.85);background:#fafafa;border-top:1px solid #e8e8e8;border-radius:0 0 4px 4px}.ant-table-footer:before{position:absolute;top:-1px;left:0;width:100%;height:1px;background:#fafafa;content:\"\"}.ant-table.ant-table-bordered .ant-table-footer{border:1px solid #e8e8e8}.ant-table-title{position:relative;top:1px;padding:16px 0;border-radius:4px 4px 0 0}.ant-table.ant-table-bordered .ant-table-title{padding-right:16px;padding-left:16px;border:1px solid #e8e8e8}.ant-table-title+.ant-table-content{position:relative;border-radius:4px 4px 0 0}.ant-table-bordered .ant-table-title+.ant-table-content,.ant-table-bordered .ant-table-title+.ant-table-content .ant-table-thead>tr:first-child>th,.ant-table-bordered .ant-table-title+.ant-table-content table,.ant-table-without-column-header .ant-table-title+.ant-table-content,.ant-table-without-column-header table{border-radius:0}.ant-table-without-column-header.ant-table-bordered.ant-table-empty .ant-table-placeholder{border-top:1px solid #e8e8e8;border-radius:4px}.ant-table-tbody>tr.ant-table-row-selected td{color:inherit;background:#fafafa}.ant-table-thead>tr>th.ant-table-column-sort{background:#f5f5f5}.ant-table-tbody>tr>td.ant-table-column-sort{background:rgba(0,0,0,.01)}.ant-table-tbody>tr>td,.ant-table-thead>tr>th{padding:16px;overflow-wrap:break-word}.ant-table-expand-icon-th,.ant-table-row-expand-icon-cell{width:50px;min-width:50px;text-align:center}.ant-table-header{overflow:hidden;background:#fafafa}.ant-table-header table{border-radius:4px 4px 0 0}.ant-table-loading{position:relative}.ant-table-loading .ant-table-body{background:#fff;opacity:.5}.ant-table-loading .ant-table-spin-holder{position:absolute;top:50%;left:50%;height:20px;margin-left:-30px;line-height:20px}.ant-table-loading .ant-table-with-pagination{margin-top:-20px}.ant-table-loading .ant-table-without-pagination{margin-top:10px}.ant-table-bordered .ant-table-body>table,.ant-table-bordered .ant-table-fixed-left table,.ant-table-bordered .ant-table-fixed-right table,.ant-table-bordered .ant-table-header>table{border:1px solid #e8e8e8;border-right:0;border-bottom:0}.ant-table-bordered.ant-table-empty .ant-table-placeholder{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8}.ant-table-bordered.ant-table-fixed-header .ant-table-header>table{border-bottom:0}.ant-table-bordered.ant-table-fixed-header .ant-table-body>table{border-top-left-radius:0;border-top-right-radius:0}.ant-table-bordered.ant-table-fixed-header .ant-table-body-inner>table,.ant-table-bordered.ant-table-fixed-header .ant-table-header+.ant-table-body>table{border-top:0}.ant-table-bordered .ant-table-thead>tr:not(:last-child)>th{border-bottom:1px solid #e8e8e8}.ant-table-bordered .ant-table-tbody>tr>td,.ant-table-bordered .ant-table-thead>tr>th{border-right:1px solid #e8e8e8}.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}.ant-table-pagination.ant-pagination{float:right;margin:16px 0}.ant-table-filter-dropdown{position:relative;min-width:96px;margin-left:-8px;background:#fff;border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-filter-dropdown .ant-dropdown-menu{max-height:calc(100vh - 130px);overflow-x:hidden;border:0;border-radius:4px 4px 0 0;-webkit-box-shadow:none;box-shadow:none}.ant-table-filter-dropdown .ant-dropdown-menu-item>label+span{padding-right:0}.ant-table-filter-dropdown .ant-dropdown-menu-sub{border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-filter-dropdown .ant-dropdown-menu .ant-dropdown-submenu-contain-selected .ant-dropdown-menu-submenu-title:after{color:#1890ff;font-weight:700;text-shadow:0 0 2px #bae7ff}.ant-table-filter-dropdown .ant-dropdown-menu-item{overflow:hidden}.ant-table-filter-dropdown>.ant-dropdown-menu>.ant-dropdown-menu-item:last-child,.ant-table-filter-dropdown>.ant-dropdown-menu>.ant-dropdown-menu-submenu:last-child .ant-dropdown-menu-submenu-title{border-radius:0}.ant-table-filter-dropdown-btns{padding:7px 8px;overflow:hidden;border-top:1px solid #e8e8e8}.ant-table-filter-dropdown-link{color:#1890ff}.ant-table-filter-dropdown-link:hover{color:#40a9ff}.ant-table-filter-dropdown-link:active{color:#096dd9}.ant-table-filter-dropdown-link.confirm{float:left}.ant-table-filter-dropdown-link.clear{float:right}.ant-table-selection{white-space:nowrap}.ant-table-selection-select-all-custom{margin-right:4px!important}.ant-table-selection .anticon-down{color:#bfbfbf;-webkit-transition:all .3s;transition:all .3s}.ant-table-selection-menu{min-width:96px;margin-top:5px;margin-left:-30px;background:#fff;border-radius:4px;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-selection-menu .ant-action-down{color:#bfbfbf}.ant-table-selection-down{display:inline-block;padding:0;line-height:1;cursor:pointer}.ant-table-selection-down:hover .anticon-down{color:rgba(0,0,0,.6)}.ant-table-row-expand-icon{color:#1890ff;text-decoration:none;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;display:inline-block;width:17px;height:17px;color:inherit;line-height:13px;text-align:center;background:#fff;border:1px solid #e8e8e8;border-radius:2px;outline:none;-webkit-transition:all .3s;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#40a9ff}.ant-table-row-expand-icon:active{color:#096dd9}.ant-table-row-expand-icon:active,.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{border-color:currentColor}.ant-table-row-expanded:after{content:\"-\"}.ant-table-row-collapsed:after{content:\"+\"}.ant-table-row-spaced{visibility:hidden}.ant-table-row-spaced:after{content:\".\"}.ant-table-row-cell-ellipsis,.ant-table-row-cell-ellipsis .ant-table-column-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-table-row-cell-ellipsis .ant-table-column-title{display:block}.ant-table-row-cell-break-word{word-wrap:break-word;word-break:break-word}tr.ant-table-expanded-row,tr.ant-table-expanded-row:hover{background:#fbfbfb}tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-16px -16px -17px}.ant-table .ant-table-row-indent+.ant-table-row-expand-icon{margin-right:8px}.ant-table-scroll{overflow:auto;overflow-x:hidden}.ant-table-scroll table{min-width:100%}.ant-table-scroll table .ant-table-fixed-columns-in-body:not([colspan]){color:transparent}.ant-table-scroll table .ant-table-fixed-columns-in-body:not([colspan])>*{visibility:hidden}.ant-table-body-inner{height:100%}.ant-table-fixed-header>.ant-table-content>.ant-table-scroll>.ant-table-body{position:relative;background:#fff}.ant-table-fixed-header .ant-table-body-inner{overflow:scroll}.ant-table-fixed-header .ant-table-scroll .ant-table-header{margin-bottom:-20px;padding-bottom:20px;overflow:scroll;opacity:.9999}.ant-table-fixed-header .ant-table-scroll .ant-table-header::-webkit-scrollbar{border:solid #e8e8e8;border-width:0 0 1px}.ant-table-hide-scrollbar{scrollbar-color:transparent transparent;min-width:unset}.ant-table-hide-scrollbar::-webkit-scrollbar{min-width:inherit;background-color:transparent}.ant-table-bordered.ant-table-fixed-header .ant-table-scroll .ant-table-header::-webkit-scrollbar{border:1px solid #e8e8e8;border-left-width:0}.ant-table-bordered.ant-table-fixed-header .ant-table-scroll .ant-table-header.ant-table-hide-scrollbar .ant-table-thead>tr:only-child>th:last-child{border-right-color:transparent}.ant-table-fixed-left,.ant-table-fixed-right{position:absolute;top:0;z-index:1;overflow:hidden;border-radius:0;-webkit-transition:-webkit-box-shadow .3s ease;transition:-webkit-box-shadow .3s ease;transition:box-shadow .3s ease;transition:box-shadow .3s ease,-webkit-box-shadow .3s ease}.ant-table-fixed-left table,.ant-table-fixed-right table{width:auto;background:#fff}.ant-table-fixed-header .ant-table-fixed-left .ant-table-body-outer .ant-table-fixed,.ant-table-fixed-header .ant-table-fixed-right .ant-table-body-outer .ant-table-fixed{border-radius:0}.ant-table-fixed-left{left:0;-webkit-box-shadow:6px 0 6px -4px rgba(0,0,0,.15);box-shadow:6px 0 6px -4px rgba(0,0,0,.15)}.ant-table-fixed-left .ant-table-header{overflow-y:hidden}.ant-table-fixed-left .ant-table-body-inner{margin-right:-20px;padding-right:20px}.ant-table-fixed-header .ant-table-fixed-left .ant-table-body-inner{padding-right:0}.ant-table-fixed-left,.ant-table-fixed-left table{border-radius:4px 0 0 0}.ant-table-fixed-left .ant-table-thead>tr>th:last-child{border-top-right-radius:0}.ant-table-fixed-right{right:0;-webkit-box-shadow:-6px 0 6px -4px rgba(0,0,0,.15);box-shadow:-6px 0 6px -4px rgba(0,0,0,.15)}.ant-table-fixed-right,.ant-table-fixed-right table{border-radius:0 4px 0 0}.ant-table-fixed-right .ant-table-expanded-row{color:transparent;pointer-events:none}.ant-table-fixed-right .ant-table-thead>tr>th:first-child{border-top-left-radius:0}.ant-table.ant-table-scroll-position-left .ant-table-fixed-left,.ant-table.ant-table-scroll-position-right .ant-table-fixed-right{-webkit-box-shadow:none;box-shadow:none}.ant-table colgroup>col.ant-table-selection-col{width:60px}.ant-table-thead>tr>th.ant-table-selection-column-custom .ant-table-selection{margin-right:-15px}.ant-table-tbody>tr>td.ant-table-selection-column,.ant-table-thead>tr>th.ant-table-selection-column{text-align:center}.ant-table-tbody>tr>td.ant-table-selection-column .ant-radio-wrapper,.ant-table-thead>tr>th.ant-table-selection-column .ant-radio-wrapper{margin-right:0}.ant-table-row[class*=ant-table-row-level-0] .ant-table-selection-column>span{display:inline-block}.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span,.ant-table-filter-dropdown .ant-checkbox-wrapper+span{padding-left:8px}@supports (-moz-appearance:meterbar){.ant-table-thead>tr>th.ant-table-column-has-actions{background-clip:padding-box}}.ant-table-middle>.ant-table-content>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-footer,.ant-table-middle>.ant-table-content>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-title{padding:12px 8px}.ant-table-middle tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-12px -8px -13px}.ant-table-small{border:1px solid #e8e8e8;border-radius:4px}.ant-table-small>.ant-table-content>.ant-table-footer,.ant-table-small>.ant-table-title{padding:8px}.ant-table-small>.ant-table-title{top:0;border-bottom:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-footer{background-color:transparent;border-top:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-footer:before{background-color:transparent}.ant-table-small>.ant-table-content>.ant-table-body{margin:0 8px}.ant-table-small>.ant-table-content>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table{border:0}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th{padding:8px}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th{background-color:transparent}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr{border-bottom:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort{background-color:rgba(0,0,0,.01)}.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table{padding:0}.ant-table-small>.ant-table-content .ant-table-header{background-color:transparent;border-radius:4px 4px 0 0}.ant-table-small>.ant-table-content .ant-table-placeholder,.ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:0}.ant-table-small.ant-table-bordered{border-right:0}.ant-table-small.ant-table-bordered .ant-table-title{border:0;border-right:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-content{border-right:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-footer{border:0;border-top:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-footer:before{display:none}.ant-table-small.ant-table-bordered .ant-table-placeholder{border-right:0;border-bottom:0;border-left:0}.ant-table-small.ant-table-bordered .ant-table-tbody>tr>td:last-child,.ant-table-small.ant-table-bordered .ant-table-thead>tr>th.ant-table-row-cell-last{border-right:none}.ant-table-small.ant-table-bordered .ant-table-fixed-left .ant-table-tbody>tr>td:last-child,.ant-table-small.ant-table-bordered .ant-table-fixed-left .ant-table-thead>tr>th:last-child{border-right:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-fixed-right{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8}.ant-table-small tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-8px -8px -9px}.ant-table-small.ant-table-fixed-header>.ant-table-content>.ant-table-scroll>.ant-table-body{border-radius:0 0 4px 4px}.ant-timeline{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";margin:0;padding:0;list-style:none}.ant-timeline-item{position:relative;margin:0;padding:0 0 20px;font-size:14px;list-style:none}.ant-timeline-item-tail{position:absolute;top:10px;left:4px;height:calc(100% - 10px);border-left:2px solid #e8e8e8}.ant-timeline-item-pending .ant-timeline-item-head{font-size:12px;background-color:transparent}.ant-timeline-item-pending .ant-timeline-item-tail{display:none}.ant-timeline-item-head{position:absolute;width:10px;height:10px;background-color:#fff;border:2px solid transparent;border-radius:100px}.ant-timeline-item-head-blue{color:#1890ff;border-color:#1890ff}.ant-timeline-item-head-red{color:#f5222d;border-color:#f5222d}.ant-timeline-item-head-green{color:#52c41a;border-color:#52c41a}.ant-timeline-item-head-gray{color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-timeline-item-head-custom{position:absolute;top:5.5px;left:5px;width:auto;height:auto;margin-top:0;padding:3px 1px;line-height:1;text-align:center;border:0;border-radius:0;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.ant-timeline-item-content{position:relative;top:-6px;margin:0 0 0 18px;word-break:break-word}.ant-timeline-item-last>.ant-timeline-item-tail{display:none}.ant-timeline-item-last>.ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-tail{left:50%}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head{margin-left:-4px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom{margin-left:1px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content{left:calc(50% - 4px);width:calc(50% - 14px);text-align:left}.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(50% - 12px);margin:0;text-align:right}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail{left:calc(100% - 6px)}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(100% - 18px)}.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail{display:block;height:calc(100% - 14px);border-left:2px dotted #e8e8e8}.ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail{display:none}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{top:15px;display:block;height:calc(100% - 15px);border-left:2px dotted #e8e8e8}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content{min-height:48px}.ant-transfer-customize-list{display:-ms-flexbox;display:flex}.ant-transfer-customize-list .ant-transfer-operation{-ms-flex:none;flex:none;-ms-flex-item-align:center;align-self:center}.ant-transfer-customize-list .ant-transfer-list{-ms-flex:auto;flex:auto;width:auto;height:auto;min-height:200px}.ant-transfer-customize-list .ant-transfer-list-body-with-search{padding-top:0}.ant-transfer-customize-list .ant-transfer-list-body-search-wrapper{position:relative;padding-bottom:0}.ant-transfer-customize-list .ant-transfer-list-body-customize-wrapper{padding:12px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small{border:0;border-radius:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th{background:#fafafa}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:1px solid #e8e8e8}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body{margin:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:16px 0 4px}.ant-transfer{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative}.ant-transfer-disabled .ant-transfer-list{background:#f5f5f5}.ant-transfer-list{position:relative;display:inline-block;width:180px;height:200px;padding-top:40px;vertical-align:middle;border:1px solid #d9d9d9;border-radius:4px}.ant-transfer-list-with-footer{padding-bottom:34px}.ant-transfer-list-search{padding:0 24px 0 8px}.ant-transfer-list-search-action{position:absolute;top:12px;right:12px;bottom:12px;width:28px;color:rgba(0,0,0,.25);line-height:32px;text-align:center}.ant-transfer-list-search-action .anticon{color:rgba(0,0,0,.25);-webkit-transition:all .3s;transition:all .3s}.ant-transfer-list-search-action .anticon:hover{color:rgba(0,0,0,.45)}span.ant-transfer-list-search-action{pointer-events:none}.ant-transfer-list-header{position:absolute;top:0;left:0;width:100%;padding:8px 12px 9px;overflow:hidden;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:4px 4px 0 0}.ant-transfer-list-header-title{position:absolute;right:12px}.ant-transfer-list-header .ant-checkbox-wrapper+span{padding-left:8px}.ant-transfer-list-body{position:relative;height:100%;font-size:14px}.ant-transfer-list-body-search-wrapper{position:absolute;top:0;left:0;width:100%;padding:12px}.ant-transfer-list-body-with-search{padding-top:56px}.ant-transfer-list-content{height:100%;margin:0;padding:0;overflow:auto;list-style:none}.ant-transfer-list-content>.LazyLoad{-webkit-animation:transferHighlightIn 1s;animation:transferHighlightIn 1s}.ant-transfer-list-content-item{min-height:32px;padding:6px 12px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-transition:all .3s;transition:all .3s}.ant-transfer-list-content-item>span{padding-right:0}.ant-transfer-list-content-item-text{padding-left:8px}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#e6f7ff;cursor:pointer}.ant-transfer-list-content-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-transfer-list-body-not-found{position:absolute;top:50%;width:100%;padding-top:0;color:rgba(0,0,0,.25);text-align:center;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ant-transfer-list-body-with-search .ant-transfer-list-body-not-found{margin-top:16px}.ant-transfer-list-footer{position:absolute;bottom:0;left:0;width:100%;border-top:1px solid #e8e8e8;border-radius:0 0 4px 4px}.ant-transfer-operation{display:inline-block;margin:0 8px;overflow:hidden;vertical-align:middle}.ant-transfer-operation .ant-btn{display:block}.ant-transfer-operation .ant-btn:first-child{margin-bottom:4px}.ant-transfer-operation .ant-btn .anticon{font-size:12px}@-webkit-keyframes transferHighlightIn{0%{background:#bae7ff}to{background:transparent}}@keyframes transferHighlightIn{0%{background:#bae7ff}to{background:transparent}}.ant-select-tree-checkbox{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-select-tree-checkbox-input:focus+.ant-select-tree-checkbox-inner,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner{border-color:#1890ff}.ant-select-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:\"\"}.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox:after,.ant-select-tree-checkbox:hover:after{visibility:visible}.ant-select-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;-webkit-transition:all .3s;transition:all .3s}.ant-select-tree-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(0) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(0) translate(-50%,-50%);transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;-webkit-transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:\" \"}.ant-select-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(1) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(1) translate(-50%,-50%);transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;-webkit-transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:\" \"}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-select-tree-checkbox-disabled{cursor:not-allowed}.ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input{cursor:not-allowed}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;-webkit-animation-name:none;animation-name:none}.ant-select-tree-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-select-tree-checkbox-disabled:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled:after{visibility:hidden}.ant-select-tree-checkbox-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;line-height:unset;cursor:pointer}.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-select-tree-checkbox-wrapper+.ant-select-tree-checkbox-wrapper{margin-left:8px}.ant-select-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-select-tree-checkbox-group{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block}.ant-select-tree-checkbox-group-item{display:inline-block;margin-right:8px}.ant-select-tree-checkbox-group-item:last-child{margin-right:0}.ant-select-tree-checkbox-group-item+.ant-select-tree-checkbox-group-item{margin-left:0}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;-webkit-transform:translate(-50%,-50%) scale(1);-ms-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1);opacity:1;content:\" \"}.ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-select-tree{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";margin:-4px 0 0;padding:0 4px}.ant-select-tree li{margin:8px 0;padding:0;white-space:nowrap;list-style:none;outline:0}.ant-select-tree li.filter-node>span{font-weight:500}.ant-select-tree li ul{margin:0;padding:0 0 0 18px}.ant-select-tree li .ant-select-tree-node-content-wrapper{display:inline-block;width:calc(100% - 24px);margin:0;padding:3px 5px;color:rgba(0,0,0,.65);text-decoration:none;border-radius:2px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-select-tree li .ant-select-tree-node-content-wrapper:hover{background-color:#e6f7ff}.ant-select-tree li .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected{background-color:#bae7ff}.ant-select-tree li span.ant-select-tree-checkbox{margin:0 4px 0 0}.ant-select-tree li span.ant-select-tree-checkbox+.ant-select-tree-node-content-wrapper{width:calc(100% - 46px)}.ant-select-tree li span.ant-select-tree-iconEle,.ant-select-tree li span.ant-select-tree-switcher{display:inline-block;width:24px;height:24px;margin:0;line-height:22px;text-align:center;vertical-align:middle;border:0;outline:none;cursor:pointer}.ant-select-tree li span.ant-select-icon_loading .ant-select-switcher-loading-icon{position:absolute;left:0;display:inline-block;color:#1890ff;font-size:14px;-webkit-transform:none;-ms-transform:none;transform:none}.ant-select-tree li span.ant-select-icon_loading .ant-select-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-select-tree li span.ant-select-tree-switcher{position:relative}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher-noop{cursor:auto}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon{font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon,:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon{font-size:12px}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon{font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon,:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon{font-size:12px}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon svg{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-loading-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-loading-icon{position:absolute;left:0;display:inline-block;width:24px;height:24px;color:#1890ff;font-size:14px;-webkit-transform:none;-ms-transform:none;transform:none}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-loading-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-select-tree-child-tree,.ant-select-tree .ant-select-tree-treenode-loading .ant-select-tree-iconEle{display:none}.ant-select-tree-child-tree-open{display:block}li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper,li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper span,li.ant-select-tree-treenode-disabled>span:not(.ant-select-tree-switcher){color:rgba(0,0,0,.25);cursor:not-allowed}li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper:hover{background:transparent}.ant-select-tree-icon__close,.ant-select-tree-icon__open{margin-right:2px;vertical-align:top}.ant-select-tree-dropdown{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\"}.ant-select-tree-dropdown .ant-select-dropdown-search{position:sticky;top:0;z-index:1;display:block;padding:4px;background:#fff}.ant-select-tree-dropdown .ant-select-dropdown-search .ant-select-search__field__wrap{width:100%}.ant-select-tree-dropdown .ant-select-dropdown-search .ant-select-search__field{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;padding:4px 7px;border:1px solid #d9d9d9;border-radius:4px;outline:none}.ant-select-tree-dropdown .ant-select-dropdown-search.ant-select-search--hide{display:none}.ant-select-tree-dropdown .ant-select-not-found{display:block;padding:7px 16px;color:rgba(0,0,0,.25);cursor:not-allowed}@-webkit-keyframes antCheckboxEffect{0%{-webkit-transform:scale(1);transform:scale(1);opacity:.5}to{-webkit-transform:scale(1.6);transform:scale(1.6);opacity:0}}@keyframes antCheckboxEffect{0%{-webkit-transform:scale(1);transform:scale(1);opacity:.5}to{-webkit-transform:scale(1.6);transform:scale(1.6);opacity:0}}.ant-tree.ant-tree-directory{position:relative}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-switcher,.ant-tree.ant-tree-directory>li span.ant-tree-switcher{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-switcher.ant-tree-switcher-noop,.ant-tree.ant-tree-directory>li span.ant-tree-switcher.ant-tree-switcher-noop{pointer-events:none}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-checkbox,.ant-tree.ant-tree-directory>li span.ant-tree-checkbox{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper{border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:hover,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:hover:before,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:hover:before{background:#e6f7ff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper.ant-tree-node-selected,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper.ant-tree-node-selected{color:#fff;background:transparent}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:before,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:before{position:absolute;right:0;left:0;height:24px;-webkit-transition:all .3s;transition:all .3s;content:\"\"}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper>span,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper>span{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-switcher,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-switcher{color:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox .ant-tree-checkbox-inner,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox .ant-tree-checkbox-inner{border-color:#1890ff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked:after,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked:after{border-color:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:#1890ff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-node-content-wrapper:before,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-node-content-wrapper:before{background:#1890ff}.ant-tree-checkbox{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-tree-checkbox-input:focus+.ant-tree-checkbox-inner,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner,.ant-tree-checkbox:hover .ant-tree-checkbox-inner{border-color:#1890ff}.ant-tree-checkbox-checked:after{top:0;height:100%;border:1px solid #1890ff;border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:\"\"}.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox:after,.ant-tree-checkbox:hover:after{visibility:visible}.ant-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;-webkit-transition:all .3s;transition:all .3s}.ant-tree-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(0) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(0) translate(-50%,-50%);transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;-webkit-transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:\" \"}.ant-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;-webkit-transform:rotate(45deg) scale(1) translate(-50%,-50%);-ms-transform:rotate(45deg) scale(1) translate(-50%,-50%);transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;-webkit-transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:\" \"}.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background-color:#1890ff;border-color:#1890ff}.ant-tree-checkbox-disabled{cursor:not-allowed}.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-input{cursor:not-allowed}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;-webkit-animation-name:none;animation-name:none}.ant-tree-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-tree-checkbox-disabled:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled:after{visibility:hidden}.ant-tree-checkbox-wrapper{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block;line-height:unset;cursor:pointer}.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-tree-checkbox-wrapper+.ant-tree-checkbox-wrapper{margin-left:8px}.ant-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-tree-checkbox-group{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";display:inline-block}.ant-tree-checkbox-group-item{display:inline-block;margin-right:8px}.ant-tree-checkbox-group-item:last-child{margin-right:0}.ant-tree-checkbox-group-item+.ant-tree-checkbox-group-item{margin-left:0}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;-webkit-transform:translate(-50%,-50%) scale(1);-ms-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1);opacity:1;content:\" \"}.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-tree{-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";margin:0;padding:0}.ant-tree-checkbox-checked:after{position:absolute;top:16.67%;left:0;width:100%;height:66.67%}.ant-tree ol,.ant-tree ul{margin:0;padding:0;list-style:none}.ant-tree li{margin:0;padding:4px 0;white-space:nowrap;list-style:none;outline:0}.ant-tree li span[draggable=true],.ant-tree li span[draggable]{line-height:20px;border-top:2px solid transparent;border-bottom:2px solid transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element}.ant-tree li.drag-over>span[draggable]{color:#fff;background-color:#1890ff;opacity:.8}.ant-tree li.drag-over-gap-top>span[draggable]{border-top-color:#1890ff}.ant-tree li.drag-over-gap-bottom>span[draggable]{border-bottom-color:#1890ff}.ant-tree li.filter-node>span{color:#f5222d!important;font-weight:500!important}.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-loading-icon,.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-loading-icon{position:absolute;left:0;display:inline-block;width:24px;height:24px;color:#1890ff;font-size:14px;-webkit-transform:none;-ms-transform:none;transform:none}.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-loading-icon svg,.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}:root .ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close:after,:root .ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open:after{opacity:0}.ant-tree li ul{margin:0;padding:0 0 0 18px}.ant-tree li .ant-tree-node-content-wrapper{display:inline-block;height:24px;margin:0;padding:0 5px;color:rgba(0,0,0,.65);line-height:24px;text-decoration:none;vertical-align:top;border-radius:2px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-tree li .ant-tree-node-content-wrapper:hover{background-color:#e6f7ff}.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected{background-color:#bae7ff}.ant-tree li span.ant-tree-checkbox{top:auto;height:24px;margin:0 4px 0 2px;padding:4px 0}.ant-tree li span.ant-tree-iconEle,.ant-tree li span.ant-tree-switcher{display:inline-block;width:24px;height:24px;margin:0;line-height:24px;text-align:center;vertical-align:top;border:0;outline:none;cursor:pointer}.ant-tree li span.ant-tree-iconEle:empty{display:none}.ant-tree li span.ant-tree-switcher{position:relative}.ant-tree li span.ant-tree-switcher.ant-tree-switcher-noop{cursor:default}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{font-size:12px}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon svg,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{font-size:12px}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon svg,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.ant-tree li:last-child>span.ant-tree-iconEle:before,.ant-tree li:last-child>span.ant-tree-switcher:before{display:none}.ant-tree>li:first-child{padding-top:7px}.ant-tree>li:last-child{padding-bottom:7px}.ant-tree-child-tree>li:first-child{padding-top:8px}.ant-tree-child-tree>li:last-child{padding-bottom:0}li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper,li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper span,li.ant-tree-treenode-disabled>span:not(.ant-tree-switcher){color:rgba(0,0,0,.25);cursor:not-allowed}li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree-icon__close,.ant-tree-icon__open{margin-right:2px;vertical-align:top}.ant-tree.ant-tree-show-line li{position:relative}.ant-tree.ant-tree-show-line li span.ant-tree-switcher{color:rgba(0,0,0,.45);background:#fff}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.ant-tree.ant-tree-show-line li:not(:last-child):before{position:absolute;left:12px;width:1px;height:100%;height:calc(100% - 22px);margin:22px 0 0;border-left:1px solid #d9d9d9;content:\" \"}.ant-tree.ant-tree-icon-hide .ant-tree-treenode-loading .ant-tree-iconEle{display:none}.ant-tree.ant-tree-block-node li .ant-tree-node-content-wrapper{width:calc(100% - 24px)}.ant-tree.ant-tree-block-node li span.ant-tree-checkbox+.ant-tree-node-content-wrapper{width:calc(100% - 46px)}.ant-typography{color:rgba(0,0,0,.65)}.ant-typography.ant-typography-secondary{color:rgba(0,0,0,.45)}.ant-typography.ant-typography-warning{color:#faad14}.ant-typography.ant-typography-danger{color:#f5222d}.ant-typography.ant-typography-disabled{color:rgba(0,0,0,.25);cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ant-typography p,div.ant-typography{margin-bottom:1em}.ant-typography h1,h1.ant-typography{margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:600;font-size:38px;line-height:1.23}.ant-typography h2,h2.ant-typography{margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:600;font-size:30px;line-height:1.35}.ant-typography h3,h3.ant-typography{margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:600;font-size:24px;line-height:1.35}.ant-typography h4,h4.ant-typography{margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:600;font-size:20px;line-height:1.4}.ant-typography+h1.ant-typography,.ant-typography+h2.ant-typography,.ant-typography+h3.ant-typography,.ant-typography+h4.ant-typography,.ant-typography div+h1,.ant-typography div+h2,.ant-typography div+h3,.ant-typography div+h4,.ant-typography h1+h1,.ant-typography h1+h2,.ant-typography h1+h3,.ant-typography h1+h4,.ant-typography h2+h1,.ant-typography h2+h2,.ant-typography h2+h3,.ant-typography h2+h4,.ant-typography h3+h1,.ant-typography h3+h2,.ant-typography h3+h3,.ant-typography h3+h4,.ant-typography h4+h1,.ant-typography h4+h2,.ant-typography h4+h3,.ant-typography h4+h4,.ant-typography li+h1,.ant-typography li+h2,.ant-typography li+h3,.ant-typography li+h4,.ant-typography p+h1,.ant-typography p+h2,.ant-typography p+h3,.ant-typography p+h4,.ant-typography ul+h1,.ant-typography ul+h2,.ant-typography ul+h3,.ant-typography ul+h4{margin-top:1.2em}span.ant-typography-ellipsis{display:inline-block}.ant-typography a{color:#1890ff;text-decoration:none;outline:none;cursor:pointer;-webkit-transition:color .3s;transition:color .3s}.ant-typography a:focus,.ant-typography a:hover{color:#40a9ff}.ant-typography a:active{color:#096dd9}.ant-typography a:active,.ant-typography a:hover{text-decoration:none}.ant-typography a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}.ant-typography code{margin:0 .2em;padding:.2em .4em .1em;font-size:85%;background:rgba(0,0,0,.06);border:1px solid rgba(0,0,0,.06);border-radius:3px}.ant-typography mark{padding:0;background-color:#ffe58f}.ant-typography ins,.ant-typography u{text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.ant-typography del,.ant-typography s{text-decoration:line-through}.ant-typography strong{font-weight:600}.ant-typography-copy,.ant-typography-edit,.ant-typography-expand{color:#1890ff;text-decoration:none;outline:none;cursor:pointer;-webkit-transition:color .3s;transition:color .3s;margin-left:8px}.ant-typography-copy:focus,.ant-typography-copy:hover,.ant-typography-edit:focus,.ant-typography-edit:hover,.ant-typography-expand:focus,.ant-typography-expand:hover{color:#40a9ff}.ant-typography-copy:active,.ant-typography-edit:active,.ant-typography-expand:active{color:#096dd9}.ant-typography-copy-success,.ant-typography-copy-success:focus,.ant-typography-copy-success:hover{color:#52c41a}.ant-typography-edit-content{position:relative}div.ant-typography-edit-content{left:-12px;margin-top:-5px;margin-bottom:calc(1em - 6px)}.ant-typography-edit-content-confirm{position:absolute;right:10px;bottom:8px;color:rgba(0,0,0,.45);pointer-events:none}.ant-typography-edit-content textarea{-moz-transition:none}.ant-typography ol,.ant-typography ul{margin:0 0 1em;padding:0}.ant-typography ol li,.ant-typography ul li{margin:0 0 0 20px;padding:0 0 0 4px}.ant-typography ul li{list-style-type:circle}.ant-typography ul li li{list-style-type:disc}.ant-typography ol li{list-style-type:decimal}.ant-typography-ellipsis-single-line{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-typography-ellipsis-multiple-line{display:-webkit-box;-webkit-line-clamp:3;\n  /*! autoprefixer: ignore next */-webkit-box-orient:vertical;overflow:hidden}.ant-upload{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";outline:0}.ant-upload p{margin:0}.ant-upload-btn{display:block;width:100%;outline:none}.ant-upload input[type=file]{cursor:pointer}.ant-upload.ant-upload-select{display:inline-block}.ant-upload.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-select-picture-card{display:table;float:left;width:104px;height:104px;margin-right:8px;margin-bottom:8px;text-align:center;vertical-align:top;background-color:#fafafa;border:1px dashed #d9d9d9;border-radius:4px;cursor:pointer;-webkit-transition:border-color .3s ease;transition:border-color .3s ease}.ant-upload.ant-upload-select-picture-card>.ant-upload{display:table-cell;width:100%;height:100%;padding:8px;text-align:center;vertical-align:middle}.ant-upload.ant-upload-select-picture-card:hover{border-color:#1890ff}.ant-upload.ant-upload-drag{position:relative;width:100%;height:100%;text-align:center;background:#fafafa;border:1px dashed #d9d9d9;border-radius:4px;cursor:pointer;-webkit-transition:border-color .3s;transition:border-color .3s}.ant-upload.ant-upload-drag .ant-upload{padding:16px 0}.ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled){border-color:#096dd9}.ant-upload.ant-upload-drag.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-drag .ant-upload-btn{display:table;height:100%}.ant-upload.ant-upload-drag .ant-upload-drag-container{display:table-cell;vertical-align:middle}.ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover{border-color:#40a9ff}.ant-upload.ant-upload-drag p.ant-upload-drag-icon{margin-bottom:20px}.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon{color:#40a9ff;font-size:48px}.ant-upload.ant-upload-drag p.ant-upload-text{margin:0 0 4px;color:rgba(0,0,0,.85);font-size:16px}.ant-upload.ant-upload-drag p.ant-upload-hint{color:rgba(0,0,0,.45);font-size:14px}.ant-upload.ant-upload-drag .anticon-plus{color:rgba(0,0,0,.25);font-size:30px;-webkit-transition:all .3s;transition:all .3s}.ant-upload.ant-upload-drag .anticon-plus:hover,.ant-upload.ant-upload-drag:hover .anticon-plus{color:rgba(0,0,0,.45)}.ant-upload-picture-card-wrapper{zoom:1;display:inline-block;width:100%}.ant-upload-picture-card-wrapper:after,.ant-upload-picture-card-wrapper:before{display:table;content:\"\"}.ant-upload-picture-card-wrapper:after{clear:both}.ant-upload-list{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;-webkit-font-feature-settings:\"tnum\";font-feature-settings:\"tnum\",\"tnum\";zoom:1}.ant-upload-list:after,.ant-upload-list:before{display:table;content:\"\"}.ant-upload-list:after{clear:both}.ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-1{padding-right:14px}.ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-2{padding-right:28px}.ant-upload-list-item{position:relative;height:22px;margin-top:8px;font-size:14px}.ant-upload-list-item-name{display:inline-block;width:100%;padding-left:22px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-upload-list-item-name-icon-count-1{padding-right:14px}.ant-upload-list-item-card-actions{position:absolute;right:0;opacity:0}.ant-upload-list-item-card-actions.picture{top:25px;line-height:1;opacity:1}.ant-upload-list-item-card-actions .anticon{padding-right:6px;color:rgba(0,0,0,.45)}.ant-upload-list-item-info{height:100%;padding:0 12px 0 4px;-webkit-transition:background-color .3s;transition:background-color .3s}.ant-upload-list-item-info>span{display:block;width:100%;height:100%}.ant-upload-list-item-info .anticon-loading,.ant-upload-list-item-info .anticon-paper-clip{position:absolute;top:5px;color:rgba(0,0,0,.45);font-size:14px}.ant-upload-list-item .anticon-close{display:inline-block;font-size:12px;font-size:10px\\9;-webkit-transform:scale(.83333333) rotate(0deg);-ms-transform:scale(.83333333) rotate(0deg);transform:scale(.83333333) rotate(0deg);position:absolute;top:6px;right:4px;color:rgba(0,0,0,.45);line-height:0;cursor:pointer;opacity:0;-webkit-transition:all .3s;transition:all .3s}:root .ant-upload-list-item .anticon-close{font-size:12px}.ant-upload-list-item .anticon-close:hover{color:rgba(0,0,0,.65)}.ant-upload-list-item:hover .ant-upload-list-item-info{background-color:#e6f7ff}.ant-upload-list-item:hover .ant-upload-list-item-card-actions,.ant-upload-list-item:hover .anticon-close{opacity:1}.ant-upload-list-item-error,.ant-upload-list-item-error .ant-upload-list-item-name,.ant-upload-list-item-error .anticon-paper-clip{color:#f5222d}.ant-upload-list-item-error .ant-upload-list-item-card-actions{opacity:1}.ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{color:#f5222d}.ant-upload-list-item-progress{position:absolute;bottom:-12px;width:100%;padding-left:26px;font-size:14px;line-height:0}.ant-upload-list-picture-card .ant-upload-list-item,.ant-upload-list-picture .ant-upload-list-item{position:relative;height:66px;padding:8px;border:1px solid #d9d9d9;border-radius:4px}.ant-upload-list-picture-card .ant-upload-list-item:hover,.ant-upload-list-picture .ant-upload-list-item:hover{background:transparent}.ant-upload-list-picture-card .ant-upload-list-item-error,.ant-upload-list-picture .ant-upload-list-item-error{border-color:#f5222d}.ant-upload-list-picture-card .ant-upload-list-item-info,.ant-upload-list-picture .ant-upload-list-item-info{padding:0}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info,.ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info{background:transparent}.ant-upload-list-picture-card .ant-upload-list-item-uploading,.ant-upload-list-picture .ant-upload-list-item-uploading{border-style:dashed}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture .ant-upload-list-item-thumbnail{position:absolute;top:8px;left:8px;width:48px;height:48px;font-size:26px;line-height:54px;text-align:center;opacity:.8}.ant-upload-list-picture-card .ant-upload-list-item-icon,.ant-upload-list-picture .ant-upload-list-item-icon{position:absolute;top:50%;left:50%;font-size:26px;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.ant-upload-list-picture-card .ant-upload-list-item-image,.ant-upload-list-picture .ant-upload-list-item-image{max-width:100%}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img,.ant-upload-list-picture .ant-upload-list-item-thumbnail img{display:block;width:48px;height:48px;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-name{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;max-width:100%;margin:0 0 0 8px;padding-right:8px;padding-left:48px;overflow:hidden;line-height:44px;white-space:nowrap;text-overflow:ellipsis;-webkit-transition:all .3s;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-1,.ant-upload-list-picture .ant-upload-list-item-name-icon-count-1{padding-right:18px}.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-2,.ant-upload-list-picture .ant-upload-list-item-name-icon-count-2{padding-right:36px}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name{line-height:28px}.ant-upload-list-picture-card .ant-upload-list-item-progress,.ant-upload-list-picture .ant-upload-list-item-progress{bottom:14px;width:calc(100% - 24px);margin-top:0;padding-left:56px}.ant-upload-list-picture-card .anticon-close,.ant-upload-list-picture .anticon-close{position:absolute;top:8px;right:8px;line-height:1;opacity:1}.ant-upload-list-picture-card.ant-upload-list:after{display:none}.ant-upload-list-picture-card-container,.ant-upload-list-picture-card .ant-upload-list-item{float:left;width:104px;height:104px;margin:0 8px 8px 0}.ant-upload-list-picture-card .ant-upload-list-item-info{position:relative;height:100%;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:rgba(0,0,0,.5);opacity:0;-webkit-transition:all .3s;transition:all .3s;content:\" \"}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info:before{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);opacity:0;-webkit-transition:all .3s;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye-o{z-index:10;width:16px;margin:0 4px;color:hsla(0,0%,100%,.85);font-size:16px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye-o:hover{color:#fff}.ant-upload-list-picture-card .ant-upload-list-item-actions:hover,.ant-upload-list-picture-card .ant-upload-list-item-info:hover+.ant-upload-list-item-actions{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{position:static;display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ant-upload-list-picture-card .ant-upload-list-item-name{display:none;margin:8px 0 0;padding:0;line-height:1.5;text-align:center}.ant-upload-list-picture-card .anticon-picture+.ant-upload-list-item-name{position:absolute;bottom:10px;display:block}.ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item{background-color:#fafafa}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info{height:auto}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye-o,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info:before{display:none}.ant-upload-list-picture-card .ant-upload-list-item-uploading-text{margin-top:18px;color:rgba(0,0,0,.45)}.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:32px;padding-left:0}.ant-upload-list .ant-upload-success-icon{color:#52c41a;font-weight:700}.ant-upload-list .ant-upload-animate-enter,.ant-upload-list .ant-upload-animate-inline-enter,.ant-upload-list .ant-upload-animate-inline-leave,.ant-upload-list .ant-upload-animate-leave{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:cubic-bezier(.78,.14,.15,.86);animation-fill-mode:cubic-bezier(.78,.14,.15,.86)}.ant-upload-list .ant-upload-animate-enter{-webkit-animation-name:uploadAnimateIn;animation-name:uploadAnimateIn}.ant-upload-list .ant-upload-animate-leave{-webkit-animation-name:uploadAnimateOut;animation-name:uploadAnimateOut}.ant-upload-list .ant-upload-animate-inline-enter{-webkit-animation-name:uploadAnimateInlineIn;animation-name:uploadAnimateInlineIn}.ant-upload-list .ant-upload-animate-inline-leave{-webkit-animation-name:uploadAnimateInlineOut;animation-name:uploadAnimateInlineOut}@-webkit-keyframes uploadAnimateIn{0%{height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateIn{0%{height:0;margin:0;padding:0;opacity:0}}@-webkit-keyframes uploadAnimateOut{to{height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateOut{to{height:0;margin:0;padding:0;opacity:0}}@-webkit-keyframes uploadAnimateInlineIn{0%{width:0;height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineIn{0%{width:0;height:0;margin:0;padding:0;opacity:0}}@-webkit-keyframes uploadAnimateInlineOut{to{width:0;height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineOut{to{width:0;height:0;margin:0;padding:0;opacity:0}}\n/*# sourceMappingURL=2.8ca66de9.chunk.css.map */"
  },
  {
    "path": "package_hub/template/inspection_html/static/css/main.041ca26a.chunk.css",
    "content": ".warningSearch{display:flex;justify-content:flex-end;margin-top:10px;margin-bottom:10px}.warningSearch>div:first-child{margin-right:auto}.rangePicker{margin-right:15px}.contentLeftMenuWrapper{display:flex}.leftMenuListContent{display:flex;flex-direction:column;font-size:16px}.leftMenu{background-color:#fafafa;padding:15px;margin-top:10px;margin-right:10px;height:82vh;overflow-y:auto}.trendContentWrapper{width:100%;padding:10px}.title{margin-bottom:5px;margin-left:15px;color:#333;font-size:16px;font-weight:500;padding:10px 0}.trendItemContent{background-color:#fff;border-radius:10px}.rangPicker{display:flex;justify-content:flex-end;width:100%;padding:10px 60px}.header{display:flex;align-items:center;padding:8px 8px 8px 15px;color:#333;border-bottom:1px solid #bfbfbf}.header>div:nth-child(2){display:flex;align-items:center;width:100%;height:35px;margin-left:5px}.pageInfo{display:flex;justify-content:flex-end;align-items:center;padding:10px}.pageInfo>div:first-child{margin-right:10px}.panelItem{background:#edf0f3;border-radius:4px;border:0;overflow:hidden;border-bottom:0!important}.panelItem>div:first-child{padding:8px 15px}.panelItem>div:nth-child(2){background-color:#fff!important}.reportContent{padding:15px}.reportTitle{display:flex;justify-content:center;color:#1890ff;margin-bottom:20px;height:40px;align-items:center;position:relative}.reportTitle>div:first-child{font-size:22px;font-weight:500;color:#333}.reportTitle>div:nth-child(2){position:absolute;right:0;display:flex;cursor:pointer;margin-right:10px}.overviewItemWrapper{display:flex;justify-content:space-between;flex-flow:wrap;margin:10px 0}.overviewItemWrapper>div:nth-child(2n){margin-right:0}.overviewItem{display:flex;width:49.5%;color:#333;margin-bottom:10px}.overviewItem>div{border:1px solid #e8e8e8;padding:10px}.overviewItem>div:first-child{display:flex;justify-content:center;align-items:center;border-right:none;width:200px}.overviewItem>div:nth-child(2){width:100%}.planChartWrapper{margin-top:10px;width:100%;border:1px solid #e8e8e8;border-radius:2px}.planChartBlockWrapper{display:flex;flex-flow:row wrap;max-height:240px;overflow-y:auto;padding:20px}.stateButton{position:relative;top:0;border:1px solid #333;transition:all .2s ease-in-out;width:178px;margin-right:32px;margin-bottom:10px;height:32px}.stateButton>div{margin:auto;width:100%;height:100%;line-height:30px;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.planChartTitle{background-color:#fafbfd;font-weight:500;height:30px;line-height:30px;border-bottom:1px solid #e8e8e8}.planChartTitleCircular{display:inline-block;width:10px;height:10px;background-color:#54bba6;border-radius:50%;margin-right:10px;margin-left:20px}.topologyWrapper{display:flex;align-items:center;margin-right:80px;margin-bottom:80px}.topologyChildren{display:flex;flex-direction:column}.topologyChildren>div:first-child{position:relative}.verticalLine{position:absolute;background-color:#333;left:0;top:22px;height:calc(100% - 55px);width:2px;border-radius:1px}.topologyItem{justify-content:center;padding:10px;border:2px solid #333;border-radius:5px}.rootItemBox,.topologyItem{display:flex;align-items:center}.rootItemBox{margin-bottom:10px}.connectLine{display:inline-block;width:30px;background-color:#333;height:2px;border-radius:1px}.basicCardWrapper{display:flex;flex-flow:row wrap}.basicCardItem{width:40%;padding:5px;margin-right:10px;margin-bottom:10px}.buttonContainer{width:100%}.greenType{background-color:#5ba165;color:#fff;border-color:#5ba165}.redType{background-color:#ff4d4f;color:#fff;border-color:#ff4d4f}.buttonContainer>button{margin-right:15px}._bigfontSize{font-size:14px}\n/*# sourceMappingURL=main.041ca26a.chunk.css.map */"
  },
  {
    "path": "package_hub/template/inspection_html/static/js/2.0ca9bd94.chunk.js",
    "content": "/*! For license information please see 2.0ca9bd94.chunk.js.LICENSE.txt */\n(this[\"webpackJsonpomp-fontend\"]=this[\"webpackJsonpomp-fontend\"]||[]).push([[2],[function(e,t,n){\"use strict\";e.exports=n(168)},function(e,t,n){e.exports=n(211)()},function(e,t,n){\"use strict\";e.exports=n(173)},function(e,t,n){var r;!function(){\"use strict\";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var c=typeof r;if(\"string\"===c||\"number\"===c)e.push(r);else if(Array.isArray(r)&&r.length){var i=o.apply(null,r);i&&e.push(i)}else if(\"object\"===c)for(var a in r)n.call(r,a)&&r[a]&&e.push(a)}}return e.join(\" \")}e.exports?(o.default=o,e.exports=o):void 0===(r=function(){return o}.apply(t,[]))||(e.exports=r)}()},function(e,t,n){\"use strict\";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}n.d(t,\"a\",(function(){return c}))},function(e,t,n){\"use strict\";t.__esModule=!0;var r,o=n(174),c=(r=o)&&r.__esModule?r:{default:r};t.default=c.default||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}},function(e,t,n){\"use strict\";t.__esModule=!0;var r,o=n(85),c=(r=o)&&r.__esModule?r:{default:r};t.default=function(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==(\"undefined\"===typeof t?\"undefined\":(0,c.default)(t))&&\"function\"!==typeof t?e:t}},function(e,t,n){\"use strict\";t.__esModule=!0;var r,o=n(133),c=(r=o)&&r.__esModule?r:{default:r};t.default=function(e,t,n){return t in e?(0,c.default)(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){var r;!function(){\"use strict\";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var c=typeof r;if(\"string\"===c||\"number\"===c)e.push(r);else if(Array.isArray(r)){if(r.length){var i=o.apply(null,r);i&&e.push(i)}}else if(\"object\"===c)if(r.toString===Object.prototype.toString)for(var a in r)n.call(r,a)&&r[a]&&e.push(a);else e.push(r.toString())}}return e.join(\" \")}e.exports?(o.default=o,e.exports=o):void 0===(r=function(){return o}.apply(t,[]))||(e.exports=r)}()},function(e,t,n){\"use strict\";!function e(){if(\"undefined\"!==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&\"function\"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(167)},function(e,t,n){\"use strict\";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}},function(e,t,n){\"use strict\";t.__esModule=!0;var r=i(n(204)),o=i(n(208)),c=i(n(85));function i(e){return e&&e.__esModule?e:{default:e}}t.default=function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+(\"undefined\"===typeof t?\"undefined\":(0,c.default)(t)));e.prototype=(0,o.default)(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(r.default?(0,r.default)(e,t):e.__proto__=t)}},function(e,t,n){\"use strict\";function r(){var e=this.constructor.getDerivedStateFromProps(this.props,this.state);null!==e&&void 0!==e&&this.setState(e)}function o(e){this.setState(function(t){var n=this.constructor.getDerivedStateFromProps(e,t);return null!==n&&void 0!==n?n:null}.bind(this))}function c(e,t){try{var n=this.props,r=this.state;this.props=e,this.state=t,this.__reactInternalSnapshotFlag=!0,this.__reactInternalSnapshot=this.getSnapshotBeforeUpdate(n,r)}finally{this.props=n,this.state=r}}function i(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Error(\"Can only polyfill class components\");if(\"function\"!==typeof e.getDerivedStateFromProps&&\"function\"!==typeof t.getSnapshotBeforeUpdate)return e;var n=null,i=null,a=null;if(\"function\"===typeof t.componentWillMount?n=\"componentWillMount\":\"function\"===typeof t.UNSAFE_componentWillMount&&(n=\"UNSAFE_componentWillMount\"),\"function\"===typeof t.componentWillReceiveProps?i=\"componentWillReceiveProps\":\"function\"===typeof t.UNSAFE_componentWillReceiveProps&&(i=\"UNSAFE_componentWillReceiveProps\"),\"function\"===typeof t.componentWillUpdate?a=\"componentWillUpdate\":\"function\"===typeof t.UNSAFE_componentWillUpdate&&(a=\"UNSAFE_componentWillUpdate\"),null!==n||null!==i||null!==a){var l=e.displayName||e.name,u=\"function\"===typeof e.getDerivedStateFromProps?\"getDerivedStateFromProps()\":\"getSnapshotBeforeUpdate()\";throw Error(\"Unsafe legacy lifecycles will not be called for components using new component APIs.\\n\\n\"+l+\" uses \"+u+\" but also contains the following legacy lifecycles:\"+(null!==n?\"\\n  \"+n:\"\")+(null!==i?\"\\n  \"+i:\"\")+(null!==a?\"\\n  \"+a:\"\")+\"\\n\\nThe above lifecycles should be removed. Learn more about this warning here:\\nhttps://fb.me/react-async-component-lifecycle-hooks\")}if(\"function\"===typeof e.getDerivedStateFromProps&&(t.componentWillMount=r,t.componentWillReceiveProps=o),\"function\"===typeof t.getSnapshotBeforeUpdate){if(\"function\"!==typeof t.componentDidUpdate)throw new Error(\"Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype\");t.componentWillUpdate=c;var s=t.componentDidUpdate;t.componentDidUpdate=function(e,t,n){var r=this.__reactInternalSnapshotFlag?this.__reactInternalSnapshot:n;s.call(this,e,t,r)}}return e}n.r(t),n.d(t,\"polyfill\",(function(){return i})),r.__suppressDeprecationWarning=!0,o.__suppressDeprecationWarning=!0,c.__suppressDeprecationWarning=!0},function(e,t,n){\"use strict\";var r=n(0),o=n(3),c=n.n(o),i=n(120),a=n(7),l=n.n(a),u=n(5),s=n.n(u),f=n(26),p=n.n(f),h=n(10),d=n.n(h),v=n(14),m=n.n(v),y=n(6),b=n.n(y),g=n(11),w=n.n(g),z=n(40),O={primaryColor:\"#333\",secondaryColor:\"#E6E6E6\"},C=function(e){function t(){return d()(this,t),b()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return w()(t,e),m()(t,[{key:\"render\",value:function(){var e,n=this.props,r=n.type,o=n.className,c=n.onClick,i=n.style,a=n.primaryColor,u=n.secondaryColor,f=p()(n,[\"type\",\"className\",\"onClick\",\"style\",\"primaryColor\",\"secondaryColor\"]),h=void 0,d=O;if(a&&(d={primaryColor:a,secondaryColor:u||Object(z.c)(a)}),Object(z.d)(r))h=r;else if(\"string\"===typeof r&&!(h=t.get(r,d)))return null;return h?(h&&\"function\"===typeof h.icon&&(h=s()({},h,{icon:h.icon(d.primaryColor,d.secondaryColor)})),Object(z.b)(h.icon,\"svg-\"+h.name,s()((e={className:o,onClick:c,style:i},l()(e,\"data-icon\",h.name),l()(e,\"width\",\"1em\"),l()(e,\"height\",\"1em\"),l()(e,\"fill\",\"currentColor\"),l()(e,\"aria-hidden\",\"true\"),l()(e,\"focusable\",\"false\"),e),f))):(Object(z.e)(\"type should be string or icon definiton, but got \"+r),null)}}],[{key:\"add\",value:function(){for(var e=this,t=arguments.length,n=Array(t),r=0;r<t;r++)n[r]=arguments[r];n.forEach((function(t){e.definitions.set(Object(z.f)(t.name,t.theme),t)}))}},{key:\"clear\",value:function(){this.definitions.clear()}},{key:\"get\",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:O;if(e){var n=this.definitions.get(e);return n&&\"function\"===typeof n.icon&&(n=s()({},n,{icon:n.icon(t.primaryColor,t.secondaryColor)})),n}}},{key:\"setTwoToneColors\",value:function(e){var t=e.primaryColor,n=e.secondaryColor;O.primaryColor=t,O.secondaryColor=n||Object(z.c)(t)}},{key:\"getTwoToneColors\",value:function(){return s()({},O)}}]),t}(r.Component);C.displayName=\"IconReact\",C.definitions=new z.a;var M=C;function S(){return(S=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var _=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},x=new Set;var k=n(16),H={width:\"1em\",height:\"1em\",fill:\"currentColor\",\"aria-hidden\":!0,focusable:\"false\"},E=/-fill$/,P=/-o$/,V=/-twotone$/;var T=n(30);function L(e){return M.setTwoToneColors({primaryColor:e})}function j(){return(j=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function N(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function D(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}var R,A=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n};M.add.apply(M,function(e){if(Array.isArray(e))return D(e)}(R=Object.keys(i).map((function(e){return i[e]})))||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(R)||function(e,t){if(e){if(\"string\"===typeof e)return D(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return\"Object\"===n&&e.constructor&&(n=e.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(e):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?D(e,t):void 0}}(R)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()),L(\"#1890ff\");var I,F=\"outlined\";var W=function(e){var t,n=e.className,o=e.type,i=e.component,a=e.viewBox,l=e.spin,u=e.rotate,s=e.tabIndex,f=e.onClick,p=e.children,h=e.theme,d=e.twoToneColor,v=A(e,[\"className\",\"type\",\"component\",\"viewBox\",\"spin\",\"rotate\",\"tabIndex\",\"onClick\",\"children\",\"theme\",\"twoToneColor\"]);Object(k.a)(Boolean(o||i||p),\"Icon\",\"Should have `type` prop or `component` prop or `children`.\");var m=c()((N(t={},\"anticon\",!0),N(t,\"anticon-\".concat(o),Boolean(o)),t),n),y=c()(N({},\"anticon-spin\",!!l||\"loading\"===o)),b=u?{msTransform:\"rotate(\".concat(u,\"deg)\"),transform:\"rotate(\".concat(u,\"deg)\")}:void 0,g=j(j({},H),{className:y,style:b,viewBox:a});a||delete g.viewBox;var w=function(){if(i)return r.createElement(i,g,p);if(p)return Object(k.a)(Boolean(a)||1===r.Children.count(p)&&r.isValidElement(p)&&\"use\"===r.Children.only(p).type,\"Icon\",\"Make sure that you provide correct `viewBox` prop (default `0 0 1024 1024`) to the icon.\"),r.createElement(\"svg\",j({},g,{viewBox:a}),p);if(\"string\"===typeof o){var e=o;if(h){var t=function(e){var t=null;return E.test(e)?t=\"filled\":P.test(e)?t=\"outlined\":V.test(e)&&(t=\"twoTone\"),t}(o);Object(k.a)(!t||h===t,\"Icon\",\"The icon name '\".concat(o,\"' already specify a theme '\").concat(t,\"',\")+\" the 'theme' prop '\".concat(h,\"' will be ignored.\"))}return e=function(e,t){var n=e;return\"filled\"===t?n+=\"-fill\":\"outlined\"===t?n+=\"-o\":\"twoTone\"===t?n+=\"-twotone\":Object(k.a)(!1,\"Icon\",\"This icon '\".concat(e,\"' has unknown theme '\").concat(t,\"'\")),n}(function(e){return e.replace(E,\"\").replace(P,\"\").replace(V,\"\")}(function(e){var t=e;switch(e){case\"cross\":t=\"close\";break;case\"interation\":t=\"interaction\";break;case\"canlendar\":t=\"calendar\";break;case\"colum-height\":t=\"column-height\"}return Object(k.a)(t===e,\"Icon\",\"Icon '\".concat(e,\"' was a typo and is now deprecated, please use '\").concat(t,\"' instead.\")),t}(e)),I||h||F),r.createElement(M,{className:y,type:e,primaryColor:d,style:b})}},z=s;return void 0===z&&f&&(z=-1),r.createElement(T.a,{componentName:\"Icon\"},(function(e){return r.createElement(\"i\",j({\"aria-label\":o&&\"\".concat(e.icon,\": \").concat(o)},v,{tabIndex:z,onClick:f,className:m}),w())}))};W.createFromIconfontCN=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.scriptUrl,n=e.extraCommonProps,o=void 0===n?{}:n;if(\"undefined\"!==typeof document&&\"undefined\"!==typeof window&&\"function\"===typeof document.createElement&&\"string\"===typeof t&&t.length&&!x.has(t)){var c=document.createElement(\"script\");c.setAttribute(\"src\",t),c.setAttribute(\"data-namespace\",t),x.add(t),document.body.appendChild(c)}var i=function(e){var t=e.type,n=e.children,c=_(e,[\"type\",\"children\"]),i=null;return e.type&&(i=r.createElement(\"use\",{xlinkHref:\"#\".concat(t)})),n&&(i=n),r.createElement(U,S({},o,c),i)};return i.displayName=\"Iconfont\",i},W.getTwoToneColor=function(){return M.getTwoToneColors().primaryColor},W.setTwoToneColor=L;var U=t.a=W},function(e,t,n){\"use strict\";t.__esModule=!0;var r,o=n(133),c=(r=o)&&r.__esModule?r:{default:r};t.default=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),(0,c.default)(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}()},function(e,t){e.exports=function(e,t,n,r){var o=n?n.call(r,e,t):void 0;if(void 0!==o)return!!o;if(e===t)return!0;if(\"object\"!==typeof e||!e||\"object\"!==typeof t||!t)return!1;var c=Object.keys(e),i=Object.keys(t);if(c.length!==i.length)return!1;for(var a=Object.prototype.hasOwnProperty.bind(t),l=0;l<c.length;l++){var u=c[l];if(!a(u))return!1;var s=e[u],f=t[u];if(!1===(o=n?n.call(r,s,f,u):void 0)||void 0===o&&s!==f)return!1}return!0}},function(e,t,n){\"use strict\";var r=n(66);t.a=function(e,t,n){Object(r.a)(e,\"[antd: \".concat(t,\"] \").concat(n))}},function(e,t,n){\"use strict\";var r=n(0),o=n(1),c=n(24);function i(e){return e.default||e}var a=n(39);function l(){return(l=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var u=l({},a.a.Modal);function s(e){u=e?l(l({},u),e):l({},a.a.Modal)}var f=n(16);function p(e){return(p=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function h(){return(h=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function d(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function v(e,t){return(v=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function m(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=b(e);if(t){var o=b(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return y(this,n)}}function y(e,t){return!t||\"object\"!==p(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function b(e){return(b=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var g=\"internalMark\";function w(e){e&&e.locale?i(c).locale(e.locale):i(c).locale(\"en\")}var z=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&v(e,t)}(c,e);var t,n,r,o=m(c);function c(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,c),t=o.call(this,e),w(e.locale),s(e.locale&&e.locale.Modal),Object(f.a)(e._ANT_MARK__===g,\"LocaleProvider\",\"`LocaleProvider` is deprecated. Please use `locale` with `ConfigProvider` instead: http://u.ant.design/locale\"),t}return t=c,(n=[{key:\"getChildContext\",value:function(){return{antLocale:h(h({},this.props.locale),{exist:!0})}}},{key:\"componentDidUpdate\",value:function(e){var t=this.props.locale;e.locale!==t&&(w(t),s(t&&t.Modal))}},{key:\"componentWillUnmount\",value:function(){s()}},{key:\"render\",value:function(){return this.props.children}}])&&d(t.prototype,n),r&&d(t,r),c}(r.Component);z.propTypes={locale:o.object},z.defaultProps={locale:{}},z.childContextTypes={antLocale:o.object};var O=n(30),C=n(59);function M(e){return(M=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function S(){return(S=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function _(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function x(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function k(e,t){return(k=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function H(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=P(e);if(t){var o=P(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return E(this,n)}}function E(e,t){return!t||\"object\"!==M(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function P(e){return(P=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var V=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&k(e,t)}(i,e);var t,n,o,c=H(i);function i(){var e;return _(this,i),(e=c.apply(this,arguments)).getPrefixCls=function(t,n){var r=e.props.prefixCls,o=void 0===r?\"ant\":r;return n||(t?\"\".concat(o,\"-\").concat(t):o)},e.renderProvider=function(t,n){var o=e.props,c=o.children,i=o.getPopupContainer,a=o.renderEmpty,l=o.csp,u=o.autoInsertSpaceInButton,s=o.locale,f=o.pageHeader,p=S(S({},t),{getPrefixCls:e.getPrefixCls,csp:l,autoInsertSpaceInButton:u});return i&&(p.getPopupContainer=i),a&&(p.renderEmpty=a),f&&(p.pageHeader=f),r.createElement(C.b.Provider,{value:p},r.createElement(z,{locale:s||n,_ANT_MARK__:g},c))},e}return t=i,(n=[{key:\"render\",value:function(){var e=this;return r.createElement(O.a,null,(function(t,n,o){return r.createElement(C.a,null,(function(t){return e.renderProvider(t,o)}))}))}}])&&x(t.prototype,n),o&&x(t,o),i}(r.Component);t.a=V},function(e,t,n){\"use strict\";var r={MAC_ENTER:3,BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,QUESTION_MARK:63,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NUMLOCK:144,SEMICOLON:186,DASH:189,EQUALS:187,COMMA:188,PERIOD:190,SLASH:191,APOSTROPHE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,WIN_IME:229,isTextModifyingKeyEvent:function(e){var t=e.keyCode;if(e.altKey&&!e.ctrlKey||e.metaKey||t>=r.F1&&t<=r.F12)return!1;switch(t){case r.ALT:case r.CAPS_LOCK:case r.CONTEXT_MENU:case r.CTRL:case r.DOWN:case r.END:case r.ESC:case r.HOME:case r.INSERT:case r.LEFT:case r.MAC_FF_META:case r.META:case r.NUMLOCK:case r.NUM_CENTER:case r.PAGE_DOWN:case r.PAGE_UP:case r.PAUSE:case r.PRINT_SCREEN:case r.RIGHT:case r.SHIFT:case r.UP:case r.WIN_KEY:case r.WIN_KEY_RIGHT:return!1;default:return!0}},isCharacterKey:function(e){if(e>=r.ZERO&&e<=r.NINE)return!0;if(e>=r.NUM_ZERO&&e<=r.NUM_MULTIPLY)return!0;if(e>=r.A&&e<=r.Z)return!0;if(-1!==window.navigator.userAgent.indexOf(\"WebKit\")&&0===e)return!0;switch(e){case r.SPACE:case r.QUESTION_MARK:case r.NUM_PLUS:case r.NUM_MINUS:case r.NUM_PERIOD:case r.NUM_DIVISION:case r.SEMICOLON:case r.DASH:case r.EQUALS:case r.COMMA:case r.PERIOD:case r.SLASH:case r.APOSTROPHE:case r.SINGLE_QUOTE:case r.OPEN_SQUARE_BRACKET:case r.BACKSLASH:case r.CLOSE_SQUARE_BRACKET:return!0;default:return!1}}};t.a=r},function(e,t,n){(function(t){for(var r=n(223),o=\"undefined\"===typeof window?t:window,c=[\"moz\",\"webkit\"],i=\"AnimationFrame\",a=o[\"request\"+i],l=o[\"cancel\"+i]||o[\"cancelRequest\"+i],u=0;!a&&u<c.length;u++)a=o[c[u]+\"Request\"+i],l=o[c[u]+\"Cancel\"+i]||o[c[u]+\"CancelRequest\"+i];if(!a||!l){var s=0,f=0,p=[];a=function(e){if(0===p.length){var t=r(),n=Math.max(0,16.666666666666668-(t-s));s=n+t,setTimeout((function(){var e=p.slice(0);p.length=0;for(var t=0;t<e.length;t++)if(!e[t].cancelled)try{e[t].callback(s)}catch(n){setTimeout((function(){throw n}),0)}}),Math.round(n))}return p.push({handle:++f,callback:e,cancelled:!1}),f},l=function(e){for(var t=0;t<p.length;t++)p[t].handle===e&&(p[t].cancelled=!0)}}e.exports=function(e){return a.call(o,e)},e.exports.cancel=function(){l.apply(o,arguments)},e.exports.polyfill=function(e){e||(e=o),e.requestAnimationFrame=a,e.cancelAnimationFrame=l}}).call(this,n(78))},function(e,t,n){\"use strict\";var r=n(5),o=n.n(r);t.a=function(e,t){for(var n=o()({},e),r=0;r<t.length;r++){delete n[t[r]]}return n}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return o}));var r=n(31);function o(e){return function t(n){return 0===arguments.length||Object(r.a)(n)?t:e.apply(this,arguments)}}},,function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return c}));var r=n(21),o=n(31);function c(e){return function t(n,c){switch(arguments.length){case 0:return t;case 1:return Object(o.a)(n)?t:Object(r.a)((function(t){return e(n,t)}));default:return Object(o.a)(n)&&Object(o.a)(c)?t:Object(o.a)(n)?Object(r.a)((function(t){return e(t,c)})):Object(o.a)(c)?Object(r.a)((function(t){return e(n,t)})):e(n,c)}}}},function(e,t,n){(function(e){e.exports=function(){\"use strict\";var t,n;function r(){return t.apply(null,arguments)}function o(e){t=e}function c(e){return e instanceof Array||\"[object Array]\"===Object.prototype.toString.call(e)}function i(e){return null!=e&&\"[object Object]\"===Object.prototype.toString.call(e)}function a(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(a(e,t))return!1;return!0}function u(e){return void 0===e}function s(e){return\"number\"===typeof e||\"[object Number]\"===Object.prototype.toString.call(e)}function f(e){return e instanceof Date||\"[object Date]\"===Object.prototype.toString.call(e)}function p(e,t){var n,r=[];for(n=0;n<e.length;++n)r.push(t(e[n],n));return r}function h(e,t){for(var n in t)a(t,n)&&(e[n]=t[n]);return a(t,\"toString\")&&(e.toString=t.toString),a(t,\"valueOf\")&&(e.valueOf=t.valueOf),e}function d(e,t,n,r){return qn(e,t,n,r,!0).utc()}function v(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidEra:null,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],era:null,meridiem:null,rfc2822:!1,weekdayMismatch:!1}}function m(e){return null==e._pf&&(e._pf=v()),e._pf}function y(e){if(null==e._isValid){var t=m(e),r=n.call(t.parsedDateParts,(function(e){return null!=e})),o=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidEra&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&r);if(e._strict&&(o=o&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return o;e._isValid=o}return e._isValid}function b(e){var t=d(NaN);return null!=e?h(m(t),e):m(t).userInvalidated=!0,t}n=Array.prototype.some?Array.prototype.some:function(e){var t,n=Object(this),r=n.length>>>0;for(t=0;t<r;t++)if(t in n&&e.call(this,n[t],t,n))return!0;return!1};var g=r.momentProperties=[],w=!1;function z(e,t){var n,r,o;if(u(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),u(t._i)||(e._i=t._i),u(t._f)||(e._f=t._f),u(t._l)||(e._l=t._l),u(t._strict)||(e._strict=t._strict),u(t._tzm)||(e._tzm=t._tzm),u(t._isUTC)||(e._isUTC=t._isUTC),u(t._offset)||(e._offset=t._offset),u(t._pf)||(e._pf=m(t)),u(t._locale)||(e._locale=t._locale),g.length>0)for(n=0;n<g.length;n++)u(o=t[r=g[n]])||(e[r]=o);return e}function O(e){z(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===w&&(w=!0,r.updateOffset(this),w=!1)}function C(e){return e instanceof O||null!=e&&null!=e._isAMomentObject}function M(e){!1===r.suppressDeprecationWarnings&&\"undefined\"!==typeof console&&console.warn&&console.warn(\"Deprecation warning: \"+e)}function S(e,t){var n=!0;return h((function(){if(null!=r.deprecationHandler&&r.deprecationHandler(null,e),n){var o,c,i,l=[];for(c=0;c<arguments.length;c++){if(o=\"\",\"object\"===typeof arguments[c]){for(i in o+=\"\\n[\"+c+\"] \",arguments[0])a(arguments[0],i)&&(o+=i+\": \"+arguments[0][i]+\", \");o=o.slice(0,-2)}else o=arguments[c];l.push(o)}M(e+\"\\nArguments: \"+Array.prototype.slice.call(l).join(\"\")+\"\\n\"+(new Error).stack),n=!1}return t.apply(this,arguments)}),t)}var _,x={};function k(e,t){null!=r.deprecationHandler&&r.deprecationHandler(e,t),x[e]||(M(t),x[e]=!0)}function H(e){return\"undefined\"!==typeof Function&&e instanceof Function||\"[object Function]\"===Object.prototype.toString.call(e)}function E(e){var t,n;for(n in e)a(e,n)&&(H(t=e[n])?this[n]=t:this[\"_\"+n]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+\"|\"+/\\d{1,2}/.source)}function P(e,t){var n,r=h({},e);for(n in t)a(t,n)&&(i(e[n])&&i(t[n])?(r[n]={},h(r[n],e[n]),h(r[n],t[n])):null!=t[n]?r[n]=t[n]:delete r[n]);for(n in e)a(e,n)&&!a(t,n)&&i(e[n])&&(r[n]=h({},r[n]));return r}function V(e){null!=e&&this.set(e)}r.suppressDeprecationWarnings=!1,r.deprecationHandler=null,_=Object.keys?Object.keys:function(e){var t,n=[];for(t in e)a(e,t)&&n.push(t);return n};var T={sameDay:\"[Today at] LT\",nextDay:\"[Tomorrow at] LT\",nextWeek:\"dddd [at] LT\",lastDay:\"[Yesterday at] LT\",lastWeek:\"[Last] dddd [at] LT\",sameElse:\"L\"};function L(e,t,n){var r=this._calendar[e]||this._calendar.sameElse;return H(r)?r.call(t,n):r}function j(e,t,n){var r=\"\"+Math.abs(e),o=t-r.length;return(e>=0?n?\"+\":\"\":\"-\")+Math.pow(10,Math.max(0,o)).toString().substr(1)+r}var N=/(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,D=/(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,R={},A={};function I(e,t,n,r){var o=r;\"string\"===typeof r&&(o=function(){return this[r]()}),e&&(A[e]=o),t&&(A[t[0]]=function(){return j(o.apply(this,arguments),t[1],t[2])}),n&&(A[n]=function(){return this.localeData().ordinal(o.apply(this,arguments),e)})}function F(e){return e.match(/\\[[\\s\\S]/)?e.replace(/^\\[|\\]$/g,\"\"):e.replace(/\\\\/g,\"\")}function W(e){var t,n,r=e.match(N);for(t=0,n=r.length;t<n;t++)A[r[t]]?r[t]=A[r[t]]:r[t]=F(r[t]);return function(t){var o,c=\"\";for(o=0;o<n;o++)c+=H(r[o])?r[o].call(t,e):r[o];return c}}function U(e,t){return e.isValid()?(t=K(t,e.localeData()),R[t]=R[t]||W(t),R[t](e)):e.localeData().invalidDate()}function K(e,t){var n=5;function r(e){return t.longDateFormat(e)||e}for(D.lastIndex=0;n>=0&&D.test(e);)e=e.replace(D,r),D.lastIndex=0,n-=1;return e}var B={LTS:\"h:mm:ss A\",LT:\"h:mm A\",L:\"MM/DD/YYYY\",LL:\"MMMM D, YYYY\",LLL:\"MMMM D, YYYY h:mm A\",LLLL:\"dddd, MMMM D, YYYY h:mm A\"};function Y(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(N).map((function(e){return\"MMMM\"===e||\"MM\"===e||\"DD\"===e||\"dddd\"===e?e.slice(1):e})).join(\"\"),this._longDateFormat[e])}var q=\"Invalid date\";function G(){return this._invalidDate}var $=\"%d\",Q=/\\d{1,2}/;function X(e){return this._ordinal.replace(\"%d\",e)}var Z={future:\"in %s\",past:\"%s ago\",s:\"a few seconds\",ss:\"%d seconds\",m:\"a minute\",mm:\"%d minutes\",h:\"an hour\",hh:\"%d hours\",d:\"a day\",dd:\"%d days\",w:\"a week\",ww:\"%d weeks\",M:\"a month\",MM:\"%d months\",y:\"a year\",yy:\"%d years\"};function J(e,t,n,r){var o=this._relativeTime[n];return H(o)?o(e,t,n,r):o.replace(/%d/i,e)}function ee(e,t){var n=this._relativeTime[e>0?\"future\":\"past\"];return H(n)?n(t):n.replace(/%s/i,t)}var te={};function ne(e,t){var n=e.toLowerCase();te[n]=te[n+\"s\"]=te[t]=e}function re(e){return\"string\"===typeof e?te[e]||te[e.toLowerCase()]:void 0}function oe(e){var t,n,r={};for(n in e)a(e,n)&&(t=re(n))&&(r[t]=e[n]);return r}var ce={};function ie(e,t){ce[e]=t}function ae(e){var t,n=[];for(t in e)a(e,t)&&n.push({unit:t,priority:ce[t]});return n.sort((function(e,t){return e.priority-t.priority})),n}function le(e){return e%4===0&&e%100!==0||e%400===0}function ue(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function se(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=ue(t)),n}function fe(e,t){return function(n){return null!=n?(he(this,e,n),r.updateOffset(this,t),this):pe(this,e)}}function pe(e,t){return e.isValid()?e._d[\"get\"+(e._isUTC?\"UTC\":\"\")+t]():NaN}function he(e,t,n){e.isValid()&&!isNaN(n)&&(\"FullYear\"===t&&le(e.year())&&1===e.month()&&29===e.date()?(n=se(n),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](n,e.month(),Je(n,e.month()))):e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+t](n))}function de(e){return H(this[e=re(e)])?this[e]():this}function ve(e,t){if(\"object\"===typeof e){var n,r=ae(e=oe(e));for(n=0;n<r.length;n++)this[r[n].unit](e[r[n].unit])}else if(H(this[e=re(e)]))return this[e](t);return this}var me,ye=/\\d/,be=/\\d\\d/,ge=/\\d{3}/,we=/\\d{4}/,ze=/[+-]?\\d{6}/,Oe=/\\d\\d?/,Ce=/\\d\\d\\d\\d?/,Me=/\\d\\d\\d\\d\\d\\d?/,Se=/\\d{1,3}/,_e=/\\d{1,4}/,xe=/[+-]?\\d{1,6}/,ke=/\\d+/,He=/[+-]?\\d+/,Ee=/Z|[+-]\\d\\d:?\\d\\d/gi,Pe=/Z|[+-]\\d\\d(?::?\\d\\d)?/gi,Ve=/[+-]?\\d+(\\.\\d{1,3})?/,Te=/[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i;function Le(e,t,n){me[e]=H(t)?t:function(e,r){return e&&n?n:t}}function je(e,t){return a(me,e)?me[e](t._strict,t._locale):new RegExp(Ne(e))}function Ne(e){return De(e.replace(\"\\\\\",\"\").replace(/\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g,(function(e,t,n,r,o){return t||n||r||o})))}function De(e){return e.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g,\"\\\\$&\")}me={};var Re={};function Ae(e,t){var n,r=t;for(\"string\"===typeof e&&(e=[e]),s(t)&&(r=function(e,n){n[t]=se(e)}),n=0;n<e.length;n++)Re[e[n]]=r}function Ie(e,t){Ae(e,(function(e,n,r,o){r._w=r._w||{},t(e,r._w,r,o)}))}function Fe(e,t,n){null!=t&&a(Re,e)&&Re[e](t,n._a,n,e)}var We,Ue=0,Ke=1,Be=2,Ye=3,qe=4,Ge=5,$e=6,Qe=7,Xe=8;function Ze(e,t){return(e%t+t)%t}function Je(e,t){if(isNaN(e)||isNaN(t))return NaN;var n=Ze(t,12);return e+=(t-n)/12,1===n?le(e)?29:28:31-n%7%2}We=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;t<this.length;++t)if(this[t]===e)return t;return-1},I(\"M\",[\"MM\",2],\"Mo\",(function(){return this.month()+1})),I(\"MMM\",0,0,(function(e){return this.localeData().monthsShort(this,e)})),I(\"MMMM\",0,0,(function(e){return this.localeData().months(this,e)})),ne(\"month\",\"M\"),ie(\"month\",8),Le(\"M\",Oe),Le(\"MM\",Oe,be),Le(\"MMM\",(function(e,t){return t.monthsShortRegex(e)})),Le(\"MMMM\",(function(e,t){return t.monthsRegex(e)})),Ae([\"M\",\"MM\"],(function(e,t){t[Ke]=se(e)-1})),Ae([\"MMM\",\"MMMM\"],(function(e,t,n,r){var o=n._locale.monthsParse(e,r,n._strict);null!=o?t[Ke]=o:m(n).invalidMonth=e}));var et=\"January_February_March_April_May_June_July_August_September_October_November_December\".split(\"_\"),tt=\"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec\".split(\"_\"),nt=/D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,rt=Te,ot=Te;function ct(e,t){return e?c(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||nt).test(t)?\"format\":\"standalone\"][e.month()]:c(this._months)?this._months:this._months.standalone}function it(e,t){return e?c(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[nt.test(t)?\"format\":\"standalone\"][e.month()]:c(this._monthsShort)?this._monthsShort:this._monthsShort.standalone}function at(e,t,n){var r,o,c,i=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],r=0;r<12;++r)c=d([2e3,r]),this._shortMonthsParse[r]=this.monthsShort(c,\"\").toLocaleLowerCase(),this._longMonthsParse[r]=this.months(c,\"\").toLocaleLowerCase();return n?\"MMM\"===t?-1!==(o=We.call(this._shortMonthsParse,i))?o:null:-1!==(o=We.call(this._longMonthsParse,i))?o:null:\"MMM\"===t?-1!==(o=We.call(this._shortMonthsParse,i))||-1!==(o=We.call(this._longMonthsParse,i))?o:null:-1!==(o=We.call(this._longMonthsParse,i))||-1!==(o=We.call(this._shortMonthsParse,i))?o:null}function lt(e,t,n){var r,o,c;if(this._monthsParseExact)return at.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),r=0;r<12;r++){if(o=d([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp(\"^\"+this.months(o,\"\").replace(\".\",\"\")+\"$\",\"i\"),this._shortMonthsParse[r]=new RegExp(\"^\"+this.monthsShort(o,\"\").replace(\".\",\"\")+\"$\",\"i\")),n||this._monthsParse[r]||(c=\"^\"+this.months(o,\"\")+\"|^\"+this.monthsShort(o,\"\"),this._monthsParse[r]=new RegExp(c.replace(\".\",\"\"),\"i\")),n&&\"MMMM\"===t&&this._longMonthsParse[r].test(e))return r;if(n&&\"MMM\"===t&&this._shortMonthsParse[r].test(e))return r;if(!n&&this._monthsParse[r].test(e))return r}}function ut(e,t){var n;if(!e.isValid())return e;if(\"string\"===typeof t)if(/^\\d+$/.test(t))t=se(t);else if(!s(t=e.localeData().monthsParse(t)))return e;return n=Math.min(e.date(),Je(e.year(),t)),e._d[\"set\"+(e._isUTC?\"UTC\":\"\")+\"Month\"](t,n),e}function st(e){return null!=e?(ut(this,e),r.updateOffset(this,!0),this):pe(this,\"Month\")}function ft(){return Je(this.year(),this.month())}function pt(e){return this._monthsParseExact?(a(this,\"_monthsRegex\")||dt.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(a(this,\"_monthsShortRegex\")||(this._monthsShortRegex=rt),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)}function ht(e){return this._monthsParseExact?(a(this,\"_monthsRegex\")||dt.call(this),e?this._monthsStrictRegex:this._monthsRegex):(a(this,\"_monthsRegex\")||(this._monthsRegex=ot),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)}function dt(){function e(e,t){return t.length-e.length}var t,n,r=[],o=[],c=[];for(t=0;t<12;t++)n=d([2e3,t]),r.push(this.monthsShort(n,\"\")),o.push(this.months(n,\"\")),c.push(this.months(n,\"\")),c.push(this.monthsShort(n,\"\"));for(r.sort(e),o.sort(e),c.sort(e),t=0;t<12;t++)r[t]=De(r[t]),o[t]=De(o[t]);for(t=0;t<24;t++)c[t]=De(c[t]);this._monthsRegex=new RegExp(\"^(\"+c.join(\"|\")+\")\",\"i\"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp(\"^(\"+o.join(\"|\")+\")\",\"i\"),this._monthsShortStrictRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\")}function vt(e){return le(e)?366:365}I(\"Y\",0,0,(function(){var e=this.year();return e<=9999?j(e,4):\"+\"+e})),I(0,[\"YY\",2],0,(function(){return this.year()%100})),I(0,[\"YYYY\",4],0,\"year\"),I(0,[\"YYYYY\",5],0,\"year\"),I(0,[\"YYYYYY\",6,!0],0,\"year\"),ne(\"year\",\"y\"),ie(\"year\",1),Le(\"Y\",He),Le(\"YY\",Oe,be),Le(\"YYYY\",_e,we),Le(\"YYYYY\",xe,ze),Le(\"YYYYYY\",xe,ze),Ae([\"YYYYY\",\"YYYYYY\"],Ue),Ae(\"YYYY\",(function(e,t){t[Ue]=2===e.length?r.parseTwoDigitYear(e):se(e)})),Ae(\"YY\",(function(e,t){t[Ue]=r.parseTwoDigitYear(e)})),Ae(\"Y\",(function(e,t){t[Ue]=parseInt(e,10)})),r.parseTwoDigitYear=function(e){return se(e)+(se(e)>68?1900:2e3)};var mt=fe(\"FullYear\",!0);function yt(){return le(this.year())}function bt(e,t,n,r,o,c,i){var a;return e<100&&e>=0?(a=new Date(e+400,t,n,r,o,c,i),isFinite(a.getFullYear())&&a.setFullYear(e)):a=new Date(e,t,n,r,o,c,i),a}function gt(e){var t,n;return e<100&&e>=0?((n=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,n)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function wt(e,t,n){var r=7+t-n;return-(7+gt(e,0,r).getUTCDay()-t)%7+r-1}function zt(e,t,n,r,o){var c,i,a=1+7*(t-1)+(7+n-r)%7+wt(e,r,o);return a<=0?i=vt(c=e-1)+a:a>vt(e)?(c=e+1,i=a-vt(e)):(c=e,i=a),{year:c,dayOfYear:i}}function Ot(e,t,n){var r,o,c=wt(e.year(),t,n),i=Math.floor((e.dayOfYear()-c-1)/7)+1;return i<1?r=i+Ct(o=e.year()-1,t,n):i>Ct(e.year(),t,n)?(r=i-Ct(e.year(),t,n),o=e.year()+1):(o=e.year(),r=i),{week:r,year:o}}function Ct(e,t,n){var r=wt(e,t,n),o=wt(e+1,t,n);return(vt(e)-r+o)/7}function Mt(e){return Ot(e,this._week.dow,this._week.doy).week}I(\"w\",[\"ww\",2],\"wo\",\"week\"),I(\"W\",[\"WW\",2],\"Wo\",\"isoWeek\"),ne(\"week\",\"w\"),ne(\"isoWeek\",\"W\"),ie(\"week\",5),ie(\"isoWeek\",5),Le(\"w\",Oe),Le(\"ww\",Oe,be),Le(\"W\",Oe),Le(\"WW\",Oe,be),Ie([\"w\",\"ww\",\"W\",\"WW\"],(function(e,t,n,r){t[r.substr(0,1)]=se(e)}));var St={dow:0,doy:6};function _t(){return this._week.dow}function xt(){return this._week.doy}function kt(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),\"d\")}function Ht(e){var t=Ot(this,1,4).week;return null==e?t:this.add(7*(e-t),\"d\")}function Et(e,t){return\"string\"!==typeof e?e:isNaN(e)?\"number\"===typeof(e=t.weekdaysParse(e))?e:null:parseInt(e,10)}function Pt(e,t){return\"string\"===typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}function Vt(e,t){return e.slice(t,7).concat(e.slice(0,t))}I(\"d\",0,\"do\",\"day\"),I(\"dd\",0,0,(function(e){return this.localeData().weekdaysMin(this,e)})),I(\"ddd\",0,0,(function(e){return this.localeData().weekdaysShort(this,e)})),I(\"dddd\",0,0,(function(e){return this.localeData().weekdays(this,e)})),I(\"e\",0,0,\"weekday\"),I(\"E\",0,0,\"isoWeekday\"),ne(\"day\",\"d\"),ne(\"weekday\",\"e\"),ne(\"isoWeekday\",\"E\"),ie(\"day\",11),ie(\"weekday\",11),ie(\"isoWeekday\",11),Le(\"d\",Oe),Le(\"e\",Oe),Le(\"E\",Oe),Le(\"dd\",(function(e,t){return t.weekdaysMinRegex(e)})),Le(\"ddd\",(function(e,t){return t.weekdaysShortRegex(e)})),Le(\"dddd\",(function(e,t){return t.weekdaysRegex(e)})),Ie([\"dd\",\"ddd\",\"dddd\"],(function(e,t,n,r){var o=n._locale.weekdaysParse(e,r,n._strict);null!=o?t.d=o:m(n).invalidWeekday=e})),Ie([\"d\",\"e\",\"E\"],(function(e,t,n,r){t[r]=se(e)}));var Tt=\"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday\".split(\"_\"),Lt=\"Sun_Mon_Tue_Wed_Thu_Fri_Sat\".split(\"_\"),jt=\"Su_Mo_Tu_We_Th_Fr_Sa\".split(\"_\"),Nt=Te,Dt=Te,Rt=Te;function At(e,t){var n=c(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?\"format\":\"standalone\"];return!0===e?Vt(n,this._week.dow):e?n[e.day()]:n}function It(e){return!0===e?Vt(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Ft(e){return!0===e?Vt(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function Wt(e,t,n){var r,o,c,i=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],r=0;r<7;++r)c=d([2e3,1]).day(r),this._minWeekdaysParse[r]=this.weekdaysMin(c,\"\").toLocaleLowerCase(),this._shortWeekdaysParse[r]=this.weekdaysShort(c,\"\").toLocaleLowerCase(),this._weekdaysParse[r]=this.weekdays(c,\"\").toLocaleLowerCase();return n?\"dddd\"===t?-1!==(o=We.call(this._weekdaysParse,i))?o:null:\"ddd\"===t?-1!==(o=We.call(this._shortWeekdaysParse,i))?o:null:-1!==(o=We.call(this._minWeekdaysParse,i))?o:null:\"dddd\"===t?-1!==(o=We.call(this._weekdaysParse,i))||-1!==(o=We.call(this._shortWeekdaysParse,i))||-1!==(o=We.call(this._minWeekdaysParse,i))?o:null:\"ddd\"===t?-1!==(o=We.call(this._shortWeekdaysParse,i))||-1!==(o=We.call(this._weekdaysParse,i))||-1!==(o=We.call(this._minWeekdaysParse,i))?o:null:-1!==(o=We.call(this._minWeekdaysParse,i))||-1!==(o=We.call(this._weekdaysParse,i))||-1!==(o=We.call(this._shortWeekdaysParse,i))?o:null}function Ut(e,t,n){var r,o,c;if(this._weekdaysParseExact)return Wt.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;r<7;r++){if(o=d([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp(\"^\"+this.weekdays(o,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._shortWeekdaysParse[r]=new RegExp(\"^\"+this.weekdaysShort(o,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\"),this._minWeekdaysParse[r]=new RegExp(\"^\"+this.weekdaysMin(o,\"\").replace(\".\",\"\\\\.?\")+\"$\",\"i\")),this._weekdaysParse[r]||(c=\"^\"+this.weekdays(o,\"\")+\"|^\"+this.weekdaysShort(o,\"\")+\"|^\"+this.weekdaysMin(o,\"\"),this._weekdaysParse[r]=new RegExp(c.replace(\".\",\"\"),\"i\")),n&&\"dddd\"===t&&this._fullWeekdaysParse[r].test(e))return r;if(n&&\"ddd\"===t&&this._shortWeekdaysParse[r].test(e))return r;if(n&&\"dd\"===t&&this._minWeekdaysParse[r].test(e))return r;if(!n&&this._weekdaysParse[r].test(e))return r}}function Kt(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=Et(e,this.localeData()),this.add(e-t,\"d\")):t}function Bt(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,\"d\")}function Yt(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var t=Pt(e,this.localeData());return this.day(this.day()%7?t:t-7)}return this.day()||7}function qt(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Qt.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(a(this,\"_weekdaysRegex\")||(this._weekdaysRegex=Nt),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)}function Gt(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Qt.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(a(this,\"_weekdaysShortRegex\")||(this._weekdaysShortRegex=Dt),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function $t(e){return this._weekdaysParseExact?(a(this,\"_weekdaysRegex\")||Qt.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(a(this,\"_weekdaysMinRegex\")||(this._weekdaysMinRegex=Rt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qt(){function e(e,t){return t.length-e.length}var t,n,r,o,c,i=[],a=[],l=[],u=[];for(t=0;t<7;t++)n=d([2e3,1]).day(t),r=De(this.weekdaysMin(n,\"\")),o=De(this.weekdaysShort(n,\"\")),c=De(this.weekdays(n,\"\")),i.push(r),a.push(o),l.push(c),u.push(r),u.push(o),u.push(c);i.sort(e),a.sort(e),l.sort(e),u.sort(e),this._weekdaysRegex=new RegExp(\"^(\"+u.join(\"|\")+\")\",\"i\"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp(\"^(\"+l.join(\"|\")+\")\",\"i\"),this._weekdaysShortStrictRegex=new RegExp(\"^(\"+a.join(\"|\")+\")\",\"i\"),this._weekdaysMinStrictRegex=new RegExp(\"^(\"+i.join(\"|\")+\")\",\"i\")}function Xt(){return this.hours()%12||12}function Zt(){return this.hours()||24}function Jt(e,t){I(e,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)}))}function en(e,t){return t._meridiemParse}function tn(e){return\"p\"===(e+\"\").toLowerCase().charAt(0)}I(\"H\",[\"HH\",2],0,\"hour\"),I(\"h\",[\"hh\",2],0,Xt),I(\"k\",[\"kk\",2],0,Zt),I(\"hmm\",0,0,(function(){return\"\"+Xt.apply(this)+j(this.minutes(),2)})),I(\"hmmss\",0,0,(function(){return\"\"+Xt.apply(this)+j(this.minutes(),2)+j(this.seconds(),2)})),I(\"Hmm\",0,0,(function(){return\"\"+this.hours()+j(this.minutes(),2)})),I(\"Hmmss\",0,0,(function(){return\"\"+this.hours()+j(this.minutes(),2)+j(this.seconds(),2)})),Jt(\"a\",!0),Jt(\"A\",!1),ne(\"hour\",\"h\"),ie(\"hour\",13),Le(\"a\",en),Le(\"A\",en),Le(\"H\",Oe),Le(\"h\",Oe),Le(\"k\",Oe),Le(\"HH\",Oe,be),Le(\"hh\",Oe,be),Le(\"kk\",Oe,be),Le(\"hmm\",Ce),Le(\"hmmss\",Me),Le(\"Hmm\",Ce),Le(\"Hmmss\",Me),Ae([\"H\",\"HH\"],Ye),Ae([\"k\",\"kk\"],(function(e,t,n){var r=se(e);t[Ye]=24===r?0:r})),Ae([\"a\",\"A\"],(function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e})),Ae([\"h\",\"hh\"],(function(e,t,n){t[Ye]=se(e),m(n).bigHour=!0})),Ae(\"hmm\",(function(e,t,n){var r=e.length-2;t[Ye]=se(e.substr(0,r)),t[qe]=se(e.substr(r)),m(n).bigHour=!0})),Ae(\"hmmss\",(function(e,t,n){var r=e.length-4,o=e.length-2;t[Ye]=se(e.substr(0,r)),t[qe]=se(e.substr(r,2)),t[Ge]=se(e.substr(o)),m(n).bigHour=!0})),Ae(\"Hmm\",(function(e,t,n){var r=e.length-2;t[Ye]=se(e.substr(0,r)),t[qe]=se(e.substr(r))})),Ae(\"Hmmss\",(function(e,t,n){var r=e.length-4,o=e.length-2;t[Ye]=se(e.substr(0,r)),t[qe]=se(e.substr(r,2)),t[Ge]=se(e.substr(o))}));var nn=/[ap]\\.?m?\\.?/i,rn=fe(\"Hours\",!0);function on(e,t,n){return e>11?n?\"pm\":\"PM\":n?\"am\":\"AM\"}var cn,an={calendar:T,longDateFormat:B,invalidDate:q,ordinal:$,dayOfMonthOrdinalParse:Q,relativeTime:Z,months:et,monthsShort:tt,week:St,weekdays:Tt,weekdaysMin:jt,weekdaysShort:Lt,meridiemParse:nn},ln={},un={};function sn(e,t){var n,r=Math.min(e.length,t.length);for(n=0;n<r;n+=1)if(e[n]!==t[n])return n;return r}function fn(e){return e?e.toLowerCase().replace(\"_\",\"-\"):e}function pn(e){for(var t,n,r,o,c=0;c<e.length;){for(t=(o=fn(e[c]).split(\"-\")).length,n=(n=fn(e[c+1]))?n.split(\"-\"):null;t>0;){if(r=hn(o.slice(0,t).join(\"-\")))return r;if(n&&n.length>=t&&sn(o,n)>=t-1)break;t--}c++}return cn}function hn(t){var n=null;if(void 0===ln[t]&&\"undefined\"!==typeof e&&e&&e.exports)try{n=cn._abbr,function(){var e=new Error(\"Cannot find module 'undefined'\");throw e.code=\"MODULE_NOT_FOUND\",e}(),dn(n)}catch(r){ln[t]=null}return ln[t]}function dn(e,t){var n;return e&&((n=u(t)?yn(e):vn(e,t))?cn=n:\"undefined\"!==typeof console&&console.warn&&console.warn(\"Locale \"+e+\" not found. Did you forget to load it?\")),cn._abbr}function vn(e,t){if(null!==t){var n,r=an;if(t.abbr=e,null!=ln[e])k(\"defineLocaleOverride\",\"use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info.\"),r=ln[e]._config;else if(null!=t.parentLocale)if(null!=ln[t.parentLocale])r=ln[t.parentLocale]._config;else{if(null==(n=hn(t.parentLocale)))return un[t.parentLocale]||(un[t.parentLocale]=[]),un[t.parentLocale].push({name:e,config:t}),null;r=n._config}return ln[e]=new V(P(r,t)),un[e]&&un[e].forEach((function(e){vn(e.name,e.config)})),dn(e),ln[e]}return delete ln[e],null}function mn(e,t){if(null!=t){var n,r,o=an;null!=ln[e]&&null!=ln[e].parentLocale?ln[e].set(P(ln[e]._config,t)):(null!=(r=hn(e))&&(o=r._config),t=P(o,t),null==r&&(t.abbr=e),(n=new V(t)).parentLocale=ln[e],ln[e]=n),dn(e)}else null!=ln[e]&&(null!=ln[e].parentLocale?(ln[e]=ln[e].parentLocale,e===dn()&&dn(e)):null!=ln[e]&&delete ln[e]);return ln[e]}function yn(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return cn;if(!c(e)){if(t=hn(e))return t;e=[e]}return pn(e)}function bn(){return _(ln)}function gn(e){var t,n=e._a;return n&&-2===m(e).overflow&&(t=n[Ke]<0||n[Ke]>11?Ke:n[Be]<1||n[Be]>Je(n[Ue],n[Ke])?Be:n[Ye]<0||n[Ye]>24||24===n[Ye]&&(0!==n[qe]||0!==n[Ge]||0!==n[$e])?Ye:n[qe]<0||n[qe]>59?qe:n[Ge]<0||n[Ge]>59?Ge:n[$e]<0||n[$e]>999?$e:-1,m(e)._overflowDayOfYear&&(t<Ue||t>Be)&&(t=Be),m(e)._overflowWeeks&&-1===t&&(t=Qe),m(e)._overflowWeekday&&-1===t&&(t=Xe),m(e).overflow=t),e}var wn=/^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,zn=/^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,On=/Z|[+-]\\d\\d(?::?\\d\\d)?/,Cn=[[\"YYYYYY-MM-DD\",/[+-]\\d{6}-\\d\\d-\\d\\d/],[\"YYYY-MM-DD\",/\\d{4}-\\d\\d-\\d\\d/],[\"GGGG-[W]WW-E\",/\\d{4}-W\\d\\d-\\d/],[\"GGGG-[W]WW\",/\\d{4}-W\\d\\d/,!1],[\"YYYY-DDD\",/\\d{4}-\\d{3}/],[\"YYYY-MM\",/\\d{4}-\\d\\d/,!1],[\"YYYYYYMMDD\",/[+-]\\d{10}/],[\"YYYYMMDD\",/\\d{8}/],[\"GGGG[W]WWE\",/\\d{4}W\\d{3}/],[\"GGGG[W]WW\",/\\d{4}W\\d{2}/,!1],[\"YYYYDDD\",/\\d{7}/],[\"YYYYMM\",/\\d{6}/,!1],[\"YYYY\",/\\d{4}/,!1]],Mn=[[\"HH:mm:ss.SSSS\",/\\d\\d:\\d\\d:\\d\\d\\.\\d+/],[\"HH:mm:ss,SSSS\",/\\d\\d:\\d\\d:\\d\\d,\\d+/],[\"HH:mm:ss\",/\\d\\d:\\d\\d:\\d\\d/],[\"HH:mm\",/\\d\\d:\\d\\d/],[\"HHmmss.SSSS\",/\\d\\d\\d\\d\\d\\d\\.\\d+/],[\"HHmmss,SSSS\",/\\d\\d\\d\\d\\d\\d,\\d+/],[\"HHmmss\",/\\d\\d\\d\\d\\d\\d/],[\"HHmm\",/\\d\\d\\d\\d/],[\"HH\",/\\d\\d/]],Sn=/^\\/?Date\\((-?\\d+)/i,_n=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,xn={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function kn(e){var t,n,r,o,c,i,a=e._i,l=wn.exec(a)||zn.exec(a);if(l){for(m(e).iso=!0,t=0,n=Cn.length;t<n;t++)if(Cn[t][1].exec(l[1])){o=Cn[t][0],r=!1!==Cn[t][2];break}if(null==o)return void(e._isValid=!1);if(l[3]){for(t=0,n=Mn.length;t<n;t++)if(Mn[t][1].exec(l[3])){c=(l[2]||\" \")+Mn[t][0];break}if(null==c)return void(e._isValid=!1)}if(!r&&null!=c)return void(e._isValid=!1);if(l[4]){if(!On.exec(l[4]))return void(e._isValid=!1);i=\"Z\"}e._f=o+(c||\"\")+(i||\"\"),In(e)}else e._isValid=!1}function Hn(e,t,n,r,o,c){var i=[En(e),tt.indexOf(t),parseInt(n,10),parseInt(r,10),parseInt(o,10)];return c&&i.push(parseInt(c,10)),i}function En(e){var t=parseInt(e,10);return t<=49?2e3+t:t<=999?1900+t:t}function Pn(e){return e.replace(/\\([^)]*\\)|[\\n\\t]/g,\" \").replace(/(\\s\\s+)/g,\" \").replace(/^\\s\\s*/,\"\").replace(/\\s\\s*$/,\"\")}function Vn(e,t,n){return!e||Lt.indexOf(e)===new Date(t[0],t[1],t[2]).getDay()||(m(n).weekdayMismatch=!0,n._isValid=!1,!1)}function Tn(e,t,n){if(e)return xn[e];if(t)return 0;var r=parseInt(n,10),o=r%100;return(r-o)/100*60+o}function Ln(e){var t,n=_n.exec(Pn(e._i));if(n){if(t=Hn(n[4],n[3],n[2],n[5],n[6],n[7]),!Vn(n[1],t,e))return;e._a=t,e._tzm=Tn(n[8],n[9],n[10]),e._d=gt.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),m(e).rfc2822=!0}else e._isValid=!1}function jn(e){var t=Sn.exec(e._i);null===t?(kn(e),!1===e._isValid&&(delete e._isValid,Ln(e),!1===e._isValid&&(delete e._isValid,e._strict?e._isValid=!1:r.createFromInputFallback(e)))):e._d=new Date(+t[1])}function Nn(e,t,n){return null!=e?e:null!=t?t:n}function Dn(e){var t=new Date(r.now());return e._useUTC?[t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()]:[t.getFullYear(),t.getMonth(),t.getDate()]}function Rn(e){var t,n,r,o,c,i=[];if(!e._d){for(r=Dn(e),e._w&&null==e._a[Be]&&null==e._a[Ke]&&An(e),null!=e._dayOfYear&&(c=Nn(e._a[Ue],r[Ue]),(e._dayOfYear>vt(c)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),n=gt(c,0,e._dayOfYear),e._a[Ke]=n.getUTCMonth(),e._a[Be]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=i[t]=r[t];for(;t<7;t++)e._a[t]=i[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[Ye]&&0===e._a[qe]&&0===e._a[Ge]&&0===e._a[$e]&&(e._nextDay=!0,e._a[Ye]=0),e._d=(e._useUTC?gt:bt).apply(null,i),o=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Ye]=24),e._w&&\"undefined\"!==typeof e._w.d&&e._w.d!==o&&(m(e).weekdayMismatch=!0)}}function An(e){var t,n,r,o,c,i,a,l,u;null!=(t=e._w).GG||null!=t.W||null!=t.E?(c=1,i=4,n=Nn(t.GG,e._a[Ue],Ot(Gn(),1,4).year),r=Nn(t.W,1),((o=Nn(t.E,1))<1||o>7)&&(l=!0)):(c=e._locale._week.dow,i=e._locale._week.doy,u=Ot(Gn(),c,i),n=Nn(t.gg,e._a[Ue],u.year),r=Nn(t.w,u.week),null!=t.d?((o=t.d)<0||o>6)&&(l=!0):null!=t.e?(o=t.e+c,(t.e<0||t.e>6)&&(l=!0)):o=c),r<1||r>Ct(n,c,i)?m(e)._overflowWeeks=!0:null!=l?m(e)._overflowWeekday=!0:(a=zt(n,r,o,c,i),e._a[Ue]=a.year,e._dayOfYear=a.dayOfYear)}function In(e){if(e._f!==r.ISO_8601)if(e._f!==r.RFC_2822){e._a=[],m(e).empty=!0;var t,n,o,c,i,a,l=\"\"+e._i,u=l.length,s=0;for(o=K(e._f,e._locale).match(N)||[],t=0;t<o.length;t++)c=o[t],(n=(l.match(je(c,e))||[])[0])&&((i=l.substr(0,l.indexOf(n))).length>0&&m(e).unusedInput.push(i),l=l.slice(l.indexOf(n)+n.length),s+=n.length),A[c]?(n?m(e).empty=!1:m(e).unusedTokens.push(c),Fe(c,n,e)):e._strict&&!n&&m(e).unusedTokens.push(c);m(e).charsLeftOver=u-s,l.length>0&&m(e).unusedInput.push(l),e._a[Ye]<=12&&!0===m(e).bigHour&&e._a[Ye]>0&&(m(e).bigHour=void 0),m(e).parsedDateParts=e._a.slice(0),m(e).meridiem=e._meridiem,e._a[Ye]=Fn(e._locale,e._a[Ye],e._meridiem),null!==(a=m(e).era)&&(e._a[Ue]=e._locale.erasConvertYear(a,e._a[Ue])),Rn(e),gn(e)}else Ln(e);else kn(e)}function Fn(e,t,n){var r;return null==n?t:null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((r=e.isPM(n))&&t<12&&(t+=12),r||12!==t||(t=0),t):t}function Wn(e){var t,n,r,o,c,i,a=!1;if(0===e._f.length)return m(e).invalidFormat=!0,void(e._d=new Date(NaN));for(o=0;o<e._f.length;o++)c=0,i=!1,t=z({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[o],In(t),y(t)&&(i=!0),c+=m(t).charsLeftOver,c+=10*m(t).unusedTokens.length,m(t).score=c,a?c<r&&(r=c,n=t):(null==r||c<r||i)&&(r=c,n=t,i&&(a=!0));h(e,n||t)}function Un(e){if(!e._d){var t=oe(e._i),n=void 0===t.day?t.date:t.day;e._a=p([t.year,t.month,n,t.hour,t.minute,t.second,t.millisecond],(function(e){return e&&parseInt(e,10)})),Rn(e)}}function Kn(e){var t=new O(gn(Bn(e)));return t._nextDay&&(t.add(1,\"d\"),t._nextDay=void 0),t}function Bn(e){var t=e._i,n=e._f;return e._locale=e._locale||yn(e._l),null===t||void 0===n&&\"\"===t?b({nullInput:!0}):(\"string\"===typeof t&&(e._i=t=e._locale.preparse(t)),C(t)?new O(gn(t)):(f(t)?e._d=t:c(n)?Wn(e):n?In(e):Yn(e),y(e)||(e._d=null),e))}function Yn(e){var t=e._i;u(t)?e._d=new Date(r.now()):f(t)?e._d=new Date(t.valueOf()):\"string\"===typeof t?jn(e):c(t)?(e._a=p(t.slice(0),(function(e){return parseInt(e,10)})),Rn(e)):i(t)?Un(e):s(t)?e._d=new Date(t):r.createFromInputFallback(e)}function qn(e,t,n,r,o){var a={};return!0!==t&&!1!==t||(r=t,t=void 0),!0!==n&&!1!==n||(r=n,n=void 0),(i(e)&&l(e)||c(e)&&0===e.length)&&(e=void 0),a._isAMomentObject=!0,a._useUTC=a._isUTC=o,a._l=n,a._i=e,a._f=t,a._strict=r,Kn(a)}function Gn(e,t,n,r){return qn(e,t,n,r,!1)}r.createFromInputFallback=S(\"value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.\",(function(e){e._d=new Date(e._i+(e._useUTC?\" UTC\":\"\"))})),r.ISO_8601=function(){},r.RFC_2822=function(){};var $n=S(\"moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Gn.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:b()})),Qn=S(\"moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/\",(function(){var e=Gn.apply(null,arguments);return this.isValid()&&e.isValid()?e>this?this:e:b()}));function Xn(e,t){var n,r;if(1===t.length&&c(t[0])&&(t=t[0]),!t.length)return Gn();for(n=t[0],r=1;r<t.length;++r)t[r].isValid()&&!t[r][e](n)||(n=t[r]);return n}function Zn(){return Xn(\"isBefore\",[].slice.call(arguments,0))}function Jn(){return Xn(\"isAfter\",[].slice.call(arguments,0))}var er=function(){return Date.now?Date.now():+new Date},tr=[\"year\",\"quarter\",\"month\",\"week\",\"day\",\"hour\",\"minute\",\"second\",\"millisecond\"];function nr(e){var t,n,r=!1;for(t in e)if(a(e,t)&&(-1===We.call(tr,t)||null!=e[t]&&isNaN(e[t])))return!1;for(n=0;n<tr.length;++n)if(e[tr[n]]){if(r)return!1;parseFloat(e[tr[n]])!==se(e[tr[n]])&&(r=!0)}return!0}function rr(){return this._isValid}function or(){return xr(NaN)}function cr(e){var t=oe(e),n=t.year||0,r=t.quarter||0,o=t.month||0,c=t.week||t.isoWeek||0,i=t.day||0,a=t.hour||0,l=t.minute||0,u=t.second||0,s=t.millisecond||0;this._isValid=nr(t),this._milliseconds=+s+1e3*u+6e4*l+1e3*a*60*60,this._days=+i+7*c,this._months=+o+3*r+12*n,this._data={},this._locale=yn(),this._bubble()}function ir(e){return e instanceof cr}function ar(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function lr(e,t,n){var r,o=Math.min(e.length,t.length),c=Math.abs(e.length-t.length),i=0;for(r=0;r<o;r++)(n&&e[r]!==t[r]||!n&&se(e[r])!==se(t[r]))&&i++;return i+c}function ur(e,t){I(e,0,0,(function(){var e=this.utcOffset(),n=\"+\";return e<0&&(e=-e,n=\"-\"),n+j(~~(e/60),2)+t+j(~~e%60,2)}))}ur(\"Z\",\":\"),ur(\"ZZ\",\"\"),Le(\"Z\",Pe),Le(\"ZZ\",Pe),Ae([\"Z\",\"ZZ\"],(function(e,t,n){n._useUTC=!0,n._tzm=fr(Pe,e)}));var sr=/([\\+\\-]|\\d\\d)/gi;function fr(e,t){var n,r,o=(t||\"\").match(e);return null===o?null:0===(r=60*(n=((o[o.length-1]||[])+\"\").match(sr)||[\"-\",0,0])[1]+se(n[2]))?0:\"+\"===n[0]?r:-r}function pr(e,t){var n,o;return t._isUTC?(n=t.clone(),o=(C(e)||f(e)?e.valueOf():Gn(e).valueOf())-n.valueOf(),n._d.setTime(n._d.valueOf()+o),r.updateOffset(n,!1),n):Gn(e).local()}function hr(e){return-Math.round(e._d.getTimezoneOffset())}function dr(e,t,n){var o,c=this._offset||0;if(!this.isValid())return null!=e?this:NaN;if(null!=e){if(\"string\"===typeof e){if(null===(e=fr(Pe,e)))return this}else Math.abs(e)<16&&!n&&(e*=60);return!this._isUTC&&t&&(o=hr(this)),this._offset=e,this._isUTC=!0,null!=o&&this.add(o,\"m\"),c!==e&&(!t||this._changeInProgress?Vr(this,xr(e-c,\"m\"),1,!1):this._changeInProgress||(this._changeInProgress=!0,r.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?c:hr(this)}function vr(e,t){return null!=e?(\"string\"!==typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}function mr(e){return this.utcOffset(0,e)}function yr(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(hr(this),\"m\")),this}function br(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if(\"string\"===typeof this._i){var e=fr(Ee,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this}function gr(e){return!!this.isValid()&&(e=e?Gn(e).utcOffset():0,(this.utcOffset()-e)%60===0)}function wr(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function zr(){if(!u(this._isDSTShifted))return this._isDSTShifted;var e,t={};return z(t,this),(t=Bn(t))._a?(e=t._isUTC?d(t._a):Gn(t._a),this._isDSTShifted=this.isValid()&&lr(t._a,e.toArray())>0):this._isDSTShifted=!1,this._isDSTShifted}function Or(){return!!this.isValid()&&!this._isUTC}function Cr(){return!!this.isValid()&&this._isUTC}function Mr(){return!!this.isValid()&&this._isUTC&&0===this._offset}r.updateOffset=function(){};var Sr=/^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,_r=/^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function xr(e,t){var n,r,o,c=e,i=null;return ir(e)?c={ms:e._milliseconds,d:e._days,M:e._months}:s(e)||!isNaN(+e)?(c={},t?c[t]=+e:c.milliseconds=+e):(i=Sr.exec(e))?(n=\"-\"===i[1]?-1:1,c={y:0,d:se(i[Be])*n,h:se(i[Ye])*n,m:se(i[qe])*n,s:se(i[Ge])*n,ms:se(ar(1e3*i[$e]))*n}):(i=_r.exec(e))?(n=\"-\"===i[1]?-1:1,c={y:kr(i[2],n),M:kr(i[3],n),w:kr(i[4],n),d:kr(i[5],n),h:kr(i[6],n),m:kr(i[7],n),s:kr(i[8],n)}):null==c?c={}:\"object\"===typeof c&&(\"from\"in c||\"to\"in c)&&(o=Er(Gn(c.from),Gn(c.to)),(c={}).ms=o.milliseconds,c.M=o.months),r=new cr(c),ir(e)&&a(e,\"_locale\")&&(r._locale=e._locale),ir(e)&&a(e,\"_isValid\")&&(r._isValid=e._isValid),r}function kr(e,t){var n=e&&parseFloat(e.replace(\",\",\".\"));return(isNaN(n)?0:n)*t}function Hr(e,t){var n={};return n.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(n.months,\"M\").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,\"M\"),n}function Er(e,t){var n;return e.isValid()&&t.isValid()?(t=pr(t,e),e.isBefore(t)?n=Hr(e,t):((n=Hr(t,e)).milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function Pr(e,t){return function(n,r){var o;return null===r||isNaN(+r)||(k(t,\"moment().\"+t+\"(period, number) is deprecated. Please use moment().\"+t+\"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.\"),o=n,n=r,r=o),Vr(this,xr(n,r),e),this}}function Vr(e,t,n,o){var c=t._milliseconds,i=ar(t._days),a=ar(t._months);e.isValid()&&(o=null==o||o,a&&ut(e,pe(e,\"Month\")+a*n),i&&he(e,\"Date\",pe(e,\"Date\")+i*n),c&&e._d.setTime(e._d.valueOf()+c*n),o&&r.updateOffset(e,i||a))}xr.fn=cr.prototype,xr.invalid=or;var Tr=Pr(1,\"add\"),Lr=Pr(-1,\"subtract\");function jr(e){return\"string\"===typeof e||e instanceof String}function Nr(e){return C(e)||f(e)||jr(e)||s(e)||Rr(e)||Dr(e)||null===e||void 0===e}function Dr(e){var t,n,r=i(e)&&!l(e),o=!1,c=[\"years\",\"year\",\"y\",\"months\",\"month\",\"M\",\"days\",\"day\",\"d\",\"dates\",\"date\",\"D\",\"hours\",\"hour\",\"h\",\"minutes\",\"minute\",\"m\",\"seconds\",\"second\",\"s\",\"milliseconds\",\"millisecond\",\"ms\"];for(t=0;t<c.length;t+=1)n=c[t],o=o||a(e,n);return r&&o}function Rr(e){var t=c(e),n=!1;return t&&(n=0===e.filter((function(t){return!s(t)&&jr(e)})).length),t&&n}function Ar(e){var t,n,r=i(e)&&!l(e),o=!1,c=[\"sameDay\",\"nextDay\",\"lastDay\",\"nextWeek\",\"lastWeek\",\"sameElse\"];for(t=0;t<c.length;t+=1)n=c[t],o=o||a(e,n);return r&&o}function Ir(e,t){var n=e.diff(t,\"days\",!0);return n<-6?\"sameElse\":n<-1?\"lastWeek\":n<0?\"lastDay\":n<1?\"sameDay\":n<2?\"nextDay\":n<7?\"nextWeek\":\"sameElse\"}function Fr(e,t){1===arguments.length&&(arguments[0]?Nr(arguments[0])?(e=arguments[0],t=void 0):Ar(arguments[0])&&(t=arguments[0],e=void 0):(e=void 0,t=void 0));var n=e||Gn(),o=pr(n,this).startOf(\"day\"),c=r.calendarFormat(this,o)||\"sameElse\",i=t&&(H(t[c])?t[c].call(this,n):t[c]);return this.format(i||this.localeData().calendar(c,this,Gn(n)))}function Wr(){return new O(this)}function Ur(e,t){var n=C(e)?e:Gn(e);return!(!this.isValid()||!n.isValid())&&(\"millisecond\"===(t=re(t)||\"millisecond\")?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(t).valueOf())}function Kr(e,t){var n=C(e)?e:Gn(e);return!(!this.isValid()||!n.isValid())&&(\"millisecond\"===(t=re(t)||\"millisecond\")?this.valueOf()<n.valueOf():this.clone().endOf(t).valueOf()<n.valueOf())}function Br(e,t,n,r){var o=C(e)?e:Gn(e),c=C(t)?t:Gn(t);return!!(this.isValid()&&o.isValid()&&c.isValid())&&(\"(\"===(r=r||\"()\")[0]?this.isAfter(o,n):!this.isBefore(o,n))&&(\")\"===r[1]?this.isBefore(c,n):!this.isAfter(c,n))}function Yr(e,t){var n,r=C(e)?e:Gn(e);return!(!this.isValid()||!r.isValid())&&(\"millisecond\"===(t=re(t)||\"millisecond\")?this.valueOf()===r.valueOf():(n=r.valueOf(),this.clone().startOf(t).valueOf()<=n&&n<=this.clone().endOf(t).valueOf()))}function qr(e,t){return this.isSame(e,t)||this.isAfter(e,t)}function Gr(e,t){return this.isSame(e,t)||this.isBefore(e,t)}function $r(e,t,n){var r,o,c;if(!this.isValid())return NaN;if(!(r=pr(e,this)).isValid())return NaN;switch(o=6e4*(r.utcOffset()-this.utcOffset()),t=re(t)){case\"year\":c=Qr(this,r)/12;break;case\"month\":c=Qr(this,r);break;case\"quarter\":c=Qr(this,r)/3;break;case\"second\":c=(this-r)/1e3;break;case\"minute\":c=(this-r)/6e4;break;case\"hour\":c=(this-r)/36e5;break;case\"day\":c=(this-r-o)/864e5;break;case\"week\":c=(this-r-o)/6048e5;break;default:c=this-r}return n?c:ue(c)}function Qr(e,t){if(e.date()<t.date())return-Qr(t,e);var n=12*(t.year()-e.year())+(t.month()-e.month()),r=e.clone().add(n,\"months\");return-(n+(t-r<0?(t-r)/(r-e.clone().add(n-1,\"months\")):(t-r)/(e.clone().add(n+1,\"months\")-r)))||0}function Xr(){return this.clone().locale(\"en\").format(\"ddd MMM DD YYYY HH:mm:ss [GMT]ZZ\")}function Zr(e){if(!this.isValid())return null;var t=!0!==e,n=t?this.clone().utc():this;return n.year()<0||n.year()>9999?U(n,t?\"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ\"):H(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace(\"Z\",U(n,\"Z\")):U(n,t?\"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]\":\"YYYY-MM-DD[T]HH:mm:ss.SSSZ\")}function Jr(){if(!this.isValid())return\"moment.invalid(/* \"+this._i+\" */)\";var e,t,n,r,o=\"moment\",c=\"\";return this.isLocal()||(o=0===this.utcOffset()?\"moment.utc\":\"moment.parseZone\",c=\"Z\"),e=\"[\"+o+'(\"]',t=0<=this.year()&&this.year()<=9999?\"YYYY\":\"YYYYYY\",n=\"-MM-DD[T]HH:mm:ss.SSS\",r=c+'[\")]',this.format(e+t+n+r)}function eo(e){e||(e=this.isUtc()?r.defaultFormatUtc:r.defaultFormat);var t=U(this,e);return this.localeData().postformat(t)}function to(e,t){return this.isValid()&&(C(e)&&e.isValid()||Gn(e).isValid())?xr({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function no(e){return this.from(Gn(),e)}function ro(e,t){return this.isValid()&&(C(e)&&e.isValid()||Gn(e).isValid())?xr({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function oo(e){return this.to(Gn(),e)}function co(e){var t;return void 0===e?this._locale._abbr:(null!=(t=yn(e))&&(this._locale=t),this)}r.defaultFormat=\"YYYY-MM-DDTHH:mm:ssZ\",r.defaultFormatUtc=\"YYYY-MM-DDTHH:mm:ss[Z]\";var io=S(\"moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.\",(function(e){return void 0===e?this.localeData():this.locale(e)}));function ao(){return this._locale}var lo=1e3,uo=60*lo,so=60*uo,fo=3506328*so;function po(e,t){return(e%t+t)%t}function ho(e,t,n){return e<100&&e>=0?new Date(e+400,t,n)-fo:new Date(e,t,n).valueOf()}function vo(e,t,n){return e<100&&e>=0?Date.UTC(e+400,t,n)-fo:Date.UTC(e,t,n)}function mo(e){var t,n;if(void 0===(e=re(e))||\"millisecond\"===e||!this.isValid())return this;switch(n=this._isUTC?vo:ho,e){case\"year\":t=n(this.year(),0,1);break;case\"quarter\":t=n(this.year(),this.month()-this.month()%3,1);break;case\"month\":t=n(this.year(),this.month(),1);break;case\"week\":t=n(this.year(),this.month(),this.date()-this.weekday());break;case\"isoWeek\":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case\"day\":case\"date\":t=n(this.year(),this.month(),this.date());break;case\"hour\":t=this._d.valueOf(),t-=po(t+(this._isUTC?0:this.utcOffset()*uo),so);break;case\"minute\":t=this._d.valueOf(),t-=po(t,uo);break;case\"second\":t=this._d.valueOf(),t-=po(t,lo)}return this._d.setTime(t),r.updateOffset(this,!0),this}function yo(e){var t,n;if(void 0===(e=re(e))||\"millisecond\"===e||!this.isValid())return this;switch(n=this._isUTC?vo:ho,e){case\"year\":t=n(this.year()+1,0,1)-1;break;case\"quarter\":t=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case\"month\":t=n(this.year(),this.month()+1,1)-1;break;case\"week\":t=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case\"isoWeek\":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case\"day\":case\"date\":t=n(this.year(),this.month(),this.date()+1)-1;break;case\"hour\":t=this._d.valueOf(),t+=so-po(t+(this._isUTC?0:this.utcOffset()*uo),so)-1;break;case\"minute\":t=this._d.valueOf(),t+=uo-po(t,uo)-1;break;case\"second\":t=this._d.valueOf(),t+=lo-po(t,lo)-1}return this._d.setTime(t),r.updateOffset(this,!0),this}function bo(){return this._d.valueOf()-6e4*(this._offset||0)}function go(){return Math.floor(this.valueOf()/1e3)}function wo(){return new Date(this.valueOf())}function zo(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function Oo(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}}function Co(){return this.isValid()?this.toISOString():null}function Mo(){return y(this)}function So(){return h({},m(this))}function _o(){return m(this).overflow}function xo(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function ko(e,t){var n,o,c,i=this._eras||yn(\"en\")._eras;for(n=0,o=i.length;n<o;++n){switch(typeof i[n].since){case\"string\":c=r(i[n].since).startOf(\"day\"),i[n].since=c.valueOf()}switch(typeof i[n].until){case\"undefined\":i[n].until=1/0;break;case\"string\":c=r(i[n].until).startOf(\"day\").valueOf(),i[n].until=c.valueOf()}}return i}function Ho(e,t,n){var r,o,c,i,a,l=this.eras();for(e=e.toUpperCase(),r=0,o=l.length;r<o;++r)if(c=l[r].name.toUpperCase(),i=l[r].abbr.toUpperCase(),a=l[r].narrow.toUpperCase(),n)switch(t){case\"N\":case\"NN\":case\"NNN\":if(i===e)return l[r];break;case\"NNNN\":if(c===e)return l[r];break;case\"NNNNN\":if(a===e)return l[r]}else if([c,i,a].indexOf(e)>=0)return l[r]}function Eo(e,t){var n=e.since<=e.until?1:-1;return void 0===t?r(e.since).year():r(e.since).year()+(t-e.offset)*n}function Po(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;e<t;++e){if(n=this.clone().startOf(\"day\").valueOf(),r[e].since<=n&&n<=r[e].until)return r[e].name;if(r[e].until<=n&&n<=r[e].since)return r[e].name}return\"\"}function Vo(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;e<t;++e){if(n=this.clone().startOf(\"day\").valueOf(),r[e].since<=n&&n<=r[e].until)return r[e].narrow;if(r[e].until<=n&&n<=r[e].since)return r[e].narrow}return\"\"}function To(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;e<t;++e){if(n=this.clone().startOf(\"day\").valueOf(),r[e].since<=n&&n<=r[e].until)return r[e].abbr;if(r[e].until<=n&&n<=r[e].since)return r[e].abbr}return\"\"}function Lo(){var e,t,n,o,c=this.localeData().eras();for(e=0,t=c.length;e<t;++e)if(n=c[e].since<=c[e].until?1:-1,o=this.clone().startOf(\"day\").valueOf(),c[e].since<=o&&o<=c[e].until||c[e].until<=o&&o<=c[e].since)return(this.year()-r(c[e].since).year())*n+c[e].offset;return this.year()}function jo(e){return a(this,\"_erasNameRegex\")||Wo.call(this),e?this._erasNameRegex:this._erasRegex}function No(e){return a(this,\"_erasAbbrRegex\")||Wo.call(this),e?this._erasAbbrRegex:this._erasRegex}function Do(e){return a(this,\"_erasNarrowRegex\")||Wo.call(this),e?this._erasNarrowRegex:this._erasRegex}function Ro(e,t){return t.erasAbbrRegex(e)}function Ao(e,t){return t.erasNameRegex(e)}function Io(e,t){return t.erasNarrowRegex(e)}function Fo(e,t){return t._eraYearOrdinalRegex||ke}function Wo(){var e,t,n=[],r=[],o=[],c=[],i=this.eras();for(e=0,t=i.length;e<t;++e)r.push(De(i[e].name)),n.push(De(i[e].abbr)),o.push(De(i[e].narrow)),c.push(De(i[e].name)),c.push(De(i[e].abbr)),c.push(De(i[e].narrow));this._erasRegex=new RegExp(\"^(\"+c.join(\"|\")+\")\",\"i\"),this._erasNameRegex=new RegExp(\"^(\"+r.join(\"|\")+\")\",\"i\"),this._erasAbbrRegex=new RegExp(\"^(\"+n.join(\"|\")+\")\",\"i\"),this._erasNarrowRegex=new RegExp(\"^(\"+o.join(\"|\")+\")\",\"i\")}function Uo(e,t){I(0,[e,e.length],0,t)}function Ko(e){return Qo.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Bo(e){return Qo.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)}function Yo(){return Ct(this.year(),1,4)}function qo(){return Ct(this.isoWeekYear(),1,4)}function Go(){var e=this.localeData()._week;return Ct(this.year(),e.dow,e.doy)}function $o(){var e=this.localeData()._week;return Ct(this.weekYear(),e.dow,e.doy)}function Qo(e,t,n,r,o){var c;return null==e?Ot(this,r,o).year:(t>(c=Ct(e,r,o))&&(t=c),Xo.call(this,e,t,n,r,o))}function Xo(e,t,n,r,o){var c=zt(e,t,n,r,o),i=gt(c.year,0,c.dayOfYear);return this.year(i.getUTCFullYear()),this.month(i.getUTCMonth()),this.date(i.getUTCDate()),this}function Zo(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)}I(\"N\",0,0,\"eraAbbr\"),I(\"NN\",0,0,\"eraAbbr\"),I(\"NNN\",0,0,\"eraAbbr\"),I(\"NNNN\",0,0,\"eraName\"),I(\"NNNNN\",0,0,\"eraNarrow\"),I(\"y\",[\"y\",1],\"yo\",\"eraYear\"),I(\"y\",[\"yy\",2],0,\"eraYear\"),I(\"y\",[\"yyy\",3],0,\"eraYear\"),I(\"y\",[\"yyyy\",4],0,\"eraYear\"),Le(\"N\",Ro),Le(\"NN\",Ro),Le(\"NNN\",Ro),Le(\"NNNN\",Ao),Le(\"NNNNN\",Io),Ae([\"N\",\"NN\",\"NNN\",\"NNNN\",\"NNNNN\"],(function(e,t,n,r){var o=n._locale.erasParse(e,r,n._strict);o?m(n).era=o:m(n).invalidEra=e})),Le(\"y\",ke),Le(\"yy\",ke),Le(\"yyy\",ke),Le(\"yyyy\",ke),Le(\"yo\",Fo),Ae([\"y\",\"yy\",\"yyy\",\"yyyy\"],Ue),Ae([\"yo\"],(function(e,t,n,r){var o;n._locale._eraYearOrdinalRegex&&(o=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[Ue]=n._locale.eraYearOrdinalParse(e,o):t[Ue]=parseInt(e,10)})),I(0,[\"gg\",2],0,(function(){return this.weekYear()%100})),I(0,[\"GG\",2],0,(function(){return this.isoWeekYear()%100})),Uo(\"gggg\",\"weekYear\"),Uo(\"ggggg\",\"weekYear\"),Uo(\"GGGG\",\"isoWeekYear\"),Uo(\"GGGGG\",\"isoWeekYear\"),ne(\"weekYear\",\"gg\"),ne(\"isoWeekYear\",\"GG\"),ie(\"weekYear\",1),ie(\"isoWeekYear\",1),Le(\"G\",He),Le(\"g\",He),Le(\"GG\",Oe,be),Le(\"gg\",Oe,be),Le(\"GGGG\",_e,we),Le(\"gggg\",_e,we),Le(\"GGGGG\",xe,ze),Le(\"ggggg\",xe,ze),Ie([\"gggg\",\"ggggg\",\"GGGG\",\"GGGGG\"],(function(e,t,n,r){t[r.substr(0,2)]=se(e)})),Ie([\"gg\",\"GG\"],(function(e,t,n,o){t[o]=r.parseTwoDigitYear(e)})),I(\"Q\",0,\"Qo\",\"quarter\"),ne(\"quarter\",\"Q\"),ie(\"quarter\",7),Le(\"Q\",ye),Ae(\"Q\",(function(e,t){t[Ke]=3*(se(e)-1)})),I(\"D\",[\"DD\",2],\"Do\",\"date\"),ne(\"date\",\"D\"),ie(\"date\",9),Le(\"D\",Oe),Le(\"DD\",Oe,be),Le(\"Do\",(function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient})),Ae([\"D\",\"DD\"],Be),Ae(\"Do\",(function(e,t){t[Be]=se(e.match(Oe)[0])}));var Jo=fe(\"Date\",!0);function ec(e){var t=Math.round((this.clone().startOf(\"day\")-this.clone().startOf(\"year\"))/864e5)+1;return null==e?t:this.add(e-t,\"d\")}I(\"DDD\",[\"DDDD\",3],\"DDDo\",\"dayOfYear\"),ne(\"dayOfYear\",\"DDD\"),ie(\"dayOfYear\",4),Le(\"DDD\",Se),Le(\"DDDD\",ge),Ae([\"DDD\",\"DDDD\"],(function(e,t,n){n._dayOfYear=se(e)})),I(\"m\",[\"mm\",2],0,\"minute\"),ne(\"minute\",\"m\"),ie(\"minute\",14),Le(\"m\",Oe),Le(\"mm\",Oe,be),Ae([\"m\",\"mm\"],qe);var tc=fe(\"Minutes\",!1);I(\"s\",[\"ss\",2],0,\"second\"),ne(\"second\",\"s\"),ie(\"second\",15),Le(\"s\",Oe),Le(\"ss\",Oe,be),Ae([\"s\",\"ss\"],Ge);var nc,rc,oc=fe(\"Seconds\",!1);for(I(\"S\",0,0,(function(){return~~(this.millisecond()/100)})),I(0,[\"SS\",2],0,(function(){return~~(this.millisecond()/10)})),I(0,[\"SSS\",3],0,\"millisecond\"),I(0,[\"SSSS\",4],0,(function(){return 10*this.millisecond()})),I(0,[\"SSSSS\",5],0,(function(){return 100*this.millisecond()})),I(0,[\"SSSSSS\",6],0,(function(){return 1e3*this.millisecond()})),I(0,[\"SSSSSSS\",7],0,(function(){return 1e4*this.millisecond()})),I(0,[\"SSSSSSSS\",8],0,(function(){return 1e5*this.millisecond()})),I(0,[\"SSSSSSSSS\",9],0,(function(){return 1e6*this.millisecond()})),ne(\"millisecond\",\"ms\"),ie(\"millisecond\",16),Le(\"S\",Se,ye),Le(\"SS\",Se,be),Le(\"SSS\",Se,ge),nc=\"SSSS\";nc.length<=9;nc+=\"S\")Le(nc,ke);function cc(e,t){t[$e]=se(1e3*(\"0.\"+e))}for(nc=\"S\";nc.length<=9;nc+=\"S\")Ae(nc,cc);function ic(){return this._isUTC?\"UTC\":\"\"}function ac(){return this._isUTC?\"Coordinated Universal Time\":\"\"}rc=fe(\"Milliseconds\",!1),I(\"z\",0,0,\"zoneAbbr\"),I(\"zz\",0,0,\"zoneName\");var lc=O.prototype;function uc(e){return Gn(1e3*e)}function sc(){return Gn.apply(null,arguments).parseZone()}function fc(e){return e}lc.add=Tr,lc.calendar=Fr,lc.clone=Wr,lc.diff=$r,lc.endOf=yo,lc.format=eo,lc.from=to,lc.fromNow=no,lc.to=ro,lc.toNow=oo,lc.get=de,lc.invalidAt=_o,lc.isAfter=Ur,lc.isBefore=Kr,lc.isBetween=Br,lc.isSame=Yr,lc.isSameOrAfter=qr,lc.isSameOrBefore=Gr,lc.isValid=Mo,lc.lang=io,lc.locale=co,lc.localeData=ao,lc.max=Qn,lc.min=$n,lc.parsingFlags=So,lc.set=ve,lc.startOf=mo,lc.subtract=Lr,lc.toArray=zo,lc.toObject=Oo,lc.toDate=wo,lc.toISOString=Zr,lc.inspect=Jr,\"undefined\"!==typeof Symbol&&null!=Symbol.for&&(lc[Symbol.for(\"nodejs.util.inspect.custom\")]=function(){return\"Moment<\"+this.format()+\">\"}),lc.toJSON=Co,lc.toString=Xr,lc.unix=go,lc.valueOf=bo,lc.creationData=xo,lc.eraName=Po,lc.eraNarrow=Vo,lc.eraAbbr=To,lc.eraYear=Lo,lc.year=mt,lc.isLeapYear=yt,lc.weekYear=Ko,lc.isoWeekYear=Bo,lc.quarter=lc.quarters=Zo,lc.month=st,lc.daysInMonth=ft,lc.week=lc.weeks=kt,lc.isoWeek=lc.isoWeeks=Ht,lc.weeksInYear=Go,lc.weeksInWeekYear=$o,lc.isoWeeksInYear=Yo,lc.isoWeeksInISOWeekYear=qo,lc.date=Jo,lc.day=lc.days=Kt,lc.weekday=Bt,lc.isoWeekday=Yt,lc.dayOfYear=ec,lc.hour=lc.hours=rn,lc.minute=lc.minutes=tc,lc.second=lc.seconds=oc,lc.millisecond=lc.milliseconds=rc,lc.utcOffset=dr,lc.utc=mr,lc.local=yr,lc.parseZone=br,lc.hasAlignedHourOffset=gr,lc.isDST=wr,lc.isLocal=Or,lc.isUtcOffset=Cr,lc.isUtc=Mr,lc.isUTC=Mr,lc.zoneAbbr=ic,lc.zoneName=ac,lc.dates=S(\"dates accessor is deprecated. Use date instead.\",Jo),lc.months=S(\"months accessor is deprecated. Use month instead\",st),lc.years=S(\"years accessor is deprecated. Use year instead\",mt),lc.zone=S(\"moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/\",vr),lc.isDSTShifted=S(\"isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information\",zr);var pc=V.prototype;function hc(e,t,n,r){var o=yn(),c=d().set(r,t);return o[n](c,e)}function dc(e,t,n){if(s(e)&&(t=e,e=void 0),e=e||\"\",null!=t)return hc(e,t,n,\"month\");var r,o=[];for(r=0;r<12;r++)o[r]=hc(e,r,n,\"month\");return o}function vc(e,t,n,r){\"boolean\"===typeof e?(s(t)&&(n=t,t=void 0),t=t||\"\"):(n=t=e,e=!1,s(t)&&(n=t,t=void 0),t=t||\"\");var o,c=yn(),i=e?c._week.dow:0,a=[];if(null!=n)return hc(t,(n+i)%7,r,\"day\");for(o=0;o<7;o++)a[o]=hc(t,(o+i)%7,r,\"day\");return a}function mc(e,t){return dc(e,t,\"months\")}function yc(e,t){return dc(e,t,\"monthsShort\")}function bc(e,t,n){return vc(e,t,n,\"weekdays\")}function gc(e,t,n){return vc(e,t,n,\"weekdaysShort\")}function wc(e,t,n){return vc(e,t,n,\"weekdaysMin\")}pc.calendar=L,pc.longDateFormat=Y,pc.invalidDate=G,pc.ordinal=X,pc.preparse=fc,pc.postformat=fc,pc.relativeTime=J,pc.pastFuture=ee,pc.set=E,pc.eras=ko,pc.erasParse=Ho,pc.erasConvertYear=Eo,pc.erasAbbrRegex=No,pc.erasNameRegex=jo,pc.erasNarrowRegex=Do,pc.months=ct,pc.monthsShort=it,pc.monthsParse=lt,pc.monthsRegex=ht,pc.monthsShortRegex=pt,pc.week=Mt,pc.firstDayOfYear=xt,pc.firstDayOfWeek=_t,pc.weekdays=At,pc.weekdaysMin=Ft,pc.weekdaysShort=It,pc.weekdaysParse=Ut,pc.weekdaysRegex=qt,pc.weekdaysShortRegex=Gt,pc.weekdaysMinRegex=$t,pc.isPM=tn,pc.meridiem=on,dn(\"en\",{eras:[{since:\"0001-01-01\",until:1/0,offset:1,name:\"Anno Domini\",narrow:\"AD\",abbr:\"AD\"},{since:\"0000-12-31\",until:-1/0,offset:1,name:\"Before Christ\",narrow:\"BC\",abbr:\"BC\"}],dayOfMonthOrdinalParse:/\\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===se(e%100/10)?\"th\":1===t?\"st\":2===t?\"nd\":3===t?\"rd\":\"th\")}}),r.lang=S(\"moment.lang is deprecated. Use moment.locale instead.\",dn),r.langData=S(\"moment.langData is deprecated. Use moment.localeData instead.\",yn);var zc=Math.abs;function Oc(){var e=this._data;return this._milliseconds=zc(this._milliseconds),this._days=zc(this._days),this._months=zc(this._months),e.milliseconds=zc(e.milliseconds),e.seconds=zc(e.seconds),e.minutes=zc(e.minutes),e.hours=zc(e.hours),e.months=zc(e.months),e.years=zc(e.years),this}function Cc(e,t,n,r){var o=xr(t,n);return e._milliseconds+=r*o._milliseconds,e._days+=r*o._days,e._months+=r*o._months,e._bubble()}function Mc(e,t){return Cc(this,e,t,1)}function Sc(e,t){return Cc(this,e,t,-1)}function _c(e){return e<0?Math.floor(e):Math.ceil(e)}function xc(){var e,t,n,r,o,c=this._milliseconds,i=this._days,a=this._months,l=this._data;return c>=0&&i>=0&&a>=0||c<=0&&i<=0&&a<=0||(c+=864e5*_c(Hc(a)+i),i=0,a=0),l.milliseconds=c%1e3,e=ue(c/1e3),l.seconds=e%60,t=ue(e/60),l.minutes=t%60,n=ue(t/60),l.hours=n%24,i+=ue(n/24),a+=o=ue(kc(i)),i-=_c(Hc(o)),r=ue(a/12),a%=12,l.days=i,l.months=a,l.years=r,this}function kc(e){return 4800*e/146097}function Hc(e){return 146097*e/4800}function Ec(e){if(!this.isValid())return NaN;var t,n,r=this._milliseconds;if(\"month\"===(e=re(e))||\"quarter\"===e||\"year\"===e)switch(t=this._days+r/864e5,n=this._months+kc(t),e){case\"month\":return n;case\"quarter\":return n/3;case\"year\":return n/12}else switch(t=this._days+Math.round(Hc(this._months)),e){case\"week\":return t/7+r/6048e5;case\"day\":return t+r/864e5;case\"hour\":return 24*t+r/36e5;case\"minute\":return 1440*t+r/6e4;case\"second\":return 86400*t+r/1e3;case\"millisecond\":return Math.floor(864e5*t)+r;default:throw new Error(\"Unknown unit \"+e)}}function Pc(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*se(this._months/12):NaN}function Vc(e){return function(){return this.as(e)}}var Tc=Vc(\"ms\"),Lc=Vc(\"s\"),jc=Vc(\"m\"),Nc=Vc(\"h\"),Dc=Vc(\"d\"),Rc=Vc(\"w\"),Ac=Vc(\"M\"),Ic=Vc(\"Q\"),Fc=Vc(\"y\");function Wc(){return xr(this)}function Uc(e){return e=re(e),this.isValid()?this[e+\"s\"]():NaN}function Kc(e){return function(){return this.isValid()?this._data[e]:NaN}}var Bc=Kc(\"milliseconds\"),Yc=Kc(\"seconds\"),qc=Kc(\"minutes\"),Gc=Kc(\"hours\"),$c=Kc(\"days\"),Qc=Kc(\"months\"),Xc=Kc(\"years\");function Zc(){return ue(this.days()/7)}var Jc=Math.round,ei={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function ti(e,t,n,r,o){return o.relativeTime(t||1,!!n,e,r)}function ni(e,t,n,r){var o=xr(e).abs(),c=Jc(o.as(\"s\")),i=Jc(o.as(\"m\")),a=Jc(o.as(\"h\")),l=Jc(o.as(\"d\")),u=Jc(o.as(\"M\")),s=Jc(o.as(\"w\")),f=Jc(o.as(\"y\")),p=c<=n.ss&&[\"s\",c]||c<n.s&&[\"ss\",c]||i<=1&&[\"m\"]||i<n.m&&[\"mm\",i]||a<=1&&[\"h\"]||a<n.h&&[\"hh\",a]||l<=1&&[\"d\"]||l<n.d&&[\"dd\",l];return null!=n.w&&(p=p||s<=1&&[\"w\"]||s<n.w&&[\"ww\",s]),(p=p||u<=1&&[\"M\"]||u<n.M&&[\"MM\",u]||f<=1&&[\"y\"]||[\"yy\",f])[2]=t,p[3]=+e>0,p[4]=r,ti.apply(null,p)}function ri(e){return void 0===e?Jc:\"function\"===typeof e&&(Jc=e,!0)}function oi(e,t){return void 0!==ei[e]&&(void 0===t?ei[e]:(ei[e]=t,\"s\"===e&&(ei.ss=t-1),!0))}function ci(e,t){if(!this.isValid())return this.localeData().invalidDate();var n,r,o=!1,c=ei;return\"object\"===typeof e&&(t=e,e=!1),\"boolean\"===typeof e&&(o=e),\"object\"===typeof t&&(c=Object.assign({},ei,t),null!=t.s&&null==t.ss&&(c.ss=t.s-1)),r=ni(this,!o,c,n=this.localeData()),o&&(r=n.pastFuture(+this,r)),n.postformat(r)}var ii=Math.abs;function ai(e){return(e>0)-(e<0)||+e}function li(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,r,o,c,i,a,l=ii(this._milliseconds)/1e3,u=ii(this._days),s=ii(this._months),f=this.asSeconds();return f?(e=ue(l/60),t=ue(e/60),l%=60,e%=60,n=ue(s/12),s%=12,r=l?l.toFixed(3).replace(/\\.?0+$/,\"\"):\"\",o=f<0?\"-\":\"\",c=ai(this._months)!==ai(f)?\"-\":\"\",i=ai(this._days)!==ai(f)?\"-\":\"\",a=ai(this._milliseconds)!==ai(f)?\"-\":\"\",o+\"P\"+(n?c+n+\"Y\":\"\")+(s?c+s+\"M\":\"\")+(u?i+u+\"D\":\"\")+(t||e||l?\"T\":\"\")+(t?a+t+\"H\":\"\")+(e?a+e+\"M\":\"\")+(l?a+r+\"S\":\"\")):\"P0D\"}var ui=cr.prototype;return ui.isValid=rr,ui.abs=Oc,ui.add=Mc,ui.subtract=Sc,ui.as=Ec,ui.asMilliseconds=Tc,ui.asSeconds=Lc,ui.asMinutes=jc,ui.asHours=Nc,ui.asDays=Dc,ui.asWeeks=Rc,ui.asMonths=Ac,ui.asQuarters=Ic,ui.asYears=Fc,ui.valueOf=Pc,ui._bubble=xc,ui.clone=Wc,ui.get=Uc,ui.milliseconds=Bc,ui.seconds=Yc,ui.minutes=qc,ui.hours=Gc,ui.days=$c,ui.weeks=Zc,ui.months=Qc,ui.years=Xc,ui.humanize=ci,ui.toISOString=li,ui.toString=li,ui.toJSON=li,ui.locale=co,ui.localeData=ao,ui.toIsoString=S(\"toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)\",li),ui.lang=io,I(\"X\",0,0,\"unix\"),I(\"x\",0,0,\"valueOf\"),Le(\"x\",He),Le(\"X\",Ve),Ae(\"X\",(function(e,t,n){n._d=new Date(1e3*parseFloat(e))})),Ae(\"x\",(function(e,t,n){n._d=new Date(se(e))})),r.version=\"2.29.1\",o(Gn),r.fn=lc,r.min=Zn,r.max=Jn,r.now=er,r.utc=d,r.unix=uc,r.months=mc,r.isDate=f,r.locale=dn,r.invalid=b,r.duration=xr,r.isMoment=C,r.weekdays=bc,r.parseZone=sc,r.localeData=yn,r.isDuration=ir,r.monthsShort=yc,r.weekdaysMin=wc,r.defineLocale=vn,r.updateLocale=mn,r.locales=bn,r.weekdaysShort=gc,r.normalizeUnits=re,r.relativeTimeRounding=ri,r.relativeTimeThreshold=oi,r.calendarFormat=Ir,r.prototype=lc,r.HTML5_FMT={DATETIME_LOCAL:\"YYYY-MM-DDTHH:mm\",DATETIME_LOCAL_SECONDS:\"YYYY-MM-DDTHH:mm:ss\",DATETIME_LOCAL_MS:\"YYYY-MM-DDTHH:mm:ss.SSS\",DATE:\"YYYY-MM-DD\",TIME:\"HH:mm\",TIME_SECONDS:\"HH:mm:ss\",TIME_MS:\"HH:mm:ss.SSS\",WEEK:\"GGGG-[W]WW\",MONTH:\"YYYY-MM\"},r}()}).call(this,n(72)(e))},function(e,t,n){\"use strict\";n.p},function(e,t,n){\"use strict\";t.__esModule=!0,t.default=function(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return r}));var r=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t}},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0}),t.create=t.connect=t.Provider=void 0;var r=i(n(226)),o=i(n(227)),c=i(n(229));function i(e){return e&&e.__esModule?e:{default:e}}t.Provider=r.default,t.connect=o.default,t.create=c.default},function(e,t,n){\"use strict\";t.__esModule=!0;var r=c(n(0)),o=c(n(217));function c(e){return e&&e.__esModule?e:{default:e}}t.default=r.default.createContext||o.default,e.exports=t.default},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return d}));var r=n(0),o=n(1),c=n(39).a;function i(e){return(i=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function a(){return(a=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function l(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function u(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function s(e,t){return(s=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function f(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=h(e);if(t){var o=h(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return p(this,n)}}function p(e,t){return!t||\"object\"!==i(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function h(e){return(h=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var d=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&s(e,t)}(i,e);var t,n,r,o=f(i);function i(){return l(this,i),o.apply(this,arguments)}return t=i,(n=[{key:\"getLocale\",value:function(){var e=this.props,t=e.componentName,n=e.defaultLocale||c[t||\"global\"],r=this.context.antLocale,o=t&&r?r[t]:{};return a(a({},\"function\"===typeof n?n():n),o||{})}},{key:\"getLocaleCode\",value:function(){var e=this.context.antLocale,t=e&&e.locale;return e&&e.exist&&!t?c.locale:t}},{key:\"render\",value:function(){return this.props.children(this.getLocale(),this.getLocaleCode(),this.context.antLocale)}}])&&u(t.prototype,n),r&&u(t,r),i}(r.Component);d.defaultProps={componentName:\"global\"},d.contextTypes={antLocale:o.object}},function(e,t,n){\"use strict\";function r(e){return null!=e&&\"object\"===typeof e&&!0===e[\"@@functional/placeholder\"]}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";var r={transitionstart:{transition:\"transitionstart\",WebkitTransition:\"webkitTransitionStart\",MozTransition:\"mozTransitionStart\",OTransition:\"oTransitionStart\",msTransition:\"MSTransitionStart\"},animationstart:{animation:\"animationstart\",WebkitAnimation:\"webkitAnimationStart\",MozAnimation:\"mozAnimationStart\",OAnimation:\"oAnimationStart\",msAnimation:\"MSAnimationStart\"}},o={transitionend:{transition:\"transitionend\",WebkitTransition:\"webkitTransitionEnd\",MozTransition:\"mozTransitionEnd\",OTransition:\"oTransitionEnd\",msTransition:\"MSTransitionEnd\"},animationend:{animation:\"animationend\",WebkitAnimation:\"webkitAnimationEnd\",MozAnimation:\"mozAnimationEnd\",OAnimation:\"oAnimationEnd\",msAnimation:\"MSAnimationEnd\"}},c=[],i=[];function a(e,t,n){e.addEventListener(t,n,!1)}function l(e,t,n){e.removeEventListener(t,n,!1)}\"undefined\"!==typeof window&&\"undefined\"!==typeof document&&function(){var e=document.createElement(\"div\").style;function t(t,n){for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];for(var c in o)if(c in e){n.push(o[c]);break}}}\"AnimationEvent\"in window||(delete r.animationstart.animation,delete o.animationend.animation),\"TransitionEvent\"in window||(delete r.transitionstart.transition,delete o.transitionend.transition),t(r,c),t(o,i)}();var u={startEvents:c,addStartEventListener:function(e,t){0!==c.length?c.forEach((function(n){a(e,n,t)})):window.setTimeout(t,0)},removeStartEventListener:function(e,t){0!==c.length&&c.forEach((function(n){l(e,n,t)}))},endEvents:i,addEndEventListener:function(e,t){0!==i.length?i.forEach((function(n){a(e,n,t)})):window.setTimeout(t,0)},removeEndEventListener:function(e,t){0!==i.length&&i.forEach((function(n){l(e,n,t)}))}};t.a=u},function(e,t,n){\"use strict\";var r=n(5),o=n.n(r),c=n(7),i=n.n(c),a=n(10),l=n.n(a),u=n(14),s=n.n(u),f=n(6),p=n.n(f),h=n(11),d=n.n(h),v=n(0),m=n.n(v),y=n(1),b=n.n(y),g=function(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Error(\"Can only polyfill class components\");return\"function\"!==typeof t.componentWillReceiveProps?e:m.a.Profiler?(t.UNSAFE_componentWillReceiveProps=t.componentWillReceiveProps,delete t.componentWillReceiveProps,e):e};function w(e){var t=[];return m.a.Children.forEach(e,(function(e){t.push(e)})),t}function z(e,t){var n=null;return e&&e.forEach((function(e){n||e&&e.key===t&&(n=e)})),n}function O(e,t,n){var r=null;return e&&e.forEach((function(e){if(e&&e.key===t&&e.props[n]){if(r)throw new Error(\"two child with same key for <rc-animate> children\");r=e}})),r}var C=n(9),M=n.n(C),S=n(45),_={isAppearSupported:function(e){return e.transitionName&&e.transitionAppear||e.animation.appear},isEnterSupported:function(e){return e.transitionName&&e.transitionEnter||e.animation.enter},isLeaveSupported:function(e){return e.transitionName&&e.transitionLeave||e.animation.leave},allowAppearCallback:function(e){return e.transitionAppear||e.animation.appear},allowEnterCallback:function(e){return e.transitionEnter||e.animation.enter},allowLeaveCallback:function(e){return e.transitionLeave||e.animation.leave}},x={enter:\"transitionEnter\",appear:\"transitionAppear\",leave:\"transitionLeave\"},k=function(e){function t(){return l()(this,t),p()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return d()(t,e),s()(t,[{key:\"componentWillUnmount\",value:function(){this.stop()}},{key:\"componentWillEnter\",value:function(e){_.isEnterSupported(this.props)?this.transition(\"enter\",e):e()}},{key:\"componentWillAppear\",value:function(e){_.isAppearSupported(this.props)?this.transition(\"appear\",e):e()}},{key:\"componentWillLeave\",value:function(e){_.isLeaveSupported(this.props)?this.transition(\"leave\",e):e()}},{key:\"transition\",value:function(e,t){var n=this,r=M.a.findDOMNode(this),o=this.props,c=o.transitionName,i=\"object\"===typeof c;this.stop();var a=function(){n.stopper=null,t()};if((S.b||!o.animation[e])&&c&&o[x[e]]){var l=i?c[e]:c+\"-\"+e,u=l+\"-active\";i&&c[e+\"Active\"]&&(u=c[e+\"Active\"]),this.stopper=Object(S.a)(r,{name:l,active:u},a)}else this.stopper=o.animation[e](r,a)}},{key:\"stop\",value:function(){var e=this.stopper;e&&(this.stopper=null,e.stop())}},{key:\"render\",value:function(){return this.props.children}}]),t}(m.a.Component);k.propTypes={children:b.a.any,animation:b.a.any,transitionName:b.a.any};var H=k,E=\"rc_animate_\"+Date.now();function P(e){var t=e.children;return m.a.isValidElement(t)&&!t.key?m.a.cloneElement(t,{key:E}):t}function V(){}var T=function(e){function t(e){l()(this,t);var n=p()(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return L.call(n),n.currentlyAnimatingKeys={},n.keysToEnter=[],n.keysToLeave=[],n.state={children:w(P(e))},n.childrenRefs={},n}return d()(t,e),s()(t,[{key:\"componentDidMount\",value:function(){var e=this,t=this.props.showProp,n=this.state.children;t&&(n=n.filter((function(e){return!!e.props[t]}))),n.forEach((function(t){t&&e.performAppear(t.key)}))}},{key:\"componentWillReceiveProps\",value:function(e){var t=this;this.nextProps=e;var n=w(P(e)),r=this.props;r.exclusive&&Object.keys(this.currentlyAnimatingKeys).forEach((function(e){t.stop(e)}));var o=r.showProp,c=this.currentlyAnimatingKeys,a=r.exclusive?w(P(r)):this.state.children,l=[];o?(a.forEach((function(e){var t=e&&z(n,e.key),r=void 0;(r=t&&t.props[o]||!e.props[o]?t:m.a.cloneElement(t||e,i()({},o,!0)))&&l.push(r)})),n.forEach((function(e){e&&z(a,e.key)||l.push(e)}))):l=function(e,t){var n=[],r={},o=[];return e.forEach((function(e){e&&z(t,e.key)?o.length&&(r[e.key]=o,o=[]):o.push(e)})),t.forEach((function(e){e&&Object.prototype.hasOwnProperty.call(r,e.key)&&(n=n.concat(r[e.key])),n.push(e)})),n=n.concat(o)}(a,n),this.setState({children:l}),n.forEach((function(e){var n=e&&e.key;if(!e||!c[n]){var r=e&&z(a,n);if(o){var i=e.props[o];if(r)!O(a,n,o)&&i&&t.keysToEnter.push(n);else i&&t.keysToEnter.push(n)}else r||t.keysToEnter.push(n)}})),a.forEach((function(e){var r=e&&e.key;if(!e||!c[r]){var i=e&&z(n,r);if(o){var a=e.props[o];if(i)!O(n,r,o)&&a&&t.keysToLeave.push(r);else a&&t.keysToLeave.push(r)}else i||t.keysToLeave.push(r)}}))}},{key:\"componentDidUpdate\",value:function(){var e=this.keysToEnter;this.keysToEnter=[],e.forEach(this.performEnter);var t=this.keysToLeave;this.keysToLeave=[],t.forEach(this.performLeave)}},{key:\"isValidChildByKey\",value:function(e,t){var n=this.props.showProp;return n?O(e,t,n):z(e,t)}},{key:\"stop\",value:function(e){delete this.currentlyAnimatingKeys[e];var t=this.childrenRefs[e];t&&t.stop()}},{key:\"render\",value:function(){var e=this,t=this.props;this.nextProps=t;var n=this.state.children,r=null;n&&(r=n.map((function(n){if(null===n||void 0===n)return n;if(!n.key)throw new Error(\"must set key for <rc-animate> children\");return m.a.createElement(H,{key:n.key,ref:function(t){e.childrenRefs[n.key]=t},animation:t.animation,transitionName:t.transitionName,transitionEnter:t.transitionEnter,transitionAppear:t.transitionAppear,transitionLeave:t.transitionLeave},n)})));var c=t.component;if(c){var i=t;return\"string\"===typeof c&&(i=o()({className:t.className,style:t.style},t.componentProps)),m.a.createElement(c,i,r)}return r[0]||null}}]),t}(m.a.Component);T.isAnimate=!0,T.propTypes={className:b.a.string,style:b.a.object,component:b.a.any,componentProps:b.a.object,animation:b.a.object,transitionName:b.a.oneOfType([b.a.string,b.a.object]),transitionEnter:b.a.bool,transitionAppear:b.a.bool,exclusive:b.a.bool,transitionLeave:b.a.bool,onEnd:b.a.func,onEnter:b.a.func,onLeave:b.a.func,onAppear:b.a.func,showProp:b.a.string,children:b.a.node},T.defaultProps={animation:{},component:\"span\",componentProps:{},transitionEnter:!0,transitionLeave:!0,transitionAppear:!1,onEnd:V,onEnter:V,onLeave:V,onAppear:V};var L=function(){var e=this;this.performEnter=function(t){e.childrenRefs[t]&&(e.currentlyAnimatingKeys[t]=!0,e.childrenRefs[t].componentWillEnter(e.handleDoneAdding.bind(e,t,\"enter\")))},this.performAppear=function(t){e.childrenRefs[t]&&(e.currentlyAnimatingKeys[t]=!0,e.childrenRefs[t].componentWillAppear(e.handleDoneAdding.bind(e,t,\"appear\")))},this.handleDoneAdding=function(t,n){var r=e.props;if(delete e.currentlyAnimatingKeys[t],!r.exclusive||r===e.nextProps){var o=w(P(r));e.isValidChildByKey(o,t)?\"appear\"===n?_.allowAppearCallback(r)&&(r.onAppear(t),r.onEnd(t,!0)):_.allowEnterCallback(r)&&(r.onEnter(t),r.onEnd(t,!0)):e.performLeave(t)}},this.performLeave=function(t){e.childrenRefs[t]&&(e.currentlyAnimatingKeys[t]=!0,e.childrenRefs[t].componentWillLeave(e.handleDoneLeaving.bind(e,t)))},this.handleDoneLeaving=function(t){var n=e.props;if(delete e.currentlyAnimatingKeys[t],!n.exclusive||n===e.nextProps){var r=w(P(n));if(e.isValidChildByKey(r,t))e.performEnter(t);else{var o=function(){_.allowLeaveCallback(n)&&(n.onLeave(t),n.onEnd(t,!1))};!function(e,t,n){var r=e.length===t.length;return r&&e.forEach((function(e,o){var c=t[o];e&&c&&(e&&!c||!e&&c||e.key!==c.key||n&&e.props[n]!==c.props[n])&&(r=!1)})),r}(e.state.children,r,n.showProp)?e.setState({children:r},o):o()}}}};t.a=g(T)},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&(\"object\"==t||\"function\"==t)}},function(e,t,n){\"use strict\";var r=n(5),o=n.n(r),c=n(10),i=n.n(c),a=n(6),l=n.n(a),u=n(11),s=n.n(u),f=n(0),p=n.n(f),h=n(1),d=n.n(h),v=n(9),m=n.n(v),y=n(12);function b(e,t){for(var n=t;n;){if(n===e)return!0;n=n.parentNode}return!1}var g=n(119),w=n.n(g);function z(e,t,n,r){var o=m.a.unstable_batchedUpdates?function(e){m.a.unstable_batchedUpdates(n,e)}:n;return w()(e,t,o,r)}var O=n(87),C=n(88),M=n(8),S=n.n(M);function _(e,t,n){return n?e[0]===t[0]:e[0]===t[0]&&e[1]===t[1]}function x(e,t){this[e]=t}var k,H=n(14),E=n.n(H);function P(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function V(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?P(Object(n),!0).forEach((function(t){L(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):P(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function T(e){return(T=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function L(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var j={Webkit:\"-webkit-\",Moz:\"-moz-\",ms:\"-ms-\",O:\"-o-\"};function N(){if(void 0!==k)return k;k=\"\";var e=document.createElement(\"p\").style;for(var t in j)t+\"Transform\"in e&&(k=t);return k}function D(){return N()?\"\".concat(N(),\"TransitionProperty\"):\"transitionProperty\"}function R(){return N()?\"\".concat(N(),\"Transform\"):\"transform\"}function A(e,t){var n=D();n&&(e.style[n]=t,\"transitionProperty\"!==n&&(e.style.transitionProperty=t))}function I(e,t){var n=R();n&&(e.style[n]=t,\"transform\"!==n&&(e.style.transform=t))}var F,W=/matrix\\((.*)\\)/,U=/matrix3d\\((.*)\\)/;function K(e){var t=e.style.display;e.style.display=\"none\",e.offsetHeight,e.style.display=t}function B(e,t,n){var r=n;if(\"object\"!==T(t))return\"undefined\"!==typeof r?(\"number\"===typeof r&&(r=\"\".concat(r,\"px\")),void(e.style[t]=r)):F(e,t);for(var o in t)t.hasOwnProperty(o)&&B(e,o,t[o])}function Y(e,t){var n=e[\"page\".concat(t?\"Y\":\"X\",\"Offset\")],r=\"scroll\".concat(t?\"Top\":\"Left\");if(\"number\"!==typeof n){var o=e.document;\"number\"!==typeof(n=o.documentElement[r])&&(n=o.body[r])}return n}function q(e){return Y(e)}function G(e){return Y(e,!0)}function $(e){var t=function(e){var t,n,r,o=e.ownerDocument,c=o.body,i=o&&o.documentElement;return n=(t=e.getBoundingClientRect()).left,r=t.top,{left:n-=i.clientLeft||c.clientLeft||0,top:r-=i.clientTop||c.clientTop||0}}(e),n=e.ownerDocument,r=n.defaultView||n.parentWindow;return t.left+=q(r),t.top+=G(r),t}function Q(e){return null!==e&&void 0!==e&&e==e.window}function X(e){return Q(e)?e.document:9===e.nodeType?e:e.ownerDocument}var Z=new RegExp(\"^(\".concat(/[\\-+]?(?:\\d*\\.|)\\d+(?:[eE][\\-+]?\\d+|)/.source,\")(?!px)[a-z%]+$\"),\"i\"),J=/^(top|right|bottom|left)$/;function ee(e,t){return\"left\"===e?t.useCssRight?\"right\":e:t.useCssBottom?\"bottom\":e}function te(e){return\"left\"===e?\"right\":\"right\"===e?\"left\":\"top\"===e?\"bottom\":\"bottom\"===e?\"top\":void 0}function ne(e,t,n){\"static\"===B(e,\"position\")&&(e.style.position=\"relative\");var r=-999,o=-999,c=ee(\"left\",n),i=ee(\"top\",n),a=te(c),l=te(i);\"left\"!==c&&(r=999),\"top\"!==i&&(o=999);var u,s=\"\",f=$(e);(\"left\"in t||\"top\"in t)&&(s=(u=e).style.transitionProperty||u.style[D()]||\"\",A(e,\"none\")),\"left\"in t&&(e.style[a]=\"\",e.style[c]=\"\".concat(r,\"px\")),\"top\"in t&&(e.style[l]=\"\",e.style[i]=\"\".concat(o,\"px\")),K(e);var p=$(e),h={};for(var d in t)if(t.hasOwnProperty(d)){var v=ee(d,n),m=\"left\"===d?r:o,y=f[d]-p[d];h[v]=v===d?m+y:m-y}B(e,h),K(e),(\"left\"in t||\"top\"in t)&&A(e,s);var b={};for(var g in t)if(t.hasOwnProperty(g)){var w=ee(g,n),z=t[g]-f[g];b[w]=g===w?h[w]+z:h[w]-z}B(e,b)}function re(e,t){var n=$(e),r=function(e){var t=window.getComputedStyle(e,null),n=t.getPropertyValue(\"transform\")||t.getPropertyValue(R());if(n&&\"none\"!==n){var r=n.replace(/[^0-9\\-.,]/g,\"\").split(\",\");return{x:parseFloat(r[12]||r[4],0),y:parseFloat(r[13]||r[5],0)}}return{x:0,y:0}}(e),o={x:r.x,y:r.y};\"left\"in t&&(o.x=r.x+t.left-n.left),\"top\"in t&&(o.y=r.y+t.top-n.top),function(e,t){var n=window.getComputedStyle(e,null),r=n.getPropertyValue(\"transform\")||n.getPropertyValue(R());if(r&&\"none\"!==r){var o,c=r.match(W);c?((o=(c=c[1]).split(\",\").map((function(e){return parseFloat(e,10)})))[4]=t.x,o[5]=t.y,I(e,\"matrix(\".concat(o.join(\",\"),\")\"))):((o=r.match(U)[1].split(\",\").map((function(e){return parseFloat(e,10)})))[12]=t.x,o[13]=t.y,I(e,\"matrix3d(\".concat(o.join(\",\"),\")\")))}else I(e,\"translateX(\".concat(t.x,\"px) translateY(\").concat(t.y,\"px) translateZ(0)\"))}(e,o)}function oe(e,t){for(var n=0;n<e.length;n++)t(e[n])}function ce(e){return\"border-box\"===F(e,\"boxSizing\")}\"undefined\"!==typeof window&&(F=window.getComputedStyle?function(e,t,n){var r=n,o=\"\",c=X(e);return(r=r||c.defaultView.getComputedStyle(e,null))&&(o=r.getPropertyValue(t)||r[t]),o}:function(e,t){var n=e.currentStyle&&e.currentStyle[t];if(Z.test(n)&&!J.test(t)){var r=e.style,o=r.left,c=e.runtimeStyle.left;e.runtimeStyle.left=e.currentStyle.left,r.left=\"fontSize\"===t?\"1em\":n||0,n=r.pixelLeft+\"px\",r.left=o,e.runtimeStyle.left=c}return\"\"===n?\"auto\":n});var ie=[\"margin\",\"border\",\"padding\"];function ae(e,t,n){var r,o={},c=e.style;for(r in t)t.hasOwnProperty(r)&&(o[r]=c[r],c[r]=t[r]);for(r in n.call(e),t)t.hasOwnProperty(r)&&(c[r]=o[r])}function le(e,t,n){var r,o,c,i=0;for(o=0;o<t.length;o++)if(r=t[o])for(c=0;c<n.length;c++){var a=void 0;a=\"border\"===r?\"\".concat(r).concat(n[c],\"Width\"):r+n[c],i+=parseFloat(F(e,a))||0}return i}var ue={getParent:function(e){var t=e;do{t=11===t.nodeType&&t.host?t.host:t.parentNode}while(t&&1!==t.nodeType&&9!==t.nodeType);return t}};function se(e,t,n){var r=n;if(Q(e))return\"width\"===t?ue.viewportWidth(e):ue.viewportHeight(e);if(9===e.nodeType)return\"width\"===t?ue.docWidth(e):ue.docHeight(e);var o=\"width\"===t?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],c=\"width\"===t?e.getBoundingClientRect().width:e.getBoundingClientRect().height,i=ce(e),a=0;(null===c||void 0===c||c<=0)&&(c=void 0,(null===(a=F(e,t))||void 0===a||Number(a)<0)&&(a=e.style[t]||0),a=parseFloat(a)||0),void 0===r&&(r=i?1:-1);var l=void 0!==c||i,u=c||a;return-1===r?l?u-le(e,[\"border\",\"padding\"],o):a:l?1===r?u:u+(2===r?-le(e,[\"border\"],o):le(e,[\"margin\"],o)):a+le(e,ie.slice(r),o)}oe([\"Width\",\"Height\"],(function(e){ue[\"doc\".concat(e)]=function(t){var n=t.document;return Math.max(n.documentElement[\"scroll\".concat(e)],n.body[\"scroll\".concat(e)],ue[\"viewport\".concat(e)](n))},ue[\"viewport\".concat(e)]=function(t){var n=\"client\".concat(e),r=t.document,o=r.body,c=r.documentElement[n];return\"CSS1Compat\"===r.compatMode&&c||o&&o[n]||c}}));var fe={position:\"absolute\",visibility:\"hidden\",display:\"block\"};function pe(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];var r,o=t[0];return 0!==o.offsetWidth?r=se.apply(void 0,t):ae(o,fe,(function(){r=se.apply(void 0,t)})),r}function he(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}oe([\"width\",\"height\"],(function(e){var t=e.charAt(0).toUpperCase()+e.slice(1);ue[\"outer\".concat(t)]=function(t,n){return t&&pe(t,e,n?0:1)};var n=\"width\"===e?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"];ue[e]=function(t,r){var o=r;return void 0!==o?t?(ce(t)&&(o+=le(t,[\"padding\",\"border\"],n)),B(t,e,o)):void 0:t&&pe(t,e,-1)}}));var de={getWindow:function(e){if(e&&e.document&&e.setTimeout)return e;var t=e.ownerDocument||e;return t.defaultView||t.parentWindow},getDocument:X,offset:function(e,t,n){if(\"undefined\"===typeof t)return $(e);!function(e,t,n){if(n.ignoreShake){var r=$(e),o=r.left.toFixed(0),c=r.top.toFixed(0),i=t.left.toFixed(0),a=t.top.toFixed(0);if(o===i&&c===a)return}n.useCssRight||n.useCssBottom?ne(e,t,n):n.useCssTransform&&R()in document.body.style?re(e,t):ne(e,t,n)}(e,t,n||{})},isWindow:Q,each:oe,css:B,clone:function(e){var t,n={};for(t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);if(e.overflow)for(t in e)e.hasOwnProperty(t)&&(n.overflow[t]=e.overflow[t]);return n},mix:he,getWindowScrollLeft:function(e){return q(e)},getWindowScrollTop:function(e){return G(e)},merge:function(){for(var e={},t=0;t<arguments.length;t++)de.mix(e,t<0||arguments.length<=t?void 0:arguments[t]);return e},viewportWidth:0,viewportHeight:0};he(de,ue);var ve=de.getParent;function me(e){if(de.isWindow(e)||9===e.nodeType)return null;var t,n=de.getDocument(e).body,r=de.css(e,\"position\");if(!(\"fixed\"===r||\"absolute\"===r))return\"html\"===e.nodeName.toLowerCase()?null:ve(e);for(t=ve(e);t&&t!==n&&9!==t.nodeType;t=ve(t))if(\"static\"!==(r=de.css(t,\"position\")))return t;return null}var ye=de.getParent;function be(e,t){for(var n={left:0,right:1/0,top:0,bottom:1/0},r=me(e),o=de.getDocument(e),c=o.defaultView||o.parentWindow,i=o.body,a=o.documentElement;r;){if(-1!==navigator.userAgent.indexOf(\"MSIE\")&&0===r.clientWidth||r===i||r===a||\"visible\"===de.css(r,\"overflow\")){if(r===i||r===a)break}else{var l=de.offset(r);l.left+=r.clientLeft,l.top+=r.clientTop,n.top=Math.max(n.top,l.top),n.right=Math.min(n.right,l.left+r.clientWidth),n.bottom=Math.min(n.bottom,l.top+r.clientHeight),n.left=Math.max(n.left,l.left)}r=me(r)}var u=null;de.isWindow(e)||9===e.nodeType||(u=e.style.position,\"absolute\"===de.css(e,\"position\")&&(e.style.position=\"fixed\"));var s=de.getWindowScrollLeft(c),f=de.getWindowScrollTop(c),p=de.viewportWidth(c),h=de.viewportHeight(c),d=a.scrollWidth,v=a.scrollHeight,m=window.getComputedStyle(i);if(\"hidden\"===m.overflowX&&(d=c.innerWidth),\"hidden\"===m.overflowY&&(v=c.innerHeight),e.style&&(e.style.position=u),t||function(e){if(de.isWindow(e)||9===e.nodeType)return!1;var t=de.getDocument(e),n=t.body,r=null;for(r=ye(e);r&&r!==n&&r!==t;r=ye(r))if(\"fixed\"===de.css(r,\"position\"))return!0;return!1}(e))n.left=Math.max(n.left,s),n.top=Math.max(n.top,f),n.right=Math.min(n.right,s+p),n.bottom=Math.min(n.bottom,f+h);else{var y=Math.max(d,s+p);n.right=Math.min(n.right,y);var b=Math.max(v,f+h);n.bottom=Math.min(n.bottom,b)}return n.top>=0&&n.left>=0&&n.bottom>n.top&&n.right>n.left?n:null}function ge(e){var t,n,r;if(de.isWindow(e)||9===e.nodeType){var o=de.getWindow(e);t={left:de.getWindowScrollLeft(o),top:de.getWindowScrollTop(o)},n=de.viewportWidth(o),r=de.viewportHeight(o)}else t=de.offset(e),n=de.outerWidth(e),r=de.outerHeight(e);return t.width=n,t.height=r,t}function we(e,t){var n=t.charAt(0),r=t.charAt(1),o=e.width,c=e.height,i=e.left,a=e.top;return\"c\"===n?a+=c/2:\"b\"===n&&(a+=c),\"c\"===r?i+=o/2:\"r\"===r&&(i+=o),{left:i,top:a}}function ze(e,t,n,r,o){var c=we(t,n[1]),i=we(e,n[0]),a=[i.left-c.left,i.top-c.top];return{left:Math.round(e.left-a[0]+r[0]-o[0]),top:Math.round(e.top-a[1]+r[1]-o[1])}}function Oe(e,t,n){return e.left<n.left||e.left+t.width>n.right}function Ce(e,t,n){return e.top<n.top||e.top+t.height>n.bottom}function Me(e,t,n){var r=[];return de.each(e,(function(e){r.push(e.replace(t,(function(e){return n[e]})))})),r}function Se(e,t){return e[t]=-e[t],e}function _e(e,t){return(/%$/.test(e)?parseInt(e.substring(0,e.length-1),10)/100*t:parseInt(e,10))||0}function xe(e,t){e[0]=_e(e[0],t.width),e[1]=_e(e[1],t.height)}function ke(e,t,n,r){var o=n.points,c=n.offset||[0,0],i=n.targetOffset||[0,0],a=n.overflow,l=n.source||e;c=[].concat(c),i=[].concat(i);var u={},s=0,f=be(l,!(!(a=a||{})||!a.alwaysByViewport)),p=ge(l);xe(c,p),xe(i,t);var h=ze(p,t,o,c,i),d=de.merge(p,h);if(f&&(a.adjustX||a.adjustY)&&r){if(a.adjustX&&Oe(h,p,f)){var v=Me(o,/[lr]/gi,{l:\"r\",r:\"l\"}),m=Se(c,0),y=Se(i,0);(function(e,t,n){return e.left>n.right||e.left+t.width<n.left})(ze(p,t,v,m,y),p,f)||(s=1,o=v,c=m,i=y)}if(a.adjustY&&Ce(h,p,f)){var b=Me(o,/[tb]/gi,{t:\"b\",b:\"t\"}),g=Se(c,1),w=Se(i,1);(function(e,t,n){return e.top>n.bottom||e.top+t.height<n.top})(ze(p,t,b,g,w),p,f)||(s=1,o=b,c=g,i=w)}s&&(h=ze(p,t,o,c,i),de.mix(d,h));var z=Oe(h,p,f),O=Ce(h,p,f);if(z||O){var C=o;z&&(C=Me(o,/[lr]/gi,{l:\"r\",r:\"l\"})),O&&(C=Me(o,/[tb]/gi,{t:\"b\",b:\"t\"})),o=C,c=n.offset||[0,0],i=n.targetOffset||[0,0]}u.adjustX=a.adjustX&&z,u.adjustY=a.adjustY&&O,(u.adjustX||u.adjustY)&&(d=function(e,t,n,r){var o=de.clone(e),c={width:t.width,height:t.height};return r.adjustX&&o.left<n.left&&(o.left=n.left),r.resizeWidth&&o.left>=n.left&&o.left+c.width>n.right&&(c.width-=o.left+c.width-n.right),r.adjustX&&o.left+c.width>n.right&&(o.left=Math.max(n.right-c.width,n.left)),r.adjustY&&o.top<n.top&&(o.top=n.top),r.resizeHeight&&o.top>=n.top&&o.top+c.height>n.bottom&&(c.height-=o.top+c.height-n.bottom),r.adjustY&&o.top+c.height>n.bottom&&(o.top=Math.max(n.bottom-c.height,n.top)),de.mix(o,c)}(h,p,f,u))}return d.width!==p.width&&de.css(l,\"width\",de.width(l)+d.width-p.width),d.height!==p.height&&de.css(l,\"height\",de.height(l)+d.height-p.height),de.offset(l,{left:d.left,top:d.top},{useCssRight:n.useCssRight,useCssBottom:n.useCssBottom,useCssTransform:n.useCssTransform,ignoreShake:n.ignoreShake}),{points:o,offset:c,targetOffset:i,overflow:u}}function He(e,t,n){var r=n.target||t;return ke(e,ge(r),n,!function(e,t){var n=be(e,t),r=ge(e);return!n||r.left+r.width<=n.left||r.top+r.height<=n.top||r.left>=n.right||r.top>=n.bottom}(r,n.overflow&&n.overflow.alwaysByViewport))}function Ee(e,t,n){var r,o,c=de.getDocument(e),i=c.defaultView||c.parentWindow,a=de.getWindowScrollLeft(i),l=de.getWindowScrollTop(i),u=de.viewportWidth(i),s=de.viewportHeight(i),f={left:r=\"pageX\"in t?t.pageX:a+t.clientX,top:o=\"pageY\"in t?t.pageY:l+t.clientY,width:0,height:0},p=r>=0&&r<=a+u&&o>=0&&o<=l+s,h=[n.points[0],\"cc\"];return ke(e,f,V(V({},n),{},{points:h}),p)}He.__getOffsetParent=me,He.__getVisibleRectForElement=be;function Pe(e){return e&&\"object\"===typeof e&&e.window===e}function Ve(e,t){var n=Math.floor(e),r=Math.floor(t);return Math.abs(n-r)<=1}function Te(e,t){e!==document.activeElement&&b(t,e)&&e.focus()}function Le(e){return\"function\"===typeof e&&e?e():null}function je(e){return\"object\"===typeof e&&e?e:null}var Ne=function(e){function t(){var e,n,r,o;i()(this,t);for(var c=arguments.length,a=Array(c),u=0;u<c;u++)a[u]=arguments[u];return n=r=l()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(a))),r.forceAlign=function(){var e=r.props,t=e.disabled,n=e.target,o=e.align,c=e.onAlign;if(!t&&n){var i=m.a.findDOMNode(r),a=void 0,l=Le(n),u=je(n),s=document.activeElement;l?a=He(i,l,o):u&&(a=Ee(i,u,o)),Te(s,i),c&&c(i,a)}},o=n,l()(r,o)}return s()(t,e),E()(t,[{key:\"componentDidMount\",value:function(){var e=this.props;this.forceAlign(),!e.disabled&&e.monitorWindowResize&&this.startMonitorWindowResize()}},{key:\"componentDidUpdate\",value:function(e){var t,n,r=!1,o=this.props;if(!o.disabled){var c=m.a.findDOMNode(this),i=c?c.getBoundingClientRect():null;if(e.disabled)r=!0;else{var a=Le(e.target),l=Le(o.target),u=je(e.target),s=je(o.target);Pe(a)&&Pe(l)?r=!1:(a!==l||a&&!l&&s||u&&s&&l||s&&!((t=u)===(n=s)||t&&n&&(\"pageX\"in n&&\"pageY\"in n?t.pageX===n.pageX&&t.pageY===n.pageY:\"clientX\"in n&&\"clientY\"in n&&t.clientX===n.clientX&&t.clientY===n.clientY)))&&(r=!0);var f=this.sourceRect||{};r||!c||Ve(f.width,i.width)&&Ve(f.height,i.height)||(r=!0)}this.sourceRect=i}r&&this.forceAlign(),o.monitorWindowResize&&!o.disabled?this.startMonitorWindowResize():this.stopMonitorWindowResize()}},{key:\"componentWillUnmount\",value:function(){this.stopMonitorWindowResize()}},{key:\"startMonitorWindowResize\",value:function(){this.resizeHandler||(this.bufferMonitor=function(e,t){var n=void 0;function r(){n&&(clearTimeout(n),n=null)}function o(){r(),n=setTimeout(e,t)}return o.clear=r,o}(this.forceAlign,this.props.monitorBufferTime),this.resizeHandler=z(window,\"resize\",this.bufferMonitor))}},{key:\"stopMonitorWindowResize\",value:function(){this.resizeHandler&&(this.bufferMonitor.clear(),this.resizeHandler.remove(),this.resizeHandler=null)}},{key:\"render\",value:function(){var e=this,t=this.props,n=t.childrenProps,r=t.children,o=p.a.Children.only(r);if(n){var c={};return Object.keys(n).forEach((function(t){c[t]=e.props[n[t]]})),p.a.cloneElement(o,c)}return o}}]),t}(f.Component);Ne.propTypes={childrenProps:d.a.object,align:d.a.object.isRequired,target:d.a.oneOfType([d.a.func,d.a.shape({clientX:d.a.number,clientY:d.a.number,pageX:d.a.number,pageY:d.a.number})]),onAlign:d.a.func,monitorBufferTime:d.a.number,monitorWindowResize:d.a.bool,disabled:d.a.bool,children:d.a.any},Ne.defaultProps={target:function(){return window},monitorBufferTime:50,monitorWindowResize:!1,disabled:!1};var De=Ne,Re=n(33),Ae=n(26),Ie=n.n(Ae),Fe=function(e){function t(){return i()(this,t),l()(this,e.apply(this,arguments))}return s()(t,e),t.prototype.shouldComponentUpdate=function(e){return e.hiddenClassName||e.visible},t.prototype.render=function(){var e=this.props,t=e.hiddenClassName,n=e.visible,r=Ie()(e,[\"hiddenClassName\",\"visible\"]);return t||p.a.Children.count(r.children)>1?(!n&&t&&(r.className+=\" \"+t),p.a.createElement(\"div\",r)):p.a.Children.only(r.children)},t}(f.Component);Fe.propTypes={children:d.a.any,className:d.a.string,visible:d.a.bool,hiddenClassName:d.a.string};var We=Fe,Ue=function(e){function t(){return i()(this,t),l()(this,e.apply(this,arguments))}return s()(t,e),t.prototype.render=function(){var e=this.props,t=e.className;return e.visible||(t+=\" \"+e.hiddenClassName),p.a.createElement(\"div\",{className:t,onMouseEnter:e.onMouseEnter,onMouseLeave:e.onMouseLeave,onMouseDown:e.onMouseDown,onTouchStart:e.onTouchStart,style:e.style},p.a.createElement(We,{className:e.prefixCls+\"-content\",visible:e.visible},e.children))},t}(f.Component);Ue.propTypes={hiddenClassName:d.a.string,className:d.a.string,prefixCls:d.a.string,onMouseEnter:d.a.func,onMouseLeave:d.a.func,onMouseDown:d.a.func,onTouchStart:d.a.func,children:d.a.any};var Ke=Ue,Be=function(e){function t(n){i()(this,t);var r=l()(this,e.call(this,n));return Ye.call(r),r.state={stretchChecked:!1,targetWidth:void 0,targetHeight:void 0},r.savePopupRef=x.bind(r,\"popupInstance\"),r.saveAlignRef=x.bind(r,\"alignInstance\"),r}return s()(t,e),t.prototype.componentDidMount=function(){this.rootNode=this.getPopupDomNode(),this.setStretchSize()},t.prototype.componentDidUpdate=function(){this.setStretchSize()},t.prototype.getPopupDomNode=function(){return m.a.findDOMNode(this.popupInstance)},t.prototype.getMaskTransitionName=function(){var e=this.props,t=e.maskTransitionName,n=e.maskAnimation;return!t&&n&&(t=e.prefixCls+\"-\"+n),t},t.prototype.getTransitionName=function(){var e=this.props,t=e.transitionName;return!t&&e.animation&&(t=e.prefixCls+\"-\"+e.animation),t},t.prototype.getClassName=function(e){return this.props.prefixCls+\" \"+this.props.className+\" \"+e},t.prototype.getPopupElement=function(){var e=this,t=this.savePopupRef,n=this.state,r=n.stretchChecked,c=n.targetHeight,i=n.targetWidth,a=this.props,l=a.align,u=a.visible,s=a.prefixCls,f=a.style,h=a.getClassNameFromAlign,d=a.destroyPopupOnHide,v=a.stretch,m=a.children,y=a.onMouseEnter,b=a.onMouseLeave,g=a.onMouseDown,w=a.onTouchStart,z=this.getClassName(this.currentAlignClassName||h(l)),O=s+\"-hidden\";u||(this.currentAlignClassName=null);var C={};v&&(-1!==v.indexOf(\"height\")?C.height=c:-1!==v.indexOf(\"minHeight\")&&(C.minHeight=c),-1!==v.indexOf(\"width\")?C.width=i:-1!==v.indexOf(\"minWidth\")&&(C.minWidth=i),r||(C.visibility=\"hidden\",setTimeout((function(){e.alignInstance&&e.alignInstance.forceAlign()}),0)));var M={className:z,prefixCls:s,ref:t,onMouseEnter:y,onMouseLeave:b,onMouseDown:g,onTouchStart:w,style:o()({},C,f,this.getZIndexStyle())};return d?p.a.createElement(Re.a,{component:\"\",exclusive:!0,transitionAppear:!0,transitionName:this.getTransitionName()},u?p.a.createElement(De,{target:this.getAlignTarget(),key:\"popup\",ref:this.saveAlignRef,monitorWindowResize:!0,align:l,onAlign:this.onAlign},p.a.createElement(Ke,o()({visible:!0},M),m)):null):p.a.createElement(Re.a,{component:\"\",exclusive:!0,transitionAppear:!0,transitionName:this.getTransitionName(),showProp:\"xVisible\"},p.a.createElement(De,{target:this.getAlignTarget(),key:\"popup\",ref:this.saveAlignRef,monitorWindowResize:!0,xVisible:u,childrenProps:{visible:\"xVisible\"},disabled:!u,align:l,onAlign:this.onAlign},p.a.createElement(Ke,o()({hiddenClassName:O},M),m)))},t.prototype.getZIndexStyle=function(){var e={},t=this.props;return void 0!==t.zIndex&&(e.zIndex=t.zIndex),e},t.prototype.getMaskElement=function(){var e=this.props,t=void 0;if(e.mask){var n=this.getMaskTransitionName();t=p.a.createElement(We,{style:this.getZIndexStyle(),key:\"mask\",className:e.prefixCls+\"-mask\",hiddenClassName:e.prefixCls+\"-mask-hidden\",visible:e.visible}),n&&(t=p.a.createElement(Re.a,{key:\"mask\",showProp:\"visible\",transitionAppear:!0,component:\"\",transitionName:n},t))}return t},t.prototype.render=function(){return p.a.createElement(\"div\",null,this.getMaskElement(),this.getPopupElement())},t}(f.Component);Be.propTypes={visible:d.a.bool,style:d.a.object,getClassNameFromAlign:d.a.func,onAlign:d.a.func,getRootDomNode:d.a.func,align:d.a.any,destroyPopupOnHide:d.a.bool,className:d.a.string,prefixCls:d.a.string,onMouseEnter:d.a.func,onMouseLeave:d.a.func,onMouseDown:d.a.func,onTouchStart:d.a.func,stretch:d.a.string,children:d.a.node,point:d.a.shape({pageX:d.a.number,pageY:d.a.number})};var Ye=function(){var e=this;this.onAlign=function(t,n){var r=e.props,o=r.getClassNameFromAlign(n);e.currentAlignClassName!==o&&(e.currentAlignClassName=o,t.className=e.getClassName(o)),r.onAlign(t,n)},this.setStretchSize=function(){var t=e.props,n=t.stretch,r=t.getRootDomNode,o=t.visible,c=e.state,i=c.stretchChecked,a=c.targetHeight,l=c.targetWidth;if(n&&o){var u=r();if(u){var s=u.offsetHeight,f=u.offsetWidth;a===s&&l===f&&i||e.setState({stretchChecked:!0,targetHeight:s,targetWidth:f})}}else i&&e.setState({stretchChecked:!1})},this.getTargetElement=function(){return e.props.getRootDomNode()},this.getAlignTarget=function(){var t=e.props.point;return t||e.getTargetElement}},qe=Be;function Ge(){}var $e=[\"onClick\",\"onMouseDown\",\"onTouchStart\",\"onMouseEnter\",\"onMouseLeave\",\"onFocus\",\"onBlur\",\"onContextMenu\"],Qe=!!v.createPortal,Xe={rcTrigger:d.a.shape({onPopupMouseDown:d.a.func})},Ze=function(e){function t(n){i()(this,t);var r=l()(this,e.call(this,n));Je.call(r);var o=void 0;return o=\"popupVisible\"in n?!!n.popupVisible:!!n.defaultPopupVisible,r.state={prevPopupVisible:o,popupVisible:o},$e.forEach((function(e){r[\"fire\"+e]=function(t){r.fireEvents(e,t)}})),r}return s()(t,e),t.prototype.getChildContext=function(){return{rcTrigger:{onPopupMouseDown:this.onPopupMouseDown}}},t.prototype.componentDidMount=function(){this.componentDidUpdate({},{popupVisible:this.state.popupVisible})},t.prototype.componentDidUpdate=function(e,t){var n=this.props,r=this.state;if(Qe||this.renderComponent(null,(function(){t.popupVisible!==r.popupVisible&&n.afterPopupVisibleChange(r.popupVisible)})),r.popupVisible){var o=void 0;return this.clickOutsideHandler||!this.isClickToHide()&&!this.isContextMenuToShow()||(o=n.getDocument(),this.clickOutsideHandler=z(o,\"mousedown\",this.onDocumentClick)),this.touchOutsideHandler||(o=o||n.getDocument(),this.touchOutsideHandler=z(o,\"touchstart\",this.onDocumentClick)),!this.contextMenuOutsideHandler1&&this.isContextMenuToShow()&&(o=o||n.getDocument(),this.contextMenuOutsideHandler1=z(o,\"scroll\",this.onContextMenuClose)),void(!this.contextMenuOutsideHandler2&&this.isContextMenuToShow()&&(this.contextMenuOutsideHandler2=z(window,\"blur\",this.onContextMenuClose)))}this.clearOutsideHandler()},t.prototype.componentWillUnmount=function(){this.clearDelayTimer(),this.clearOutsideHandler(),clearTimeout(this.mouseDownTimeout)},t.getDerivedStateFromProps=function(e,t){var n=e.popupVisible,r={};return void 0!==n&&t.popupVisible!==n&&(r.popupVisible=n,r.prevPopupVisible=t.popupVisible),r},t.prototype.getPopupDomNode=function(){return this._component&&this._component.getPopupDomNode?this._component.getPopupDomNode():null},t.prototype.getPopupAlign=function(){var e=this.props,t=e.popupPlacement,n=e.popupAlign,r=e.builtinPlacements;return t&&r?function(e,t,n){var r=e[t]||{};return o()({},r,n)}(r,t,n):n},t.prototype.setPopupVisible=function(e,t){var n=this.props.alignPoint,r=this.state.popupVisible;this.clearDelayTimer(),r!==e&&(\"popupVisible\"in this.props||this.setState({popupVisible:e,prevPopupVisible:r}),this.props.onPopupVisibleChange(e)),n&&t&&this.setPoint(t)},t.prototype.delaySetPopupVisible=function(e,t,n){var r=this,o=1e3*t;if(this.clearDelayTimer(),o){var c=n?{pageX:n.pageX,pageY:n.pageY}:null;this.delayTimer=setTimeout((function(){r.setPopupVisible(e,c),r.clearDelayTimer()}),o)}else this.setPopupVisible(e,n)},t.prototype.clearDelayTimer=function(){this.delayTimer&&(clearTimeout(this.delayTimer),this.delayTimer=null)},t.prototype.clearOutsideHandler=function(){this.clickOutsideHandler&&(this.clickOutsideHandler.remove(),this.clickOutsideHandler=null),this.contextMenuOutsideHandler1&&(this.contextMenuOutsideHandler1.remove(),this.contextMenuOutsideHandler1=null),this.contextMenuOutsideHandler2&&(this.contextMenuOutsideHandler2.remove(),this.contextMenuOutsideHandler2=null),this.touchOutsideHandler&&(this.touchOutsideHandler.remove(),this.touchOutsideHandler=null)},t.prototype.createTwoChains=function(e){var t=this.props.children.props,n=this.props;return t[e]&&n[e]?this[\"fire\"+e]:t[e]||n[e]},t.prototype.isClickToShow=function(){var e=this.props,t=e.action,n=e.showAction;return-1!==t.indexOf(\"click\")||-1!==n.indexOf(\"click\")},t.prototype.isContextMenuToShow=function(){var e=this.props,t=e.action,n=e.showAction;return-1!==t.indexOf(\"contextMenu\")||-1!==n.indexOf(\"contextMenu\")},t.prototype.isClickToHide=function(){var e=this.props,t=e.action,n=e.hideAction;return-1!==t.indexOf(\"click\")||-1!==n.indexOf(\"click\")},t.prototype.isMouseEnterToShow=function(){var e=this.props,t=e.action,n=e.showAction;return-1!==t.indexOf(\"hover\")||-1!==n.indexOf(\"mouseEnter\")},t.prototype.isMouseLeaveToHide=function(){var e=this.props,t=e.action,n=e.hideAction;return-1!==t.indexOf(\"hover\")||-1!==n.indexOf(\"mouseLeave\")},t.prototype.isFocusToShow=function(){var e=this.props,t=e.action,n=e.showAction;return-1!==t.indexOf(\"focus\")||-1!==n.indexOf(\"focus\")},t.prototype.isBlurToHide=function(){var e=this.props,t=e.action,n=e.hideAction;return-1!==t.indexOf(\"focus\")||-1!==n.indexOf(\"blur\")},t.prototype.forcePopupAlign=function(){this.state.popupVisible&&this._component&&this._component.alignInstance&&this._component.alignInstance.forceAlign()},t.prototype.fireEvents=function(e,t){var n=this.props.children.props[e];n&&n(t);var r=this.props[e];r&&r(t)},t.prototype.close=function(){this.setPopupVisible(!1)},t.prototype.render=function(){var e=this,t=this.state.popupVisible,n=this.props,r=n.children,o=n.forceRender,c=n.alignPoint,i=n.className,a=p.a.Children.only(r),l={key:\"trigger\"};this.isContextMenuToShow()?l.onContextMenu=this.onContextMenu:l.onContextMenu=this.createTwoChains(\"onContextMenu\"),this.isClickToHide()||this.isClickToShow()?(l.onClick=this.onClick,l.onMouseDown=this.onMouseDown,l.onTouchStart=this.onTouchStart):(l.onClick=this.createTwoChains(\"onClick\"),l.onMouseDown=this.createTwoChains(\"onMouseDown\"),l.onTouchStart=this.createTwoChains(\"onTouchStart\")),this.isMouseEnterToShow()?(l.onMouseEnter=this.onMouseEnter,c&&(l.onMouseMove=this.onMouseMove)):l.onMouseEnter=this.createTwoChains(\"onMouseEnter\"),this.isMouseLeaveToHide()?l.onMouseLeave=this.onMouseLeave:l.onMouseLeave=this.createTwoChains(\"onMouseLeave\"),this.isFocusToShow()||this.isBlurToHide()?(l.onFocus=this.onFocus,l.onBlur=this.onBlur):(l.onFocus=this.createTwoChains(\"onFocus\"),l.onBlur=this.createTwoChains(\"onBlur\"));var u=S()(a&&a.props&&a.props.className,i);u&&(l.className=u);var s=p.a.cloneElement(a,l);if(!Qe)return p.a.createElement(O.a,{parent:this,visible:t,autoMount:!1,forceRender:o,getComponent:this.getComponent,getContainer:this.getContainer},(function(t){var n=t.renderComponent;return e.renderComponent=n,s}));var f=void 0;return(t||this._component||o)&&(f=p.a.createElement(C.a,{key:\"portal\",getContainer:this.getContainer,didUpdate:this.handlePortalUpdate},this.getComponent())),[s,f]},t}(p.a.Component);Ze.propTypes={children:d.a.any,action:d.a.oneOfType([d.a.string,d.a.arrayOf(d.a.string)]),showAction:d.a.any,hideAction:d.a.any,getPopupClassNameFromAlign:d.a.any,onPopupVisibleChange:d.a.func,afterPopupVisibleChange:d.a.func,popup:d.a.oneOfType([d.a.node,d.a.func]).isRequired,popupStyle:d.a.object,prefixCls:d.a.string,popupClassName:d.a.string,className:d.a.string,popupPlacement:d.a.string,builtinPlacements:d.a.object,popupTransitionName:d.a.oneOfType([d.a.string,d.a.object]),popupAnimation:d.a.any,mouseEnterDelay:d.a.number,mouseLeaveDelay:d.a.number,zIndex:d.a.number,focusDelay:d.a.number,blurDelay:d.a.number,getPopupContainer:d.a.func,getDocument:d.a.func,forceRender:d.a.bool,destroyPopupOnHide:d.a.bool,mask:d.a.bool,maskClosable:d.a.bool,onPopupAlign:d.a.func,popupAlign:d.a.object,popupVisible:d.a.bool,defaultPopupVisible:d.a.bool,maskTransitionName:d.a.oneOfType([d.a.string,d.a.object]),maskAnimation:d.a.string,stretch:d.a.string,alignPoint:d.a.bool},Ze.contextTypes=Xe,Ze.childContextTypes=Xe,Ze.defaultProps={prefixCls:\"rc-trigger-popup\",getPopupClassNameFromAlign:function(){return\"\"},getDocument:function(){return window.document},onPopupVisibleChange:Ge,afterPopupVisibleChange:Ge,onPopupAlign:Ge,popupClassName:\"\",mouseEnterDelay:0,mouseLeaveDelay:.1,focusDelay:0,blurDelay:.15,popupStyle:{},destroyPopupOnHide:!1,popupAlign:{},defaultPopupVisible:!1,mask:!1,maskClosable:!0,action:[],showAction:[],hideAction:[]};var Je=function(){var e=this;this.onMouseEnter=function(t){var n=e.props.mouseEnterDelay;e.fireEvents(\"onMouseEnter\",t),e.delaySetPopupVisible(!0,n,n?null:t)},this.onMouseMove=function(t){e.fireEvents(\"onMouseMove\",t),e.setPoint(t)},this.onMouseLeave=function(t){e.fireEvents(\"onMouseLeave\",t),e.delaySetPopupVisible(!1,e.props.mouseLeaveDelay)},this.onPopupMouseEnter=function(){e.clearDelayTimer()},this.onPopupMouseLeave=function(t){t.relatedTarget&&!t.relatedTarget.setTimeout&&e._component&&e._component.getPopupDomNode&&b(e._component.getPopupDomNode(),t.relatedTarget)||e.delaySetPopupVisible(!1,e.props.mouseLeaveDelay)},this.onFocus=function(t){e.fireEvents(\"onFocus\",t),e.clearDelayTimer(),e.isFocusToShow()&&(e.focusTime=Date.now(),e.delaySetPopupVisible(!0,e.props.focusDelay))},this.onMouseDown=function(t){e.fireEvents(\"onMouseDown\",t),e.preClickTime=Date.now()},this.onTouchStart=function(t){e.fireEvents(\"onTouchStart\",t),e.preTouchTime=Date.now()},this.onBlur=function(t){e.fireEvents(\"onBlur\",t),e.clearDelayTimer(),e.isBlurToHide()&&e.delaySetPopupVisible(!1,e.props.blurDelay)},this.onContextMenu=function(t){t.preventDefault(),e.fireEvents(\"onContextMenu\",t),e.setPopupVisible(!0,t)},this.onContextMenuClose=function(){e.isContextMenuToShow()&&e.close()},this.onClick=function(t){if(e.fireEvents(\"onClick\",t),e.focusTime){var n=void 0;if(e.preClickTime&&e.preTouchTime?n=Math.min(e.preClickTime,e.preTouchTime):e.preClickTime?n=e.preClickTime:e.preTouchTime&&(n=e.preTouchTime),Math.abs(n-e.focusTime)<20)return;e.focusTime=0}e.preClickTime=0,e.preTouchTime=0,e.isClickToShow()&&(e.isClickToHide()||e.isBlurToHide())&&t&&t.preventDefault&&t.preventDefault();var r=!e.state.popupVisible;(e.isClickToHide()&&!r||r&&e.isClickToShow())&&e.setPopupVisible(!e.state.popupVisible,t)},this.onPopupMouseDown=function(){var t=e.context.rcTrigger,n=void 0===t?{}:t;e.hasPopupMouseDown=!0,clearTimeout(e.mouseDownTimeout),e.mouseDownTimeout=setTimeout((function(){e.hasPopupMouseDown=!1}),0),n.onPopupMouseDown&&n.onPopupMouseDown.apply(n,arguments)},this.onDocumentClick=function(t){if(!e.props.mask||e.props.maskClosable){var n=t.target;b(Object(v.findDOMNode)(e),n)||e.hasPopupMouseDown||e.close()}},this.getRootDomNode=function(){return Object(v.findDOMNode)(e)},this.getPopupClassNameFromAlign=function(t){var n=[],r=e.props,o=r.popupPlacement,c=r.builtinPlacements,i=r.prefixCls,a=r.alignPoint,l=r.getPopupClassNameFromAlign;return o&&c&&n.push(function(e,t,n,r){var o=n.points;for(var c in e)if(e.hasOwnProperty(c)&&_(e[c].points,o,r))return t+\"-placement-\"+c;return\"\"}(c,i,t,a)),l&&n.push(l(t)),n.join(\" \")},this.getComponent=function(){var t=e.props,n=t.prefixCls,r=t.destroyPopupOnHide,c=t.popupClassName,i=t.action,a=t.onPopupAlign,l=t.popupAnimation,u=t.popupTransitionName,s=t.popupStyle,f=t.mask,h=t.maskAnimation,d=t.maskTransitionName,v=t.zIndex,m=t.popup,y=t.stretch,b=t.alignPoint,g=e.state,w=g.popupVisible,z=g.point,O=e.getPopupAlign(),C={};return e.isMouseEnterToShow()&&(C.onMouseEnter=e.onPopupMouseEnter),e.isMouseLeaveToHide()&&(C.onMouseLeave=e.onPopupMouseLeave),C.onMouseDown=e.onPopupMouseDown,C.onTouchStart=e.onPopupMouseDown,p.a.createElement(qe,o()({prefixCls:n,destroyPopupOnHide:r,visible:w,point:b&&z,className:c,action:i,align:O,onAlign:a,animation:l,getClassNameFromAlign:e.getPopupClassNameFromAlign},C,{stretch:y,getRootDomNode:e.getRootDomNode,style:s,mask:f,zIndex:v,transitionName:u,maskAnimation:h,maskTransitionName:d,ref:e.savePopup}),\"function\"===typeof m?m():m)},this.getContainer=function(){var t=e.props,n=document.createElement(\"div\");return n.style.position=\"absolute\",n.style.top=\"0\",n.style.left=\"0\",n.style.width=\"100%\",(t.getPopupContainer?t.getPopupContainer(Object(v.findDOMNode)(e)):t.getDocument().body).appendChild(n),n},this.setPoint=function(t){e.props.alignPoint&&t&&e.setState({point:{pageX:t.pageX,pageY:t.pageY}})},this.handlePortalUpdate=function(){e.state.prevPopupVisible!==e.state.popupVisible&&e.props.afterPopupVisibleChange(e.state.popupVisible)},this.savePopup=function(t){e._component=t}};Object(y.polyfill)(Ze);t.a=Ze},function(e,t){var n=e.exports=\"undefined\"!=typeof window&&window.Math==Math?window:\"undefined\"!=typeof self&&self.Math==Math?self:Function(\"return this\")();\"number\"==typeof __g&&(__g=n)},function(e,t){var n=e.exports={version:\"2.6.12\"};\"number\"==typeof __e&&(__e=n)},function(e,t,n){e.exports=!n(63)((function(){return 7!=Object.defineProperty({},\"a\",{get:function(){return 7}}).a}))},function(e,t,n){\"use strict\";var r=n(89),o={placeholder:\"Select time\"};function c(){return(c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var i={lang:c({placeholder:\"Select date\",rangePlaceholder:[\"Start date\",\"End date\"]},{today:\"Today\",now:\"Now\",backToToday:\"Back to today\",ok:\"Ok\",clear:\"Clear\",month:\"Month\",year:\"Year\",timeSelect:\"select time\",dateSelect:\"select date\",weekSelect:\"Choose a week\",monthSelect:\"Choose a month\",yearSelect:\"Choose a year\",decadeSelect:\"Choose a decade\",yearFormat:\"YYYY\",dateFormat:\"M/D/YYYY\",dayFormat:\"D\",dateTimeFormat:\"M/D/YYYY HH:mm:ss\",monthBeforeYear:!0,previousMonth:\"Previous month (PageUp)\",nextMonth:\"Next month (PageDown)\",previousYear:\"Last year (Control + left)\",nextYear:\"Next year (Control + right)\",previousDecade:\"Last decade\",nextDecade:\"Next decade\",previousCentury:\"Last century\",nextCentury:\"Next century\"}),timePickerLocale:c({},o)},a=i;t.a={locale:\"en\",Pagination:r.a,DatePicker:i,TimePicker:o,Calendar:a,global:{placeholder:\"Please select\"},Table:{filterTitle:\"Filter menu\",filterConfirm:\"OK\",filterReset:\"Reset\",selectAll:\"Select current page\",selectInvert:\"Invert current page\",sortTitle:\"Sort\",expand:\"Expand row\",collapse:\"Collapse row\"},Modal:{okText:\"OK\",cancelText:\"Cancel\",justOkText:\"OK\"},Popconfirm:{okText:\"OK\",cancelText:\"Cancel\"},Transfer:{titles:[\"\",\"\"],searchPlaceholder:\"Search here\",itemUnit:\"item\",itemsUnit:\"items\"},Upload:{uploading:\"Uploading...\",removeFile:\"Remove file\",uploadError:\"Upload error\",previewFile:\"Preview file\",downloadFile:\"Download file\"},Empty:{description:\"No Data\"},Icon:{icon:\"icon\"},Text:{edit:\"Edit\",copy:\"Copy\",copied:\"Copied\",expand:\"Expand\"},PageHeader:{back:\"Back\"}}},function(e,t,n){\"use strict\";(function(e){n.d(t,\"e\",(function(){return f})),n.d(t,\"d\",(function(){return p})),n.d(t,\"a\",(function(){return d})),n.d(t,\"b\",(function(){return v})),n.d(t,\"c\",(function(){return m})),n.d(t,\"f\",(function(){return y}));var r=n(5),o=n.n(r),c=n(10),i=n.n(c),a=n(14),l=n.n(a),u=n(158),s=n(0);function f(t){e&&Object({NODE_ENV:\"production\",PUBLIC_URL:\"\",WDS_SOCKET_HOST:void 0,WDS_SOCKET_PATH:void 0,WDS_SOCKET_PORT:void 0,FAST_REFRESH:!0})||console.error(\"[@ant-design/icons-react]: \"+t+\".\")}function p(e){return\"object\"===typeof e&&\"string\"===typeof e.name&&\"string\"===typeof e.theme&&(\"object\"===typeof e.icon||\"function\"===typeof e.icon)}function h(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return Object.keys(e).reduce((function(t,n){var r=e[n];switch(n){case\"class\":t.className=r,delete t.class;break;default:t[n]=r}return t}),{})}var d=function(){function e(){i()(this,e),this.collection={}}return l()(e,[{key:\"clear\",value:function(){this.collection={}}},{key:\"delete\",value:function(e){return delete this.collection[e]}},{key:\"get\",value:function(e){return this.collection[e]}},{key:\"has\",value:function(e){return Boolean(this.collection[e])}},{key:\"set\",value:function(e,t){return this.collection[e]=t,this}},{key:\"size\",get:function(){return Object.keys(this.collection).length}}]),e}();function v(e,t,n){return n?s.createElement(e.tag,o()({key:t},h(e.attrs),n),(e.children||[]).map((function(n,r){return v(n,t+\"-\"+e.tag+\"-\"+r)}))):s.createElement(e.tag,o()({key:t},h(e.attrs)),(e.children||[]).map((function(n,r){return v(n,t+\"-\"+e.tag+\"-\"+r)})))}function m(e){return Object(u.generate)(e)[0]}function y(e,t){switch(t){case\"fill\":return e+\"-fill\";case\"outline\":return e+\"-o\";case\"twotone\":return e+\"-twotone\";default:throw new TypeError(\"Unknown theme type: \"+t+\", name: \"+e)}}}).call(this,n(135))},function(e,t,n){\"use strict\";var r=n(0),o=n.n(r),c=n(20),i=n(121),a=n.n(i),l=n(1),u=n.n(l),s=n(3),f=n.n(s),p=n(15),h=n.n(p),d=n(12),v=n(9),m=n.n(v),y=n(28),b=n(18),g=n(90),w=n(8),z=n.n(w);function O(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function C(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function M(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(!(Symbol.iterator in Object(e))&&\"[object Arguments]\"!==Object.prototype.toString.call(e))return;var n=[],r=!0,o=!1,c=void 0;try{for(var i,a=e[Symbol.iterator]();!(r=(i=a.next()).done)&&(n.push(i.value),!t||n.length!==t);r=!0);}catch(l){o=!0,c=l}finally{try{r||null==a.return||a.return()}finally{if(o)throw c}}return n}(e,t)||function(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance\")}()}var S=/iPhone/i,_=/iPod/i,x=/iPad/i,k=/\\bAndroid(?:.+)Mobile\\b/i,H=/Android/i,E=/\\bAndroid(?:.+)SD4930UR\\b/i,P=/\\bAndroid(?:.+)(?:KF[A-Z]{2,4})\\b/i,V=/Windows Phone/i,T=/\\bWindows(?:.+)ARM\\b/i,L=/BlackBerry/i,j=/BB10/i,N=/Opera Mini/i,D=/\\b(CriOS|Chrome)(?:.+)Mobile/i,R=/Mobile(?:.+)Firefox\\b/i;function A(e,t){return e.test(t)}function I(e){var t=e||(\"undefined\"!==typeof navigator?navigator.userAgent:\"\"),n=t.split(\"[FBAN\");\"undefined\"!==typeof n[1]&&(t=M(n,1)[0]);\"undefined\"!==typeof(n=t.split(\"Twitter\"))[1]&&(t=M(n,1)[0]);var r={apple:{phone:A(S,t)&&!A(V,t),ipod:A(_,t),tablet:!A(S,t)&&A(x,t)&&!A(V,t),device:(A(S,t)||A(_,t)||A(x,t))&&!A(V,t)},amazon:{phone:A(E,t),tablet:!A(E,t)&&A(P,t),device:A(E,t)||A(P,t)},android:{phone:!A(V,t)&&A(E,t)||!A(V,t)&&A(k,t),tablet:!A(V,t)&&!A(E,t)&&!A(k,t)&&(A(P,t)||A(H,t)),device:!A(V,t)&&(A(E,t)||A(P,t)||A(k,t)||A(H,t))||A(/\\bokhttp\\b/i,t)},windows:{phone:A(V,t),tablet:A(T,t),device:A(V,t)||A(T,t)},other:{blackberry:A(L,t),blackberry10:A(j,t),opera:A(N,t),firefox:A(R,t),chrome:A(D,t),device:A(L,t)||A(j,t)||A(N,t)||A(R,t)||A(D,t)},any:null,phone:null,tablet:null};return r.any=r.apple.device||r.android.device||r.windows.device||r.other.device,r.phone=r.apple.phone||r.android.phone||r.windows.phone,r.tablet=r.apple.tablet||r.android.tablet||r.windows.tablet,r}var F=function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?O(Object(n),!0).forEach((function(t){C(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):O(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},I(),{isMobile:I});function W(e){return(W=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function U(){}function K(e,t,n){var r=t||\"\";return e.key||\"\".concat(r,\"item_\").concat(n)}function B(e){return\"\".concat(e,\"-menu-\")}function Y(e,t){var n=-1;r.Children.forEach(e,(function(e){n+=1,e&&e.type&&e.type.isMenuItemGroup?r.Children.forEach(e.props.children,(function(e){t(e,n+=1)})):t(e,n)}))}function q(e,t,n){e&&!n.find&&r.Children.forEach(e,(function(e){if(e){var r=e.type;if(!r||!(r.isSubMenu||r.isMenuItem||r.isMenuItemGroup))return;-1!==t.indexOf(e.key)?n.find=!0:e.props.children&&q(e.props.children,t,n)}}))}var G=[\"defaultSelectedKeys\",\"selectedKeys\",\"defaultOpenKeys\",\"openKeys\",\"mode\",\"getPopupContainer\",\"onSelect\",\"onDeselect\",\"onDestroy\",\"openTransitionName\",\"openAnimation\",\"subMenuOpenDelay\",\"subMenuCloseDelay\",\"forceSubMenuRender\",\"triggerSubMenuAction\",\"level\",\"selectable\",\"multiple\",\"onOpenChange\",\"visible\",\"focusable\",\"defaultActiveFirst\",\"prefixCls\",\"inlineIndent\",\"parentMenu\",\"title\",\"rootPrefixCls\",\"eventKey\",\"active\",\"onItemHover\",\"onTitleMouseEnter\",\"onTitleMouseLeave\",\"onTitleClick\",\"popupAlign\",\"popupOffset\",\"isOpen\",\"renderMenuItem\",\"manualRef\",\"subMenuKey\",\"disabled\",\"index\",\"isSelected\",\"store\",\"activeKey\",\"builtinPlacements\",\"overflowedIndicator\",\"motion\",\"attribute\",\"value\",\"popupClassName\",\"inlineCollapsed\",\"menu\",\"theme\",\"itemIcon\",\"expandIcon\"],$=function(e){var t=e&&\"function\"===typeof e.getBoundingClientRect&&e.getBoundingClientRect().width;return t&&(t=+t.toFixed(6)),t||0},Q=function(e,t,n){e&&\"object\"===W(e.style)&&(e.style[t]=n)},X=n(95),Z=n(35),J=n(7),ee=n.n(J),te=n(5),ne=n.n(te),re=n(10),oe=n.n(re),ce=n(14),ie=n.n(ce),ae=n(6),le=n.n(ae),ue=n(11),se=n.n(ue);var fe=n(19),pe=n.n(fe),he=!(\"undefined\"===typeof window||!window.document||!window.document.createElement);function de(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n[\"Webkit\"+e]=\"webkit\"+t,n[\"Moz\"+e]=\"moz\"+t,n[\"ms\"+e]=\"MS\"+t,n[\"O\"+e]=\"o\"+t.toLowerCase(),n}var ve=function(e,t){var n={animationend:de(\"Animation\",\"AnimationEnd\"),transitionend:de(\"Transition\",\"TransitionEnd\")};return e&&(\"AnimationEvent\"in t||delete n.animationend.animation,\"TransitionEvent\"in t||delete n.transitionend.transition),n}(he,\"undefined\"!==typeof window?window:{}),me={};he&&(me=document.createElement(\"div\").style);var ye={};function be(e){if(ye[e])return ye[e];var t=ve[e];if(t)for(var n=Object.keys(t),r=n.length,o=0;o<r;o+=1){var c=n[o];if(Object.prototype.hasOwnProperty.call(t,c)&&c in me)return ye[e]=t[c],ye[e]}return\"\"}var ge=be(\"animationend\"),we=be(\"transitionend\"),ze=!(!ge||!we);function Oe(e,t){return e?\"object\"===typeof e?e[t.replace(/-\\w/g,(function(e){return e[1].toUpperCase()}))]:e+\"-\"+t:null}var Ce=\"none\",Me=\"appear\",Se=\"enter\",_e=\"leave\",xe={eventProps:u.a.object,visible:u.a.bool,children:u.a.func,motionName:u.a.oneOfType([u.a.string,u.a.object]),motionAppear:u.a.bool,motionEnter:u.a.bool,motionLeave:u.a.bool,motionLeaveImmediately:u.a.bool,motionDeadline:u.a.number,removeOnLeave:u.a.bool,leavedClassName:u.a.string,onAppearStart:u.a.func,onAppearActive:u.a.func,onAppearEnd:u.a.func,onEnterStart:u.a.func,onEnterActive:u.a.func,onEnterEnd:u.a.func,onLeaveStart:u.a.func,onLeaveActive:u.a.func,onLeaveEnd:u.a.func};var ke=function(e){var t=e,n=!!o.a.forwardRef;function r(e){return!(!e.motionName||!t)}\"object\"===typeof e&&(t=e.transitionSupport,n=\"forwardRef\"in e?e.forwardRef:n);var c=function(e){function t(){oe()(this,t);var e=le()(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return e.onDomUpdate=function(){var t=e.state,n=t.status,o=t.newStatus,c=e.props,i=c.onAppearStart,a=c.onEnterStart,l=c.onLeaveStart,u=c.onAppearActive,s=c.onEnterActive,f=c.onLeaveActive,p=c.motionAppear,h=c.motionEnter,d=c.motionLeave;if(r(e.props)){var v=e.getElement();e.$cacheEle!==v&&(e.removeEventListener(e.$cacheEle),e.addEventListener(v),e.$cacheEle=v),o&&n===Me&&p?e.updateStatus(i,null,null,(function(){e.updateActiveStatus(u,Me)})):o&&n===Se&&h?e.updateStatus(a,null,null,(function(){e.updateActiveStatus(s,Se)})):o&&n===_e&&d&&e.updateStatus(l,null,null,(function(){e.updateActiveStatus(f,_e)}))}},e.onMotionEnd=function(t){var n=e.state,r=n.status,o=n.statusActive,c=e.props,i=c.onAppearEnd,a=c.onEnterEnd,l=c.onLeaveEnd;r===Me&&o?e.updateStatus(i,{status:Ce},t):r===Se&&o?e.updateStatus(a,{status:Ce},t):r===_e&&o&&e.updateStatus(l,{status:Ce},t)},e.setNodeRef=function(t){var n=e.props.internalRef;e.node=t,\"function\"===typeof n?n(t):n&&\"current\"in n&&(n.current=t)},e.getElement=function(){try{return(t=e.node||e)instanceof HTMLElement?t:m.a.findDOMNode(t)}catch(n){return e.$cacheEle}var t},e.addEventListener=function(t){t&&(t.addEventListener(we,e.onMotionEnd),t.addEventListener(ge,e.onMotionEnd))},e.removeEventListener=function(t){t&&(t.removeEventListener(we,e.onMotionEnd),t.removeEventListener(ge,e.onMotionEnd))},e.updateStatus=function(t,n,r,o){var c=t?t(e.getElement(),r):null;if(!1!==c&&!e._destroyed){var i=void 0;o&&(i=function(){e.nextFrame(o)}),e.setState(ne()({statusStyle:\"object\"===typeof c?c:null,newStatus:!1},n),i)}},e.updateActiveStatus=function(t,n){e.nextFrame((function(){if(e.state.status===n){var r=e.props.motionDeadline;e.updateStatus(t,{statusActive:!0}),r>0&&setTimeout((function(){e.onMotionEnd({deadline:!0})}),r)}}))},e.nextFrame=function(t){e.cancelNextFrame(),e.raf=pe()(t)},e.cancelNextFrame=function(){e.raf&&(pe.a.cancel(e.raf),e.raf=null)},e.state={status:Ce,statusActive:!1,newStatus:!1,statusStyle:null},e.$cacheEle=null,e.node=null,e.raf=null,e}return se()(t,e),ie()(t,[{key:\"componentDidMount\",value:function(){this.onDomUpdate()}},{key:\"componentDidUpdate\",value:function(){this.onDomUpdate()}},{key:\"componentWillUnmount\",value:function(){this._destroyed=!0,this.removeEventListener(this.$cacheEle),this.cancelNextFrame()}},{key:\"render\",value:function(){var e,t=this.state,n=t.status,o=t.statusActive,c=t.statusStyle,i=this.props,a=i.children,l=i.motionName,u=i.visible,s=i.removeOnLeave,f=i.leavedClassName,p=i.eventProps;return a?n!==Ce&&r(this.props)?a(ne()({},p,{className:z()((e={},ee()(e,Oe(l,n),n!==Ce),ee()(e,Oe(l,n+\"-active\"),n!==Ce&&o),ee()(e,l,\"string\"===typeof l),e)),style:c}),this.setNodeRef):u?a(ne()({},p),this.setNodeRef):s?null:a(ne()({},p,{className:f}),this.setNodeRef):null}}],[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=t.prevProps,o=t.status;if(!r(e))return{};var c=e.visible,i=e.motionAppear,a=e.motionEnter,l=e.motionLeave,u=e.motionLeaveImmediately,s={prevProps:e};return(o===Me&&!i||o===Se&&!a||o===_e&&!l)&&(s.status=Ce,s.statusActive=!1,s.newStatus=!1),!n&&c&&i&&(s.status=Me,s.statusActive=!1,s.newStatus=!0),n&&!n.visible&&c&&a&&(s.status=Se,s.statusActive=!1,s.newStatus=!0),(n&&n.visible&&!c&&l||!n&&u&&!c&&l)&&(s.status=_e,s.statusActive=!1,s.newStatus=!0),s}}]),t}(o.a.Component);return c.propTypes=ne()({},xe,{internalRef:u.a.oneOfType([u.a.object,u.a.func])}),c.defaultProps={visible:!0,motionEnter:!0,motionAppear:!0,motionLeave:!0,removeOnLeave:!0},Object(d.polyfill)(c),n?o.a.forwardRef((function(e,t){return o.a.createElement(c,ne()({internalRef:t},e))})):c}(ze),He={adjustX:1,adjustY:1},Ee={topLeft:{points:[\"bl\",\"tl\"],overflow:He,offset:[0,-7]},bottomLeft:{points:[\"tl\",\"bl\"],overflow:He,offset:[0,7]},leftTop:{points:[\"tr\",\"tl\"],overflow:He,offset:[-4,0]},rightTop:{points:[\"tl\",\"tr\"],overflow:He,offset:[4,0]}};function Pe(e){return(Pe=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ve(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Te(e){return(Te=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Le(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function je(e,t){return(je=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ne(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function De(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?Ne(Object(n),!0).forEach((function(t){Re(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Ne(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function Re(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var Ae=0,Ie={horizontal:\"bottomLeft\",vertical:\"rightTop\",\"vertical-left\":\"rightTop\",\"vertical-right\":\"leftTop\"},Fe=function(e,t,n){var r=B(t),o=e.getState();e.setState({defaultActiveFirst:De({},o.defaultActiveFirst,Re({},r,n))})},We=function(e){function t(e){var n;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==Pe(t)&&\"function\"!==typeof t?Le(e):t}(this,Te(t).call(this,e))).onDestroy=function(e){n.props.onDestroy(e)},n.onKeyDown=function(e){var t=e.keyCode,r=n.menuInstance,o=n.props,c=o.isOpen,i=o.store;if(t===b.a.ENTER)return n.onTitleClick(e),Fe(i,n.props.eventKey,!0),!0;if(t===b.a.RIGHT)return c?r.onKeyDown(e):(n.triggerOpenChange(!0),Fe(i,n.props.eventKey,!0)),!0;if(t===b.a.LEFT){var a;if(!c)return;return(a=r.onKeyDown(e))||(n.triggerOpenChange(!1),a=!0),a}return!c||t!==b.a.UP&&t!==b.a.DOWN?void 0:r.onKeyDown(e)},n.onOpenChange=function(e){n.props.onOpenChange(e)},n.onPopupVisibleChange=function(e){n.triggerOpenChange(e,e?\"mouseenter\":\"mouseleave\")},n.onMouseEnter=function(e){var t=n.props,r=t.eventKey,o=t.onMouseEnter,c=t.store;Fe(c,n.props.eventKey,!1),o({key:r,domEvent:e})},n.onMouseLeave=function(e){var t=n.props,r=t.parentMenu,o=t.eventKey,c=t.onMouseLeave;r.subMenuInstance=Le(n),c({key:o,domEvent:e})},n.onTitleMouseEnter=function(e){var t=n.props,r=t.eventKey,o=t.onItemHover,c=t.onTitleMouseEnter;o({key:r,hover:!0}),c({key:r,domEvent:e})},n.onTitleMouseLeave=function(e){var t=n.props,r=t.parentMenu,o=t.eventKey,c=t.onItemHover,i=t.onTitleMouseLeave;r.subMenuInstance=Le(n),c({key:o,hover:!1}),i({key:o,domEvent:e})},n.onTitleClick=function(e){var t=Le(n).props;t.onTitleClick({key:t.eventKey,domEvent:e}),\"hover\"!==t.triggerSubMenuAction&&(n.triggerOpenChange(!t.isOpen,\"click\"),Fe(t.store,n.props.eventKey,!1))},n.onSubMenuClick=function(e){\"function\"===typeof n.props.onClick&&n.props.onClick(n.addKeyPath(e))},n.onSelect=function(e){n.props.onSelect(e)},n.onDeselect=function(e){n.props.onDeselect(e)},n.getPrefixCls=function(){return\"\".concat(n.props.rootPrefixCls,\"-submenu\")},n.getActiveClassName=function(){return\"\".concat(n.getPrefixCls(),\"-active\")},n.getDisabledClassName=function(){return\"\".concat(n.getPrefixCls(),\"-disabled\")},n.getSelectedClassName=function(){return\"\".concat(n.getPrefixCls(),\"-selected\")},n.getOpenClassName=function(){return\"\".concat(n.props.rootPrefixCls,\"-submenu-open\")},n.saveMenuInstance=function(e){n.menuInstance=e},n.addKeyPath=function(e){return De({},e,{keyPath:(e.keyPath||[]).concat(n.props.eventKey)})},n.triggerOpenChange=function(e,t){var r=n.props.eventKey,o=function(){n.onOpenChange({key:r,item:Le(n),trigger:t,open:e})};\"mouseenter\"===t?n.mouseenterTimeout=setTimeout((function(){o()}),0):o()},n.isChildrenSelected=function(){var e={find:!1};return q(n.props.children,n.props.selectedKeys,e),e.find},n.isOpen=function(){return-1!==n.props.openKeys.indexOf(n.props.eventKey)},n.adjustWidth=function(){if(n.subMenuTitle&&n.menuInstance){var e=v.findDOMNode(n.menuInstance);e.offsetWidth>=n.subMenuTitle.offsetWidth||(e.style.minWidth=\"\".concat(n.subMenuTitle.offsetWidth,\"px\"))}},n.saveSubMenuTitle=function(e){n.subMenuTitle=e};var r=e.store,o=e.eventKey,c=r.getState().defaultActiveFirst;n.isRootMenu=!1;var i=!1;return c&&(i=c[o]),Fe(r,o,i),n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&je(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.componentDidUpdate()}},{key:\"componentDidUpdate\",value:function(){var e=this,t=this.props,n=t.mode,r=t.parentMenu,o=t.manualRef;o&&o(this),\"horizontal\"===n&&r.isRootMenu&&this.props.isOpen&&(this.minWidthTimeout=setTimeout((function(){return e.adjustWidth()}),0))}},{key:\"componentWillUnmount\",value:function(){var e=this.props,t=e.onDestroy,n=e.eventKey;t&&t(n),this.minWidthTimeout&&clearTimeout(this.minWidthTimeout),this.mouseenterTimeout&&clearTimeout(this.mouseenterTimeout)}},{key:\"renderChildren\",value:function(e){var t=this,n=this.props,o={mode:\"horizontal\"===n.mode?\"vertical\":n.mode,visible:this.props.isOpen,level:n.level+1,inlineIndent:n.inlineIndent,focusable:!1,onClick:this.onSubMenuClick,onSelect:this.onSelect,onDeselect:this.onDeselect,onDestroy:this.onDestroy,selectedKeys:n.selectedKeys,eventKey:\"\".concat(n.eventKey,\"-menu-\"),openKeys:n.openKeys,motion:n.motion,onOpenChange:this.onOpenChange,subMenuOpenDelay:n.subMenuOpenDelay,parentMenu:this,subMenuCloseDelay:n.subMenuCloseDelay,forceSubMenuRender:n.forceSubMenuRender,triggerSubMenuAction:n.triggerSubMenuAction,builtinPlacements:n.builtinPlacements,defaultActiveFirst:n.store.getState().defaultActiveFirst[B(n.eventKey)],multiple:n.multiple,prefixCls:n.rootPrefixCls,id:this.internalMenuId,manualRef:this.saveMenuInstance,itemIcon:n.itemIcon,expandIcon:n.expandIcon},c=this.haveRendered;if(this.haveRendered=!0,this.haveOpened=this.haveOpened||o.visible||o.forceSubMenuRender,!this.haveOpened)return r.createElement(\"div\",null);var i=De({},n.motion,{leavedClassName:\"\".concat(n.rootPrefixCls,\"-hidden\"),removeOnLeave:!1,motionAppear:c||!o.visible||\"inline\"!==o.mode});return r.createElement(ke,Object.assign({visible:o.visible},i),(function(n){var c=n.className,i=n.style,a=z()(\"\".concat(o.prefixCls,\"-sub\"),c);return r.createElement(zt,Object.assign({},o,{id:t.internalMenuId,className:a,style:i}),e)}))}},{key:\"render\",value:function(){var e,t=De({},this.props),n=t.isOpen,o=this.getPrefixCls(),c=\"inline\"===t.mode,i=z()(o,\"\".concat(o,\"-\").concat(t.mode),(Re(e={},t.className,!!t.className),Re(e,this.getOpenClassName(),n),Re(e,this.getActiveClassName(),t.active||n&&!c),Re(e,this.getDisabledClassName(),t.disabled),Re(e,this.getSelectedClassName(),this.isChildrenSelected()),e));this.internalMenuId||(t.eventKey?this.internalMenuId=\"\".concat(t.eventKey,\"$Menu\"):(Ae+=1,this.internalMenuId=\"$__$\".concat(Ae,\"$Menu\")));var a={},l={},u={};t.disabled||(a={onMouseLeave:this.onMouseLeave,onMouseEnter:this.onMouseEnter},l={onClick:this.onTitleClick},u={onMouseEnter:this.onTitleMouseEnter,onMouseLeave:this.onTitleMouseLeave});var s={};c&&(s.paddingLeft=t.inlineIndent*t.level);var f={};this.props.isOpen&&(f={\"aria-owns\":this.internalMenuId});var p=null;\"horizontal\"!==t.mode&&(p=this.props.expandIcon,\"function\"===typeof this.props.expandIcon&&(p=r.createElement(this.props.expandIcon,De({},this.props))));var h=r.createElement(\"div\",Object.assign({ref:this.saveSubMenuTitle,style:s,className:\"\".concat(o,\"-title\")},u,l,{\"aria-expanded\":n},f,{\"aria-haspopup\":\"true\",title:\"string\"===typeof t.title?t.title:void 0}),t.title,p||r.createElement(\"i\",{className:\"\".concat(o,\"-arrow\")})),d=this.renderChildren(t.children),v=t.parentMenu.isRootMenu?t.parentMenu.props.getPopupContainer:function(e){return e.parentNode},m=Ie[t.mode],y=t.popupOffset?{offset:t.popupOffset}:{},b=\"inline\"===t.mode?\"\":t.popupClassName,g=t.disabled,w=t.triggerSubMenuAction,O=t.subMenuOpenDelay,C=t.forceSubMenuRender,M=t.subMenuCloseDelay,S=t.builtinPlacements;return G.forEach((function(e){return delete t[e]})),delete t.onClick,r.createElement(\"li\",Object.assign({},t,a,{className:i,role:\"menuitem\"}),c&&h,c&&d,!c&&r.createElement(Z.a,{prefixCls:o,popupClassName:\"\".concat(o,\"-popup \").concat(b),getPopupContainer:v,builtinPlacements:Object.assign({},Ee,S),popupPlacement:m,popupVisible:n,popupAlign:y,popup:d,action:g?[]:[w],mouseEnterDelay:O,mouseLeaveDelay:M,onPopupVisibleChange:this.onPopupVisibleChange,forceRender:C},h))}}])&&Ve(n.prototype,o),c&&Ve(n,c),t}(r.Component);We.defaultProps={onMouseEnter:U,onMouseLeave:U,onTitleMouseEnter:U,onTitleMouseLeave:U,onTitleClick:U,manualRef:U,mode:\"vertical\",title:\"\"};var Ue=Object(y.connect)((function(e,t){var n=e.openKeys,r=e.activeKey,o=e.selectedKeys,c=t.eventKey,i=t.subMenuKey;return{isOpen:n.indexOf(c)>-1,active:r[i]===c,selectedKeys:o}}))(We);Ue.isSubMenu=!0;var Ke=Ue;function Be(e){return(Be=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ye(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new Array(e.length);t<e.length;t++)n[t]=e[t];return n}}(e)||function(e){if(Symbol.iterator in Object(e)||\"[object Arguments]\"===Object.prototype.toString.call(e))return Array.from(e)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance\")}()}function qe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ge(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?qe(Object(n),!0).forEach((function(t){$e(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):qe(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function $e(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Qe(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function Xe(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Ze(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Je(e,t){return!t||\"object\"!==Be(t)&&\"function\"!==typeof t?tt(e):t}function et(e){return(et=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function tt(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function nt(e,t){return(nt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var rt=!(\"undefined\"===typeof window||!window.document||!window.document.createElement),ot=\"menuitem-overflowed\";rt&&n(321);var ct=function(e){function t(){var e;return Xe(this,t),(e=Je(this,et(t).apply(this,arguments))).resizeObserver=null,e.mutationObserver=null,e.originalTotalWidth=0,e.overflowedItems=[],e.menuItemSizes=[],e.state={lastVisibleIndex:void 0},e.getMenuItemNodes=function(){var t=e.props.prefixCls,n=v.findDOMNode(tt(e));return n?[].slice.call(n.children).filter((function(e){return e.className.split(\" \").indexOf(\"\".concat(t,\"-overflowed-submenu\"))<0})):[]},e.getOverflowedSubMenuItem=function(t,n,o){var c=e.props,i=c.overflowedIndicator,a=c.level,l=c.mode,u=c.prefixCls,s=c.theme;if(1!==a||\"horizontal\"!==l)return null;var f=e.props.children[0].props,p=(f.children,f.title,f.style),h=Qe(f,[\"children\",\"title\",\"style\"]),d=Ge({},p),v=\"\".concat(t,\"-overflowed-indicator\"),m=\"\".concat(t,\"-overflowed-indicator\");0===n.length&&!0!==o?d=Ge({},d,{display:\"none\"}):o&&(d=Ge({},d,{visibility:\"hidden\",position:\"absolute\"}),v=\"\".concat(v,\"-placeholder\"),m=\"\".concat(m,\"-placeholder\"));var y=s?\"\".concat(u,\"-\").concat(s):\"\",b={};return G.forEach((function(e){void 0!==h[e]&&(b[e]=h[e])})),r.createElement(Ke,Object.assign({title:i,className:\"\".concat(u,\"-overflowed-submenu\"),popupClassName:y},b,{key:v,eventKey:m,disabled:!1,style:d}),n)},e.setChildrenWidthAndResize=function(){if(\"horizontal\"===e.props.mode){var t=v.findDOMNode(tt(e));if(t){var n=t.children;if(n&&0!==n.length){var r=t.children[n.length-1];Q(r,\"display\",\"inline-block\");var o=e.getMenuItemNodes(),c=o.filter((function(e){return e.className.split(\" \").indexOf(ot)>=0}));c.forEach((function(e){Q(e,\"display\",\"inline-block\")})),e.menuItemSizes=o.map((function(e){return $(e)})),c.forEach((function(e){Q(e,\"display\",\"none\")})),e.overflowedIndicatorWidth=$(t.children[t.children.length-1]),e.originalTotalWidth=e.menuItemSizes.reduce((function(e,t){return e+t}),0),e.handleResize(),Q(r,\"display\",\"none\")}}}},e.handleResize=function(){if(\"horizontal\"===e.props.mode){var t=v.findDOMNode(tt(e));if(t){var n=$(t);e.overflowedItems=[];var r,o=0;e.originalTotalWidth>n+.5&&(r=-1,e.menuItemSizes.forEach((function(t){(o+=t)+e.overflowedIndicatorWidth<=n&&(r+=1)}))),e.setState({lastVisibleIndex:r})}}},e}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&nt(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){var e=this;if(this.setChildrenWidthAndResize(),1===this.props.level&&\"horizontal\"===this.props.mode){var t=v.findDOMNode(this);if(!t)return;this.resizeObserver=new X.a((function(t){t.forEach(e.setChildrenWidthAndResize)})),[].slice.call(t.children).concat(t).forEach((function(t){e.resizeObserver.observe(t)})),\"undefined\"!==typeof MutationObserver&&(this.mutationObserver=new MutationObserver((function(){e.resizeObserver.disconnect(),[].slice.call(t.children).concat(t).forEach((function(t){e.resizeObserver.observe(t)})),e.setChildrenWidthAndResize()})),this.mutationObserver.observe(t,{attributes:!1,childList:!0,subTree:!1}))}}},{key:\"componentWillUnmount\",value:function(){this.resizeObserver&&this.resizeObserver.disconnect(),this.mutationObserver&&this.mutationObserver.disconnect()}},{key:\"renderChildren\",value:function(e){var t=this,n=this.state.lastVisibleIndex;return(e||[]).reduce((function(o,c,i){var a=c;if(\"horizontal\"===t.props.mode){var l=t.getOverflowedSubMenuItem(c.props.eventKey,[]);void 0!==n&&-1!==t.props.className.indexOf(\"\".concat(t.props.prefixCls,\"-root\"))&&(i>n&&(a=r.cloneElement(c,{style:{display:\"none\"},eventKey:\"\".concat(c.props.eventKey,\"-hidden\"),className:\"\".concat(ot)})),i===n+1&&(t.overflowedItems=e.slice(n+1).map((function(e){return r.cloneElement(e,{key:e.props.eventKey,mode:\"vertical-left\"})})),l=t.getOverflowedSubMenuItem(c.props.eventKey,t.overflowedItems)));var u=[].concat(Ye(o),[l,a]);return i===e.length-1&&u.push(t.getOverflowedSubMenuItem(c.props.eventKey,[],!0)),u}return[].concat(Ye(o),[a])}),[])}},{key:\"render\",value:function(){var e=this.props,t=(e.visible,e.prefixCls,e.overflowedIndicator,e.mode,e.level,e.tag),n=e.children,o=(e.theme,Qe(e,[\"visible\",\"prefixCls\",\"overflowedIndicator\",\"mode\",\"level\",\"tag\",\"children\",\"theme\"])),c=t;return r.createElement(c,Object.assign({},o),this.renderChildren(n))}}])&&Ze(n.prototype,o),c&&Ze(n,c),t}(r.Component);ct.defaultProps={tag:\"div\",className:\"\"};var it=ct;function at(e){return(at=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function lt(){return(lt=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function ut(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function st(e){return(st=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function ft(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function pt(e,t){return(pt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function ht(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function dt(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?ht(Object(n),!0).forEach((function(t){vt(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):ht(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function vt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function mt(e,t,n){var r=e.getState();e.setState({activeKey:dt({},r.activeKey,vt({},t,n))})}function yt(e){return e.eventKey||\"0-menu-\"}function bt(e,t){var n,r=t,o=e.children,c=e.eventKey;if(r&&(Y(o,(function(e,t){e&&e.props&&!e.props.disabled&&r===K(e,c,t)&&(n=!0)})),n))return r;return r=null,e.defaultActiveFirst?(Y(o,(function(e,t){r||!e||e.props.disabled||(r=K(e,c,t))})),r):r}function gt(e){if(e){var t=this.instanceArray.indexOf(e);-1!==t?this.instanceArray[t]=e:this.instanceArray.push(e)}}var wt=function(e){function t(e){var n;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==at(t)&&\"function\"!==typeof t?ft(e):t}(this,st(t).call(this,e))).onKeyDown=function(e,t){var r,o=e.keyCode;if(n.getFlatInstanceArray().forEach((function(t){t&&t.props.active&&t.onKeyDown&&(r=t.onKeyDown(e))})),r)return 1;var c=null;return o!==b.a.UP&&o!==b.a.DOWN||(c=n.step(o===b.a.UP?-1:1)),c?(e.preventDefault(),mt(n.props.store,yt(n.props),c.props.eventKey),\"function\"===typeof t&&t(c),1):void 0},n.onItemHover=function(e){var t=e.key,r=e.hover;mt(n.props.store,yt(n.props),r?t:null)},n.onDeselect=function(e){n.props.onDeselect(e)},n.onSelect=function(e){n.props.onSelect(e)},n.onClick=function(e){n.props.onClick(e)},n.onOpenChange=function(e){n.props.onOpenChange(e)},n.onDestroy=function(e){n.props.onDestroy(e)},n.getFlatInstanceArray=function(){return n.instanceArray},n.step=function(e){var t=n.getFlatInstanceArray(),r=n.props.store.getState().activeKey[yt(n.props)],o=t.length;if(!o)return null;e<0&&(t=t.concat().reverse());var c=-1;if(t.every((function(e,t){return!e||e.props.eventKey!==r||(c=t,!1)})),n.props.defaultActiveFirst||-1===c||(i=t.slice(c,o-1)).length&&!i.every((function(e){return!!e.props.disabled}))){var i,a=(c+1)%o,l=a;do{var u=t[l];if(u&&!u.props.disabled)return u;l=(l+1)%o}while(l!==a);return null}},n.renderCommonMenuItem=function(e,t,o){var c=n.props.store.getState(),i=ft(n).props,a=K(e,i.eventKey,t),l=e.props;if(!l||\"string\"===typeof e.type)return e;var u=a===c.activeKey,s=dt({mode:l.mode||i.mode,level:i.level,inlineIndent:i.inlineIndent,renderMenuItem:n.renderMenuItem,rootPrefixCls:i.prefixCls,index:t,parentMenu:i.parentMenu,manualRef:l.disabled?void 0:Object(g.a)(e.ref,gt.bind(ft(n))),eventKey:a,active:!l.disabled&&u,multiple:i.multiple,onClick:function(e){(l.onClick||U)(e),n.onClick(e)},onItemHover:n.onItemHover,motion:i.motion,subMenuOpenDelay:i.subMenuOpenDelay,subMenuCloseDelay:i.subMenuCloseDelay,forceSubMenuRender:i.forceSubMenuRender,onOpenChange:n.onOpenChange,onDeselect:n.onDeselect,onSelect:n.onSelect,builtinPlacements:i.builtinPlacements,itemIcon:l.itemIcon||n.props.itemIcon,expandIcon:l.expandIcon||n.props.expandIcon},o);return(\"inline\"===i.mode||F.any)&&(s.triggerSubMenuAction=\"click\"),r.cloneElement(e,s)},n.renderMenuItem=function(e,t,r){if(!e)return null;var o=n.props.store.getState(),c={openKeys:o.openKeys,selectedKeys:o.selectedKeys,triggerSubMenuAction:n.props.triggerSubMenuAction,subMenuKey:r};return n.renderCommonMenuItem(e,t,c)},e.store.setState({activeKey:dt({},e.store.getState().activeKey,vt({},e.eventKey,bt(e,e.activeKey)))}),n.instanceArray=[],n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&pt(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.props.manualRef&&this.props.manualRef(this)}},{key:\"shouldComponentUpdate\",value:function(e){return this.props.visible||e.visible||this.props.className!==e.className||!h()(this.props.style,e.style)}},{key:\"componentDidUpdate\",value:function(e){var t=this.props,n=\"activeKey\"in t?t.activeKey:t.store.getState().activeKey[yt(t)],r=bt(t,n);(r!==n||\"activeKey\"in e&&r!==bt(e,e.activeKey))&&mt(t.store,yt(t),r)}},{key:\"render\",value:function(){var e=this,t=lt({},this.props);this.instanceArray=[];var n={className:z()(t.prefixCls,t.className,\"\".concat(t.prefixCls,\"-\").concat(t.mode)),role:t.role||\"menu\"};t.id&&(n.id=t.id),t.focusable&&(n.tabIndex=0,n.onKeyDown=this.onKeyDown);var o=t.prefixCls,c=t.eventKey,i=t.visible,a=t.level,l=t.mode,u=t.overflowedIndicator,s=t.theme;return G.forEach((function(e){return delete t[e]})),delete t.onClick,r.createElement(it,Object.assign({},t,{prefixCls:o,mode:l,tag:\"ul\",level:a,theme:s,visible:i,overflowedIndicator:u},n),r.Children.map(t.children,(function(t,n){return e.renderMenuItem(t,n,c||\"0-menu-\")})))}}])&&ut(n.prototype,o),c&&ut(n,c),t}(r.Component);wt.defaultProps={prefixCls:\"rc-menu\",className:\"\",mode:\"vertical\",level:1,inlineIndent:24,visible:!0,focusable:!0,style:{},manualRef:U};var zt=Object(y.connect)()(wt),Ot=n(66);function Ct(e){return(Ct=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Mt(e){var t=e.prefixCls,n=e.motion,r=e.openAnimation,o=e.openTransitionName;if(n)return n;if(\"object\"===Ct(r)&&r)Object(Ot.a)(!1,\"Object type of `openAnimation` is removed. Please use `motion` instead.\");else if(\"string\"===typeof r)return{motionName:\"\".concat(t,\"-open-\").concat(r)};return o?{motionName:o}:null}function St(e){return(St=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function _t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function xt(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?_t(Object(n),!0).forEach((function(t){kt(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):_t(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function kt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ht(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Et(e){return(Et=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Pt(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Vt(e,t){return(Vt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var Tt=function(e){function t(e){var n;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==St(t)&&\"function\"!==typeof t?Pt(e):t}(this,Et(t).call(this,e))).onSelect=function(e){var t=Pt(n).props;if(t.selectable){var r=n.store.getState().selectedKeys,o=e.key;r=t.multiple?r.concat([o]):[o],\"selectedKeys\"in t||n.store.setState({selectedKeys:r}),t.onSelect(xt({},e,{selectedKeys:r}))}},n.onClick=function(e){n.props.onClick(e)},n.onKeyDown=function(e,t){n.innerMenu.getWrappedInstance().onKeyDown(e,t)},n.onOpenChange=function(e){var t=Pt(n).props,r=n.store.getState().openKeys.concat(),o=!1,c=function(e){var t=!1;if(e.open)(t=-1===r.indexOf(e.key))&&r.push(e.key);else{var n=r.indexOf(e.key);(t=-1!==n)&&r.splice(n,1)}o=o||t};Array.isArray(e)?e.forEach(c):c(e),o&&(\"openKeys\"in n.props||n.store.setState({openKeys:r}),t.onOpenChange(r))},n.onDeselect=function(e){var t=Pt(n).props;if(t.selectable){var r=n.store.getState().selectedKeys.concat(),o=e.key,c=r.indexOf(o);-1!==c&&r.splice(c,1),\"selectedKeys\"in t||n.store.setState({selectedKeys:r}),t.onDeselect(xt({},e,{selectedKeys:r}))}},n.getOpenTransitionName=function(){var e=Pt(n).props,t=e.openTransitionName,r=e.openAnimation;return t||\"string\"!==typeof r||(t=\"\".concat(e.prefixCls,\"-open-\").concat(r)),t},n.setInnerMenu=function(e){n.innerMenu=e},n.isRootMenu=!0;var r=e.defaultSelectedKeys,o=e.defaultOpenKeys;return\"selectedKeys\"in e&&(r=e.selectedKeys||[]),\"openKeys\"in e&&(o=e.openKeys||[]),n.store=Object(y.create)({selectedKeys:r,openKeys:o,activeKey:{\"0-menu-\":bt(e,e.activeKey)}}),n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Vt(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.updateMiniStore()}},{key:\"componentDidUpdate\",value:function(){this.updateMiniStore()}},{key:\"updateMiniStore\",value:function(){\"selectedKeys\"in this.props&&this.store.setState({selectedKeys:this.props.selectedKeys||[]}),\"openKeys\"in this.props&&this.store.setState({openKeys:this.props.openKeys||[]})}},{key:\"render\",value:function(){var e=xt({},this.props);return e.className+=\" \".concat(e.prefixCls,\"-root\"),delete(e=xt({},e,{onClick:this.onClick,onOpenChange:this.onOpenChange,onDeselect:this.onDeselect,onSelect:this.onSelect,parentMenu:this,motion:Mt(this.props)})).openAnimation,delete e.openTransitionName,r.createElement(y.Provider,{store:this.store},r.createElement(zt,Object.assign({},e,{ref:this.setInnerMenu}),this.props.children))}}])&&Ht(n.prototype,o),c&&Ht(n,c),t}(r.Component);Tt.defaultProps={selectable:!0,onClick:U,onSelect:U,onOpenChange:U,onDeselect:U,defaultSelectedKeys:[],defaultOpenKeys:[],subMenuOpenDelay:.1,subMenuCloseDelay:.1,triggerSubMenuAction:\"hover\",prefixCls:\"rc-menu\",className:\"\",mode:\"vertical\",style:{},builtinPlacements:{},overflowedIndicator:r.createElement(\"span\",null,\"\\xb7\\xb7\\xb7\")};var Lt=Tt,jt=n(96),Nt=n.n(jt);function Dt(e){return(Dt=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Rt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function At(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?Rt(Object(n),!0).forEach((function(t){It(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Rt(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function It(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ft(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Wt(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Ut(e,t){return!t||\"object\"!==Dt(t)&&\"function\"!==typeof t?Bt(e):t}function Kt(e){return(Kt=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Bt(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Yt(e,t){return(Yt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var qt=function(e){function t(){var e;return Ft(this,t),(e=Ut(this,Kt(t).apply(this,arguments))).onKeyDown=function(t){if(t.keyCode===b.a.ENTER)return e.onClick(t),!0},e.onMouseLeave=function(t){var n=e.props,r=n.eventKey,o=n.onItemHover,c=n.onMouseLeave;o({key:r,hover:!1}),c({key:r,domEvent:t})},e.onMouseEnter=function(t){var n=e.props,r=n.eventKey,o=n.onItemHover,c=n.onMouseEnter;o({key:r,hover:!0}),c({key:r,domEvent:t})},e.onClick=function(t){var n=e.props,r=n.eventKey,o=n.multiple,c=n.onClick,i=n.onSelect,a=n.onDeselect,l=n.isSelected,u={key:r,keyPath:[r],item:Bt(e),domEvent:t};c(u),o?l?a(u):i(u):l||i(u)},e.saveNode=function(t){e.node=t},e}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Yt(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.callRef()}},{key:\"componentDidUpdate\",value:function(e){var t=this.props,n=t.active,r=t.parentMenu,o=t.eventKey;e.active||!n||r&&r[\"scrolled-\".concat(o)]?r&&r[\"scrolled-\".concat(o)]&&delete r[\"scrolled-\".concat(o)]:this.node&&(Nt()(this.node,v.findDOMNode(r),{onlyScrollIfNeeded:!0}),r[\"scrolled-\".concat(o)]=!0),this.callRef()}},{key:\"componentWillUnmount\",value:function(){var e=this.props;e.onDestroy&&e.onDestroy(e.eventKey)}},{key:\"getPrefixCls\",value:function(){return\"\".concat(this.props.rootPrefixCls,\"-item\")}},{key:\"getActiveClassName\",value:function(){return\"\".concat(this.getPrefixCls(),\"-active\")}},{key:\"getSelectedClassName\",value:function(){return\"\".concat(this.getPrefixCls(),\"-selected\")}},{key:\"getDisabledClassName\",value:function(){return\"\".concat(this.getPrefixCls(),\"-disabled\")}},{key:\"callRef\",value:function(){this.props.manualRef&&this.props.manualRef(this)}},{key:\"render\",value:function(){var e,t=At({},this.props),n=z()(this.getPrefixCls(),t.className,(It(e={},this.getActiveClassName(),!t.disabled&&t.active),It(e,this.getSelectedClassName(),t.isSelected),It(e,this.getDisabledClassName(),t.disabled),e)),o=At({},t.attribute,{title:t.title,className:n,role:t.role||\"menuitem\",\"aria-disabled\":t.disabled});\"option\"===t.role?o=At({},o,{role:\"option\",\"aria-selected\":t.isSelected}):null!==t.role&&\"none\"!==t.role||(o.role=\"none\");var c={onClick:t.disabled?null:this.onClick,onMouseLeave:t.disabled?null:this.onMouseLeave,onMouseEnter:t.disabled?null:this.onMouseEnter},i=At({},t.style);\"inline\"===t.mode&&(i.paddingLeft=t.inlineIndent*t.level),G.forEach((function(e){return delete t[e]}));var a=this.props.itemIcon;return\"function\"===typeof this.props.itemIcon&&(a=r.createElement(this.props.itemIcon,this.props)),r.createElement(\"li\",Object.assign({},t,o,c,{style:i,ref:this.saveNode}),t.children,a)}}])&&Wt(n.prototype,o),c&&Wt(n,c),t}(r.Component);qt.isMenuItem=!0,qt.defaultProps={onSelect:U,onMouseEnter:U,onMouseLeave:U,manualRef:U};var Gt=Object(y.connect)((function(e,t){var n=e.activeKey,r=e.selectedKeys,o=t.eventKey;return{active:n[t.subMenuKey]===o,isSelected:-1!==r.indexOf(o)}}))(qt);function $t(e){return($t=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Qt(){return(Qt=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Xt(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Zt(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Jt(e,t){return!t||\"object\"!==$t(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function en(e){return(en=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function tn(e,t){return(tn=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var nn=function(e){function t(){var e;return Xt(this,t),(e=Jt(this,en(t).apply(this,arguments))).renderInnerMenuItem=function(t){var n=e.props;return(0,n.renderMenuItem)(t,n.index,e.props.subMenuKey)},e}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&tn(e,t)}(t,e),n=t,(o=[{key:\"render\",value:function(){var e=Qt({},this.props),t=e.className,n=void 0===t?\"\":t,o=e.rootPrefixCls,c=\"\".concat(o,\"-item-group-title\"),i=\"\".concat(o,\"-item-group-list\"),a=e.title,l=e.children;return G.forEach((function(t){return delete e[t]})),delete e.onClick,r.createElement(\"li\",Object.assign({},e,{className:\"\".concat(n,\" \").concat(o,\"-item-group\")}),r.createElement(\"div\",{className:c,title:\"string\"===typeof a?a:void 0},a),r.createElement(\"ul\",{className:i},r.Children.map(l,this.renderInnerMenuItem)))}}])&&Zt(n.prototype,o),c&&Zt(n,c),t}(r.Component);nn.isMenuItemGroup=!0,nn.defaultProps={disabled:!0};var rn=nn,on=function(e){var t=e.className,n=e.rootPrefixCls,o=e.style;return r.createElement(\"li\",{className:\"\".concat(t,\" \").concat(n,\"-item-divider\"),style:o})};on.defaultProps={disabled:!0,className:\"\",style:{}};var cn=on,an=Lt,ln=n(159),un=n.n(ln),sn={adjustX:1,adjustY:1},fn=[0,0],pn={topLeft:{points:[\"bl\",\"tl\"],overflow:sn,offset:[0,-4],targetOffset:fn},topCenter:{points:[\"bc\",\"tc\"],overflow:sn,offset:[0,-4],targetOffset:fn},topRight:{points:[\"br\",\"tr\"],overflow:sn,offset:[0,-4],targetOffset:fn},bottomLeft:{points:[\"tl\",\"bl\"],overflow:sn,offset:[0,4],targetOffset:fn},bottomCenter:{points:[\"tc\",\"bc\"],overflow:sn,offset:[0,4],targetOffset:fn},bottomRight:{points:[\"tr\",\"br\"],overflow:sn,offset:[0,4],targetOffset:fn}},hn=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e};var dn=function(e){function t(n){!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t);var r=function(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}(this,e.call(this,n));return vn.call(r),r.state=\"visible\"in n?{visible:n.visible}:{visible:n.defaultVisible},r}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.getDerivedStateFromProps=function(e){return\"visible\"in e?{visible:e.visible}:null},t.prototype.getOverlayElement=function(){var e=this.props.overlay;return\"function\"===typeof e?e():e},t.prototype.getMenuElementOrLambda=function(){return\"function\"===typeof this.props.overlay?this.getMenuElement:this.getMenuElement()},t.prototype.getPopupDomNode=function(){return this.trigger.getPopupDomNode()},t.prototype.getOpenClassName=function(){var e=this.props,t=e.openClassName,n=e.prefixCls;return void 0!==t?t:n+\"-open\"},t.prototype.renderChildren=function(){var e=this.props.children,t=this.state.visible,n=e.props?e.props:{},o=z()(n.className,this.getOpenClassName());return t&&e?Object(r.cloneElement)(e,{className:o}):e},t.prototype.render=function(){var e=this.props,t=e.prefixCls,n=e.transitionName,r=e.animation,c=e.align,i=e.placement,a=e.getPopupContainer,l=e.showAction,u=e.hideAction,s=e.overlayClassName,f=e.overlayStyle,p=e.trigger,h=function(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}(e,[\"prefixCls\",\"transitionName\",\"animation\",\"align\",\"placement\",\"getPopupContainer\",\"showAction\",\"hideAction\",\"overlayClassName\",\"overlayStyle\",\"trigger\"]),d=u;return d||-1===p.indexOf(\"contextMenu\")||(d=[\"click\"]),o.a.createElement(Z.a,hn({},h,{prefixCls:t,ref:this.saveTrigger,popupClassName:s,popupStyle:f,builtinPlacements:pn,action:p,showAction:l,hideAction:d||[],popupPlacement:i,popupAlign:c,popupTransitionName:n,popupAnimation:r,popupVisible:this.state.visible,afterPopupVisibleChange:this.afterVisibleChange,popup:this.getMenuElementOrLambda(),onPopupVisibleChange:this.onVisibleChange,getPopupContainer:a}),this.renderChildren())},t}(r.Component);dn.propTypes={minOverlayWidthMatchTrigger:u.a.bool,onVisibleChange:u.a.func,onOverlayClick:u.a.func,prefixCls:u.a.string,children:u.a.any,transitionName:u.a.string,overlayClassName:u.a.string,openClassName:u.a.string,animation:u.a.any,align:u.a.object,overlayStyle:u.a.object,placement:u.a.string,overlay:u.a.oneOfType([u.a.node,u.a.func]),trigger:u.a.array,alignPoint:u.a.bool,showAction:u.a.array,hideAction:u.a.array,getPopupContainer:u.a.func,visible:u.a.bool,defaultVisible:u.a.bool},dn.defaultProps={prefixCls:\"rc-dropdown\",trigger:[\"hover\"],showAction:[],overlayClassName:\"\",overlayStyle:{},defaultVisible:!1,onVisibleChange:function(){},placement:\"bottomLeft\"};var vn=function(){var e=this;this.onClick=function(t){var n=e.props,r=e.getOverlayElement().props;\"visible\"in n||e.setState({visible:!1}),n.onOverlayClick&&n.onOverlayClick(t),r.onClick&&r.onClick(t)},this.onVisibleChange=function(t){var n=e.props;\"visible\"in n||e.setState({visible:t}),n.onVisibleChange(t)},this.getMinOverlayWidthMatchTrigger=function(){var t=e.props,n=t.minOverlayWidthMatchTrigger,r=t.alignPoint;return\"minOverlayWidthMatchTrigger\"in e.props?n:!r},this.getMenuElement=function(){var t=e.props.prefixCls,n=e.getOverlayElement(),r={prefixCls:t+\"-menu\",onClick:e.onClick};return\"string\"===typeof n.type&&delete r.prefixCls,o.a.cloneElement(n,r)},this.afterVisibleChange=function(t){if(t&&e.getMinOverlayWidthMatchTrigger()){var n=e.getPopupDomNode(),r=m.a.findDOMNode(e);r&&n&&r.offsetWidth>n.offsetWidth&&(n.style.minWidth=r.offsetWidth+\"px\",e.trigger&&e.trigger._component&&e.trigger._component.alignInstance&&e.trigger._component.alignInstance.forceAlign())}},this.saveTrigger=function(t){e.trigger=t}};Object(d.polyfill)(dn);var mn=dn,yn=n(59),bn=n(16),gn=n(13),wn=n(27);function zn(e){return(zn=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function On(){return(On=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Cn(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Mn(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Sn(e,t){return(Sn=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function _n(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=kn(e);if(t){var o=kn(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return xn(this,n)}}function xn(e,t){return!t||\"object\"!==zn(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function kn(e){return(kn=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}Object(wn.a)(\"topLeft\",\"topCenter\",\"topRight\",\"bottomLeft\",\"bottomCenter\",\"bottomRight\");var Hn=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Sn(e,t)}(i,e);var t,n,o,c=_n(i);function i(){var e;return Cn(this,i),(e=c.apply(this,arguments)).renderOverlay=function(t){var n,o=e.props.overlay;n=\"function\"===typeof o?o():o;var c=(n=r.Children.only(n)).props;Object(bn.a)(!c.mode||\"vertical\"===c.mode,\"Dropdown\",'mode=\"'.concat(c.mode,\"\\\" is not supported for Dropdown's Menu.\"));var i=c.selectable,a=void 0!==i&&i,l=c.focusable,u=void 0===l||l,s=r.createElement(\"span\",{className:\"\".concat(t,\"-menu-submenu-arrow\")},r.createElement(gn.a,{type:\"right\",className:\"\".concat(t,\"-menu-submenu-arrow-icon\")}));return\"string\"===typeof n.type?o:r.cloneElement(n,{mode:\"vertical\",selectable:a,focusable:u,expandIcon:s})},e.renderDropDown=function(t){var n,o=t.getPopupContainer,c=t.getPrefixCls,i=e.props,a=i.prefixCls,l=i.children,u=i.trigger,s=i.disabled,p=i.getPopupContainer,h=c(\"dropdown\",a),d=r.Children.only(l),v=r.cloneElement(d,{className:f()(d.props.className,\"\".concat(h,\"-trigger\")),disabled:s}),m=s?[]:u;return m&&-1!==m.indexOf(\"contextMenu\")&&(n=!0),r.createElement(mn,On({alignPoint:n},e.props,{prefixCls:h,getPopupContainer:p||o,transitionName:e.getTransitionName(),trigger:m,overlay:function(){return e.renderOverlay(h)}}),v)},e}return t=i,(n=[{key:\"getTransitionName\",value:function(){var e=this.props,t=e.placement,n=void 0===t?\"\":t,r=e.transitionName;return void 0!==r?r:n.indexOf(\"top\")>=0?\"slide-down\":\"slide-up\"}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderDropDown)}}])&&Mn(t.prototype,n),o&&Mn(t,o),i}(r.Component);Hn.defaultProps={mouseEnterDelay:.15,mouseLeaveDelay:.1,placement:\"bottomLeft\"};var En,Pn=n(32),Vn=0,Tn={};function Ln(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=Vn++,r=t;function o(){(r-=1)<=0?(e(),delete Tn[n]):Tn[n]=pe()(o)}return Tn[n]=pe()(o),n}function jn(e){return(jn=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Nn(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Dn(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Rn(e,t){return(Rn=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function An(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Wn(e);if(t){var o=Wn(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return In(this,n)}}function In(e,t){return!t||\"object\"!==jn(t)&&\"function\"!==typeof t?Fn(e):t}function Fn(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Wn(e){return(Wn=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Un(e){return!e||null===e.offsetParent}function Kn(e){var t=(e||\"\").match(/rgba?\\((\\d*), (\\d*), (\\d*)(, [\\.\\d]*)?\\)/);return!(t&&t[1]&&t[2]&&t[3])||!(t[1]===t[2]&&t[2]===t[3])}Ln.cancel=function(e){void 0!==e&&(pe.a.cancel(Tn[e]),delete Tn[e])},Ln.ids=Tn;var Bn=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Rn(e,t)}(i,e);var t,n,o,c=An(i);function i(){var e;return Nn(this,i),(e=c.apply(this,arguments)).animationStart=!1,e.destroy=!1,e.onClick=function(t,n){if(!(!t||Un(t)||t.className.indexOf(\"-leave\")>=0)){var r=e.props.insertExtraNode;e.extraNode=document.createElement(\"div\");var o=Fn(e).extraNode;o.className=\"ant-click-animating-node\";var c=e.getAttributeName();t.setAttribute(c,\"true\"),En=En||document.createElement(\"style\"),n&&\"#ffffff\"!==n&&\"rgb(255, 255, 255)\"!==n&&Kn(n)&&!/rgba\\(\\d*, \\d*, \\d*, 0\\)/.test(n)&&\"transparent\"!==n&&(e.csp&&e.csp.nonce&&(En.nonce=e.csp.nonce),o.style.borderColor=n,En.innerHTML=\"\\n      [ant-click-animating-without-extra-node='true']::after, .ant-click-animating-node {\\n        --antd-wave-shadow-color: \".concat(n,\";\\n      }\"),document.body.contains(En)||document.body.appendChild(En)),r&&t.appendChild(o),Pn.a.addStartEventListener(t,e.onTransitionStart),Pn.a.addEndEventListener(t,e.onTransitionEnd)}},e.onTransitionStart=function(t){if(!e.destroy){var n=Object(v.findDOMNode)(Fn(e));t&&t.target===n&&(e.animationStart||e.resetEffect(n))}},e.onTransitionEnd=function(t){t&&\"fadeEffect\"===t.animationName&&e.resetEffect(t.target)},e.bindAnimationEvent=function(t){if(t&&t.getAttribute&&!t.getAttribute(\"disabled\")&&!(t.className.indexOf(\"disabled\")>=0)){var n=function(n){if(\"INPUT\"!==n.target.tagName&&!Un(n.target)){e.resetEffect(t);var r=getComputedStyle(t).getPropertyValue(\"border-top-color\")||getComputedStyle(t).getPropertyValue(\"border-color\")||getComputedStyle(t).getPropertyValue(\"background-color\");e.clickWaveTimeoutId=window.setTimeout((function(){return e.onClick(t,r)}),0),Ln.cancel(e.animationStartId),e.animationStart=!0,e.animationStartId=Ln((function(){e.animationStart=!1}),10)}};return t.addEventListener(\"click\",n,!0),{cancel:function(){t.removeEventListener(\"click\",n,!0)}}}},e.renderWave=function(t){var n=t.csp,r=e.props.children;return e.csp=n,r},e}return t=i,(n=[{key:\"componentDidMount\",value:function(){var e=Object(v.findDOMNode)(this);e&&1===e.nodeType&&(this.instance=this.bindAnimationEvent(e))}},{key:\"componentWillUnmount\",value:function(){this.instance&&this.instance.cancel(),this.clickWaveTimeoutId&&clearTimeout(this.clickWaveTimeoutId),this.destroy=!0}},{key:\"getAttributeName\",value:function(){return this.props.insertExtraNode?\"ant-click-animating\":\"ant-click-animating-without-extra-node\"}},{key:\"resetEffect\",value:function(e){if(e&&e!==this.extraNode&&e instanceof Element){var t=this.props.insertExtraNode,n=this.getAttributeName();e.setAttribute(n,\"false\"),En&&(En.innerHTML=\"\"),t&&this.extraNode&&e.contains(this.extraNode)&&e.removeChild(this.extraNode),Pn.a.removeStartEventListener(e,this.onTransitionStart),Pn.a.removeEndEventListener(e,this.onTransitionEnd)}}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderWave)}}])&&Dn(t.prototype,n),o&&Dn(t,o),i}(r.Component);function Yn(){return(Yn=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function qn(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Gn(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function $n(e,t){return($n=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Qn(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Zn(e);if(t){var o=Zn(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Xn(this,n)}}function Xn(e,t){return!t||\"object\"!==Jn(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Zn(e){return(Zn=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Jn(e){return(Jn=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}var er=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},tr=/^[\\u4e00-\\u9fa5]{2}$/,nr=tr.test.bind(tr);function rr(e,t){var n=!1,o=[];return r.Children.forEach(e,(function(e){var t=Jn(e),r=\"string\"===t||\"number\"===t;if(n&&r){var c=o.length-1,i=o[c];o[c]=\"\".concat(i).concat(e)}else o.push(e);n=r})),r.Children.map(o,(function(e){return function(e,t){if(null!=e){var n=t?\" \":\"\";return\"string\"!==typeof e&&\"number\"!==typeof e&&\"string\"===typeof e.type&&nr(e.props.children)?r.cloneElement(e,{},e.props.children.split(\"\").join(n)):\"string\"===typeof e?(nr(e)&&(e=e.split(\"\").join(n)),r.createElement(\"span\",null,e)):e}}(e,t)}))}Object(wn.a)(\"default\",\"primary\",\"ghost\",\"dashed\",\"danger\",\"link\");var or=Object(wn.a)(\"circle\",\"circle-outline\",\"round\"),cr=Object(wn.a)(\"large\",\"default\",\"small\"),ir=Object(wn.a)(\"submit\",\"button\",\"reset\"),ar=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&$n(e,t)}(a,e);var t,n,o,i=Qn(a);function a(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,a),(t=i.call(this,e)).saveButtonRef=function(e){t.buttonNode=e},t.handleClick=function(e){var n=t.state.loading,r=t.props.onClick;n||r&&r(e)},t.renderButton=function(e){var n,o=e.getPrefixCls,i=e.autoInsertSpaceInButton,a=t.props,l=a.prefixCls,u=a.type,s=a.shape,p=a.size,h=a.className,d=a.children,v=a.icon,m=a.ghost,y=a.block,b=er(a,[\"prefixCls\",\"type\",\"shape\",\"size\",\"className\",\"children\",\"icon\",\"ghost\",\"block\"]),g=t.state,w=g.loading,z=g.hasTwoCNChar,O=o(\"btn\",l),C=!1!==i,M=\"\";switch(p){case\"large\":M=\"lg\";break;case\"small\":M=\"sm\"}var S=w?\"loading\":v,_=f()(O,h,(qn(n={},\"\".concat(O,\"-\").concat(u),u),qn(n,\"\".concat(O,\"-\").concat(s),s),qn(n,\"\".concat(O,\"-\").concat(M),M),qn(n,\"\".concat(O,\"-icon-only\"),!d&&0!==d&&S),qn(n,\"\".concat(O,\"-loading\"),!!w),qn(n,\"\".concat(O,\"-background-ghost\"),m),qn(n,\"\".concat(O,\"-two-chinese-chars\"),z&&C),qn(n,\"\".concat(O,\"-block\"),y),n)),x=S?r.createElement(gn.a,{type:S}):null,k=d||0===d?rr(d,t.isNeedInserted()&&C):null,H=Object(c.a)(b,[\"htmlType\",\"loading\"]);if(void 0!==H.href)return r.createElement(\"a\",Yn({},H,{className:_,onClick:t.handleClick,ref:t.saveButtonRef}),x,k);var E=b,P=E.htmlType,V=er(E,[\"htmlType\"]),T=r.createElement(\"button\",Yn({},Object(c.a)(V,[\"loading\"]),{type:P,className:_,onClick:t.handleClick,ref:t.saveButtonRef}),x,k);return\"link\"===u?T:r.createElement(Bn,null,T)},t.state={loading:e.loading,hasTwoCNChar:!1},t}return t=a,(n=[{key:\"componentDidMount\",value:function(){this.fixTwoCNChar()}},{key:\"componentDidUpdate\",value:function(e){var t=this;this.fixTwoCNChar(),e.loading&&\"boolean\"!==typeof e.loading&&clearTimeout(this.delayTimeout);var n=this.props.loading;n&&\"boolean\"!==typeof n&&n.delay?this.delayTimeout=window.setTimeout((function(){t.setState({loading:n})}),n.delay):e.loading!==n&&this.setState({loading:n})}},{key:\"componentWillUnmount\",value:function(){this.delayTimeout&&clearTimeout(this.delayTimeout)}},{key:\"fixTwoCNChar\",value:function(){if(this.buttonNode){var e=this.buttonNode.textContent;this.isNeedInserted()&&nr(e)?this.state.hasTwoCNChar||this.setState({hasTwoCNChar:!0}):this.state.hasTwoCNChar&&this.setState({hasTwoCNChar:!1})}}},{key:\"isNeedInserted\",value:function(){var e=this.props,t=e.icon,n=e.children,o=e.type;return 1===r.Children.count(n)&&!t&&\"link\"!==o}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderButton)}}])&&Gn(t.prototype,n),o&&Gn(t,o),a}(r.Component);ar.__ANT_BUTTON=!0,ar.defaultProps={loading:!1,ghost:!1,block:!1,htmlType:\"button\"},ar.propTypes={type:l.string,shape:l.oneOf(or),size:l.oneOf(cr),htmlType:l.oneOf(ir),onClick:l.func,loading:l.oneOfType([l.bool,l.object]),className:l.string,icon:l.string,block:l.bool,title:l.string},Object(d.polyfill)(ar);var lr=ar;function ur(){return(ur=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var sr=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},fr=function(e){return r.createElement(yn.a,null,(function(t){var n=t.getPrefixCls,o=e.prefixCls,c=e.size,i=e.className,a=sr(e,[\"prefixCls\",\"size\",\"className\"]),l=n(\"btn-group\",o),u=\"\";switch(c){case\"large\":u=\"lg\";break;case\"small\":u=\"sm\"}var s,p,h,d=f()(l,(s={},p=\"\".concat(l,\"-\").concat(u),h=u,p in s?Object.defineProperty(s,p,{value:h,enumerable:!0,configurable:!0,writable:!0}):s[p]=h,s),i);return r.createElement(\"div\",ur({},a,{className:d}))}))};lr.Group=fr;var pr=lr;function hr(e){return(hr=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function dr(){return(dr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function vr(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function mr(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function yr(e,t){return(yr=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function br(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=wr(e);if(t){var o=wr(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return gr(this,n)}}function gr(e,t){return!t||\"object\"!==hr(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function wr(e){return(wr=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var zr=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Or=pr.Group,Cr=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&yr(e,t)}(i,e);var t,n,o,c=br(i);function i(){var e;return vr(this,i),(e=c.apply(this,arguments)).renderButton=function(t){var n=t.getPopupContainer,o=t.getPrefixCls,c=e.props,i=c.prefixCls,a=c.type,l=c.disabled,u=c.onClick,s=c.htmlType,p=c.children,h=c.className,d=c.overlay,v=c.trigger,m=c.align,y=c.visible,b=c.onVisibleChange,g=c.placement,w=c.getPopupContainer,z=c.href,O=c.icon,C=void 0===O?r.createElement(gn.a,{type:\"ellipsis\"}):O,M=c.title,S=zr(c,[\"prefixCls\",\"type\",\"disabled\",\"onClick\",\"htmlType\",\"children\",\"className\",\"overlay\",\"trigger\",\"align\",\"visible\",\"onVisibleChange\",\"placement\",\"getPopupContainer\",\"href\",\"icon\",\"title\"]),_=o(\"dropdown-button\",i),x={align:m,overlay:d,disabled:l,trigger:l?[]:v,onVisibleChange:b,placement:g,getPopupContainer:w||n};return\"visible\"in e.props&&(x.visible=y),r.createElement(Or,dr({},S,{className:f()(_,h)}),r.createElement(pr,{type:a,disabled:l,onClick:u,htmlType:s,href:z,title:M},p),r.createElement(Hn,x,r.createElement(pr,{type:a},C)))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderButton)}}])&&mr(t.prototype,n),o&&mr(t,o),i}(r.Component);Cr.defaultProps={placement:\"bottomRight\",type:\"default\"},Hn.Button=Cr;var Mr=Hn,Sr=n(26),_r=n.n(Sr),xr=function(e){function t(n){oe()(this,t);var r=le()(this,e.call(this,n));r.handleChange=function(e){var t=r.props,n=t.disabled,o=t.onChange;n||(\"checked\"in r.props||r.setState({checked:e.target.checked}),o&&o({target:ne()({},r.props,{checked:e.target.checked}),stopPropagation:function(){e.stopPropagation()},preventDefault:function(){e.preventDefault()},nativeEvent:e.nativeEvent}))},r.saveInput=function(e){r.input=e};var o=\"checked\"in n?n.checked:n.defaultChecked;return r.state={checked:o},r}return se()(t,e),t.getDerivedStateFromProps=function(e,t){return\"checked\"in e?ne()({},t,{checked:e.checked}):null},t.prototype.focus=function(){this.input.focus()},t.prototype.blur=function(){this.input.blur()},t.prototype.render=function(){var e,t=this.props,n=t.prefixCls,r=t.className,c=t.style,i=t.name,a=t.id,l=t.type,u=t.disabled,s=t.readOnly,f=t.tabIndex,p=t.onClick,h=t.onFocus,d=t.onBlur,v=t.autoFocus,m=t.value,y=_r()(t,[\"prefixCls\",\"className\",\"style\",\"name\",\"id\",\"type\",\"disabled\",\"readOnly\",\"tabIndex\",\"onClick\",\"onFocus\",\"onBlur\",\"autoFocus\",\"value\"]),b=Object.keys(y).reduce((function(e,t){return\"aria-\"!==t.substr(0,5)&&\"data-\"!==t.substr(0,5)&&\"role\"!==t||(e[t]=y[t]),e}),{}),g=this.state.checked,w=z()(n,r,((e={})[n+\"-checked\"]=g,e[n+\"-disabled\"]=u,e));return o.a.createElement(\"span\",{className:w,style:c},o.a.createElement(\"input\",ne()({name:i,id:a,type:l,readOnly:s,disabled:u,tabIndex:f,className:n+\"-input\",checked:!!g,onClick:p,onFocus:h,onBlur:d,onChange:this.handleChange,autoFocus:v,ref:this.saveInput,value:m},b)),o.a.createElement(\"span\",{className:n+\"-inner\"}))},t}(r.Component);xr.propTypes={prefixCls:u.a.string,className:u.a.string,style:u.a.object,name:u.a.string,id:u.a.string,type:u.a.string,defaultChecked:u.a.oneOfType([u.a.number,u.a.bool]),checked:u.a.oneOfType([u.a.number,u.a.bool]),disabled:u.a.bool,onFocus:u.a.func,onBlur:u.a.func,onChange:u.a.func,onClick:u.a.func,tabIndex:u.a.oneOfType([u.a.string,u.a.number]),readOnly:u.a.bool,autoFocus:u.a.bool,value:u.a.any},xr.defaultProps={prefixCls:\"rc-checkbox\",className:\"\",style:{},type:\"checkbox\",defaultChecked:!1,onFocus:function(){},onBlur:function(){},onChange:function(){}},Object(d.polyfill)(xr);var kr=xr;function Hr(e){return(Hr=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Er(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Pr(){return(Pr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Vr(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Tr(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Lr(e,t){return(Lr=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function jr(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Rr(e);if(t){var o=Rr(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Nr(this,n)}}function Nr(e,t){return!t||\"object\"!==Hr(t)&&\"function\"!==typeof t?Dr(e):t}function Dr(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Rr(e){return(Rr=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Ar=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Ir=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Lr(e,t)}(i,e);var t,n,o,c=jr(i);function i(){var e;return Vr(this,i),(e=c.apply(this,arguments)).saveCheckbox=function(t){e.rcCheckbox=t},e.renderCheckbox=function(t){var n,o=t.getPrefixCls,c=Dr(e),i=c.props,a=c.context,l=i.prefixCls,u=i.className,s=i.children,p=i.indeterminate,h=i.style,d=i.onMouseEnter,v=i.onMouseLeave,m=Ar(i,[\"prefixCls\",\"className\",\"children\",\"indeterminate\",\"style\",\"onMouseEnter\",\"onMouseLeave\"]),y=a.checkboxGroup,b=o(\"checkbox\",l),g=Pr({},m);y&&(g.onChange=function(){m.onChange&&m.onChange.apply(m,arguments),y.toggleOption({label:s,value:i.value})},g.name=y.name,g.checked=-1!==y.value.indexOf(i.value),g.disabled=i.disabled||y.disabled);var w=f()(u,(Er(n={},\"\".concat(b,\"-wrapper\"),!0),Er(n,\"\".concat(b,\"-wrapper-checked\"),g.checked),Er(n,\"\".concat(b,\"-wrapper-disabled\"),g.disabled),n)),z=f()(Er({},\"\".concat(b,\"-indeterminate\"),p));return r.createElement(\"label\",{className:w,style:h,onMouseEnter:d,onMouseLeave:v},r.createElement(kr,Pr({},g,{prefixCls:b,className:z,ref:e.saveCheckbox})),void 0!==s&&r.createElement(\"span\",null,s))},e}return t=i,(n=[{key:\"componentDidMount\",value:function(){var e=this.props.value,t=(this.context||{}).checkboxGroup,n=void 0===t?{}:t;n.registerValue&&n.registerValue(e),Object(bn.a)(\"checked\"in this.props||(this.context||{}).checkboxGroup||!(\"value\"in this.props),\"Checkbox\",\"`value` is not validate prop, do you mean `checked`?\")}},{key:\"shouldComponentUpdate\",value:function(e,t,n){return!h()(this.props,e)||!h()(this.state,t)||!h()(this.context.checkboxGroup,n.checkboxGroup)}},{key:\"componentDidUpdate\",value:function(e){var t=e.value,n=this.props.value,r=(this.context||{}).checkboxGroup,o=void 0===r?{}:r;n!==t&&o.registerValue&&o.cancelValue&&(o.cancelValue(t),o.registerValue(n))}},{key:\"componentWillUnmount\",value:function(){var e=this.props.value,t=(this.context||{}).checkboxGroup,n=void 0===t?{}:t;n.cancelValue&&n.cancelValue(e)}},{key:\"focus\",value:function(){this.rcCheckbox.focus()}},{key:\"blur\",value:function(){this.rcCheckbox.blur()}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderCheckbox)}}])&&Tr(t.prototype,n),o&&Tr(t,o),i}(r.Component);Ir.__ANT_CHECKBOX=!0,Ir.defaultProps={indeterminate:!1},Ir.contextTypes={checkboxGroup:l.any},Object(d.polyfill)(Ir);var Fr=Ir;function Wr(e){return(Wr=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ur(){return(Ur=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Kr(e){return function(e){if(Array.isArray(e))return Br(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if(\"string\"===typeof e)return Br(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);\"Object\"===n&&e.constructor&&(n=e.constructor.name);if(\"Map\"===n||\"Set\"===n)return Array.from(e);if(\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Br(e,t)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function Br(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function Yr(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function qr(e,t){return(qr=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Gr(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Xr(e);if(t){var o=Xr(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return $r(this,n)}}function $r(e,t){return!t||\"object\"!==Wr(t)&&\"function\"!==typeof t?Qr(e):t}function Qr(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Xr(e){return(Xr=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Zr=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Jr=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&qr(e,t)}(a,e);var t,n,o,i=Gr(a);function a(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,a),(t=i.call(this,e)).cancelValue=function(e){t.setState((function(t){return{registeredValues:t.registeredValues.filter((function(t){return t!==e}))}}))},t.registerValue=function(e){t.setState((function(t){var n=t.registeredValues;return{registeredValues:[].concat(Kr(n),[e])}}))},t.toggleOption=function(e){var n=t.state.registeredValues,r=t.state.value.indexOf(e.value),o=Kr(t.state.value);-1===r?o.push(e.value):o.splice(r,1),\"value\"in t.props||t.setState({value:o});var c=t.props.onChange;if(c){var i=t.getOptions();c(o.filter((function(e){return-1!==n.indexOf(e)})).sort((function(e,t){return i.findIndex((function(t){return t.value===e}))-i.findIndex((function(e){return e.value===t}))})))}},t.renderGroup=function(e){var n=e.getPrefixCls,o=Qr(t),i=o.props,a=o.state,l=i.prefixCls,u=i.className,s=i.style,p=i.options,h=Zr(i,[\"prefixCls\",\"className\",\"style\",\"options\"]),d=n(\"checkbox\",l),v=\"\".concat(d,\"-group\"),m=Object(c.a)(h,[\"children\",\"defaultValue\",\"value\",\"onChange\",\"disabled\"]),y=i.children;p&&p.length>0&&(y=t.getOptions().map((function(e){return r.createElement(Fr,{prefixCls:d,key:e.value.toString(),disabled:\"disabled\"in e?e.disabled:i.disabled,value:e.value,checked:-1!==a.value.indexOf(e.value),onChange:e.onChange,className:\"\".concat(v,\"-item\")},e.label)})));var b=f()(v,u);return r.createElement(\"div\",Ur({className:b,style:s},m),y)},t.state={value:e.value||e.defaultValue||[],registeredValues:[]},t}return t=a,o=[{key:\"getDerivedStateFromProps\",value:function(e){return\"value\"in e?{value:e.value||[]}:null}}],(n=[{key:\"getChildContext\",value:function(){return{checkboxGroup:{toggleOption:this.toggleOption,value:this.state.value,disabled:this.props.disabled,name:this.props.name,registerValue:this.registerValue,cancelValue:this.cancelValue}}}},{key:\"shouldComponentUpdate\",value:function(e,t){return!h()(this.props,e)||!h()(this.state,t)}},{key:\"getOptions\",value:function(){return this.props.options.map((function(e){return\"string\"===typeof e?{label:e,value:e}:e}))}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderGroup)}}])&&Yr(t.prototype,n),o&&Yr(t,o),a}(r.Component);Jr.defaultProps={options:[]},Jr.propTypes={defaultValue:l.array,value:l.array,options:l.array.isRequired,onChange:l.func},Jr.childContextTypes={checkboxGroup:l.any},Object(d.polyfill)(Jr);var eo=Jr;Fr.Group=eo;var to=Fr;function no(e){return(no=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function ro(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function oo(){return(oo=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function co(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function io(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function ao(e,t){return(ao=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function lo(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=fo(e);if(t){var o=fo(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return uo(this,n)}}function uo(e,t){return!t||\"object\"!==no(t)&&\"function\"!==typeof t?so(e):t}function so(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function fo(e){return(fo=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var po=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},ho=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&ao(e,t)}(i,e);var t,n,o,c=lo(i);function i(){var e;return co(this,i),(e=c.apply(this,arguments)).saveCheckbox=function(t){e.rcCheckbox=t},e.onChange=function(t){e.props.onChange&&e.props.onChange(t),e.context.radioGroup&&e.context.radioGroup.onChange&&e.context.radioGroup.onChange(t)},e.renderRadio=function(t){var n,o=t.getPrefixCls,c=so(e),i=c.props,a=c.context,l=i.prefixCls,u=i.className,s=i.children,p=i.style,h=po(i,[\"prefixCls\",\"className\",\"children\",\"style\"]),d=a.radioGroup,v=o(\"radio\",l),m=oo({},h);d&&(m.name=d.name,m.onChange=e.onChange,m.checked=i.value===d.value,m.disabled=i.disabled||d.disabled);var y=f()(u,(ro(n={},\"\".concat(v,\"-wrapper\"),!0),ro(n,\"\".concat(v,\"-wrapper-checked\"),m.checked),ro(n,\"\".concat(v,\"-wrapper-disabled\"),m.disabled),n));return r.createElement(\"label\",{className:y,style:p,onMouseEnter:i.onMouseEnter,onMouseLeave:i.onMouseLeave},r.createElement(kr,oo({},m,{prefixCls:v,ref:e.saveCheckbox})),void 0!==s?r.createElement(\"span\",null,s):null)},e}return t=i,(n=[{key:\"shouldComponentUpdate\",value:function(e,t,n){return!h()(this.props,e)||!h()(this.state,t)||!h()(this.context.radioGroup,n.radioGroup)}},{key:\"focus\",value:function(){this.rcCheckbox.focus()}},{key:\"blur\",value:function(){this.rcCheckbox.blur()}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderRadio)}}])&&io(t.prototype,n),o&&io(t,o),i}(r.Component);function vo(e){return(vo=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function mo(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function yo(e,t){return(yo=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function bo(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=zo(e);if(t){var o=zo(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return go(this,n)}}function go(e,t){return!t||\"object\"!==vo(t)&&\"function\"!==typeof t?wo(e):t}function wo(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function zo(e){return(zo=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Oo(e){var t=null,n=!1;return r.Children.forEach(e,(function(e){e&&e.props&&e.props.checked&&(t=e.props.value,n=!0)})),n?{value:t}:void 0}ho.defaultProps={type:\"radio\"},ho.contextTypes={radioGroup:l.any};var Co=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&yo(e,t)}(i,e);var t,n,o,c=bo(i);function i(e){var t,n;if(function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=c.call(this,e)).onRadioChange=function(e){var n=t.state.value,r=e.target.value;\"value\"in t.props||t.setState({value:r});var o=t.props.onChange;o&&r!==n&&o(e)},t.renderGroup=function(e){var n=e.getPrefixCls,o=wo(t).props,c=o.prefixCls,i=o.className,a=void 0===i?\"\":i,l=o.options,u=o.buttonStyle,s=n(\"radio\",c),p=\"\".concat(s,\"-group\"),h=f()(p,\"\".concat(p,\"-\").concat(u),function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}({},\"\".concat(p,\"-\").concat(o.size),o.size),a),d=o.children;return l&&l.length>0&&(d=l.map((function(e){return\"string\"===typeof e?r.createElement(ho,{key:e,prefixCls:s,disabled:t.props.disabled,value:e,checked:t.state.value===e},e):r.createElement(ho,{key:\"radio-group-value-options-\".concat(e.value),prefixCls:s,disabled:e.disabled||t.props.disabled,value:e.value,checked:t.state.value===e.value},e.label)}))),r.createElement(\"div\",{className:h,style:o.style,onMouseEnter:o.onMouseEnter,onMouseLeave:o.onMouseLeave,id:o.id},d)},\"value\"in e)n=e.value;else if(\"defaultValue\"in e)n=e.defaultValue;else{var o=Oo(e.children);n=o&&o.value}return t.state={value:n},t}return t=i,o=[{key:\"getDerivedStateFromProps\",value:function(e){if(\"value\"in e)return{value:e.value};var t=Oo(e.children);return t?{value:t.value}:null}}],(n=[{key:\"getChildContext\",value:function(){return{radioGroup:{onChange:this.onRadioChange,value:this.state.value,disabled:this.props.disabled,name:this.props.name}}}},{key:\"shouldComponentUpdate\",value:function(e,t){return!h()(this.props,e)||!h()(this.state,t)}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderGroup)}}])&&mo(t.prototype,n),o&&mo(t,o),i}(r.Component);Co.defaultProps={buttonStyle:\"outline\"},Co.childContextTypes={radioGroup:l.any},Object(d.polyfill)(Co);var Mo=Co;function So(e){return(So=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function _o(){return(_o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function xo(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function ko(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Ho(e,t){return(Ho=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Eo(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Vo(e);if(t){var o=Vo(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Po(this,n)}}function Po(e,t){return!t||\"object\"!==So(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Vo(e){return(Vo=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var To=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Lo=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Ho(e,t)}(i,e);var t,n,o,c=Eo(i);function i(){var e;return xo(this,i),(e=c.apply(this,arguments)).renderRadioButton=function(t){var n=t.getPrefixCls,o=e.props,c=o.prefixCls,i=To(o,[\"prefixCls\"]),a=n(\"radio-button\",c);return e.context.radioGroup&&(i.checked=e.props.value===e.context.radioGroup.value,i.disabled=e.props.disabled||e.context.radioGroup.disabled),r.createElement(ho,_o({prefixCls:a},i))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderRadioButton)}}])&&ko(t.prototype,n),o&&ko(t,o),i}(r.Component);Lo.contextTypes={radioGroup:l.any},ho.Button=Lo,ho.Group=Mo;var jo=ho,No=function(e){return r.createElement(\"div\",{className:e.className,onClick:function(e){return e.stopPropagation()}},e.children)};function Do(e){return function(e){if(Array.isArray(e))return Ro(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if(\"string\"===typeof e)return Ro(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);\"Object\"===n&&e.constructor&&(n=e.constructor.name);if(\"Map\"===n||\"Set\"===n)return Array.from(e);if(\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Ro(e,t)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function Ro(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function Ao(){return(Ao=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Io(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"children\",n=[],r=function e(r){r.forEach((function(r){if(r[t]){var o=Ao({},r);delete o[t],n.push(o),r[t].length>0&&e(r[t])}else n.push(r)}))};return r(e),n}function Fo(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:\"children\";return e.map((function(e,r){var o={};return e[n]&&(o[n]=Fo(e[n],t,n)),Ao(Ao({},t(e,r)),o)}))}function Wo(e,t){return e.reduce((function(e,n){if(t(n)&&e.push(n),n.children){var r=Wo(n.children,t);e.push.apply(e,Do(r))}return e}),[])}function Uo(e){var t=[];return r.Children.forEach(e,(function(e){if(r.isValidElement(e)){var n=Ao({},e.props);e.key&&(n.key=e.key),e.type&&e.type.__ANT_TABLE_COLUMN_GROUP&&(n.children=Uo(n.children)),t.push(n)}})),t}function Ko(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return(e||[]).forEach((function(e){var n=e.value,r=e.children;t[n.toString()]=n,Ko(r,t)})),t}function Bo(e){return(Bo=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Yo(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function qo(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Go(e,t){return(Go=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function $o(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Zo(e);if(t){var o=Zo(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Qo(this,n)}}function Qo(e,t){return!t||\"object\"!==Bo(t)&&\"function\"!==typeof t?Xo(e):t}function Xo(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Zo(e){return(Zo=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Jo(e){e.stopPropagation(),e.nativeEvent.stopImmediatePropagation&&e.nativeEvent.stopImmediatePropagation()}var ec=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Go(e,t)}(i,e);var t,n,o,c=$o(i);function i(e){var t;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=c.call(this,e)).setNeverShown=function(e){var n=v.findDOMNode(Xo(t));!!un()(n,\".ant-table-scroll\")&&(t.neverShown=!!e.fixed)},t.setSelectedKeys=function(e){var n=e.selectedKeys;t.setState({selectedKeys:n})},t.handleClearFilters=function(){t.setState({selectedKeys:[]},t.handleConfirm)},t.handleConfirm=function(){t.setVisible(!1),t.setState({},t.confirmFilter)},t.onVisibleChange=function(e){t.setVisible(e);var n=t.props.column;e||n.filterDropdown instanceof Function||t.confirmFilter()},t.handleMenuItemClick=function(e){var n=t.state.selectedKeys;if(e.keyPath&&!(e.keyPath.length<=1)){var r=t.state.keyPathOfSelectedItem;n&&n.indexOf(e.key)>=0?delete r[e.key]:r[e.key]=e.keyPath,t.setState({keyPathOfSelectedItem:r})}},t.renderFilterIcon=function(){var e,n=t.props,o=n.column,c=n.locale,i=n.prefixCls,a=n.selectedKeys,l=a&&a.length>0,u=o.filterIcon;\"function\"===typeof u&&(u=u(l));var s=f()((Yo(e={},\"\".concat(i,\"-selected\"),\"filtered\"in o?o.filtered:l),Yo(e,\"\".concat(i,\"-open\"),t.getDropdownVisible()),e));return u?r.isValidElement(u)?r.cloneElement(u,{title:u.props.title||c.filterTitle,className:f()(\"\".concat(i,\"-icon\"),s,u.props.className),onClick:Jo}):r.createElement(\"span\",{className:f()(\"\".concat(i,\"-icon\"),s)},u):r.createElement(gn.a,{title:c.filterTitle,type:\"filter\",theme:\"filled\",className:s,onClick:Jo})};var n=\"filterDropdownVisible\"in e.column&&e.column.filterDropdownVisible;return t.state={selectedKeys:e.selectedKeys,valueKeys:Ko(e.column.filters),keyPathOfSelectedItem:{},visible:n,prevProps:e},t}return t=i,o=[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=e.column,r=t.prevProps,o={prevProps:e};return\"selectedKeys\"in e&&!h()(r.selectedKeys,e.selectedKeys)&&(o.selectedKeys=e.selectedKeys),h()((r.column||{}).filters,(e.column||{}).filters)||(o.valueKeys=Ko(e.column.filters)),\"filterDropdownVisible\"in n&&(o.visible=n.filterDropdownVisible),o}}],(n=[{key:\"componentDidMount\",value:function(){var e=this.props.column;this.setNeverShown(e)}},{key:\"componentDidUpdate\",value:function(){var e=this.props.column;this.setNeverShown(e)}},{key:\"getDropdownVisible\",value:function(){return!this.neverShown&&this.state.visible}},{key:\"setVisible\",value:function(e){var t=this.props.column;\"filterDropdownVisible\"in t||this.setState({visible:e}),t.onFilterDropdownVisibleChange&&t.onFilterDropdownVisibleChange(e)}},{key:\"hasSubMenu\",value:function(){var e=this.props.column.filters;return(void 0===e?[]:e).some((function(e){return!!(e.children&&e.children.length>0)}))}},{key:\"confirmFilter\",value:function(){var e=this.props,t=e.column,n=e.selectedKeys,r=e.confirmFilter,o=this.state,c=o.selectedKeys,i=o.valueKeys,a=t.filterDropdown;h()(c,n)||r(t,a?c:c.map((function(e){return i[e]})).filter((function(e){return void 0!==e})))}},{key:\"renderMenus\",value:function(e){var t=this,n=this.props,o=n.dropdownPrefixCls,c=n.prefixCls;return e.map((function(e){if(e.children&&e.children.length>0){var n=t.state.keyPathOfSelectedItem,i=Object.keys(n).some((function(t){return n[t].indexOf(e.value)>=0})),a=f()(\"\".concat(c,\"-dropdown-submenu\"),Yo({},\"\".concat(o,\"-submenu-contain-selected\"),i));return r.createElement(Ke,{title:e.text,popupClassName:a,key:e.value.toString()},t.renderMenus(e.children))}return t.renderMenuItem(e)}))}},{key:\"renderMenuItem\",value:function(e){var t=this.props.column,n=this.state.selectedKeys,o=!(\"filterMultiple\"in t)||t.filterMultiple,c=(n||[]).map((function(e){return e.toString()})),i=o?r.createElement(to,{checked:c.indexOf(e.value.toString())>=0}):r.createElement(jo,{checked:c.indexOf(e.value.toString())>=0});return r.createElement(Gt,{key:e.value},i,r.createElement(\"span\",null,e.text))}},{key:\"render\",value:function(){var e=this,t=this.state.selectedKeys,n=this.props,o=n.column,c=n.locale,i=n.prefixCls,a=n.dropdownPrefixCls,l=n.getPopupContainer,u=!(\"filterMultiple\"in o)||o.filterMultiple,s=f()(Yo({},\"\".concat(a,\"-menu-without-submenu\"),!this.hasSubMenu())),p=o.filterDropdown;p instanceof Function&&(p=p({prefixCls:\"\".concat(a,\"-custom\"),setSelectedKeys:function(t){return e.setSelectedKeys({selectedKeys:t})},selectedKeys:t,confirm:this.handleConfirm,clearFilters:this.handleClearFilters,filters:o.filters,visible:this.getDropdownVisible()}));var h=p?r.createElement(No,{className:\"\".concat(i,\"-dropdown\")},p):r.createElement(No,{className:\"\".concat(i,\"-dropdown\")},r.createElement(an,{multiple:u,onClick:this.handleMenuItemClick,prefixCls:\"\".concat(a,\"-menu\"),className:s,onSelect:this.setSelectedKeys,onDeselect:this.setSelectedKeys,selectedKeys:t&&t.map((function(e){return e.toString()})),getPopupContainer:l},this.renderMenus(o.filters)),r.createElement(\"div\",{className:\"\".concat(i,\"-dropdown-btns\")},r.createElement(\"a\",{className:\"\".concat(i,\"-dropdown-link confirm\"),onClick:this.handleConfirm},c.filterConfirm),r.createElement(\"a\",{className:\"\".concat(i,\"-dropdown-link clear\"),onClick:this.handleClearFilters},c.filterReset)));return r.createElement(Mr,{trigger:[\"click\"],placement:\"bottomRight\",overlay:h,visible:this.getDropdownVisible(),onVisibleChange:this.onVisibleChange,getPopupContainer:l,forceRender:!0},this.renderFilterIcon())}}])&&qo(t.prototype,n),o&&qo(t,o),i}(r.Component);ec.defaultProps={column:{}},Object(d.polyfill)(ec);var tc=ec;function nc(){return(nc=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function rc(e){return(rc=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function oc(){return(oc=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function cc(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function ic(e,t){return(ic=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function ac(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=uc(e);if(t){var o=uc(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return lc(this,n)}}function lc(e,t){return!t||\"object\"!==rc(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function uc(e){return(uc=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var sc=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},fc=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&ic(e,t)}(i,e);var t,n,o,c=ac(i);function i(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=c.call(this,e)).state={checked:t.getCheckState(e)},t}return t=i,(n=[{key:\"componentDidMount\",value:function(){this.subscribe()}},{key:\"componentWillUnmount\",value:function(){this.unsubscribe&&this.unsubscribe()}},{key:\"getCheckState\",value:function(e){var t=e.store,n=e.defaultSelection,r=e.rowIndex;return t.getState().selectionDirty?t.getState().selectedRowKeys.indexOf(r)>=0:t.getState().selectedRowKeys.indexOf(r)>=0||n.indexOf(r)>=0}},{key:\"subscribe\",value:function(){var e=this,t=this.props.store;this.unsubscribe=t.subscribe((function(){var t=e.getCheckState(e.props);e.setState({checked:t})}))}},{key:\"render\",value:function(){var e=this.props,t=e.type,n=e.rowIndex,o=sc(e,[\"type\",\"rowIndex\"]),c=this.state.checked;return\"radio\"===t?r.createElement(jo,oc({checked:c,value:n},o)):r.createElement(to,oc({checked:c},o))}}])&&cc(t.prototype,n),o&&cc(t,o),i}(r.Component),pc=n(29),hc=n.n(pc),dc=hc()({inlineCollapsed:!1});function vc(e){return(vc=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function mc(){return(mc=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function yc(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function bc(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function gc(e,t){return(gc=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function wc(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Oc(e);if(t){var o=Oc(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return zc(this,n)}}function zc(e,t){return!t||\"object\"!==vc(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Oc(e){return(Oc=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Cc=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&gc(e,t)}(i,e);var t,n,o,c=wc(i);function i(){var e;return yc(this,i),(e=c.apply(this,arguments)).onKeyDown=function(t){e.subMenu.onKeyDown(t)},e.saveSubMenu=function(t){e.subMenu=t},e}return t=i,(n=[{key:\"render\",value:function(){var e=this,t=this.props,n=t.rootPrefixCls,o=t.popupClassName;return r.createElement(dc.Consumer,null,(function(t){var c=t.antdMenuTheme;return r.createElement(Ke,mc({},e.props,{ref:e.saveSubMenu,popupClassName:f()(\"\".concat(n,\"-\").concat(c),o)}))}))}}])&&bc(t.prototype,n),o&&bc(t,o),i}(r.Component);Cc.contextTypes={antdMenuTheme:l.string},Cc.isSubMenu=1;var Mc=Cc,Sc=n(98);function _c(e){return(_c=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function xc(e){return function(e){if(Array.isArray(e))return kc(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if(\"string\"===typeof e)return kc(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);\"Object\"===n&&e.constructor&&(n=e.constructor.name);if(\"Map\"===n||\"Set\"===n)return Array.from(e);if(\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return kc(e,t)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function kc(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function Hc(){return(Hc=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Ec(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Pc(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Vc(e,t,n){return t&&Pc(e.prototype,t),n&&Pc(e,n),e}function Tc(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Lc(e,t)}function Lc(e,t){return(Lc=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function jc(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Dc(e);if(t){var o=Dc(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Nc(this,n)}}function Nc(e,t){return!t||\"object\"!==_c(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Dc(e){return(Dc=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Rc=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Ac=hc()({siderHook:{addSider:function(){return null},removeSider:function(){return null}}});function Ic(e){var t=e.suffixCls,n=e.tagName,o=e.displayName;return function(e){var c;return(c=function(o){Tc(i,o);var c=jc(i);function i(){var o;return Ec(this,i),(o=c.apply(this,arguments)).renderComponent=function(c){var i=c.getPrefixCls,a=o.props.prefixCls,l=i(t,a);return r.createElement(e,Hc({prefixCls:l,tagName:n},o.props))},o}return Vc(i,[{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderComponent)}}]),i}(r.Component)).displayName=o,c}}var Fc=function(e){var t=e.prefixCls,n=e.className,o=e.children,c=e.tagName,i=Rc(e,[\"prefixCls\",\"className\",\"children\",\"tagName\"]),a=f()(n,t);return r.createElement(c,Hc({className:a},i),o)},Wc=function(e){Tc(n,e);var t=jc(n);function n(){var e;return Ec(this,n),(e=t.apply(this,arguments)).state={siders:[]},e}return Vc(n,[{key:\"getSiderHook\",value:function(){var e=this;return{addSider:function(t){e.setState((function(e){return{siders:[].concat(xc(e.siders),[t])}}))},removeSider:function(t){e.setState((function(e){return{siders:e.siders.filter((function(e){return e!==t}))}}))}}}},{key:\"render\",value:function(){var e,t,n,o=this.props,c=o.prefixCls,i=o.className,a=o.children,l=o.hasSider,u=o.tagName,s=Rc(o,[\"prefixCls\",\"className\",\"children\",\"hasSider\",\"tagName\"]),p=f()(i,c,(e={},t=\"\".concat(c,\"-has-sider\"),n=\"boolean\"===typeof l?l:this.state.siders.length>0,t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e));return r.createElement(Ac.Provider,{value:{siderHook:this.getSiderHook()}},r.createElement(u,Hc({className:p},s),a))}}]),n}(r.Component),Uc=Ic({suffixCls:\"layout\",tagName:\"section\",displayName:\"Layout\"})(Wc),Kc=Ic({suffixCls:\"layout-header\",tagName:\"header\",displayName:\"Header\"})(Fc),Bc=Ic({suffixCls:\"layout-footer\",tagName:\"footer\",displayName:\"Footer\"})(Fc),Yc=Ic({suffixCls:\"layout-content\",tagName:\"main\",displayName:\"Content\"})(Fc);Uc.Header=Kc,Uc.Footer=Bc,Uc.Content=Yc;var qc=function(e){return!isNaN(parseFloat(e))&&isFinite(e)};function Gc(e){return(Gc=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function $c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Qc(){return(Qc=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Xc(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Zc(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Jc(e,t,n){return t&&Zc(e.prototype,t),n&&Zc(e,n),e}function ei(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&ti(e,t)}function ti(e,t){return(ti=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function ni(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=oi(e);if(t){var o=oi(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return ri(this,n)}}function ri(e,t){return!t||\"object\"!==Gc(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function oi(e){return(oi=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var ci=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n};if(\"undefined\"!==typeof window){window.matchMedia||(window.matchMedia=function(e){return{media:e,matches:!1,addListener:function(){},removeListener:function(){}}})}var ii={xs:\"479.98px\",sm:\"575.98px\",md:\"767.98px\",lg:\"991.98px\",xl:\"1199.98px\",xxl:\"1599.98px\"},ai=hc()({}),li=function(){var e=0;return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:\"\";return e+=1,\"\".concat(t).concat(e)}}(),ui=function(e){ei(n,e);var t=ni(n);function n(e){var o,i,a;return Xc(this,n),(o=t.call(this,e)).responsiveHandler=function(e){o.setState({below:e.matches});var t=o.props.onBreakpoint;t&&t(e.matches),o.state.collapsed!==e.matches&&o.setCollapsed(e.matches,\"responsive\")},o.setCollapsed=function(e,t){\"collapsed\"in o.props||o.setState({collapsed:e});var n=o.props.onCollapse;n&&n(e,t)},o.toggle=function(){var e=!o.state.collapsed;o.setCollapsed(e,\"clickTrigger\")},o.belowShowChange=function(){o.setState((function(e){return{belowShow:!e.belowShow}}))},o.renderSider=function(e){var t,n=e.getPrefixCls,i=o.props,a=i.prefixCls,l=i.className,u=i.theme,s=i.collapsible,p=i.reverseArrow,h=i.trigger,d=i.style,v=i.width,m=i.collapsedWidth,y=i.zeroWidthTriggerStyle,b=ci(i,[\"prefixCls\",\"className\",\"theme\",\"collapsible\",\"reverseArrow\",\"trigger\",\"style\",\"width\",\"collapsedWidth\",\"zeroWidthTriggerStyle\"]),g=n(\"layout-sider\",a),w=Object(c.a)(b,[\"collapsed\",\"defaultCollapsed\",\"onCollapse\",\"breakpoint\",\"onBreakpoint\",\"siderHook\",\"zeroWidthTriggerStyle\"]),z=o.state.collapsed?m:v,O=qc(z)?\"\".concat(z,\"px\"):String(z),C=0===parseFloat(String(m||0))?r.createElement(\"span\",{onClick:o.toggle,className:\"\".concat(g,\"-zero-width-trigger \").concat(g,\"-zero-width-trigger-\").concat(p?\"right\":\"left\"),style:y},r.createElement(gn.a,{type:\"bars\"})):null,M={expanded:p?r.createElement(gn.a,{type:\"right\"}):r.createElement(gn.a,{type:\"left\"}),collapsed:p?r.createElement(gn.a,{type:\"left\"}):r.createElement(gn.a,{type:\"right\"})}[o.state.collapsed?\"collapsed\":\"expanded\"],S=null!==h?C||r.createElement(\"div\",{className:\"\".concat(g,\"-trigger\"),onClick:o.toggle,style:{width:O}},h||M):null,_=Qc(Qc({},d),{flex:\"0 0 \".concat(O),maxWidth:O,minWidth:O,width:O}),x=f()(l,g,\"\".concat(g,\"-\").concat(u),($c(t={},\"\".concat(g,\"-collapsed\"),!!o.state.collapsed),$c(t,\"\".concat(g,\"-has-trigger\"),s&&null!==h&&!C),$c(t,\"\".concat(g,\"-below\"),!!o.state.below),$c(t,\"\".concat(g,\"-zero-width\"),0===parseFloat(O)),t));return r.createElement(\"aside\",Qc({className:x},w,{style:_}),r.createElement(\"div\",{className:\"\".concat(g,\"-children\")},o.props.children),s||o.state.below&&C?S:null)},o.uniqueId=li(\"ant-sider-\"),\"undefined\"!==typeof window&&(i=window.matchMedia),i&&e.breakpoint&&e.breakpoint in ii&&(o.mql=i(\"(max-width: \".concat(ii[e.breakpoint],\")\"))),a=\"collapsed\"in e?e.collapsed:e.defaultCollapsed,o.state={collapsed:a,below:!1},o}return Jc(n,[{key:\"componentDidMount\",value:function(){this.mql&&(this.mql.addListener(this.responsiveHandler),this.responsiveHandler(this.mql)),this.props.siderHook&&this.props.siderHook.addSider(this.uniqueId)}},{key:\"componentWillUnmount\",value:function(){this.mql&&this.mql.removeListener(this.responsiveHandler),this.props.siderHook&&this.props.siderHook.removeSider(this.uniqueId)}},{key:\"render\",value:function(){var e=this.state.collapsed,t=this.props.collapsedWidth;return r.createElement(ai.Provider,{value:{siderCollapsed:e,collapsedWidth:t}},r.createElement(yn.a,null,this.renderSider))}}],[{key:\"getDerivedStateFromProps\",value:function(e){return\"collapsed\"in e?{collapsed:e.collapsed}:null}}]),n}(r.Component);ui.defaultProps={collapsible:!1,defaultCollapsed:!1,reverseArrow:!1,width:200,collapsedWidth:80,style:{},theme:\"dark\"},Object(d.polyfill)(ui);r.Component;function si(e){return(si=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function fi(){return(fi=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function pi(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function hi(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function di(e,t){return(di=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function vi(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=yi(e);if(t){var o=yi(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return mi(this,n)}}function mi(e,t){return!t||\"object\"!==si(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function yi(e){return(yi=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var bi=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},gi=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&di(e,t)}(i,e);var t,n,o,c=vi(i);function i(){var e;return pi(this,i),(e=c.apply(this,arguments)).onKeyDown=function(t){e.menuItem.onKeyDown(t)},e.saveMenuItem=function(t){e.menuItem=t},e.renderItem=function(t){var n=t.siderCollapsed,o=e.props,c=o.level,i=o.children,a=o.rootPrefixCls,l=e.props,u=l.title,s=bi(l,[\"title\"]);return r.createElement(dc.Consumer,null,(function(t){var o=t.inlineCollapsed,l={title:u||(1===c?i:\"\")};return n||o||(l.title=null,l.visible=!1),r.createElement(Sc.a,fi({},l,{placement:\"right\",overlayClassName:\"\".concat(a,\"-inline-collapsed-tooltip\")}),r.createElement(Gt,fi({},s,{title:u,ref:e.saveMenuItem})))}))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(ai.Consumer,null,this.renderItem)}}])&&hi(t.prototype,n),o&&hi(t,o),i}(r.Component);gi.isMenuItem=!0;var wi=function(){return{height:0,opacity:0}},zi=function(e){return{height:e.scrollHeight,opacity:1}},Oi={motionName:\"ant-motion-collapse\",onAppearStart:wi,onEnterStart:wi,onAppearActive:zi,onEnterActive:zi,onLeaveStart:function(e){return{height:e.offsetHeight}},onLeaveActive:wi};function Ci(e){return(Ci=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Mi(){return(Mi=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Si(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function _i(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function xi(e,t,n){return t&&_i(e.prototype,t),n&&_i(e,n),e}function ki(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Hi(e,t)}function Hi(e,t){return(Hi=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ei(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Vi(e);if(t){var o=Vi(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Pi(this,n)}}function Pi(e,t){return!t||\"object\"!==Ci(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Vi(e){return(Vi=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Ti=function(e){ki(n,e);var t=Ei(n);function n(e){var o,i;return Si(this,n),(o=t.call(this,e)).handleMouseEnter=function(e){o.restoreModeVerticalFromInline();var t=o.props.onMouseEnter;t&&t(e)},o.handleTransitionEnd=function(e){var t=\"width\"===e.propertyName&&e.target===e.currentTarget,n=e.target.className,r=\"[object SVGAnimatedString]\"===Object.prototype.toString.call(n)?n.animVal:n,c=\"font-size\"===e.propertyName&&r.indexOf(\"anticon\")>=0;(t||c)&&o.restoreModeVerticalFromInline()},o.handleClick=function(e){o.handleOpenChange([]);var t=o.props.onClick;t&&t(e)},o.handleOpenChange=function(e){o.setOpenKeys(e);var t=o.props.onOpenChange;t&&t(e)},o.renderMenu=function(e){var t,n,i,a=e.getPopupContainer,l=e.getPrefixCls,u=o.props,s=u.prefixCls,p=u.className,h=u.theme,d=u.collapsedWidth,v=Object(c.a)(o.props,[\"collapsedWidth\",\"siderCollapsed\"]),m=o.getRealMenuMode(),y=o.getOpenMotionProps(m),b=l(\"menu\",s),g=f()(p,\"\".concat(b,\"-\").concat(h),(t={},n=\"\".concat(b,\"-inline-collapsed\"),i=o.getInlineCollapsed(),n in t?Object.defineProperty(t,n,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[n]=i,t)),w=Mi({openKeys:o.state.openKeys,onOpenChange:o.handleOpenChange,className:g,mode:m},y);return\"inline\"!==m&&(w.onClick=o.handleClick),o.getInlineCollapsed()&&(0===d||\"0\"===d||\"0px\"===d)&&(w.openKeys=[]),r.createElement(an,Mi({getPopupContainer:a},v,w,{prefixCls:b,onTransitionEnd:o.handleTransitionEnd,onMouseEnter:o.handleMouseEnter}))},Object(bn.a)(!(\"onOpen\"in e||\"onClose\"in e),\"Menu\",\"`onOpen` and `onClose` are removed, please use `onOpenChange` instead, see: https://u.ant.design/menu-on-open-change.\"),Object(bn.a)(!(\"inlineCollapsed\"in e&&\"inline\"!==e.mode),\"Menu\",\"`inlineCollapsed` should only be used when `mode` is inline.\"),Object(bn.a)(!(void 0!==e.siderCollapsed&&\"inlineCollapsed\"in e),\"Menu\",\"`inlineCollapsed` not control Menu under Sider. Should set `collapsed` on Sider instead.\"),\"openKeys\"in e?i=e.openKeys:\"defaultOpenKeys\"in e&&(i=e.defaultOpenKeys),o.state={openKeys:i||[],switchingModeFromInline:!1,inlineOpenKeys:[],prevProps:e},o}return xi(n,[{key:\"componentWillUnmount\",value:function(){Ln.cancel(this.mountRafId)}},{key:\"setOpenKeys\",value:function(e){\"openKeys\"in this.props||this.setState({openKeys:e})}},{key:\"getRealMenuMode\",value:function(){var e=this.getInlineCollapsed();if(this.state.switchingModeFromInline&&e)return\"inline\";var t=this.props.mode;return e?\"vertical\":t}},{key:\"getInlineCollapsed\",value:function(){var e=this.props.inlineCollapsed;return void 0!==this.props.siderCollapsed?this.props.siderCollapsed:e}},{key:\"getOpenMotionProps\",value:function(e){var t=this.props,n=t.openTransitionName,r=t.openAnimation,o=t.motion;return o?{motion:o}:r?(Object(bn.a)(\"string\"===typeof r,\"Menu\",\"`openAnimation` do not support object. Please use `motion` instead.\"),{openAnimation:r}):n?{openTransitionName:n}:\"horizontal\"===e?{motion:{motionName:\"slide-up\"}}:\"inline\"===e?{motion:Oi}:{motion:{motionName:this.state.switchingModeFromInline?\"\":\"zoom-big\"}}}},{key:\"restoreModeVerticalFromInline\",value:function(){this.state.switchingModeFromInline&&this.setState({switchingModeFromInline:!1})}},{key:\"render\",value:function(){return r.createElement(dc.Provider,{value:{inlineCollapsed:this.getInlineCollapsed()||!1,antdMenuTheme:this.props.theme}},r.createElement(yn.a,null,this.renderMenu))}}],[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=t.prevProps,r={prevProps:e};return\"inline\"===n.mode&&\"inline\"!==e.mode&&(r.switchingModeFromInline=!0),\"openKeys\"in e?r.openKeys=e.openKeys:((e.inlineCollapsed&&!n.inlineCollapsed||e.siderCollapsed&&!n.siderCollapsed)&&(r.switchingModeFromInline=!0,r.inlineOpenKeys=t.openKeys,r.openKeys=[]),(!e.inlineCollapsed&&n.inlineCollapsed||!e.siderCollapsed&&n.siderCollapsed)&&(r.openKeys=t.inlineOpenKeys,r.inlineOpenKeys=[])),r}}]),n}(r.Component);Ti.defaultProps={className:\"\",theme:\"light\",focusable:!1},Object(d.polyfill)(Ti);var Li=function(e){ki(n,e);var t=Ei(n);function n(){return Si(this,n),t.apply(this,arguments)}return xi(n,[{key:\"render\",value:function(){var e=this;return r.createElement(ai.Consumer,null,(function(t){return r.createElement(Ti,Mi({},e.props,t))}))}}]),n}(r.Component);function ji(e){return(ji=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ni(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Di(e,t){return(Di=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ri(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Ii(e);if(t){var o=Ii(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Ai(this,n)}}function Ai(e,t){return!t||\"object\"!==ji(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Ii(e){return(Ii=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Fi(){return(Fi=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Wi(e){var t=e.store,n=e.getCheckboxPropsByItem,r=e.getRecordKey,o=e.data,c=e.type;return e.byDefaultChecked?o[c]((function(e,t){return n(e,t).defaultChecked})):o[c]((function(e,n){return t.getState().selectedRowKeys.indexOf(r(e,n))>=0}))}function Ui(e){var t=e.store,n=e.data;if(!n.length)return!1;var r=Wi(Fi(Fi({},e),{data:n,type:\"some\",byDefaultChecked:!1}))&&!Wi(Fi(Fi({},e),{data:n,type:\"every\",byDefaultChecked:!1})),o=Wi(Fi(Fi({},e),{data:n,type:\"some\",byDefaultChecked:!0}))&&!Wi(Fi(Fi({},e),{data:n,type:\"every\",byDefaultChecked:!0}));return t.getState().selectionDirty?r:r||o}function Ki(e){var t=e.store,n=e.data;return!!n.length&&(t.getState().selectionDirty?Wi(Fi(Fi({},e),{data:n,type:\"every\",byDefaultChecked:!1})):Wi(Fi(Fi({},e),{data:n,type:\"every\",byDefaultChecked:!1}))||Wi(Fi(Fi({},e),{data:n,type:\"every\",byDefaultChecked:!0})))}Li.Divider=cn,Li.Item=gi,Li.SubMenu=Mc,Li.ItemGroup=rn;var Bi=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Di(e,t)}(i,e);var t,n,o,c=Ri(i);function i(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=c.call(this,e)).state={checked:!1,indeterminate:!1},t.handleSelectAllChange=function(e){var n=e.target.checked;t.props.onSelect(n?\"all\":\"removeAll\",0,null)},t.defaultSelections=e.hideDefaultSelections?[]:[{key:\"all\",text:e.locale.selectAll},{key:\"invert\",text:e.locale.selectInvert}],t}return t=i,o=[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=Ki(e),r=Ui(e),o={};return r!==t.indeterminate&&(o.indeterminate=r),n!==t.checked&&(o.checked=n),o}}],(n=[{key:\"componentDidMount\",value:function(){this.subscribe()}},{key:\"componentWillUnmount\",value:function(){this.unsubscribe&&this.unsubscribe()}},{key:\"setCheckState\",value:function(e){var t=Ki(e),n=Ui(e);this.setState((function(e){var r={};return n!==e.indeterminate&&(r.indeterminate=n),t!==e.checked&&(r.checked=t),r}))}},{key:\"subscribe\",value:function(){var e=this,t=this.props.store;this.unsubscribe=t.subscribe((function(){e.setCheckState(e.props)}))}},{key:\"renderMenus\",value:function(e){var t=this;return e.map((function(e,n){return r.createElement(Li.Item,{key:e.key||n},r.createElement(\"div\",{onClick:function(){t.props.onSelect(e.key,n,e.onSelect)}},e.text))}))}},{key:\"render\",value:function(){var e,t,n,o=this.props,c=o.disabled,i=o.prefixCls,a=o.selections,l=o.getPopupContainer,u=this.state,s=u.checked,p=u.indeterminate,h=\"\".concat(i,\"-selection\"),d=null;if(a){var v=Array.isArray(a)?this.defaultSelections.concat(a):this.defaultSelections,m=r.createElement(Li,{className:\"\".concat(h,\"-menu\"),selectedKeys:[]},this.renderMenus(v));d=v.length>0?r.createElement(Mr,{overlay:m,getPopupContainer:l},r.createElement(\"div\",{className:\"\".concat(h,\"-down\")},r.createElement(gn.a,{type:\"down\"}))):null}return r.createElement(\"div\",{className:h},r.createElement(to,{className:f()((e={},t=\"\".concat(h,\"-select-all-custom\"),n=d,t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e)),checked:s,indeterminate:p,disabled:c,onChange:this.handleSelectAllChange}),d)}}])&&Ni(t.prototype,n),o&&Ni(t,o),i}(r.Component);Object(d.polyfill)(Bi);var Yi=Bi;function qi(e){return(qi=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Gi(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function $i(e,t){return($i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Qi(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Zi(e);if(t){var o=Zi(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Xi(this,n)}}function Xi(e,t){return!t||\"object\"!==qi(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Zi(e){return(Zi=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Ji=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&$i(e,t)}(n,e);var t=Qi(n);function n(){return Gi(this,n),t.apply(this,arguments)}return n}(r.Component);function ea(e){return(ea=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function ta(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function na(e,t){return(na=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function ra(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=ca(e);if(t){var o=ca(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return oa(this,n)}}function oa(e,t){return!t||\"object\"!==ea(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function ca(e){return(ca=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var ia=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&na(e,t)}(n,e);var t=ra(n);function n(){return ta(this,n),t.apply(this,arguments)}return n}(r.Component);function aa(e){return(aa=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function la(){return(la=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function ua(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function sa(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function fa(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function pa(e,t,n){return t&&fa(e.prototype,t),n&&fa(e,n),e}function ha(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&da(e,t)}function da(e,t){return(da=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function va(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=ya(e);if(t){var o=ya(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return ma(this,n)}}function ma(e,t){return!t||\"object\"!==aa(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function ya(e){return(ya=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function ba(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:\"tr\",t=function(t){ha(o,t);var n=va(o);function o(e){var t;sa(this,o),(t=n.call(this,e)).store=e.store;var r=t.store.getState().selectedRowKeys;return t.state={selected:r.indexOf(e.rowKey)>=0},t}return pa(o,[{key:\"componentDidMount\",value:function(){this.subscribe()}},{key:\"componentWillUnmount\",value:function(){this.unsubscribe&&this.unsubscribe()}},{key:\"subscribe\",value:function(){var e=this,t=this.props,n=t.store,r=t.rowKey;this.unsubscribe=n.subscribe((function(){var t=e.store.getState().selectedRowKeys.indexOf(r)>=0;t!==e.state.selected&&e.setState({selected:t})}))}},{key:\"render\",value:function(){var t=Object(c.a)(this.props,[\"prefixCls\",\"rowKey\",\"store\"]),n=f()(this.props.className,ua({},\"\".concat(this.props.prefixCls,\"-row-selected\"),this.state.selected));return r.createElement(e,la(la({},t),{className:n}),this.props.children)}}]),o}(r.Component);return t}function ga(e,t){if(\"undefined\"===typeof window)return 0;var n=t?\"scrollTop\":\"scrollLeft\",r=e===window,o=r?e[t?\"pageYOffset\":\"pageXOffset\"]:e[n];return r&&\"number\"!==typeof o&&(o=document.documentElement[n]),o}function wa(e,t,n,r){var o=n-t;return(e/=r/2)<1?o/2*e*e*e+t:o/2*((e-=2)*e*e+2)+t}ia.__ANT_TABLE_COLUMN_GROUP=!0;var za=function(e){var t,n=e.rootPrefixCls+\"-item\",r=z()(n,n+\"-\"+e.page,(t={},ee()(t,n+\"-active\",e.active),ee()(t,e.className,!!e.className),ee()(t,n+\"-disabled\",!e.page),t));return o.a.createElement(\"li\",{title:e.showTitle?e.page:null,className:r,onClick:function(){e.onClick(e.page)},onKeyPress:function(t){e.onKeyPress(t,e.onClick,e.page)},tabIndex:\"0\"},e.itemRender(e.page,\"page\",o.a.createElement(\"a\",null,e.page)))};za.propTypes={page:u.a.number,active:u.a.bool,last:u.a.bool,locale:u.a.object,className:u.a.string,showTitle:u.a.bool,rootPrefixCls:u.a.string,onClick:u.a.func,onKeyPress:u.a.func,itemRender:u.a.func};var Oa=za,Ca=13,Ma=38,Sa=40,_a=function(e){function t(){var e,n,r,o;oe()(this,t);for(var c=arguments.length,i=Array(c),a=0;a<c;a++)i[a]=arguments[a];return n=r=le()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.state={goInputText:\"\"},r.buildOptionText=function(e){return e+\" \"+r.props.locale.items_per_page},r.changeSize=function(e){r.props.changeSize(Number(e))},r.handleChange=function(e){r.setState({goInputText:e.target.value})},r.handleBlur=function(e){var t=r.props,n=t.goButton,o=t.quickGo,c=t.rootPrefixCls;n||e.relatedTarget&&(e.relatedTarget.className.indexOf(c+\"-prev\")>=0||e.relatedTarget.className.indexOf(c+\"-next\")>=0)||o(r.getValidValue())},r.go=function(e){\"\"!==r.state.goInputText&&(e.keyCode!==Ca&&\"click\"!==e.type||(r.setState({goInputText:\"\"}),r.props.quickGo(r.getValidValue())))},o=n,le()(r,o)}return se()(t,e),ie()(t,[{key:\"getValidValue\",value:function(){var e=this.state,t=e.goInputText,n=e.current;return!t||isNaN(t)?n:Number(t)}},{key:\"render\",value:function(){var e=this,t=this.props,n=t.pageSize,r=t.pageSizeOptions,c=t.locale,i=t.rootPrefixCls,a=t.changeSize,l=t.quickGo,u=t.goButton,s=t.selectComponentClass,f=t.buildOptionText,p=t.selectPrefixCls,h=t.disabled,d=this.state.goInputText,v=i+\"-options\",m=s,y=null,b=null,g=null;if(!a&&!l)return null;if(a&&m){var w=r.map((function(t,n){return o.a.createElement(m.Option,{key:n,value:t},(f||e.buildOptionText)(t))}));y=o.a.createElement(m,{disabled:h,prefixCls:p,showSearch:!1,className:v+\"-size-changer\",optionLabelProp:\"children\",dropdownMatchSelectWidth:!1,value:(n||r[0]).toString(),onChange:this.changeSize,getPopupContainer:function(e){return e.parentNode}},w)}return l&&(u&&(g=\"boolean\"===typeof u?o.a.createElement(\"button\",{type:\"button\",onClick:this.go,onKeyUp:this.go,disabled:h},c.jump_to_confirm):o.a.createElement(\"span\",{onClick:this.go,onKeyUp:this.go},u)),b=o.a.createElement(\"div\",{className:v+\"-quick-jumper\"},c.jump_to,o.a.createElement(\"input\",{disabled:h,type:\"text\",value:d,onChange:this.handleChange,onKeyUp:this.go,onBlur:this.handleBlur}),c.page,g)),o.a.createElement(\"li\",{className:\"\"+v},y,b)}}]),t}(o.a.Component);_a.propTypes={disabled:u.a.bool,changeSize:u.a.func,quickGo:u.a.func,selectComponentClass:u.a.func,current:u.a.number,pageSizeOptions:u.a.arrayOf(u.a.string),pageSize:u.a.number,buildOptionText:u.a.func,locale:u.a.object,rootPrefixCls:u.a.string,selectPrefixCls:u.a.string,goButton:u.a.oneOfType([u.a.bool,u.a.node])},_a.defaultProps={pageSizeOptions:[\"10\",\"20\",\"30\",\"40\"]};var xa=_a,ka=n(94);function Ha(){}function Ea(e,t,n){var r=e;return\"undefined\"===typeof r&&(r=t.pageSize),Math.floor((n.total-1)/r)+1}var Pa=function(e){function t(e){oe()(this,t);var n=le()(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));Va.call(n);var r=e.onChange!==Ha;\"current\"in e&&!r&&console.warn(\"Warning: You provided a `current` prop to a Pagination component without an `onChange` handler. This will render a read-only component.\");var o=e.defaultCurrent;\"current\"in e&&(o=e.current);var c=e.defaultPageSize;return\"pageSize\"in e&&(c=e.pageSize),o=Math.min(o,Ea(c,void 0,e)),n.state={current:o,currentInputValue:o,pageSize:c},n}return se()(t,e),ie()(t,[{key:\"componentDidUpdate\",value:function(e,t){var n=this.props.prefixCls;if(t.current!==this.state.current&&this.paginationNode){var r=this.paginationNode.querySelector(\".\"+n+\"-item-\"+t.current);r&&document.activeElement===r&&r.blur()}}},{key:\"getValidValue\",value:function(e){var t=e.target.value,n=Ea(void 0,this.state,this.props),r=this.state.currentInputValue;return\"\"===t?t:isNaN(Number(t))?r:t>=n?n:Number(t)}},{key:\"render\",value:function(){var e=this.props,t=e.prefixCls,n=e.className,r=e.disabled;if(!0===this.props.hideOnSinglePage&&this.props.total<=this.state.pageSize)return null;var c=this.props,i=c.locale,a=Ea(void 0,this.state,this.props),l=[],u=null,s=null,f=null,p=null,h=null,d=c.showQuickJumper&&c.showQuickJumper.goButton,v=c.showLessItems?1:2,m=this.state,y=m.current,b=m.pageSize,g=y-1>0?y-1:0,w=y+1<a?y+1:a,O=Object.keys(c).reduce((function(e,t){return\"data-\"!==t.substr(0,5)&&\"aria-\"!==t.substr(0,5)&&\"role\"!==t||(e[t]=c[t]),e}),{});if(c.simple)return d&&(h=\"boolean\"===typeof d?o.a.createElement(\"button\",{type:\"button\",onClick:this.handleGoTO,onKeyUp:this.handleGoTO},i.jump_to_confirm):o.a.createElement(\"span\",{onClick:this.handleGoTO,onKeyUp:this.handleGoTO},d),h=o.a.createElement(\"li\",{title:c.showTitle?\"\"+i.jump_to+this.state.current+\"/\"+a:null,className:t+\"-simple-pager\"},h)),o.a.createElement(\"ul\",ne()({className:t+\" \"+t+\"-simple \"+c.className,style:c.style,ref:this.savePaginationNode},O),o.a.createElement(\"li\",{title:c.showTitle?i.prev_page:null,onClick:this.prev,tabIndex:this.hasPrev()?0:null,onKeyPress:this.runIfEnterPrev,className:(this.hasPrev()?\"\":t+\"-disabled\")+\" \"+t+\"-prev\",\"aria-disabled\":!this.hasPrev()},c.itemRender(g,\"prev\",this.getItemIcon(c.prevIcon))),o.a.createElement(\"li\",{title:c.showTitle?this.state.current+\"/\"+a:null,className:t+\"-simple-pager\"},o.a.createElement(\"input\",{type:\"text\",value:this.state.currentInputValue,onKeyDown:this.handleKeyDown,onKeyUp:this.handleKeyUp,onChange:this.handleKeyUp,size:\"3\"}),o.a.createElement(\"span\",{className:t+\"-slash\"},\"/\"),a),o.a.createElement(\"li\",{title:c.showTitle?i.next_page:null,onClick:this.next,tabIndex:this.hasPrev()?0:null,onKeyPress:this.runIfEnterNext,className:(this.hasNext()?\"\":t+\"-disabled\")+\" \"+t+\"-next\",\"aria-disabled\":!this.hasNext()},c.itemRender(w,\"next\",this.getItemIcon(c.nextIcon))),h);if(a<=5+2*v){var C={locale:i,rootPrefixCls:t,onClick:this.handleChange,onKeyPress:this.runIfEnter,showTitle:c.showTitle,itemRender:c.itemRender};a||l.push(o.a.createElement(Oa,ne()({},C,{key:\"noPager\",page:a,className:t+\"-disabled\"})));for(var M=1;M<=a;M++){var S=this.state.current===M;l.push(o.a.createElement(Oa,ne()({},C,{key:M,page:M,active:S})))}}else{var _=c.showLessItems?i.prev_3:i.prev_5,x=c.showLessItems?i.next_3:i.next_5;if(c.showPrevNextJumpers){var k=t+\"-jump-prev\";c.jumpPrevIcon&&(k+=\" \"+t+\"-jump-prev-custom-icon\"),u=o.a.createElement(\"li\",{title:c.showTitle?_:null,key:\"prev\",onClick:this.jumpPrev,tabIndex:\"0\",onKeyPress:this.runIfEnterJumpPrev,className:k},c.itemRender(this.getJumpPrevPage(),\"jump-prev\",this.getItemIcon(c.jumpPrevIcon)));var H=t+\"-jump-next\";c.jumpNextIcon&&(H+=\" \"+t+\"-jump-next-custom-icon\"),s=o.a.createElement(\"li\",{title:c.showTitle?x:null,key:\"next\",tabIndex:\"0\",onClick:this.jumpNext,onKeyPress:this.runIfEnterJumpNext,className:H},c.itemRender(this.getJumpNextPage(),\"jump-next\",this.getItemIcon(c.jumpNextIcon)))}p=o.a.createElement(Oa,{locale:c.locale,last:!0,rootPrefixCls:t,onClick:this.handleChange,onKeyPress:this.runIfEnter,key:a,page:a,active:!1,showTitle:c.showTitle,itemRender:c.itemRender}),f=o.a.createElement(Oa,{locale:c.locale,rootPrefixCls:t,onClick:this.handleChange,onKeyPress:this.runIfEnter,key:1,page:1,active:!1,showTitle:c.showTitle,itemRender:c.itemRender});var E=Math.max(1,y-v),P=Math.min(y+v,a);y-1<=v&&(P=1+2*v),a-y<=v&&(E=a-2*v);for(var V=E;V<=P;V++){var T=y===V;l.push(o.a.createElement(Oa,{locale:c.locale,rootPrefixCls:t,onClick:this.handleChange,onKeyPress:this.runIfEnter,key:V,page:V,active:T,showTitle:c.showTitle,itemRender:c.itemRender}))}y-1>=2*v&&3!==y&&(l[0]=o.a.cloneElement(l[0],{className:t+\"-item-after-jump-prev\"}),l.unshift(u)),a-y>=2*v&&y!==a-2&&(l[l.length-1]=o.a.cloneElement(l[l.length-1],{className:t+\"-item-before-jump-next\"}),l.push(s)),1!==E&&l.unshift(f),P!==a&&l.push(p)}var L=null;c.showTotal&&(L=o.a.createElement(\"li\",{className:t+\"-total-text\"},c.showTotal(c.total,[0===c.total?0:(y-1)*b+1,y*b>c.total?c.total:y*b])));var j=!this.hasPrev()||!a,N=!this.hasNext()||!a;return o.a.createElement(\"ul\",ne()({className:z()(t,n,ee()({},t+\"-disabled\",r)),style:c.style,unselectable:\"unselectable\",ref:this.savePaginationNode},O),L,o.a.createElement(\"li\",{title:c.showTitle?i.prev_page:null,onClick:this.prev,tabIndex:j?null:0,onKeyPress:this.runIfEnterPrev,className:(j?t+\"-disabled\":\"\")+\" \"+t+\"-prev\",\"aria-disabled\":j},c.itemRender(g,\"prev\",this.getItemIcon(c.prevIcon))),l,o.a.createElement(\"li\",{title:c.showTitle?i.next_page:null,onClick:this.next,tabIndex:N?null:0,onKeyPress:this.runIfEnterNext,className:(N?t+\"-disabled\":\"\")+\" \"+t+\"-next\",\"aria-disabled\":N},c.itemRender(w,\"next\",this.getItemIcon(c.nextIcon))),o.a.createElement(xa,{disabled:r,locale:c.locale,rootPrefixCls:t,selectComponentClass:c.selectComponentClass,selectPrefixCls:c.selectPrefixCls,changeSize:this.props.showSizeChanger?this.changePageSize:null,current:this.state.current,pageSize:this.state.pageSize,pageSizeOptions:this.props.pageSizeOptions,quickGo:this.shouldDisplayQuickJumper()?this.handleChange:null,goButton:d}))}}],[{key:\"getDerivedStateFromProps\",value:function(e,t){var n={};if(\"current\"in e&&(n.current=e.current,e.current!==t.current&&(n.currentInputValue=n.current)),\"pageSize\"in e&&e.pageSize!==t.pageSize){var r=t.current,o=Ea(e.pageSize,t,e);r=r>o?o:r,\"current\"in e||(n.current=r,n.currentInputValue=r),n.pageSize=e.pageSize}return n}}]),t}(o.a.Component);Pa.propTypes={disabled:u.a.bool,prefixCls:u.a.string,className:u.a.string,current:u.a.number,defaultCurrent:u.a.number,total:u.a.number,pageSize:u.a.number,defaultPageSize:u.a.number,onChange:u.a.func,hideOnSinglePage:u.a.bool,showSizeChanger:u.a.bool,showLessItems:u.a.bool,onShowSizeChange:u.a.func,selectComponentClass:u.a.func,showPrevNextJumpers:u.a.bool,showQuickJumper:u.a.oneOfType([u.a.bool,u.a.object]),showTitle:u.a.bool,pageSizeOptions:u.a.arrayOf(u.a.string),showTotal:u.a.func,locale:u.a.object,style:u.a.object,itemRender:u.a.func,prevIcon:u.a.oneOfType([u.a.func,u.a.node]),nextIcon:u.a.oneOfType([u.a.func,u.a.node]),jumpPrevIcon:u.a.oneOfType([u.a.func,u.a.node]),jumpNextIcon:u.a.oneOfType([u.a.func,u.a.node])},Pa.defaultProps={defaultCurrent:1,total:0,defaultPageSize:10,onChange:Ha,className:\"\",selectPrefixCls:\"rc-select\",prefixCls:\"rc-pagination\",selectComponentClass:null,hideOnSinglePage:!1,showPrevNextJumpers:!0,showQuickJumper:!1,showSizeChanger:!1,showLessItems:!1,showTitle:!0,onShowSizeChange:Ha,locale:ka.a,style:{},itemRender:function(e,t,n){return n}};var Va=function(){var e=this;this.getJumpPrevPage=function(){return Math.max(1,e.state.current-(e.props.showLessItems?3:5))},this.getJumpNextPage=function(){return Math.min(Ea(void 0,e.state,e.props),e.state.current+(e.props.showLessItems?3:5))},this.getItemIcon=function(t){var n=e.props.prefixCls,r=t||o.a.createElement(\"a\",{className:n+\"-item-link\"});return\"function\"===typeof t&&(r=o.a.createElement(t,ne()({},e.props))),r},this.savePaginationNode=function(t){e.paginationNode=t},this.isValid=function(t){return\"number\"===typeof(n=t)&&isFinite(n)&&Math.floor(n)===n&&t!==e.state.current;var n},this.shouldDisplayQuickJumper=function(){var t=e.props,n=t.showQuickJumper,r=t.pageSize;return!(t.total<=r)&&n},this.handleKeyDown=function(e){e.keyCode!==Ma&&e.keyCode!==Sa||e.preventDefault()},this.handleKeyUp=function(t){var n=e.getValidValue(t);n!==e.state.currentInputValue&&e.setState({currentInputValue:n}),t.keyCode===Ca?e.handleChange(n):t.keyCode===Ma?e.handleChange(n-1):t.keyCode===Sa&&e.handleChange(n+1)},this.changePageSize=function(t){var n=e.state.current,r=Ea(t,e.state,e.props);n=n>r?r:n,0===r&&(n=e.state.current),\"number\"===typeof t&&(\"pageSize\"in e.props||e.setState({pageSize:t}),\"current\"in e.props||e.setState({current:n,currentInputValue:n})),e.props.onShowSizeChange(n,t)},this.handleChange=function(t){var n=e.props.disabled,r=t;if(e.isValid(r)&&!n){var o=Ea(void 0,e.state,e.props);r>o?r=o:r<1&&(r=1),\"current\"in e.props||e.setState({current:r,currentInputValue:r});var c=e.state.pageSize;return e.props.onChange(r,c),r}return e.state.current},this.prev=function(){e.hasPrev()&&e.handleChange(e.state.current-1)},this.next=function(){e.hasNext()&&e.handleChange(e.state.current+1)},this.jumpPrev=function(){e.handleChange(e.getJumpPrevPage())},this.jumpNext=function(){e.handleChange(e.getJumpNextPage())},this.hasPrev=function(){return e.state.current>1},this.hasNext=function(){return e.state.current<Ea(void 0,e.state,e.props)},this.runIfEnter=function(e,t){for(var n=arguments.length,r=Array(n>2?n-2:0),o=2;o<n;o++)r[o-2]=arguments[o];\"Enter\"!==e.key&&13!==e.charCode||t.apply(void 0,r)},this.runIfEnterPrev=function(t){e.runIfEnter(t,e.prev)},this.runIfEnterNext=function(t){e.runIfEnter(t,e.next)},this.runIfEnterJumpPrev=function(t){e.runIfEnter(t,e.jumpPrev)},this.runIfEnterJumpNext=function(t){e.runIfEnter(t,e.jumpNext)},this.handleGoTO=function(t){t.keyCode!==Ca&&\"click\"!==t.type||e.handleChange(e.state.currentInputValue)}};Object(d.polyfill)(Pa);var Ta=Pa,La=n(89);function ja(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Na(e,t){return!t||\"object\"!==typeof t&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Da(e){return(Da=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Ra(e,t){return(Ra=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var Aa=function(e){function t(){return ja(this,t),Na(this,Da(t).apply(this,arguments))}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Ra(e,t)}(t,e),t}(r.Component);function Ia(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Fa(e,t){return!t||\"object\"!==typeof t&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Wa(e){return(Wa=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Ua(e,t){return(Ua=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}Aa.isSelectOptGroup=!0;var Ka=function(e){function t(){return Ia(this,t),Fa(this,Wa(t).apply(this,arguments))}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Ua(e,t)}(t,e),t}(r.Component);function Ba(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new Array(e.length);t<e.length;t++)n[t]=e[t];return n}}(e)||function(e){if(Symbol.iterator in Object(e)||\"[object Arguments]\"===Object.prototype.toString.call(e))return Array.from(e)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance\")}()}function Ya(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];var r=t[0],o=t[1],c=t[2],i=t.slice(3),a=l.oneOfType([l.string,l.number]),u=l.shape({key:a.isRequired,label:l.node});if(!r.labelInValue){if((\"multiple\"===r.mode||\"tags\"===r.mode||r.multiple||r.tags)&&\"\"===r[o])return new Error(\"Invalid prop `\".concat(o,\"` of type `string` supplied to `\").concat(c,\"`, \")+\"expected `array` when `multiple` or `tags` is `true`.\");var s=l.oneOfType([l.arrayOf(a),a]);return s.apply(void 0,[r,o,c].concat(Ba(i)))}var f=l.oneOfType([l.arrayOf(u),u]),p=f.apply(void 0,[r,o,c].concat(Ba(i)));return p?new Error(\"Invalid prop `\".concat(o,\"` supplied to `\").concat(c,\"`, \")+\"when you set `labelInValue` to `true`, `\".concat(o,\"` should in \")+\"shape of `{ key: string | number, label?: ReactNode }`.\"):null}Ka.propTypes={value:l.oneOfType([l.string,l.number])},Ka.isSelectOption=!0;var qa={id:l.string,defaultActiveFirstOption:l.bool,multiple:l.bool,filterOption:l.any,children:l.any,showSearch:l.bool,disabled:l.bool,allowClear:l.bool,showArrow:l.bool,tags:l.bool,prefixCls:l.string,className:l.string,transitionName:l.string,optionLabelProp:l.string,optionFilterProp:l.string,animation:l.string,choiceTransitionName:l.string,open:l.bool,defaultOpen:l.bool,onChange:l.func,onBlur:l.func,onFocus:l.func,onSelect:l.func,onSearch:l.func,onPopupScroll:l.func,onMouseEnter:l.func,onMouseLeave:l.func,onInputKeyDown:l.func,placeholder:l.any,onDeselect:l.func,labelInValue:l.bool,loading:l.bool,value:Ya,defaultValue:Ya,dropdownStyle:l.object,maxTagTextLength:l.number,maxTagCount:l.number,maxTagPlaceholder:l.oneOfType([l.node,l.func]),tokenSeparators:l.arrayOf(l.string),getInputElement:l.func,showAction:l.arrayOf(l.string),clearIcon:l.node,inputIcon:l.node,removeIcon:l.node,menuItemSelectedIcon:l.oneOfType([l.func,l.node]),dropdownRender:l.func},Ga=n(55),$a=n.n(Ga),Qa=n(33),Xa=n(68);function Za(e){var t=[];return o.a.Children.forEach(e,(function(e){void 0!==e&&null!==e&&(Array.isArray(e)?t=t.concat(Za(e)):Object(Xa.isFragment)(e)&&e.props?t=t.concat(Za(e.props.children)):t.push(e))})),t}var Ja=n(56),el=n.n(Ja);function tl(e){return\"string\"===typeof e?e:\"\"}function nl(e){if(!e)return null;var t=e.props;if(\"value\"in t)return t.value;if(e.key)return e.key;if(e.type&&e.type.isSelectOptGroup&&t.label)return t.label;throw new Error(\"Need at least a key or a value or a label (only for OptGroup) for \".concat(e))}function rl(e,t){return\"value\"===t?nl(e):e.props[t]}function ol(e){return e.combobox}function cl(e){return e.multiple||e.tags}function il(e){return cl(e)||ol(e)}function al(e){return!il(e)}function ll(e){var t=e;return void 0===e?t=[]:Array.isArray(e)||(t=[e]),t}function ul(e){return\"\".concat(typeof e,\"-\").concat(e)}function sl(e){e.preventDefault()}function fl(e,t){var n=-1;if(e)for(var r=0;r<e.length;r++)if(e[r]===t){n=r;break}return n}function pl(e,t){var n;if(e=ll(e))for(var r=0;r<e.length;r++)if(e[r].key===t){n=e[r].label;break}return n}function hl(e,t){if(null===t||void 0===t)return[];var n=[];return o.a.Children.forEach(e,(function(e){if(e.type.isMenuItemGroup)n=n.concat(hl(e.props.children,t));else{var r=nl(e),o=e.key;-1!==fl(t,r)&&o&&n.push(o)}})),n}var dl={userSelect:\"none\",WebkitUserSelect:\"none\"},vl={unselectable:\"on\"};function ml(e){for(var t=0;t<e.length;t++){var n=e[t];if(n.type.isMenuItemGroup){var r=ml(n.props.children);if(r)return r}else if(!n.props.disabled)return n}return null}function yl(e,t){return!t.props.disabled&&ll(rl(t,this.props.optionFilterProp)).join(\"\").toLowerCase().indexOf(e.toLowerCase())>-1}function bl(e,t){return function(n){e[t]=n}}function gl(){var e=(new Date).getTime();return\"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g,(function(t){var n=(e+16*Math.random())%16|0;return e=Math.floor(e/16),(\"x\"===t?n:7&n|8).toString(16)}))}function wl(){return(wl=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function zl(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Ol(e){return(Ol=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Cl(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Ml(e,t){return(Ml=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var Sl=function(e){function t(e){var n;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==typeof t&&\"function\"!==typeof t?Cl(e):t}(this,Ol(t).call(this,e))).rafInstance=null,n.lastVisible=!1,n.scrollActiveItemToView=function(){var e=Object(v.findDOMNode)(n.firstActiveItem),t=n.props,r=t.visible,o=t.firstActiveValue,c=n.props.value;if(e&&r){var i={onlyScrollIfNeeded:!0};c&&0!==c.length||!o||(i.alignWithTop=!0),n.rafInstance=pe()((function(){Nt()(e,Object(v.findDOMNode)(n.menuRef),i)}))}},n.renderMenu=function(){var e=n.props,t=e.menuItems,o=e.menuItemSelectedIcon,c=e.defaultActiveFirstOption,i=e.prefixCls,a=e.multiple,l=e.onMenuSelect,u=e.inputValue,s=e.backfillValue,f=e.onMenuDeselect,p=e.visible,h=n.props.firstActiveValue;if(t&&t.length){var d={};a?(d.onDeselect=f,d.onSelect=l):d.onClick=l;var v=n.props.value,m=hl(t,v),y={},b=c,g=t;if(m.length||h){p&&!n.lastVisible?y.activeKey=m[0]||h:p||(m[0]&&(b=!1),y.activeKey=void 0);var w=!1,z=function(e){var t=e.key;return!w&&-1!==m.indexOf(t)||!w&&!m.length&&-1!==h.indexOf(e.key)?(w=!0,r.cloneElement(e,{ref:function(e){n.firstActiveItem=e}})):e};g=t.map((function(e){if(e.type.isMenuItemGroup){var t=Za(e.props.children).map(z);return r.cloneElement(e,{},t)}return z(e)}))}else n.firstActiveItem=null;var O=v&&v[v.length-1];return u===n.lastInputValue||O&&O===s||(y.activeKey=\"\"),r.createElement(an,wl({ref:n.saveMenuRef,style:n.props.dropdownMenuStyle,defaultActiveFirst:b,role:\"listbox\",itemIcon:a?o:null},y,{multiple:a},d,{selectedKeys:m,prefixCls:\"\".concat(i,\"-menu\")}),g)}return null},n.lastInputValue=e.inputValue,n.saveMenuRef=bl(Cl(n),\"menuRef\"),n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Ml(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.scrollActiveItemToView(),this.lastVisible=this.props.visible}},{key:\"shouldComponentUpdate\",value:function(e){return e.visible||(this.lastVisible=!1),this.props.visible&&!e.visible||e.visible||e.inputValue!==this.props.inputValue}},{key:\"componentDidUpdate\",value:function(e){var t=this.props;!e.visible&&t.visible&&this.scrollActiveItemToView(),this.lastVisible=t.visible,this.lastInputValue=t.inputValue}},{key:\"componentWillUnmount\",value:function(){this.rafInstance&&pe.a.cancel(this.rafInstance)}},{key:\"render\",value:function(){var e=this.renderMenu();return e?r.createElement(\"div\",{style:{overflow:\"auto\",transform:\"translateZ(0)\"},id:this.props.ariaId,onFocus:this.props.onPopupFocus,onMouseDown:sl,onScroll:this.props.onPopupScroll},e):null}}])&&zl(n.prototype,o),c&&zl(n,c),t}(r.Component);function _l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function xl(){return(xl=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function kl(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Hl(e){return(Hl=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function El(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Pl(e,t){return(Pl=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}Sl.displayName=\"DropdownMenu\",Sl.propTypes={ariaId:l.string,defaultActiveFirstOption:l.bool,value:l.any,dropdownMenuStyle:l.object,multiple:l.bool,onPopupFocus:l.func,onPopupScroll:l.func,onMenuDeSelect:l.func,onMenuSelect:l.func,prefixCls:l.string,menuItems:l.any,inputValue:l.string,visible:l.bool,firstActiveValue:l.string,menuItemSelectedIcon:l.oneOfType([l.func,l.node])};var Vl=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n};Z.a.displayName=\"Trigger\";var Tl={bottomLeft:{points:[\"tl\",\"bl\"],offset:[0,4],overflow:{adjustX:0,adjustY:1}},topLeft:{points:[\"bl\",\"tl\"],offset:[0,-4],overflow:{adjustX:0,adjustY:1}}},Ll=function(e){function t(e){var n;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==typeof t&&\"function\"!==typeof t?El(e):t}(this,Hl(t).call(this,e))).dropdownMenuRef=null,n.rafInstance=null,n.setDropdownWidth=function(){n.cancelRafInstance(),n.rafInstance=pe()((function(){var e=v.findDOMNode(El(n)).offsetWidth;e!==n.state.dropdownWidth&&n.setState({dropdownWidth:e})}))},n.cancelRafInstance=function(){n.rafInstance&&pe.a.cancel(n.rafInstance)},n.getInnerMenu=function(){return n.dropdownMenuRef&&n.dropdownMenuRef.menuRef},n.getPopupDOMNode=function(){return n.triggerRef.getPopupDomNode()},n.getDropdownElement=function(e){var t=n.props,o=t.dropdownRender,c=t.ariaId,i=r.createElement(Sl,xl({ref:n.saveDropdownMenuRef},e,{ariaId:c,prefixCls:n.getDropdownPrefixCls(),onMenuSelect:t.onMenuSelect,onMenuDeselect:t.onMenuDeselect,onPopupScroll:t.onPopupScroll,value:t.value,backfillValue:t.backfillValue,firstActiveValue:t.firstActiveValue,defaultActiveFirstOption:t.defaultActiveFirstOption,dropdownMenuStyle:t.dropdownMenuStyle,menuItemSelectedIcon:t.menuItemSelectedIcon}));return o?o(i,t):null},n.getDropdownTransitionName=function(){var e=n.props,t=e.transitionName;return!t&&e.animation&&(t=\"\".concat(n.getDropdownPrefixCls(),\"-\").concat(e.animation)),t},n.getDropdownPrefixCls=function(){return\"\".concat(n.props.prefixCls,\"-dropdown\")},n.saveDropdownMenuRef=bl(El(n),\"dropdownMenuRef\"),n.saveTriggerRef=bl(El(n),\"triggerRef\"),n.state={dropdownWidth:0},n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Pl(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){this.setDropdownWidth()}},{key:\"componentDidUpdate\",value:function(){this.setDropdownWidth()}},{key:\"componentWillUnmount\",value:function(){this.cancelRafInstance()}},{key:\"render\",value:function(){var e,t,n=this.props,o=n.onPopupFocus,c=n.empty,i=Vl(n,[\"onPopupFocus\",\"empty\"]),a=i.multiple,l=i.visible,u=i.inputValue,s=i.dropdownAlign,f=i.disabled,p=i.showSearch,h=i.dropdownClassName,d=i.dropdownStyle,v=i.dropdownMatchSelectWidth,m=this.getDropdownPrefixCls(),y=(_l(e={},h,!!h),_l(e,\"\".concat(m,\"--\").concat(a?\"multiple\":\"single\"),1),_l(e,\"\".concat(m,\"--empty\"),c),e),b=this.getDropdownElement({menuItems:i.options,onPopupFocus:o,multiple:a,inputValue:u,visible:l});t=f?[]:al(i)&&!p?[\"click\"]:[\"blur\"];var g=xl({},d),w=v?\"width\":\"minWidth\";return this.state.dropdownWidth&&(g[w]=\"\".concat(this.state.dropdownWidth,\"px\")),r.createElement(Z.a,xl({},i,{showAction:f?[]:this.props.showAction,hideAction:t,ref:this.saveTriggerRef,popupPlacement:\"bottomLeft\",builtinPlacements:Tl,prefixCls:m,popupTransitionName:this.getDropdownTransitionName(),onPopupVisibleChange:i.onDropdownVisibleChange,popup:b,popupAlign:s,popupVisible:l,getPopupContainer:i.getPopupContainer,popupClassName:z()(y),popupStyle:g}),i.children)}}])&&kl(n.prototype,o),c&&kl(n,c),t}(r.Component);function jl(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Nl(){return(Nl=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Dl(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Rl(e){return(Rl=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function Al(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Il(e,t){return(Il=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}Ll.defaultProps={dropdownRender:function(e){return e}},Ll.propTypes={onPopupFocus:l.func,onPopupScroll:l.func,dropdownMatchSelectWidth:l.bool,dropdownAlign:l.object,visible:l.bool,disabled:l.bool,showSearch:l.bool,dropdownClassName:l.string,multiple:l.bool,inputValue:l.string,filterOption:l.any,options:l.any,prefixCls:l.string,popupClassName:l.string,children:l.any,showAction:l.arrayOf(l.string),menuItemSelectedIcon:l.oneOfType([l.func,l.node]),dropdownRender:l.func,ariaId:l.string},Ll.displayName=\"SelectTrigger\";var Fl=function(){return null};function Wl(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];for(var o=0;o<t.length;o++)t[o]&&\"function\"===typeof t[o]&&t[o].apply(Wl,n)}}var Ul=function(e){function t(e){var n;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==typeof t&&\"function\"!==typeof t?Al(e):t}(this,Rl(t).call(this,e))).inputRef=null,n.inputMirrorRef=null,n.topCtrlRef=null,n.selectTriggerRef=null,n.rootRef=null,n.selectionRef=null,n.dropdownContainer=null,n.blurTimer=null,n.focusTimer=null,n.comboboxTimer=null,n._focused=!1,n._mouseDown=!1,n._options=[],n._empty=!1,n.onInputChange=function(e){var t=n.props.tokenSeparators,r=e.target.value;if(cl(n.props)&&t.length&&function(e,t){for(var n=0;n<t.length;++n)if(e.lastIndexOf(t[n])>0)return!0;return!1}(r,t)){var o=n.getValueByInput(r);return void 0!==o&&n.fireChange(o),n.setOpenState(!1,{needFocus:!0}),void n.setInputValue(\"\",!1)}n.setInputValue(r),n.setState({open:!0}),ol(n.props)&&n.fireChange([r])},n.onDropdownVisibleChange=function(e){e&&!n._focused&&(n.clearBlurTime(),n.timeoutFocus(),n._focused=!0,n.updateFocusClassName()),n.setOpenState(e)},n.onKeyDown=function(e){var t=n.state.open;if(!n.props.disabled){var r=e.keyCode;t&&!n.getInputDOMNode()?n.onInputKeyDown(e):r===b.a.ENTER||r===b.a.DOWN?(t||n.setOpenState(!0),e.preventDefault()):r===b.a.SPACE&&(t||(n.setOpenState(!0),e.preventDefault()))}},n.onInputKeyDown=function(e){var t=n.props,r=t.disabled,o=t.combobox,c=t.defaultActiveFirstOption;if(!r){var i=n.state,a=n.getRealOpenState(i),l=e.keyCode;if(!cl(n.props)||e.target.value||l!==b.a.BACKSPACE){if(l===b.a.DOWN){if(!i.open)return n.openIfHasChildren(),e.preventDefault(),void e.stopPropagation()}else if(l===b.a.ENTER&&i.open)!a&&o||e.preventDefault(),a&&o&&!1===c&&(n.comboboxTimer=setTimeout((function(){n.setOpenState(!1)})));else if(l===b.a.ESC)return void(i.open&&(n.setOpenState(!1),e.preventDefault(),e.stopPropagation()));if(a&&n.selectTriggerRef){var u=n.selectTriggerRef.getInnerMenu();u&&u.onKeyDown(e,n.handleBackfill)&&(e.preventDefault(),e.stopPropagation())}}else{e.preventDefault();var s=i.value;s.length&&n.removeSelected(s[s.length-1])}}},n.onMenuSelect=function(e){var t=e.item;if(t){var r=n.state.value,o=n.props,c=nl(t),i=r[r.length-1],a=!1;if(cl(o)?-1!==fl(r,c)?a=!0:r=r.concat([c]):ol(o)||void 0===i||i!==c||c===n.state.backfillValue?(r=[c],n.setOpenState(!1,{needFocus:!0,fireSearch:!1})):(n.setOpenState(!1,{needFocus:!0,fireSearch:!1}),a=!0),a||n.fireChange(r),n.fireSelect(c),!a){var l=ol(o)?rl(t,o.optionLabelProp):\"\";o.autoClearSearchValue&&n.setInputValue(l,!1)}}},n.onMenuDeselect=function(e){var t=e.item,r=e.domEvent;if(\"keydown\"!==r.type||r.keyCode!==b.a.ENTER){var o;\"click\"===r.type&&n.removeSelected(nl(t)),n.props.autoClearSearchValue&&n.setInputValue(\"\")}else{var c=v.findDOMNode(t);(o=c)&&null!==o.offsetParent&&n.removeSelected(nl(t))}},n.onArrowClick=function(e){e.stopPropagation(),e.preventDefault(),n.props.disabled||n.setOpenState(!n.state.open,{needFocus:!n.state.open})},n.onPlaceholderClick=function(){n.getInputDOMNode&&n.getInputDOMNode()&&n.getInputDOMNode().focus()},n.onOuterFocus=function(e){if(n.props.disabled)e.preventDefault();else{n.clearBlurTime();var t=n.getInputDOMNode();t&&e.target===n.rootRef||(il(n.props)||e.target!==t)&&(n._focused||(n._focused=!0,n.updateFocusClassName(),cl(n.props)&&n._mouseDown||n.timeoutFocus()))}},n.onPopupFocus=function(){n.maybeFocus(!0,!0)},n.onOuterBlur=function(e){n.props.disabled?e.preventDefault():n.blurTimer=window.setTimeout((function(){n._focused=!1,n.updateFocusClassName();var e=n.props,t=n.state.value,r=n.state.inputValue;if(al(e)&&e.showSearch&&r&&e.defaultActiveFirstOption){var o=n._options||[];if(o.length){var c=ml(o);c&&(t=[nl(c)],n.fireChange(t))}}else if(cl(e)&&r){n._mouseDown?n.setInputValue(\"\"):(n.state.inputValue=\"\",n.getInputDOMNode&&n.getInputDOMNode()&&(n.getInputDOMNode().value=\"\"));var i=n.getValueByInput(r);void 0!==i&&(t=i,n.fireChange(t))}if(cl(e)&&n._mouseDown)return n.maybeFocus(!0,!0),void(n._mouseDown=!1);n.setOpenState(!1),e.onBlur&&e.onBlur(n.getVLForOnChange(t))}),10)},n.onClearSelection=function(e){var t=n.props,r=n.state;if(!t.disabled){var o=r.inputValue,c=r.value;e.stopPropagation(),(o||c.length)&&(c.length&&n.fireChange([]),n.setOpenState(!1,{needFocus:!0}),o&&n.setInputValue(\"\"))}},n.onChoiceAnimationLeave=function(){n.forcePopupAlign()},n.getOptionInfoBySingleValue=function(e,t){var o;if((t=t||n.state.optionsInfo)[ul(e)]&&(o=t[ul(e)]),o)return o;var c=e;if(n.props.labelInValue){var i=pl(n.props.value,e),a=pl(n.props.defaultValue,e);void 0!==i?c=i:void 0!==a&&(c=a)}return{option:r.createElement(Ka,{value:e,key:e},e),value:e,label:c}},n.getOptionBySingleValue=function(e){return n.getOptionInfoBySingleValue(e).option},n.getOptionsBySingleValue=function(e){return e.map((function(e){return n.getOptionBySingleValue(e)}))},n.getValueByLabel=function(e){if(void 0===e)return null;var t=null;return Object.keys(n.state.optionsInfo).forEach((function(r){var o=n.state.optionsInfo[r];if(!o.disabled){var c=ll(o.label);c&&c.join(\"\")===e&&(t=o.value)}})),t},n.getVLBySingleValue=function(e){return n.props.labelInValue?{key:e,label:n.getLabelBySingleValue(e)}:e},n.getVLForOnChange=function(e){var t=e;return void 0!==t?(t=n.props.labelInValue?t.map((function(e){return{key:e,label:n.getLabelBySingleValue(e)}})):t.map((function(e){return e})),cl(n.props)?t:t[0]):t},n.getLabelBySingleValue=function(e,t){return n.getOptionInfoBySingleValue(e,t).label},n.getDropdownContainer=function(){return n.dropdownContainer||(n.dropdownContainer=document.createElement(\"div\"),document.body.appendChild(n.dropdownContainer)),n.dropdownContainer},n.getPlaceholderElement=function(){var e=n.props,t=n.state,o=!1;t.inputValue&&(o=!0);var c=t.value;c.length&&(o=!0),ol(e)&&1===c.length&&t.value&&!t.value[0]&&(o=!1);var i=e.placeholder;return i?r.createElement(\"div\",Nl({onMouseDown:sl,style:Nl({display:o?\"none\":\"block\"},dl)},vl,{onClick:n.onPlaceholderClick,className:\"\".concat(e.prefixCls,\"-selection__placeholder\")}),i):null},n.getInputElement=function(){var e=n.props,t=r.createElement(\"input\",{id:e.id,autoComplete:\"off\"}),o=e.getInputElement?e.getInputElement():t,c=z()(o.props.className,jl({},\"\".concat(e.prefixCls,\"-search__field\"),!0));return r.createElement(\"div\",{className:\"\".concat(e.prefixCls,\"-search__field__wrap\")},r.cloneElement(o,{ref:n.saveInputRef,onChange:n.onInputChange,onKeyDown:Wl(n.onInputKeyDown,o.props.onKeyDown,n.props.onInputKeyDown),value:n.state.inputValue,disabled:e.disabled,className:c}),r.createElement(\"span\",{ref:n.saveInputMirrorRef,className:\"\".concat(e.prefixCls,\"-search__field__mirror\")},n.state.inputValue,\"\\xa0\"))},n.getInputDOMNode=function(){return n.topCtrlRef?n.topCtrlRef.querySelector(\"input,textarea,div[contentEditable]\"):n.inputRef},n.getInputMirrorDOMNode=function(){return n.inputMirrorRef},n.getPopupDOMNode=function(){if(n.selectTriggerRef)return n.selectTriggerRef.getPopupDOMNode()},n.getPopupMenuComponent=function(){if(n.selectTriggerRef)return n.selectTriggerRef.getInnerMenu()},n.setOpenState=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.needFocus,o=t.fireSearch,c=n.props,i=n.state;if(i.open!==e){n.props.onDropdownVisibleChange&&n.props.onDropdownVisibleChange(e);var a={open:e,backfillValue:\"\"};!e&&al(c)&&c.showSearch&&n.setInputValue(\"\",o),e||n.maybeFocus(e,!!r),n.setState(Nl({open:e},a),(function(){e&&n.maybeFocus(e,!!r)}))}else n.maybeFocus(e,!!r)},n.setInputValue=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=n.props.onSearch;e!==n.state.inputValue&&n.setState((function(n){return t&&e!==n.inputValue&&r&&r(e),{inputValue:e}}),n.forcePopupAlign)},n.getValueByInput=function(e){var t=n.props,r=t.multiple,o=t.tokenSeparators,c=n.state.value,i=!1;return function(e,t){var n=new RegExp(\"[\".concat(t.join(),\"]\"));return e.split(n).filter((function(e){return e}))}(e,o).forEach((function(e){var t=[e];if(r){var o=n.getValueByLabel(e);o&&-1===fl(c,o)&&(c=c.concat(o),i=!0,n.fireSelect(o))}else-1===fl(c,e)&&(c=c.concat(t),i=!0,n.fireSelect(e))})),i?c:void 0},n.getRealOpenState=function(e){var t=n.props.open;if(\"boolean\"===typeof t)return t;var r=(e||n.state).open,o=n._options||[];return!il(n.props)&&n.props.showSearch||r&&!o.length&&(r=!1),r},n.markMouseDown=function(){n._mouseDown=!0},n.markMouseLeave=function(){n._mouseDown=!1},n.handleBackfill=function(e){if(n.props.backfill&&(al(n.props)||ol(n.props))){var t=nl(e);ol(n.props)&&n.setInputValue(t,!1),n.setState({value:[t],backfillValue:t})}},n.filterOption=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:yl,o=n.state.value,c=o[o.length-1];if(!e||c&&c===n.state.backfillValue)return!0;var i=n.props.filterOption;return\"filterOption\"in n.props?!0===i&&(i=r.bind(Al(n))):i=r.bind(Al(n)),!i||(\"function\"===typeof i?i.call(Al(n),e,t):!t.props.disabled)},n.timeoutFocus=function(){var e=n.props.onFocus;n.focusTimer&&n.clearFocusTime(),n.focusTimer=window.setTimeout((function(){e&&e()}),10)},n.clearFocusTime=function(){n.focusTimer&&(clearTimeout(n.focusTimer),n.focusTimer=null)},n.clearBlurTime=function(){n.blurTimer&&(clearTimeout(n.blurTimer),n.blurTimer=null)},n.clearComboboxTime=function(){n.comboboxTimer&&(clearTimeout(n.comboboxTimer),n.comboboxTimer=null)},n.updateFocusClassName=function(){var e=n.rootRef,t=n.props;n._focused?$a()(e).add(\"\".concat(t.prefixCls,\"-focused\")):$a()(e).remove(\"\".concat(t.prefixCls,\"-focused\"))},n.maybeFocus=function(e,t){if(t||e){var r=n.getInputDOMNode(),o=document.activeElement;r&&(e||il(n.props))?o!==r&&(r.focus(),n._focused=!0):o!==n.selectionRef&&n.selectionRef&&(n.selectionRef.focus(),n._focused=!0)}},n.removeSelected=function(e,t){var r=n.props;if(!r.disabled&&!n.isChildDisabled(e)){t&&t.stopPropagation&&t.stopPropagation();var o=n.state.value.filter((function(t){return t!==e}));if(cl(r)){var c=e;r.labelInValue&&(c={key:e,label:n.getLabelBySingleValue(e)}),r.onDeselect&&r.onDeselect(c,n.getOptionBySingleValue(e))}n.fireChange(o)}},n.openIfHasChildren=function(){var e=n.props;(r.Children.count(e.children)||al(e))&&n.setOpenState(!0)},n.fireSelect=function(e){n.props.onSelect&&n.props.onSelect(n.getVLBySingleValue(e),n.getOptionBySingleValue(e))},n.fireChange=function(e){var t=n.props;\"value\"in t||n.setState({value:e},n.forcePopupAlign);var r=n.getVLForOnChange(e),o=n.getOptionsBySingleValue(e);t.onChange&&t.onChange(r,cl(n.props)?o:o[0])},n.isChildDisabled=function(e){return Za(n.props.children).some((function(t){return nl(t)===e&&t.props&&t.props.disabled}))},n.forcePopupAlign=function(){n.state.open&&n.selectTriggerRef&&n.selectTriggerRef.triggerRef&&n.selectTriggerRef.triggerRef.forcePopupAlign()},n.renderFilterOptions=function(){var e=n.state.inputValue,t=n.props,o=t.children,c=t.tags,i=t.notFoundContent,a=[],l=[],u=!1,s=n.renderFilterOptionsFromChildren(o,l,a);if(c){var f=n.state.value;(f=f.filter((function(t){return-1===l.indexOf(t)&&(!e||String(t).indexOf(String(e))>-1)}))).sort((function(e,t){return e.length-t.length})),f.forEach((function(e){var t=e,n=r.createElement(Gt,{style:dl,role:\"option\",attribute:vl,value:t,key:t},t);s.push(n),a.push(n)})),e&&a.every((function(t){return nl(t)!==e}))&&s.unshift(r.createElement(Gt,{style:dl,role:\"option\",attribute:vl,value:e,key:e},e))}return!s.length&&i&&(u=!0,s=[r.createElement(Gt,{style:dl,attribute:vl,disabled:!0,role:\"option\",value:\"NOT_FOUND\",key:\"NOT_FOUND\"},i)]),{empty:u,options:s}},n.renderFilterOptionsFromChildren=function(e,t,o){var c=[],i=n.props,a=n.state.inputValue,l=i.tags;return r.Children.forEach(e,(function(e){if(e){var i=e.type;if(i.isSelectOptGroup){var u=e.props.label,s=e.key;if(s||\"string\"!==typeof u?!u&&s&&(u=s):s=u,a&&n.filterOption(a,e)){var f=Za(e.props.children).map((function(e){var t=nl(e)||e.key;return r.createElement(Gt,Nl({key:t,value:t},e.props))}));c.push(r.createElement(rn,{key:s,title:u},f))}else{var p=n.renderFilterOptionsFromChildren(e.props.children,t,o);p.length&&c.push(r.createElement(rn,{key:s,title:u},p))}}else{el()(i.isSelectOption,\"the children of `Select` should be `Select.Option` or `Select.OptGroup`, \"+\"instead of `\".concat(i.name||i.displayName||e.type,\"`.\"));var h=nl(e);if(function(e,t){if(!al(t)&&!function(e){return e.multiple}(t)&&\"string\"!==typeof e)throw new Error(\"Invalid `value` of type `\".concat(typeof e,\"` supplied to Option, \")+\"expected `string` when `tags/combobox` is `true`.\")}(h,n.props),n.filterOption(a,e)){var d=r.createElement(Gt,Nl({style:dl,attribute:vl,value:h,key:h,role:\"option\"},e.props));c.push(d),o.push(d)}l&&t.push(h)}}})),c},n.renderTopControlNode=function(){var e=n.state,t=e.open,o=e.inputValue,c=n.state.value,i=n.props,a=i.choiceTransitionName,l=i.prefixCls,u=i.maxTagTextLength,s=i.maxTagCount,f=i.showSearch,p=i.removeIcon,h=i.maxTagPlaceholder,d=\"\".concat(l,\"-selection__rendered\"),v=null;if(al(i)){var m=null;if(c.length){var y=!1,b=1;f&&t?(y=!o)&&(b=.4):y=!0;var g=c[0],w=n.getOptionInfoBySingleValue(g),z=w.label,O=w.title;m=r.createElement(\"div\",{key:\"value\",className:\"\".concat(l,\"-selection-selected-value\"),title:tl(O||z),style:{display:y?\"block\":\"none\",opacity:b}},z)}v=f?[m,r.createElement(\"div\",{className:\"\".concat(l,\"-search \").concat(l,\"-search--inline\"),key:\"input\",style:{display:t?\"block\":\"none\"}},n.getInputElement())]:[m]}else{var C,M=[],S=c;if(void 0!==s&&c.length>s){S=S.slice(0,s);var _=n.getVLForOnChange(c.slice(s,c.length)),x=\"+ \".concat(c.length-s,\" ...\");h&&(x=\"function\"===typeof h?h(_):h),C=r.createElement(\"li\",Nl({style:dl},vl,{role:\"presentation\",onMouseDown:sl,className:\"\".concat(l,\"-selection__choice \").concat(l,\"-selection__choice__disabled\"),key:\"maxTagPlaceholder\",title:tl(x)}),r.createElement(\"div\",{className:\"\".concat(l,\"-selection__choice__content\")},x))}cl(i)&&(M=S.map((function(e){var t=n.getOptionInfoBySingleValue(e),o=t.label,c=t.title||o;u&&\"string\"===typeof o&&o.length>u&&(o=\"\".concat(o.slice(0,u),\"...\"));var i=n.isChildDisabled(e),a=i?\"\".concat(l,\"-selection__choice \").concat(l,\"-selection__choice__disabled\"):\"\".concat(l,\"-selection__choice\");return r.createElement(\"li\",Nl({style:dl},vl,{onMouseDown:sl,className:a,role:\"presentation\",key:e||\"RC_SELECT_EMPTY_VALUE_KEY\",title:tl(c)}),r.createElement(\"div\",{className:\"\".concat(l,\"-selection__choice__content\")},o),i?null:r.createElement(\"span\",{onClick:function(t){n.removeSelected(e,t)},className:\"\".concat(l,\"-selection__choice__remove\")},p||r.createElement(\"i\",{className:\"\".concat(l,\"-selection__choice__remove-icon\")},\"\\xd7\")))}))),C&&M.push(C),M.push(r.createElement(\"li\",{className:\"\".concat(l,\"-search \").concat(l,\"-search--inline\"),key:\"__input\"},n.getInputElement())),v=cl(i)&&a?r.createElement(Qa.a,{onLeave:n.onChoiceAnimationLeave,component:\"ul\",transitionName:a},M):r.createElement(\"ul\",null,M)}return r.createElement(\"div\",{className:d,ref:n.saveTopCtrlRef},n.getPlaceholderElement(),v)};var o=t.getOptionsInfoFromProps(e);if(e.tags&&\"function\"!==typeof e.filterOption){var c=Object.keys(o).some((function(e){return o[e].disabled}));el()(!c,\"Please avoid setting option to disabled in tags mode since user can always type text as tag.\")}return n.state={value:t.getValueFromProps(e,!0),inputValue:e.combobox?t.getInputValueForCombobox(e,o,!0):\"\",open:e.defaultOpen,optionsInfo:o,backfillValue:\"\",skipBuildOptionsInfo:!0,ariaId:\"\"},n.saveInputRef=bl(Al(n),\"inputRef\"),n.saveInputMirrorRef=bl(Al(n),\"inputMirrorRef\"),n.saveTopCtrlRef=bl(Al(n),\"topCtrlRef\"),n.saveSelectTriggerRef=bl(Al(n),\"selectTriggerRef\"),n.saveRootRef=bl(Al(n),\"rootRef\"),n.saveSelectionRef=bl(Al(n),\"selectionRef\"),n}var n,o,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Il(e,t)}(t,e),n=t,(o=[{key:\"componentDidMount\",value:function(){(this.props.autoFocus||this.state.open)&&this.focus(),this.setState({ariaId:gl()})}},{key:\"componentDidUpdate\",value:function(){if(cl(this.props)){var e=this.getInputDOMNode(),t=this.getInputMirrorDOMNode();e&&e.value&&t?(e.style.width=\"\",e.style.width=\"\".concat(t.clientWidth,\"px\")):e&&(e.style.width=\"\")}this.forcePopupAlign()}},{key:\"componentWillUnmount\",value:function(){this.clearFocusTime(),this.clearBlurTime(),this.clearComboboxTime(),this.dropdownContainer&&(v.unmountComponentAtNode(this.dropdownContainer),document.body.removeChild(this.dropdownContainer),this.dropdownContainer=null)}},{key:\"focus\",value:function(){al(this.props)&&this.selectionRef?this.selectionRef.focus():this.getInputDOMNode()&&this.getInputDOMNode().focus()}},{key:\"blur\",value:function(){al(this.props)&&this.selectionRef?this.selectionRef.blur():this.getInputDOMNode()&&this.getInputDOMNode().blur()}},{key:\"renderArrow\",value:function(e){var t=this.props,n=t.showArrow,o=void 0===n?!e:n,c=t.loading,i=t.inputIcon,a=t.prefixCls;if(!o&&!c)return null;var l=c?r.createElement(\"i\",{className:\"\".concat(a,\"-arrow-loading\")}):r.createElement(\"i\",{className:\"\".concat(a,\"-arrow-icon\")});return r.createElement(\"span\",Nl({key:\"arrow\",className:\"\".concat(a,\"-arrow\"),style:dl},vl,{onClick:this.onArrowClick}),i||l)}},{key:\"renderClear\",value:function(){var e=this.props,t=e.prefixCls,n=e.allowClear,o=e.clearIcon,c=this.state.inputValue,i=this.state.value,a=r.createElement(\"span\",Nl({key:\"clear\",className:\"\".concat(t,\"-selection__clear\"),onMouseDown:sl,style:dl},vl,{onClick:this.onClearSelection}),o||r.createElement(\"i\",{className:\"\".concat(t,\"-selection__clear-icon\")},\"\\xd7\"));return n?ol(this.props)?c?a:null:c||i.length?a:null:null}},{key:\"render\",value:function(){var e,t=this.props,n=cl(t),o=t.showArrow,c=void 0===o||o,i=this.state,a=t.className,l=t.disabled,u=t.prefixCls,s=t.loading,f=this.renderTopControlNode(),p=this.state,h=p.open,d=p.ariaId;if(h){var v=this.renderFilterOptions();this._empty=v.empty,this._options=v.options}var m=this.getRealOpenState(),y=this._empty,b=this._options||[],g={};Object.keys(t).forEach((function(e){!Object.prototype.hasOwnProperty.call(t,e)||\"data-\"!==e.substr(0,5)&&\"aria-\"!==e.substr(0,5)&&\"role\"!==e||(g[e]=t[e])}));var w=Nl({},g);il(t)||(w=Nl(Nl({},w),{onKeyDown:this.onKeyDown,tabIndex:t.disabled?-1:t.tabIndex}));var O=(jl(e={},a,!!a),jl(e,u,1),jl(e,\"\".concat(u,\"-open\"),h),jl(e,\"\".concat(u,\"-focused\"),h||!!this._focused),jl(e,\"\".concat(u,\"-combobox\"),ol(t)),jl(e,\"\".concat(u,\"-disabled\"),l),jl(e,\"\".concat(u,\"-enabled\"),!l),jl(e,\"\".concat(u,\"-allow-clear\"),!!t.allowClear),jl(e,\"\".concat(u,\"-no-arrow\"),!c),jl(e,\"\".concat(u,\"-loading\"),!!s),e);return r.createElement(Ll,{onPopupFocus:this.onPopupFocus,onMouseEnter:this.props.onMouseEnter,onMouseLeave:this.props.onMouseLeave,dropdownAlign:t.dropdownAlign,dropdownClassName:t.dropdownClassName,dropdownMatchSelectWidth:t.dropdownMatchSelectWidth,defaultActiveFirstOption:t.defaultActiveFirstOption,dropdownMenuStyle:t.dropdownMenuStyle,transitionName:t.transitionName,animation:t.animation,prefixCls:t.prefixCls,dropdownStyle:t.dropdownStyle,combobox:t.combobox,showSearch:t.showSearch,options:b,empty:y,multiple:n,disabled:l,visible:m,inputValue:i.inputValue,value:i.value,backfillValue:i.backfillValue,firstActiveValue:t.firstActiveValue,onDropdownVisibleChange:this.onDropdownVisibleChange,getPopupContainer:t.getPopupContainer,onMenuSelect:this.onMenuSelect,onMenuDeselect:this.onMenuDeselect,onPopupScroll:t.onPopupScroll,showAction:t.showAction,ref:this.saveSelectTriggerRef,menuItemSelectedIcon:t.menuItemSelectedIcon,dropdownRender:t.dropdownRender,ariaId:d},r.createElement(\"div\",{id:t.id,style:t.style,ref:this.saveRootRef,onBlur:this.onOuterBlur,onFocus:this.onOuterFocus,className:z()(O),onMouseDown:this.markMouseDown,onMouseUp:this.markMouseLeave,onMouseOut:this.markMouseLeave},r.createElement(\"div\",Nl({ref:this.saveSelectionRef,key:\"selection\",className:\"\".concat(u,\"-selection\\n            \").concat(u,\"-selection--\").concat(n?\"multiple\":\"single\"),role:\"combobox\",\"aria-autocomplete\":\"list\",\"aria-haspopup\":\"true\",\"aria-controls\":d,\"aria-expanded\":m},w),f,this.renderClear(),this.renderArrow(!!n))))}}])&&Dl(n.prototype,o),c&&Dl(n,c),t}(r.Component);Ul.propTypes=qa,Ul.defaultProps={prefixCls:\"rc-select\",defaultOpen:!1,labelInValue:!1,defaultActiveFirstOption:!0,showSearch:!0,allowClear:!1,placeholder:\"\",onChange:Fl,onFocus:Fl,onBlur:Fl,onSelect:Fl,onSearch:Fl,onDeselect:Fl,onInputKeyDown:Fl,dropdownMatchSelectWidth:!0,dropdownStyle:{},dropdownMenuStyle:{},optionFilterProp:\"value\",optionLabelProp:\"value\",notFoundContent:\"Not Found\",backfill:!1,showAction:[\"click\"],tokenSeparators:[],autoClearSearchValue:!0,tabIndex:0,dropdownRender:function(e){return e}},Ul.getDerivedStateFromProps=function(e,t){var n=t.skipBuildOptionsInfo?t.optionsInfo:Ul.getOptionsInfoFromProps(e,t),r={optionsInfo:n,skipBuildOptionsInfo:!1};if(\"open\"in e&&(r.open=e.open),e.disabled&&t.open&&(r.open=!1),\"value\"in e){var o=Ul.getValueFromProps(e);r.value=o,e.combobox&&(r.inputValue=Ul.getInputValueForCombobox(e,n))}return r},Ul.getOptionsFromChildren=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];return r.Children.forEach(e,(function(e){e&&(e.type.isSelectOptGroup?Ul.getOptionsFromChildren(e.props.children,t):t.push(e))})),t},Ul.getInputValueForCombobox=function(e,t,n){var r=[];if(\"value\"in e&&!n&&(r=ll(e.value)),\"defaultValue\"in e&&n&&(r=ll(e.defaultValue)),!r.length)return\"\";var o=r=r[0];return e.labelInValue?o=r.label:t[ul(r)]&&(o=t[ul(r)].label),void 0===o&&(o=\"\"),o},Ul.getLabelFromOption=function(e,t){return rl(t,e.optionLabelProp)},Ul.getOptionsInfoFromProps=function(e,t){var n=Ul.getOptionsFromChildren(e.children),r={};if(n.forEach((function(t){var n=nl(t);r[ul(n)]={option:t,value:n,label:Ul.getLabelFromOption(e,t),title:t.props.title,disabled:t.props.disabled}})),t){var o=t.optionsInfo,c=t.value;c&&c.forEach((function(e){var t=ul(e);r[t]||void 0===o[t]||(r[t]=o[t])}))}return r},Ul.getValueFromProps=function(e,t){var n=[];return\"value\"in e&&!t&&(n=ll(e.value)),\"defaultValue\"in e&&t&&(n=ll(e.defaultValue)),e.labelInValue&&(n=n.map((function(e){return e.key}))),n},Ul.displayName=\"Select\",Object(d.polyfill)(Ul);var Kl=Ul;Kl.Option=Ka,Kl.OptGroup=Aa;var Bl=Kl;function Yl(e){return(Yl=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function ql(){return(ql=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Gl(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function $l(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Ql(e,t){return(Ql=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Xl(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Jl(e);if(t){var o=Jl(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Zl(this,n)}}function Zl(e,t){return!t||\"object\"!==Yl(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Jl(e){return(Jl=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var eu=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},tu=Object(wn.a)(\"default\",\"large\",\"small\"),nu=(Object(wn.a)(\"default\",\"multiple\",\"tags\",\"combobox\",\"SECRET_COMBOBOX_MODE_DO_NOT_USE\"),{prefixCls:l.string,className:l.string,size:l.oneOf(tu),notFoundContent:l.any,showSearch:l.bool,optionLabelProp:l.string,transitionName:l.string,choiceTransitionName:l.string,id:l.string}),ru=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Ql(e,t)}(a,e);var t,n,o,i=Xl(a);function a(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,a),(t=i.call(this,e)).saveSelect=function(e){t.rcSelect=e},t.renderSelect=function(e){var n,o=e.getPopupContainer,i=e.getPrefixCls,a=e.renderEmpty,l=t.props,u=l.prefixCls,s=l.className,p=void 0===s?\"\":s,h=l.size,d=l.mode,v=l.getPopupContainer,m=l.removeIcon,y=l.clearIcon,b=l.menuItemSelectedIcon,g=l.showArrow,w=eu(l,[\"prefixCls\",\"className\",\"size\",\"mode\",\"getPopupContainer\",\"removeIcon\",\"clearIcon\",\"menuItemSelectedIcon\",\"showArrow\"]),z=Object(c.a)(w,[\"inputIcon\"]),O=i(\"select\",u),C=f()((Gl(n={},\"\".concat(O,\"-lg\"),\"large\"===h),Gl(n,\"\".concat(O,\"-sm\"),\"small\"===h),Gl(n,\"\".concat(O,\"-show-arrow\"),g),n),p),M=t.props.optionLabelProp;t.isCombobox()&&(M=M||\"value\");var S={multiple:\"multiple\"===d,tags:\"tags\"===d,combobox:t.isCombobox()},_=m&&(r.isValidElement(m)?r.cloneElement(m,{className:f()(m.props.className,\"\".concat(O,\"-remove-icon\"))}):m)||r.createElement(gn.a,{type:\"close\",className:\"\".concat(O,\"-remove-icon\")}),x=y&&(r.isValidElement(y)?r.cloneElement(y,{className:f()(y.props.className,\"\".concat(O,\"-clear-icon\"))}):y)||r.createElement(gn.a,{type:\"close-circle\",theme:\"filled\",className:\"\".concat(O,\"-clear-icon\")}),k=b&&(r.isValidElement(b)?r.cloneElement(b,{className:f()(b.props.className,\"\".concat(O,\"-selected-icon\"))}):b)||r.createElement(gn.a,{type:\"check\",className:\"\".concat(O,\"-selected-icon\")});return r.createElement(Bl,ql({inputIcon:t.renderSuffixIcon(O),removeIcon:_,clearIcon:x,menuItemSelectedIcon:k,showArrow:g},z,S,{prefixCls:O,className:C,optionLabelProp:M||\"children\",notFoundContent:t.getNotFoundContent(a),getPopupContainer:v||o,ref:t.saveSelect}))},Object(bn.a)(\"combobox\"!==e.mode,\"Select\",\"The combobox mode is deprecated, it will be removed in next major version, please use AutoComplete instead\"),t}return t=a,(n=[{key:\"getNotFoundContent\",value:function(e){var t=this.props.notFoundContent;return void 0!==t?t:this.isCombobox()?null:e(\"Select\")}},{key:\"focus\",value:function(){this.rcSelect.focus()}},{key:\"blur\",value:function(){this.rcSelect.blur()}},{key:\"isCombobox\",value:function(){var e=this.props.mode;return\"combobox\"===e||e===a.SECRET_COMBOBOX_MODE_DO_NOT_USE}},{key:\"renderSuffixIcon\",value:function(e){var t=this.props,n=t.loading,o=t.suffixIcon;return o?r.isValidElement(o)?r.cloneElement(o,{className:f()(o.props.className,\"\".concat(e,\"-arrow-icon\"))}):o:n?r.createElement(gn.a,{type:\"loading\"}):r.createElement(gn.a,{type:\"down\",className:\"\".concat(e,\"-arrow-icon\")})}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderSelect)}}])&&$l(t.prototype,n),o&&$l(t,o),a}(r.Component);function ou(e){return(ou=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function cu(){return(cu=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function iu(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function au(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function lu(e,t){return(lu=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function uu(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=fu(e);if(t){var o=fu(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return su(this,n)}}function su(e,t){return!t||\"object\"!==ou(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function fu(e){return(fu=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}ru.Option=Ka,ru.OptGroup=Aa,ru.SECRET_COMBOBOX_MODE_DO_NOT_USE=\"SECRET_COMBOBOX_MODE_DO_NOT_USE\",ru.defaultProps={showSearch:!1,transitionName:\"slide-up\",choiceTransitionName:\"zoom\"},ru.propTypes=nu;var pu=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&lu(e,t)}(i,e);var t,n,o,c=uu(i);function i(){return iu(this,i),c.apply(this,arguments)}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(ru,cu({size:\"small\"},this.props))}}])&&au(t.prototype,n),o&&au(t,o),i}(r.Component);pu.Option=ru.Option;var hu=n(30);function du(e){return(du=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function vu(){return(vu=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function mu(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function yu(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function bu(e,t){return(bu=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function gu(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=zu(e);if(t){var o=zu(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return wu(this,n)}}function wu(e,t){return!t||\"object\"!==du(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function zu(e){return(zu=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Ou=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Cu=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&bu(e,t)}(i,e);var t,n,o,c=gu(i);function i(){var e;return mu(this,i),(e=c.apply(this,arguments)).getIconsProps=function(e){return{prevIcon:r.createElement(\"a\",{className:\"\".concat(e,\"-item-link\")},r.createElement(gn.a,{type:\"left\"})),nextIcon:r.createElement(\"a\",{className:\"\".concat(e,\"-item-link\")},r.createElement(gn.a,{type:\"right\"})),jumpPrevIcon:r.createElement(\"a\",{className:\"\".concat(e,\"-item-link\")},r.createElement(\"div\",{className:\"\".concat(e,\"-item-container\")},r.createElement(gn.a,{className:\"\".concat(e,\"-item-link-icon\"),type:\"double-left\"}),r.createElement(\"span\",{className:\"\".concat(e,\"-item-ellipsis\")},\"\\u2022\\u2022\\u2022\"))),jumpNextIcon:r.createElement(\"a\",{className:\"\".concat(e,\"-item-link\")},r.createElement(\"div\",{className:\"\".concat(e,\"-item-container\")},r.createElement(gn.a,{className:\"\".concat(e,\"-item-link-icon\"),type:\"double-right\"}),r.createElement(\"span\",{className:\"\".concat(e,\"-item-ellipsis\")},\"\\u2022\\u2022\\u2022\")))}},e.renderPagination=function(t){var n=e.props,o=n.prefixCls,c=n.selectPrefixCls,i=n.className,a=n.size,l=n.locale,u=Ou(n,[\"prefixCls\",\"selectPrefixCls\",\"className\",\"size\",\"locale\"]),s=vu(vu({},t),l),p=\"small\"===a;return r.createElement(yn.a,null,(function(t){var n=t.getPrefixCls,a=n(\"pagination\",o),l=n(\"select\",c);return r.createElement(Ta,vu({},u,{prefixCls:a,selectPrefixCls:l},e.getIconsProps(a),{className:f()(i,{mini:p}),selectComponentClass:p?pu:ru,locale:s}))}))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(hu.a,{componentName:\"Pagination\",defaultLocale:La.a},this.renderPagination)}}])&&yu(t.prototype,n),o&&yu(t,o),i}(r.Component),Mu=n(93),Su=n.n(Mu);function _u(e){return(_u=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function xu(){return(xu=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function ku(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Hu(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Eu(e,t){return(Eu=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Pu(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Tu(e);if(t){var o=Tu(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Vu(this,n)}}function Vu(e,t){return!t||\"object\"!==_u(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Tu(e){return(Tu=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Lu=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},ju=Object(wn.a)(\"small\",\"default\",\"large\"),Nu=null;var Du=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Eu(e,t)}(a,e);var t,n,o,i=Pu(a);function a(e){var t;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,a),(t=i.call(this,e)).debouncifyUpdateSpinning=function(e){var n=(e||t.props).delay;n&&(t.cancelExistingSpin(),t.updateSpinning=Su()(t.originalUpdateSpinning,n))},t.updateSpinning=function(){var e=t.props.spinning;t.state.spinning!==e&&t.setState({spinning:e})},t.renderSpin=function(e){var n,o=e.getPrefixCls,i=t.props,a=i.prefixCls,l=i.className,u=i.size,s=i.tip,p=i.wrapperClassName,h=i.style,d=Lu(i,[\"prefixCls\",\"className\",\"size\",\"tip\",\"wrapperClassName\",\"style\"]),v=t.state.spinning,m=o(\"spin\",a),y=f()(m,(ku(n={},\"\".concat(m,\"-sm\"),\"small\"===u),ku(n,\"\".concat(m,\"-lg\"),\"large\"===u),ku(n,\"\".concat(m,\"-spinning\"),v),ku(n,\"\".concat(m,\"-show-text\"),!!s),n),l),b=Object(c.a)(d,[\"spinning\",\"delay\",\"indicator\"]),g=r.createElement(\"div\",xu({},b,{style:h,className:y}),function(e,t){var n=t.indicator,o=\"\".concat(e,\"-dot\");return null===n?null:r.isValidElement(n)?r.cloneElement(n,{className:f()(n.props.className,o)}):r.isValidElement(Nu)?r.cloneElement(Nu,{className:f()(Nu.props.className,o)}):r.createElement(\"span\",{className:f()(o,\"\".concat(e,\"-dot-spin\"))},r.createElement(\"i\",{className:\"\".concat(e,\"-dot-item\")}),r.createElement(\"i\",{className:\"\".concat(e,\"-dot-item\")}),r.createElement(\"i\",{className:\"\".concat(e,\"-dot-item\")}),r.createElement(\"i\",{className:\"\".concat(e,\"-dot-item\")}))}(m,t.props),s?r.createElement(\"div\",{className:\"\".concat(m,\"-text\")},s):null);if(t.isNestedPattern()){var w=f()(\"\".concat(m,\"-container\"),ku({},\"\".concat(m,\"-blur\"),v));return r.createElement(\"div\",xu({},b,{className:f()(\"\".concat(m,\"-nested-loading\"),p)}),v&&r.createElement(\"div\",{key:\"loading\"},g),r.createElement(\"div\",{className:w,key:\"container\"},t.props.children))}return g};var n=e.spinning,o=function(e,t){return!!e&&!!t&&!isNaN(Number(t))}(n,e.delay);return t.state={spinning:n&&!o},t.originalUpdateSpinning=t.updateSpinning,t.debouncifyUpdateSpinning(e),t}return t=a,o=[{key:\"setDefaultIndicator\",value:function(e){Nu=e}}],(n=[{key:\"componentDidMount\",value:function(){this.updateSpinning()}},{key:\"componentDidUpdate\",value:function(){this.debouncifyUpdateSpinning(),this.updateSpinning()}},{key:\"componentWillUnmount\",value:function(){this.cancelExistingSpin()}},{key:\"cancelExistingSpin\",value:function(){var e=this.updateSpinning;e&&e.cancel&&e.cancel()}},{key:\"isNestedPattern\",value:function(){return!(!this.props||!this.props.children)}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderSpin)}}])&&Hu(t.prototype,n),o&&Hu(t,o),a}(r.Component);Du.defaultProps={spinning:!0,size:\"default\",wrapperClassName:\"\"},Du.propTypes={prefixCls:l.string,className:l.string,spinning:l.bool,size:l.oneOf(ju),wrapperClassName:l.string,indicator:l.element};var Ru=Du;function Au(e){return(Au=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Iu(){return(Iu=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Fu(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Wu(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Uu(e,t){return(Uu=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ku(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Yu(e);if(t){var o=Yu(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Bu(this,n)}}function Bu(e,t){return!t||\"object\"!==Au(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Yu(e){return(Yu=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var qu=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Gu={border:0,background:\"transparent\",padding:0,lineHeight:\"inherit\",display:\"inline-block\"},$u=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Uu(e,t)}(i,e);var t,n,o,c=Ku(i);function i(){var e;return Fu(this,i),(e=c.apply(this,arguments)).onKeyDown=function(e){e.keyCode===b.a.ENTER&&e.preventDefault()},e.onKeyUp=function(t){var n=t.keyCode,r=e.props.onClick;n===b.a.ENTER&&r&&r()},e.setRef=function(t){e.div=t},e}return t=i,(n=[{key:\"focus\",value:function(){this.div&&this.div.focus()}},{key:\"blur\",value:function(){this.div&&this.div.blur()}},{key:\"render\",value:function(){var e=this.props,t=e.style,n=e.noStyle,o=qu(e,[\"style\",\"noStyle\"]);return r.createElement(\"div\",Iu({role:\"button\",tabIndex:0,ref:this.setRef},o,{onKeyDown:this.onKeyDown,onKeyUp:this.onKeyUp,style:Iu(Iu({},n?null:Gu),t)}))}}])&&Wu(t.prototype,n),o&&Wu(t,o),i}(r.Component),Qu=n(39);function Xu(e){return(Xu=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Zu(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ju(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function es(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function ts(e,t,n){return t&&es(e.prototype,t),n&&es(e,n),e}function ns(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&rs(e,t)}function rs(e,t){return(rs=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function os(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=is(e);if(t){var o=is(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return cs(this,n)}}function cs(e,t){return!t||\"object\"!==Xu(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function is(e){return(is=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function as(){return(as=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var ls=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n};function us(){}function ss(e){e.stopPropagation()}function fs(e){return e.rowSelection||{}}function ps(e,t){return e.key||e.dataIndex||t}function hs(e,t){return!!(e&&t&&e.key&&e.key===t.key)||(e===t||h()(e,t,(function(e,t){return\"function\"===typeof e&&\"function\"===typeof t?e===t||e.toString()===t.toString():Array.isArray(e)&&Array.isArray(t)?e===t||h()(e,t):void 0})))}var ds={onChange:us,onShowSizeChange:us},vs={},ms=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e&&e.body&&e.body.row;return as(as({},e),{body:as(as({},e.body),{row:ba(t)})})};function ys(e,t){return Wo(t||(e||{}).columns||[],(function(e){return\"undefined\"!==typeof e.filteredValue}))}function bs(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,n={};return ys(e,t).forEach((function(e){var t=ps(e);n[t]=e.filteredValue})),n}var gs=function(e){ns(n,e);var t=os(n);function n(e){var o;Ju(this,n),(o=t.call(this,e)).setTableRef=function(e){o.rcTable=e},o.getCheckboxPropsByItem=function(e,t){var n=fs(o.props);if(!n.getCheckboxProps)return{};var r=o.getRecordKey(e,t);if(!o.props.checkboxPropsCache[r]){o.props.checkboxPropsCache[r]=n.getCheckboxProps(e)||{};var c=o.props.checkboxPropsCache[r];Object(bn.a)(!(\"checked\"in c)&&!(\"defaultChecked\"in c),\"Table\",\"Do not set `checked` or `defaultChecked` in `getCheckboxProps`. Please use `selectedRowKeys` instead.\")}return o.props.checkboxPropsCache[r]},o.getRecordKey=function(e,t){var n=o.props.rowKey,r=\"function\"===typeof n?n(e,t):e[n];return Object(bn.a)(void 0!==r,\"Table\",\"Each record in dataSource of table should have a unique `key` prop, or set `rowKey` of Table to an unique primary key, see https://u.ant.design/table-row-key\"),void 0===r?t:r},o.onRow=function(e,t,n){var r=o.props.onRow;return as(as({},r?r(t,n):{}),{prefixCls:e,store:o.props.store,rowKey:o.getRecordKey(t,n)})},o.generatePopupContainerFunc=function(e){var t=o.props.scroll,n=o.rcTable;return e||(t&&n?function(){return n.tableNode}:void 0)},o.scrollToFirstRow=function(){var e=o.props.scroll;e&&!1!==e.scrollToFirstRowOnChange&&function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.getContainer,r=void 0===n?function(){return window}:n,o=t.callback,c=t.duration,i=void 0===c?450:c,a=r(),l=ga(a,!0),u=Date.now(),s=function t(){var n=Date.now()-u,r=wa(n>i?i:n,l,e,i);a===window?window.scrollTo(window.pageXOffset,r):a.scrollTop=r,n<i?pe()(t):\"function\"===typeof o&&o()};pe()(s)}(0,{getContainer:function(){return o.rcTable.bodyTable}})},o.handleFilter=function(e,t){var n=o.props,r=as({},o.state.pagination),c=as(as({},o.state.filters),Zu({},ps(e),t)),i=[];Fo(o.state.columns,(function(e){e.children||i.push(ps(e))})),Object.keys(c).forEach((function(e){i.indexOf(e)<0&&delete c[e]})),n.pagination&&(r.current=1,r.onChange(r.current));var a={pagination:r,filters:{}},l=as({},c);ys(o.state).forEach((function(e){var t=ps(e);t&&delete l[t]})),Object.keys(l).length>0&&(a.filters=l),\"object\"===Xu(n.pagination)&&\"current\"in n.pagination&&(a.pagination=as(as({},r),{current:o.state.pagination.current})),o.setState(a,(function(){o.scrollToFirstRow(),o.props.store.setState({selectionDirty:!1});var e=o.props.onChange;e&&e.apply(null,o.prepareParamsArguments(as(as({},o.state),{selectionDirty:!1,filters:c,pagination:r})))}))},o.handleSelect=function(e,t,n){var r=n.target.checked,c=n.nativeEvent,i=o.props.store.getState().selectionDirty?[]:o.getDefaultSelection(),a=o.props.store.getState().selectedRowKeys.concat(i),l=o.getRecordKey(e,t),u=o.state.pivot,s=o.getFlatCurrentPageData(),f=t;if(o.props.expandedRowRender&&(f=s.findIndex((function(e){return o.getRecordKey(e,t)===l}))),c.shiftKey&&void 0!==u&&f!==u){for(var p=[],h=Math.sign(u-f),d=Math.abs(u-f),v=0,m=function(){var e=f+v*h;v+=1;var t=s[e],n=o.getRecordKey(t,e);o.getCheckboxPropsByItem(t,e).disabled||(a.includes(n)?r||(a=a.filter((function(e){return n!==e})),p.push(n)):r&&(a.push(n),p.push(n)))};v<=d;)m();o.setState({pivot:f}),o.props.store.setState({selectionDirty:!0}),o.setSelectedRowKeys(a,{selectWay:\"onSelectMultiple\",record:e,checked:r,changeRowKeys:p,nativeEvent:c})}else r?a.push(o.getRecordKey(e,f)):a=a.filter((function(e){return l!==e})),o.setState({pivot:f}),o.props.store.setState({selectionDirty:!0}),o.setSelectedRowKeys(a,{selectWay:\"onSelect\",record:e,checked:r,changeRowKeys:void 0,nativeEvent:c})},o.handleRadioSelect=function(e,t,n){var r=n.target.checked,c=n.nativeEvent,i=[o.getRecordKey(e,t)];o.props.store.setState({selectionDirty:!0}),o.setSelectedRowKeys(i,{selectWay:\"onSelect\",record:e,checked:r,changeRowKeys:void 0,nativeEvent:c})},o.handleSelectRow=function(e,t,n){var r,c=o.getFlatCurrentPageData(),i=o.props.store.getState().selectionDirty?[]:o.getDefaultSelection(),a=o.props.store.getState().selectedRowKeys.concat(i),l=c.filter((function(e,t){return!o.getCheckboxPropsByItem(e,t).disabled})).map((function(e,t){return o.getRecordKey(e,t)})),u=[],s=\"onSelectAll\";switch(e){case\"all\":l.forEach((function(e){a.indexOf(e)<0&&(a.push(e),u.push(e))})),s=\"onSelectAll\",r=!0;break;case\"removeAll\":l.forEach((function(e){a.indexOf(e)>=0&&(a.splice(a.indexOf(e),1),u.push(e))})),s=\"onSelectAll\",r=!1;break;case\"invert\":l.forEach((function(e){a.indexOf(e)<0?a.push(e):a.splice(a.indexOf(e),1),u.push(e),s=\"onSelectInvert\"}))}o.props.store.setState({selectionDirty:!0});var f=o.props.rowSelection,p=2;if(f&&f.hideDefaultSelections&&(p=0),t>=p&&\"function\"===typeof n)return n(l);o.setSelectedRowKeys(a,{selectWay:s,checked:r,changeRowKeys:u})},o.handlePageChange=function(e){var t=o.props,n=as({},o.state.pagination);n.current=e||(n.current||1);for(var r=arguments.length,c=new Array(r>1?r-1:0),i=1;i<r;i++)c[i-1]=arguments[i];n.onChange.apply(n,[n.current].concat(c));var a={pagination:n};t.pagination&&\"object\"===Xu(t.pagination)&&\"current\"in t.pagination&&(a.pagination=as(as({},n),{current:o.state.pagination.current})),o.setState(a,o.scrollToFirstRow),o.props.store.setState({selectionDirty:!1});var l=o.props.onChange;l&&l.apply(null,o.prepareParamsArguments(as(as({},o.state),{selectionDirty:!1,pagination:n})))},o.handleShowSizeChange=function(e,t){var n=o.state.pagination;n.onShowSizeChange(e,t);var r=as(as({},n),{pageSize:t,current:e});o.setState({pagination:r},o.scrollToFirstRow);var c=o.props.onChange;c&&c.apply(null,o.prepareParamsArguments(as(as({},o.state),{pagination:r})))},o.renderExpandIcon=function(e){return function(t){var n=t.expandable,o=t.expanded,c=t.needIndentSpaced,i=t.record,a=t.onExpand;return n?r.createElement(hu.a,{componentName:\"Table\",defaultLocale:Qu.a.Table},(function(t){var n;return r.createElement($u,{className:f()(\"\".concat(e,\"-row-expand-icon\"),(n={},Zu(n,\"\".concat(e,\"-row-collapsed\"),!o),Zu(n,\"\".concat(e,\"-row-expanded\"),o),n)),onClick:function(e){a(i,e)},\"aria-label\":o?t.collapse:t.expand,noStyle:!0})})):c?r.createElement(\"span\",{className:\"\".concat(e,\"-row-expand-icon \").concat(e,\"-row-spaced\")}):null}},o.renderSelectionBox=function(e){return function(t,n,c){var i=o.getRecordKey(n,c),a=o.getCheckboxPropsByItem(n,c);return r.createElement(\"span\",{onClick:ss},r.createElement(fc,as({type:e,store:o.props.store,rowIndex:i,onChange:function(t){return\"radio\"===e?o.handleRadioSelect(n,c,t):o.handleSelect(n,c,t)},defaultSelection:o.getDefaultSelection()},a)))}},o.renderTable=function(e){var t,n=e.prefixCls,i=e.renderEmpty,l=e.dropdownPrefixCls,u=e.contextLocale,s=e.getPopupContainer,p=o.props,h=p.showHeader,d=p.locale,v=p.getPopupContainer,m=ls(p,[\"showHeader\",\"locale\",\"getPopupContainer\"]),y=Object(c.a)(m,[\"style\"]),b=o.getCurrentPageData(),g=o.props.expandedRowRender&&!1!==o.props.expandIconAsCell,w=v||s,z=as(as({},u),d);d&&d.emptyText||(z.emptyText=i(\"Table\"));var O=f()(\"\".concat(n,\"-\").concat(o.props.size),(Zu(t={},\"\".concat(n,\"-bordered\"),o.props.bordered),Zu(t,\"\".concat(n,\"-empty\"),!b.length),Zu(t,\"\".concat(n,\"-without-column-header\"),!h),t)),C=o.renderRowSelection({prefixCls:n,locale:z,getPopupContainer:w}),M=o.renderColumnsDropdown({columns:C,prefixCls:n,dropdownPrefixCls:l,locale:z,getPopupContainer:w}).map((function(e,t){var n=as({},e);return n.key=ps(n,t),n})),S=M[0]&&\"selection-column\"===M[0].key?1:0;return\"expandIconColumnIndex\"in y&&(S=y.expandIconColumnIndex),r.createElement(a.a,as({ref:o.setTableRef,key:\"table\",expandIcon:o.renderExpandIcon(n)},y,{onRow:function(e,t){return o.onRow(n,e,t)},components:o.state.components,prefixCls:n,data:b,columns:M,showHeader:h,className:O,expandIconColumnIndex:S,expandIconAsCell:g,emptyText:z.emptyText}))},o.renderComponent=function(e){var t=e.getPrefixCls,n=e.renderEmpty,c=e.getPopupContainer,i=o.props,a=i.prefixCls,l=i.dropdownPrefixCls,u=i.style,s=i.className,p=o.getCurrentPageData(),h=o.props.loading;\"boolean\"===typeof h&&(h={spinning:h});var d=t(\"table\",a),v=t(\"dropdown\",l),m=r.createElement(hu.a,{componentName:\"Table\",defaultLocale:Qu.a.Table},(function(e){return o.renderTable({prefixCls:d,renderEmpty:n,dropdownPrefixCls:v,contextLocale:e,getPopupContainer:c})})),y=o.hasPagination()&&p&&0!==p.length?\"\".concat(d,\"-with-pagination\"):\"\".concat(d,\"-without-pagination\");return r.createElement(\"div\",{className:f()(\"\".concat(d,\"-wrapper\"),s),style:u},r.createElement(Ru,as({},h,{className:h.spinning?\"\".concat(y,\" \").concat(d,\"-spin-holder\"):\"\"}),o.renderPagination(d,\"top\"),m,o.renderPagination(d,\"bottom\")))};var i=e.expandedRowRender,l=e.columns;Object(bn.a)(!(\"columnsPageRange\"in e||\"columnsPageSize\"in e),\"Table\",\"`columnsPageRange` and `columnsPageSize` are removed, please use fixed columns instead, see: https://u.ant.design/fixed-columns.\"),i&&(l||[]).some((function(e){return!!e.fixed}))&&Object(bn.a)(!1,\"Table\",\"`expandedRowRender` and `Column.fixed` are not compatible. Please use one of them at one time.\");var u=l||Uo(e.children);return o.state=as(as({},o.getDefaultSortOrder(u||[])),{filters:o.getDefaultFilters(u),pagination:o.getDefaultPagination(e),pivot:void 0,prevProps:e,components:ms(e.components),columns:u}),o}return ts(n,[{key:\"componentDidUpdate\",value:function(){var e=this.state,t=e.columns,n=e.sortColumn,r=e.sortOrder;if(this.getSortOrderColumns(t).length>0){var o=this.getSortStateFromColumns(t);hs(o.sortColumn,n)&&o.sortOrder===r||this.setState(o)}}},{key:\"getDefaultSelection\",value:function(){var e=this;return fs(this.props).getCheckboxProps?this.getFlatData().filter((function(t,n){return e.getCheckboxPropsByItem(t,n).defaultChecked})).map((function(t,n){return e.getRecordKey(t,n)})):[]}},{key:\"getDefaultPagination\",value:function(e){var t,n,r=\"object\"===Xu(e.pagination)?e.pagination:{};return\"current\"in r?t=r.current:\"defaultCurrent\"in r&&(t=r.defaultCurrent),\"pageSize\"in r?n=r.pageSize:\"defaultPageSize\"in r&&(n=r.defaultPageSize),this.hasPagination(e)?as(as(as({},ds),r),{current:t||1,pageSize:n||10}):{}}},{key:\"getSortOrderColumns\",value:function(e){return Wo(e||(this.state||{}).columns||[],(function(e){return\"sortOrder\"in e}))}},{key:\"getDefaultFilters\",value:function(e){var t=bs(this.state,e);return as(as({},Wo(e||[],(function(e){return\"undefined\"!==typeof e.defaultFilteredValue})).reduce((function(e,t){return e[ps(t)]=t.defaultFilteredValue,e}),{})),t)}},{key:\"getDefaultSortOrder\",value:function(e){var t=this.getSortStateFromColumns(e),n=Wo(e||[],(function(e){return null!=e.defaultSortOrder}))[0];return n&&!t.sortColumn?{sortColumn:n,sortOrder:n.defaultSortOrder}:t}},{key:\"getSortStateFromColumns\",value:function(e){var t=this.getSortOrderColumns(e).filter((function(e){return e.sortOrder}))[0];return t?{sortColumn:t,sortOrder:t.sortOrder}:{sortColumn:null,sortOrder:null}}},{key:\"getMaxCurrent\",value:function(e){var t=this.state.pagination,n=t.current,r=t.pageSize;return(n-1)*r>=e?Math.floor((e-1)/r)+1:n}},{key:\"getSorterFn\",value:function(e){var t=e||this.state,n=t.sortOrder,r=t.sortColumn;if(n&&r&&\"function\"===typeof r.sorter)return function(e,t){var o=r.sorter(e,t,n);return 0!==o?\"descend\"===n?-o:o:0}}},{key:\"getCurrentPageData\",value:function(){var e,t,n=this.getLocalData(),r=this.state;return this.hasPagination()?(t=r.pagination.pageSize,e=this.getMaxCurrent(r.pagination.total||n.length)):(t=Number.MAX_VALUE,e=1),(n.length>t||t===Number.MAX_VALUE)&&(n=n.slice((e-1)*t,e*t)),n}},{key:\"getFlatData\",value:function(){var e=this.props.childrenColumnName;return Io(this.getLocalData(null,!1),e)}},{key:\"getFlatCurrentPageData\",value:function(){var e=this.props.childrenColumnName;return Io(this.getCurrentPageData(),e)}},{key:\"getLocalData\",value:function(e){var t=this,n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=e||this.state,o=this.props.dataSource,c=o||[];c=c.slice(0);var i=this.getSorterFn(r);return i&&(c=this.recursiveSort(c,i)),n&&r.filters&&Object.keys(r.filters).forEach((function(e){var n=t.findColumn(e);if(n){var o=r.filters[e]||[];if(0!==o.length){var i=n.onFilter;c=i?c.filter((function(e){return o.some((function(t){return i(t,e)}))})):c}}})),c}},{key:\"setSelectedRowKeys\",value:function(e,t){var n=this,r=t.selectWay,o=t.record,c=t.checked,i=t.changeRowKeys,a=t.nativeEvent,l=fs(this.props);l&&!(\"selectedRowKeys\"in l)&&this.props.store.setState({selectedRowKeys:e});var u=this.getFlatData();if(l.onChange||l[r]){var s=u.filter((function(t,r){return e.indexOf(n.getRecordKey(t,r))>=0}));if(l.onChange&&l.onChange(e,s),\"onSelect\"===r&&l.onSelect)l.onSelect(o,c,s,a);else if(\"onSelectMultiple\"===r&&l.onSelectMultiple){var f=u.filter((function(e,t){return i.indexOf(n.getRecordKey(e,t))>=0}));l.onSelectMultiple(c,s,f)}else if(\"onSelectAll\"===r&&l.onSelectAll){var p=u.filter((function(e,t){return i.indexOf(n.getRecordKey(e,t))>=0}));l.onSelectAll(c,s,p)}else\"onSelectInvert\"===r&&l.onSelectInvert&&l.onSelectInvert(e)}}},{key:\"toggleSortOrder\",value:function(e){var t,n=e.sortDirections||this.props.sortDirections,r=this.state,o=r.sortOrder;if(hs(r.sortColumn,e)&&void 0!==o){var c=n.indexOf(o)+1;t=c===n.length?void 0:n[c]}else t=n[0];var i={sortOrder:t,sortColumn:t?e:null};0===this.getSortOrderColumns().length&&this.setState(i,this.scrollToFirstRow);var a=this.props.onChange;a&&a.apply(null,this.prepareParamsArguments(as(as({},this.state),i),e))}},{key:\"hasPagination\",value:function(e){return!1!==(e||this.props).pagination}},{key:\"isSortColumn\",value:function(e){var t=this.state.sortColumn;return!(!e||!t)&&ps(t)===ps(e)}},{key:\"prepareParamsArguments\",value:function(e,t){var n=as({},e.pagination);delete n.onChange,delete n.onShowSizeChange;var r=e.filters,o={},c=t;return e.sortColumn&&e.sortOrder&&(c=e.sortColumn,o.column=e.sortColumn,o.order=e.sortOrder),c&&(o.field=c.dataIndex,o.columnKey=ps(c)),[n,r,o,{currentDataSource:this.getLocalData(e)}]}},{key:\"findColumn\",value:function(e){var t;return Fo(this.state.columns,(function(n){ps(n)===e&&(t=n)})),t}},{key:\"recursiveSort\",value:function(e,t){var n=this,r=this.props.childrenColumnName,o=void 0===r?\"children\":r;return e.sort(t).map((function(e){return e[o]?as(as({},e),Zu({},o,n.recursiveSort(e[o],t))):e}))}},{key:\"renderPagination\",value:function(e,t){if(!this.hasPagination())return null;var n=\"default\",o=this.state.pagination;o.size?n=o.size:\"middle\"!==this.props.size&&\"small\"!==this.props.size||(n=\"small\");var c=o.position||\"bottom\",i=o.total||this.getLocalData().length;return i>0&&(c===t||\"both\"===c)?r.createElement(Cu,as({key:\"pagination-\".concat(t)},o,{className:f()(o.className,\"\".concat(e,\"-pagination\")),onChange:this.handlePageChange,total:i,size:n,current:this.getMaxCurrent(i),onShowSizeChange:this.handleShowSizeChange})):null}},{key:\"renderRowSelection\",value:function(e){var t=this,n=e.prefixCls,o=e.locale,c=e.getPopupContainer,a=this.props.rowSelection,l=this.state.columns.concat();if(a){var u=this.getFlatCurrentPageData().filter((function(e,n){return!a.getCheckboxProps||!t.getCheckboxPropsByItem(e,n).disabled})),s=f()(\"\".concat(n,\"-selection-column\"),Zu({},\"\".concat(n,\"-selection-column-custom\"),a.selections)),p=Zu({key:\"selection-column\",render:this.renderSelectionBox(a.type),className:s,fixed:a.fixed,width:a.columnWidth,title:a.columnTitle},i.INTERNAL_COL_DEFINE,{className:\"\".concat(n,\"-selection-col\")});if(\"radio\"!==a.type){var h=u.every((function(e,n){return t.getCheckboxPropsByItem(e,n).disabled}));p.title=p.title||r.createElement(Yi,{store:this.props.store,locale:o,data:u,getCheckboxPropsByItem:this.getCheckboxPropsByItem,getRecordKey:this.getRecordKey,disabled:h,prefixCls:n,onSelect:this.handleSelectRow,selections:a.selections,hideDefaultSelections:a.hideDefaultSelections,getPopupContainer:this.generatePopupContainerFunc(c)})}\"fixed\"in a?p.fixed=a.fixed:l.some((function(e){return\"left\"===e.fixed||!0===e.fixed}))&&(p.fixed=\"left\"),l[0]&&\"selection-column\"===l[0].key?l[0]=p:l.unshift(p)}return l}},{key:\"renderColumnsDropdown\",value:function(e){var t=this,n=e.prefixCls,o=e.dropdownPrefixCls,c=e.columns,i=e.locale,a=e.getPopupContainer,l=this.state,u=l.sortOrder,s=l.filters;return Fo(c,(function(e,c){var l,p,h,d=ps(e,c),v=e.onHeaderCell,m=t.isSortColumn(e);if(e.filters&&e.filters.length>0||e.filterDropdown){var y=d in s?s[d]:[];p=r.createElement(tc,{locale:i,column:e,selectedKeys:y,confirmFilter:t.handleFilter,prefixCls:\"\".concat(n,\"-filter\"),dropdownPrefixCls:o||\"ant-dropdown\",getPopupContainer:t.generatePopupContainerFunc(a),key:\"filter-dropdown\"})}if(e.sorter){var b=e.sortDirections||t.props.sortDirections,g=m&&\"ascend\"===u,w=m&&\"descend\"===u,z=-1!==b.indexOf(\"ascend\")&&r.createElement(gn.a,{className:\"\".concat(n,\"-column-sorter-up \").concat(g?\"on\":\"off\"),type:\"caret-up\",theme:\"filled\"}),O=-1!==b.indexOf(\"descend\")&&r.createElement(gn.a,{className:\"\".concat(n,\"-column-sorter-down \").concat(w?\"on\":\"off\"),type:\"caret-down\",theme:\"filled\"});h=r.createElement(\"div\",{title:i.sortTitle,className:f()(\"\".concat(n,\"-column-sorter-inner\"),z&&O&&\"\".concat(n,\"-column-sorter-inner-full\")),key:\"sorter\"},z,O),v=function(n){var r={};e.onHeaderCell&&(r=as({},e.onHeaderCell(n)));var o=r.onClick;return r.onClick=function(){t.toggleSortOrder(e),o&&o.apply(void 0,arguments)},r}}return as(as({},e),{className:f()(e.className,(l={},Zu(l,\"\".concat(n,\"-column-has-actions\"),h||p),Zu(l,\"\".concat(n,\"-column-has-filters\"),p),Zu(l,\"\".concat(n,\"-column-has-sorters\"),h),Zu(l,\"\".concat(n,\"-column-sort\"),m&&u),l)),title:[r.createElement(\"span\",{key:\"title\",className:\"\".concat(n,\"-header-column\")},r.createElement(\"div\",{className:h?\"\".concat(n,\"-column-sorters\"):void 0},r.createElement(\"span\",{className:\"\".concat(n,\"-column-title\")},t.renderColumnTitle(e.title)),r.createElement(\"span\",{className:\"\".concat(n,\"-column-sorter\")},h))),p],onHeaderCell:v})}))}},{key:\"renderColumnTitle\",value:function(e){var t=this.state,n=t.filters,r=t.sortOrder,o=t.sortColumn;return e instanceof Function?e({filters:n,sortOrder:r,sortColumn:o}):e}},{key:\"render\",value:function(){return r.createElement(yn.a,null,this.renderComponent)}}],[{key:\"getDerivedStateFromProps\",value:function(e,t){var n,r,o=t.prevProps,c=e.columns||Uo(e.children),i=as(as({},t),{prevProps:e,columns:c});if(\"pagination\"in e||\"pagination\"in o){var a=as(as(as({},ds),t.pagination),e.pagination);a.current=a.current||1,a.pageSize=a.pageSize||10,i=as(as({},i),{pagination:!1!==e.pagination?a:vs})}if(e.rowSelection&&\"selectedRowKeys\"in e.rowSelection?e.store.setState({selectedRowKeys:e.rowSelection.selectedRowKeys||[]}):o.rowSelection&&!e.rowSelection&&e.store.setState({selectedRowKeys:[]}),\"dataSource\"in e&&e.dataSource!==o.dataSource&&e.store.setState({selectionDirty:!1}),e.setCheckboxPropsCache({}),ys(i,i.columns).length>0){var l=bs(i,i.columns),u=as({},i.filters);Object.keys(l).forEach((function(e){u[e]=l[e]})),n=i,r=u,(Object.keys(r).length!==Object.keys(n.filters).length||Object.keys(r).some((function(e){return r[e]!==n.filters[e]})))&&(i=as(as({},i),{filters:u}))}if(!function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e===t||[\"table\",\"header\",\"body\"].every((function(n){return h()(e[n],t[n])}))}(e.components,o.components)){var s=ms(e.components);i=as(as({},i),{components:s})}return i}}]),n}(r.Component);gs.propTypes={dataSource:l.array,columns:l.array,prefixCls:l.string,useFixedHeader:l.bool,rowSelection:l.object,className:l.string,size:l.string,loading:l.oneOfType([l.bool,l.object]),bordered:l.bool,onChange:l.func,locale:l.object,dropdownPrefixCls:l.string,sortDirections:l.array,getPopupContainer:l.func},gs.defaultProps={dataSource:[],useFixedHeader:!1,className:\"\",size:\"default\",loading:!1,bordered:!1,indentSize:20,locale:{},rowKey:\"key\",showHeader:!0,sortDirections:[\"ascend\",\"descend\"],childrenColumnName:\"children\"},Object(d.polyfill)(gs);var ws=function(e){ns(n,e);var t=os(n);function n(e){var r;return Ju(this,n),(r=t.call(this,e)).setCheckboxPropsCache=function(e){return r.CheckboxPropsCache=e},r.CheckboxPropsCache={},r.store=function(e){var t=e,n=[];return{setState:function(e){t=nc(nc({},t),e);for(var r=0;r<n.length;r++)n[r]()},getState:function(){return t},subscribe:function(e){return n.push(e),function(){var t=n.indexOf(e);n.splice(t,1)}}}}({selectedRowKeys:fs(e).selectedRowKeys||[],selectionDirty:!1}),r}return ts(n,[{key:\"render\",value:function(){return r.createElement(gs,as({},this.props,{store:this.store,checkboxPropsCache:this.CheckboxPropsCache,setCheckboxPropsCache:this.setCheckboxPropsCache}))}}]),n}(r.Component);ws.displayName=\"withStore(Table)\",ws.Column=Ji,ws.ColumnGroup=ia;var zs=ws;t.a=zs},function(e,t,n){var r=n(62),o=n(124),c=n(101),i=Object.defineProperty;t.f=n(38)?Object.defineProperty:function(e,t,n){if(r(e),t=c(t,!0),r(n),o)try{return i(e,t,n)}catch(a){}if(\"get\"in n||\"set\"in n)throw TypeError(\"Accessors not supported!\");return\"value\"in n&&(e[t]=n.value),e}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(139),o=\"object\"==typeof self&&self&&self.Object===Object&&self,c=r||o||Function(\"return this\")();e.exports=c},function(e,t,n){\"use strict\";n.d(t,\"b\",(function(){return l}));var r=n(85),o=n.n(r),c=n(32),i=n(55),a=n.n(i),l=0!==c.a.endEvents.length,u=[\"Webkit\",\"Moz\",\"O\",\"ms\"],s=[\"-webkit-\",\"-moz-\",\"-o-\",\"ms-\",\"\"];function f(e,t){for(var n=window.getComputedStyle(e,null),r=\"\",o=0;o<s.length&&!(r=n.getPropertyValue(s[o]+t));o++);return r}function p(e){if(l){var t=parseFloat(f(e,\"transition-delay\"))||0,n=parseFloat(f(e,\"transition-duration\"))||0,r=parseFloat(f(e,\"animation-delay\"))||0,o=parseFloat(f(e,\"animation-duration\"))||0,c=Math.max(n+t,o+r);e.rcEndAnimTimeout=setTimeout((function(){e.rcEndAnimTimeout=null,e.rcEndListener&&e.rcEndListener()}),1e3*c+200)}}function h(e){e.rcEndAnimTimeout&&(clearTimeout(e.rcEndAnimTimeout),e.rcEndAnimTimeout=null)}var d=function(e,t,n){var r=\"object\"===(\"undefined\"===typeof t?\"undefined\":o()(t)),i=r?t.name:t,l=r?t.active:t+\"-active\",u=n,s=void 0,f=void 0,d=a()(e);return n&&\"[object Object]\"===Object.prototype.toString.call(n)&&(u=n.end,s=n.start,f=n.active),e.rcEndListener&&e.rcEndListener(),e.rcEndListener=function(t){t&&t.target!==e||(e.rcAnimTimeout&&(clearTimeout(e.rcAnimTimeout),e.rcAnimTimeout=null),h(e),d.remove(i),d.remove(l),c.a.removeEndEventListener(e,e.rcEndListener),e.rcEndListener=null,u&&u())},c.a.addEndEventListener(e,e.rcEndListener),s&&s(),d.add(i),e.rcAnimTimeout=setTimeout((function(){e.rcAnimTimeout=null,d.add(l),f&&setTimeout(f,0),p(e)}),30),{stop:function(){e.rcEndListener&&e.rcEndListener()}}};d.style=function(e,t,n){e.rcEndListener&&e.rcEndListener(),e.rcEndListener=function(t){t&&t.target!==e||(e.rcAnimTimeout&&(clearTimeout(e.rcAnimTimeout),e.rcAnimTimeout=null),h(e),c.a.removeEndEventListener(e,e.rcEndListener),e.rcEndListener=null,n&&n())},c.a.addEndEventListener(e,e.rcEndListener),e.rcAnimTimeout=setTimeout((function(){for(var n in t)t.hasOwnProperty(n)&&(e.style[n]=t[n]);e.rcAnimTimeout=null,p(e)}),0)},d.setTransition=function(e,t,n){var r=t,o=n;void 0===n&&(o=r,r=\"\"),r=r||\"\",u.forEach((function(t){e.style[t+\"Transition\"+r]=o}))},d.isCssAnimationSupported=l,t.a=d},function(e,t,n){\"use strict\";function r(e,t){return Object.prototype.hasOwnProperty.call(t,e)}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";var r=n(58),o=n(21),c=n(23),i=n(31);function a(e,t,n){return function(){for(var o=[],c=0,l=e,u=0;u<t.length||c<arguments.length;){var s;u<t.length&&(!Object(i.a)(t[u])||c>=arguments.length)?s=t[u]:(s=arguments[c],c+=1),o[u]=s,Object(i.a)(s)||(l-=1),u+=1}return l<=0?n.apply(this,o):Object(r.a)(l,a(e,o,n))}}var l=Object(c.a)((function(e,t){return 1===e?Object(o.a)(t):Object(r.a)(e,a(e,[],t))}));t.a=l},function(e,t,n){var r=n(36),o=n(37),c=n(123),i=n(49),a=n(43),l=function e(t,n,l){var u,s,f,p=t&e.F,h=t&e.G,d=t&e.S,v=t&e.P,m=t&e.B,y=t&e.W,b=h?o:o[n]||(o[n]={}),g=b.prototype,w=h?r:d?r[n]:(r[n]||{}).prototype;for(u in h&&(l=n),l)(s=!p&&w&&void 0!==w[u])&&a(b,u)||(f=s?w[u]:l[u],b[u]=h&&\"function\"!=typeof w[u]?l[u]:m&&s?c(f,r):y&&w[u]==f?function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t.prototype=e.prototype,t}(f):v&&\"function\"==typeof f?c(Function.call,f):f,v&&((b.virtual||(b.virtual={}))[u]=f,t&e.R&&g&&!g[u]&&i(g,u,f)))};l.F=1,l.G=2,l.S=4,l.P=8,l.B=16,l.W=32,l.U=64,l.R=128,e.exports=l},function(e,t,n){var r=n(42),o=n(73);e.exports=n(38)?function(e,t,n){return r.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t){e.exports=function(e){return\"object\"===typeof e?null!==e:\"function\"===typeof e}},function(e,t,n){var r=n(127),o=n(102);e.exports=function(e){return r(o(e))}},function(e,t,n){var r=n(105)(\"wks\"),o=n(76),c=n(36).Symbol,i=\"function\"==typeof c;(e.exports=function(e){return r[e]||(r[e]=i&&c[e]||(i?c:o)(\"Symbol.\"+e))}).store=r},function(e,t){e.exports=function(e){return null!=e&&\"object\"==typeof e}},function(e,t,n){\"use strict\";var r,o;Object.defineProperty(t,\"__esModule\",{value:!0});var c={position:\"absolute\",top:\"-9999px\",width:\"50px\",height:\"50px\"};t.INTERNAL_COL_DEFINE=\"RC_TABLE_INTERNAL_COL_DEFINE\",t.measureScrollbar=function(e){var t=e.direction,n=void 0===t?\"vertical\":t,i=e.prefixCls;if(\"undefined\"===typeof document||\"undefined\"===typeof window)return 0;var a=\"vertical\"===n;if(a&&r)return r;if(!a&&o)return o;var l=document.createElement(\"div\");Object.keys(c).forEach((function(e){l.style[e]=c[e]})),l.className=\"\".concat(i,\"-hide-scrollbar scroll-div-append-to-body\"),a?l.style.overflowY=\"scroll\":l.style.overflowX=\"scroll\",document.body.appendChild(l);var u=0;return a?(u=l.offsetWidth-l.clientWidth,r=u):(u=l.offsetHeight-l.clientHeight,o=u),document.body.removeChild(l),u},t.debounce=function(e,t,n){var r;function o(){for(var o=arguments.length,c=new Array(o),i=0;i<o;i++)c[i]=arguments[i];var a=this;c[0]&&c[0].persist&&c[0].persist();var l=function(){r=null,n||e.apply(a,c)},u=n&&!r;clearTimeout(r),r=setTimeout(l,t),u&&e.apply(a,c)}return o.cancel=function(){r&&(clearTimeout(r),r=null)},o},t.remove=function(e,t){var n=e.indexOf(t),r=e.slice(0,n),o=e.slice(n+1,e.length);return r.concat(o)},t.getDataAndAriaProps=function(e){return Object.keys(e).reduce((function(t,n){return\"data-\"!==n.substr(0,5)&&\"aria-\"!==n.substr(0,5)||(t[n]=e[n]),t}),{})}},function(e,t,n){try{var r=n(134)}catch(a){r=n(134)}var o=/\\s+/,c=Object.prototype.toString;function i(e){if(!e||!e.nodeType)throw new Error(\"A DOM element reference is required\");this.el=e,this.list=e.classList}e.exports=function(e){return new i(e)},i.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array();return~r(t,e)||t.push(e),this.el.className=t.join(\" \"),this},i.prototype.remove=function(e){if(\"[object RegExp]\"==c.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),n=r(t,e);return~n&&t.splice(n,1),this.el.className=t.join(\" \"),this},i.prototype.removeMatching=function(e){for(var t=this.array(),n=0;n<t.length;n++)e.test(t[n])&&this.remove(t[n]);return this},i.prototype.toggle=function(e,t){return this.list?(\"undefined\"!==typeof t?t!==this.list.toggle(e,t)&&this.list.toggle(e):this.list.toggle(e),this):(\"undefined\"!==typeof t?t?this.add(e):this.remove(e):this.has(e)?this.remove(e):this.add(e),this)},i.prototype.array=function(){var e=(this.el.getAttribute(\"class\")||\"\").replace(/^\\s+|\\s+$/g,\"\").split(o);return\"\"===e[0]&&e.shift(),e},i.prototype.has=i.prototype.contains=function(e){return this.list?this.list.contains(e):!!~r(this.array(),e)}},function(e,t,n){\"use strict\";var r=function(){};e.exports=r},function(e,t,n){\"use strict\";t.a=Array.isArray||function(e){return null!=e&&e.length>=0&&\"[object Array]\"===Object.prototype.toString.call(e)}},function(e,t,n){\"use strict\";function r(e,t){switch(e){case 0:return function(){return t.apply(this,arguments)};case 1:return function(e){return t.apply(this,arguments)};case 2:return function(e,n){return t.apply(this,arguments)};case 3:return function(e,n,r){return t.apply(this,arguments)};case 4:return function(e,n,r,o){return t.apply(this,arguments)};case 5:return function(e,n,r,o,c){return t.apply(this,arguments)};case 6:return function(e,n,r,o,c,i){return t.apply(this,arguments)};case 7:return function(e,n,r,o,c,i,a){return t.apply(this,arguments)};case 8:return function(e,n,r,o,c,i,a,l){return t.apply(this,arguments)};case 9:return function(e,n,r,o,c,i,a,l,u){return t.apply(this,arguments)};case 10:return function(e,n,r,o,c,i,a,l,u,s){return t.apply(this,arguments)};default:throw new Error(\"First argument to _arity must be a non-negative integer no greater than ten\")}}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";n.d(t,\"b\",(function(){return g})),n.d(t,\"a\",(function(){return w})),n.d(t,\"c\",(function(){return z}));var r=n(0),o=n(29),c=n.n(o),i=n(3),a=n.n(i),l=n(30),u=function(){return r.createElement(\"svg\",{width:\"184\",height:\"152\",viewBox:\"0 0 184 152\",xmlns:\"http://www.w3.org/2000/svg\"},r.createElement(\"g\",{fill:\"none\",fillRule:\"evenodd\"},r.createElement(\"g\",{transform:\"translate(24 31.67)\"},r.createElement(\"ellipse\",{fillOpacity:\".8\",fill:\"#F5F5F7\",cx:\"67.797\",cy:\"106.89\",rx:\"67.797\",ry:\"12.668\"}),r.createElement(\"path\",{d:\"M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z\",fill:\"#AEB8C2\"}),r.createElement(\"path\",{d:\"M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z\",fill:\"url(#linearGradient-1)\",transform:\"translate(13.56)\"}),r.createElement(\"path\",{d:\"M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z\",fill:\"#F5F5F7\"}),r.createElement(\"path\",{d:\"M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z\",fill:\"#DCE0E6\"})),r.createElement(\"path\",{d:\"M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z\",fill:\"#DCE0E6\"}),r.createElement(\"g\",{transform:\"translate(149.65 15.383)\",fill:\"#FFF\"},r.createElement(\"ellipse\",{cx:\"20.654\",cy:\"3.167\",rx:\"2.849\",ry:\"2.815\"}),r.createElement(\"path\",{d:\"M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z\"}))))},s=function(){return r.createElement(\"svg\",{width:\"64\",height:\"41\",viewBox:\"0 0 64 41\",xmlns:\"http://www.w3.org/2000/svg\"},r.createElement(\"g\",{transform:\"translate(0 1)\",fill:\"none\",fillRule:\"evenodd\"},r.createElement(\"ellipse\",{fill:\"#F5F5F5\",cx:\"32\",cy:\"33\",rx:\"32\",ry:\"7\"}),r.createElement(\"g\",{fillRule:\"nonzero\",stroke:\"#D9D9D9\"},r.createElement(\"path\",{d:\"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z\"}),r.createElement(\"path\",{d:\"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z\",fill:\"#FAFAFA\"}))))};function f(){return(f=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var p=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},h=r.createElement(u,null),d=r.createElement(s,null),v=function(e){return r.createElement(w,null,(function(t){var n=t.getPrefixCls,o=e.className,c=e.prefixCls,i=e.image,u=void 0===i?h:i,s=e.description,v=e.children,m=e.imageStyle,y=p(e,[\"className\",\"prefixCls\",\"image\",\"description\",\"children\",\"imageStyle\"]);return r.createElement(l.a,{componentName:\"Empty\"},(function(e){var t,i,l,p=n(\"empty\",c),h=\"undefined\"!==typeof s?s:e.description,b=\"string\"===typeof h?h:\"empty\",g=null;return g=\"string\"===typeof u?r.createElement(\"img\",{alt:b,src:u}):u,r.createElement(\"div\",f({className:a()(p,(t={},i=\"\".concat(p,\"-normal\"),l=u===d,i in t?Object.defineProperty(t,i,{value:l,enumerable:!0,configurable:!0,writable:!0}):t[i]=l,t),o)},y),r.createElement(\"div\",{className:\"\".concat(p,\"-image\"),style:m},g),h&&r.createElement(\"p\",{className:\"\".concat(p,\"-description\")},h),v&&r.createElement(\"div\",{className:\"\".concat(p,\"-footer\")},v))}))}))};v.PRESENTED_IMAGE_DEFAULT=h,v.PRESENTED_IMAGE_SIMPLE=d;var m=v,y=function(e){return r.createElement(w,null,(function(t){var n=(0,t.getPrefixCls)(\"empty\");switch(e){case\"Table\":case\"List\":return r.createElement(m,{image:m.PRESENTED_IMAGE_SIMPLE});case\"Select\":case\"TreeSelect\":case\"Cascader\":case\"Transfer\":case\"Mentions\":return r.createElement(m,{image:m.PRESENTED_IMAGE_SIMPLE,className:\"\".concat(n,\"-small\")});default:return r.createElement(m,null)}}))};function b(){return(b=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var g=c()({getPrefixCls:function(e,t){return t||\"ant-\".concat(e)},renderEmpty:y}),w=g.Consumer;function z(e){return function(t){var n=function(n){return r.createElement(w,null,(function(o){var c=e.prefixCls,i=(0,o.getPrefixCls)(c,n.prefixCls);return r.createElement(t,b({},o,n,{prefixCls:i}))}))},o=t.constructor,c=o&&o.displayName||t.name||\"Component\";return n.displayName=\"withConfigConsumer(\".concat(c,\")\"),n}}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return d}));var r=n(21),o=n(57),c=n(92),i=Object(r.a)((function(e){return!!Object(o.a)(e)||!!e&&(\"object\"===typeof e&&(!Object(c.a)(e)&&(1===e.nodeType?!!e.length:0===e.length||e.length>0&&(e.hasOwnProperty(0)&&e.hasOwnProperty(e.length-1)))))})),a=function(){function e(e){this.f=e}return e.prototype[\"@@transducer/init\"]=function(){throw new Error(\"init not implemented on XWrap\")},e.prototype[\"@@transducer/result\"]=function(e){return e},e.prototype[\"@@transducer/step\"]=function(e,t){return this.f(e,t)},e}();var l=n(58),u=n(23),s=Object(u.a)((function(e,t){return Object(l.a)(e.length,(function(){return e.apply(t,arguments)}))}));function f(e,t,n){for(var r=n.next();!r.done;){if((t=e[\"@@transducer/step\"](t,r.value))&&t[\"@@transducer/reduced\"]){t=t[\"@@transducer/value\"];break}r=n.next()}return e[\"@@transducer/result\"](t)}function p(e,t,n,r){return e[\"@@transducer/result\"](n[r](s(e[\"@@transducer/step\"],e),t))}var h=\"undefined\"!==typeof Symbol?Symbol.iterator:\"@@iterator\";function d(e,t,n){if(\"function\"===typeof e&&(e=function(e){return new a(e)}(e)),i(n))return function(e,t,n){for(var r=0,o=n.length;r<o;){if((t=e[\"@@transducer/step\"](t,n[r]))&&t[\"@@transducer/reduced\"]){t=t[\"@@transducer/value\"];break}r+=1}return e[\"@@transducer/result\"](t)}(e,t,n);if(\"function\"===typeof n[\"fantasy-land/reduce\"])return p(e,t,n,\"fantasy-land/reduce\");if(null!=n[h])return f(e,t,n[h]());if(\"function\"===typeof n.next)return f(e,t,n);if(\"function\"===typeof n.reduce)return p(e,t,n,\"reduce\");throw new TypeError(\"reduce: list must be array or iterable\")}},function(e,t,n){\"use strict\";var r=n(0),o=n.n(r),c=n(26),i=n.n(c),a=n(7),l=n.n(a),u=n(5),s=n.n(u),f=n(10),p=n.n(f),h=n(14),d=n.n(h),v=n(6),m=n.n(v),y=n(11),b=n.n(y),g=n(1),w=n.n(g),z=n(9),O=n.n(z),C=n(33),M=n(90),S=n(8),_=n.n(S),x=function(e){function t(){var e,n,r,o;p()(this,t);for(var c=arguments.length,i=Array(c),a=0;a<c;a++)i[a]=arguments[a];return n=r=m()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.close=function(e){e&&e.stopPropagation(),r.clearCloseTimer(),r.props.onClose()},r.startCloseTimer=function(){r.props.duration&&(r.closeTimer=setTimeout((function(){r.close()}),1e3*r.props.duration))},r.clearCloseTimer=function(){r.closeTimer&&(clearTimeout(r.closeTimer),r.closeTimer=null)},o=n,m()(r,o)}return b()(t,e),d()(t,[{key:\"componentDidMount\",value:function(){this.startCloseTimer()}},{key:\"componentDidUpdate\",value:function(e){(this.props.duration!==e.duration||this.props.update)&&this.restartCloseTimer()}},{key:\"componentWillUnmount\",value:function(){this.clearCloseTimer()}},{key:\"restartCloseTimer\",value:function(){this.clearCloseTimer(),this.startCloseTimer()}},{key:\"render\",value:function(){var e,t=this.props,n=t.prefixCls+\"-notice\",r=(e={},l()(e,\"\"+n,1),l()(e,n+\"-closable\",t.closable),l()(e,t.className,!!t.className),e);return o.a.createElement(\"div\",{className:_()(r),style:t.style,onMouseEnter:this.clearCloseTimer,onMouseLeave:this.startCloseTimer,onClick:t.onClick},o.a.createElement(\"div\",{className:n+\"-content\"},t.children),t.closable?o.a.createElement(\"a\",{tabIndex:\"0\",onClick:this.close,className:n+\"-close\"},t.closeIcon||o.a.createElement(\"span\",{className:n+\"-close-x\"})):null)}}]),t}(r.Component);x.propTypes={duration:w.a.number,onClose:w.a.func,children:w.a.any,update:w.a.bool,closeIcon:w.a.node},x.defaultProps={onEnd:function(){},onClose:function(){},duration:1.5,style:{right:\"50%\"}};var k=x,H=0,E=Date.now();function P(){return\"rcNotification_\"+E+\"_\"+H++}var V=function(e){function t(){var e,n,r,o;p()(this,t);for(var c=arguments.length,i=Array(c),a=0;a<c;a++)i[a]=arguments[a];return n=r=m()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.state={notices:[]},r.add=function(e){var t=e.key=e.key||P(),n=r.props.maxCount;r.setState((function(r){var o=r.notices,c=o.map((function(e){return e.key})).indexOf(t),i=o.concat();return-1!==c?i.splice(c,1,e):(n&&o.length>=n&&(e.updateKey=i[0].updateKey||i[0].key,i.shift()),i.push(e)),{notices:i}}))},r.remove=function(e){r.setState((function(t){return{notices:t.notices.filter((function(t){return t.key!==e}))}}))},o=n,m()(r,o)}return b()(t,e),d()(t,[{key:\"getTransitionName\",value:function(){var e=this.props,t=e.transitionName;return!t&&e.animation&&(t=e.prefixCls+\"-\"+e.animation),t}},{key:\"render\",value:function(){var e,t=this,n=this.props,r=this.state.notices,c=r.map((function(e,c){var i=Boolean(c===r.length-1&&e.updateKey),a=e.updateKey?e.updateKey:e.key,l=Object(M.a)(t.remove.bind(t,e.key),e.onClose);return o.a.createElement(k,s()({prefixCls:n.prefixCls},e,{key:a,update:i,onClose:l,onClick:e.onClick,closeIcon:n.closeIcon}),e.content)})),i=(e={},l()(e,n.prefixCls,1),l()(e,n.className,!!n.className),e);return o.a.createElement(\"div\",{className:_()(i),style:n.style},o.a.createElement(C.a,{transitionName:this.getTransitionName()},c))}}]),t}(r.Component);V.propTypes={prefixCls:w.a.string,transitionName:w.a.string,animation:w.a.oneOfType([w.a.string,w.a.object]),style:w.a.object,maxCount:w.a.number,closeIcon:w.a.node},V.defaultProps={prefixCls:\"rc-notification\",animation:\"fade\",style:{top:65,left:\"50%\"}},V.newInstance=function(e,t){var n=e||{},r=n.getContainer,c=i()(n,[\"getContainer\"]),a=document.createElement(\"div\");r?r().appendChild(a):document.body.appendChild(a);var l=!1;O.a.render(o.a.createElement(V,s()({},c,{ref:function(e){l||(l=!0,t({notice:function(t){e.add(t)},removeNotice:function(t){e.remove(t)},component:e,destroy:function(){O.a.unmountComponentAtNode(a),a.parentNode.removeChild(a)}}))}})),a)};var T=V,L=n(13);function j(){return(j=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var N,D,R,A,I=3,F=1,W=\"ant-message\",U=\"move-up\";var K={open:function(e){var t=void 0!==e.duration?e.duration:I,n={info:\"info-circle\",success:\"check-circle\",error:\"close-circle\",warning:\"exclamation-circle\",loading:\"loading\"}[e.type],o=e.key||F++,c=new Promise((function(c){var i=function(){return\"function\"===typeof e.onClose&&e.onClose(),c(!0)};!function(e){D?e(D):T.newInstance({prefixCls:W,transitionName:U,style:{top:N},getContainer:R,maxCount:A},(function(t){D?e(D):(D=t,e(t))}))}((function(c){var a=r.createElement(L.a,{type:n,theme:\"loading\"===n?\"outlined\":\"filled\"}),l=n?a:\"\";c.notice({key:o,duration:t,style:{},content:r.createElement(\"div\",{className:\"\".concat(W,\"-custom-content\").concat(e.type?\" \".concat(W,\"-\").concat(e.type):\"\")},e.icon?e.icon:l,r.createElement(\"span\",null,e.content)),onClose:i})}))})),i=function(){D&&D.removeNotice(o)};return i.then=function(e,t){return c.then(e,t)},i.promise=c,i},config:function(e){void 0!==e.top&&(N=e.top,D=null),void 0!==e.duration&&(I=e.duration),void 0!==e.prefixCls&&(W=e.prefixCls),void 0!==e.getContainer&&(R=e.getContainer),void 0!==e.transitionName&&(U=e.transitionName,D=null),void 0!==e.maxCount&&(A=e.maxCount,D=null)},destroy:function(){D&&(D.destroy(),D=null)}};[\"success\",\"info\",\"warning\",\"error\",\"loading\"].forEach((function(e){K[e]=function(t,n,r){return function(e){return\"[object Object]\"===Object.prototype.toString.call(e)&&!!e.content}(t)?K.open(j(j({},t),{type:e})):(\"function\"===typeof n&&(r=n,n=void 0),K.open({content:t,duration:n,type:e,onClose:r}))}})),K.warn=K.warning;t.a=K},function(e,t,n){var r=n(50);e.exports=function(e){if(!r(e))throw TypeError(e+\" is not an object!\");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(t){return!0}}},function(e,t,n){var r=n(116),o=n(244),c=n(245),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?\"[object Undefined]\":\"[object Null]\":i&&i in Object(e)?o(e):c(e)}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){\"use strict\";var r={};function o(e,t){0}function c(e,t,n){t||r[n]||(e(!1,n),r[n]=!0)}t.a=function(e,t){c(o,e,t)}},function(e,t,n){\"use strict\";function r(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";e.exports=n(222)},function(e,t,n){\"use strict\";var r=n(21),o=n(46),c=n(91),i=!{toString:null}.propertyIsEnumerable(\"toString\"),a=[\"constructor\",\"valueOf\",\"isPrototypeOf\",\"toString\",\"propertyIsEnumerable\",\"hasOwnProperty\",\"toLocaleString\"],l=function(){return arguments.propertyIsEnumerable(\"length\")}(),u=function(e,t){for(var n=0;n<e.length;){if(e[n]===t)return!0;n+=1}return!1},s=\"function\"!==typeof Object.keys||l?Object(r.a)((function(e){if(Object(e)!==e)return[];var t,n,r=[],s=l&&Object(c.a)(e);for(t in e)!Object(o.a)(t,e)||s&&\"length\"===t||(r[r.length]=t);if(i)for(n=a.length-1;n>=0;)t=a[n],Object(o.a)(t,e)&&!u(r,t)&&(r[r.length]=t),n-=1;return r})):Object(r.a)((function(e){return Object(e)!==e?[]:Object.keys(e)}));t.a=s},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return o}));var r=n(86);function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e)){var n=[],r=!0,o=!1,c=void 0;try{for(var i,a=e[Symbol.iterator]();!(r=(i=a.next()).done)&&(n.push(i.value),!t||n.length!==t);r=!0);}catch(l){o=!0,c=l}finally{try{r||null==a.return||a.return()}finally{if(o)throw c}}return n}}(e,t)||Object(r.a)(e,t)||function(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}},function(e,t,n){\"use strict\";var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,c=Object.prototype.propertyIsEnumerable;function i(e){if(null===e||void 0===e)throw new TypeError(\"Object.assign cannot be called with null or undefined\");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String(\"abc\");if(e[5]=\"de\",\"5\"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t[\"_\"+String.fromCharCode(n)]=n;if(\"0123456789\"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(\"\"))return!1;var r={};return\"abcdefghijklmnopqrst\".split(\"\").forEach((function(e){r[e]=e})),\"abcdefghijklmnopqrst\"===Object.keys(Object.assign({},r)).join(\"\")}catch(o){return!1}}()?Object.assign:function(e,t){for(var n,a,l=i(e),u=1;u<arguments.length;u++){for(var s in n=Object(arguments[u]))o.call(n,s)&&(l[s]=n[s]);if(r){a=r(n);for(var f=0;f<a.length;f++)c.call(n,a[f])&&(l[a[f]]=n[a[f]])}}return l}},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,\"loaded\",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,\"id\",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){var r=n(126),o=n(106);e.exports=Object.keys||function(e){return r(e,o)}},function(e,t){e.exports=!0},function(e,t){var n=0,r=Math.random();e.exports=function(e){return\"Symbol(\".concat(void 0===e?\"\":e,\")_\",(++n+r).toString(36))}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t){var n;n=function(){return this}();try{n=n||new Function(\"return this\")()}catch(r){\"object\"===typeof window&&(n=window)}e.exports=n},function(e,t,n){var r=n(233),o=n(234),c=n(235),i=n(236),a=n(237);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}l.prototype.clear=r,l.prototype.delete=o,l.prototype.get=c,l.prototype.has=i,l.prototype.set=a,e.exports=l},function(e,t,n){var r=n(81);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},function(e,t){e.exports=function(e,t){return e===t||e!==e&&t!==t}},function(e,t,n){var r=n(114)(Object,\"create\");e.exports=r},function(e,t,n){var r=n(258);e.exports=function(e,t){var n=e.__data__;return r(t)?n[\"string\"==typeof t?\"string\":\"hash\"]:n.map}},function(e,t,n){var r=n(64),o=n(53);e.exports=function(e){return\"symbol\"==typeof e||o(e)&&\"[object Symbol]\"==r(e)}},function(e,t,n){\"use strict\";t.__esModule=!0;var r=i(n(182)),o=i(n(194)),c=\"function\"===typeof o.default&&\"symbol\"===typeof r.default?function(e){return typeof e}:function(e){return e&&\"function\"===typeof o.default&&e.constructor===o.default&&e!==o.default.prototype?\"symbol\":typeof e};function i(e){return e&&e.__esModule?e:{default:e}}t.default=\"function\"===typeof o.default&&\"symbol\"===c(r.default)?function(e){return\"undefined\"===typeof e?\"undefined\":c(e)}:function(e){return e&&\"function\"===typeof o.default&&e.constructor===o.default&&e!==o.default.prototype?\"symbol\":\"undefined\"===typeof e?\"undefined\":c(e)}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return o}));var r=n(67);function o(e,t){if(e){if(\"string\"===typeof e)return Object(r.a)(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return\"Object\"===n&&e.constructor&&(n=e.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(e):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Object(r.a)(e,t):void 0}}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return m}));var r=n(0),o=n.n(r),c=n(9),i=n.n(c),a=n(1),l=n.n(a);function u(e){return(u=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function s(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function f(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function p(e,t){return(p=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function h(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=v(e);if(t){var o=v(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return d(this,n)}}function d(e,t){return!t||\"object\"!==u(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function v(e){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var m=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&p(e,t)}(c,e);var t,n,r,o=h(c);function c(){var e;s(this,c);for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];return(e=o.call.apply(o,[this].concat(n))).removeContainer=function(){e.container&&(i.a.unmountComponentAtNode(e.container),e.container.parentNode.removeChild(e.container),e.container=null)},e.renderComponent=function(t,n){var r=e.props,o=r.visible,c=r.getComponent,a=r.forceRender,l=r.getContainer,u=r.parent;(o||u._component||a)&&(e.container||(e.container=l()),i.a.unstable_renderSubtreeIntoContainer(u,c(t),e.container,(function(){n&&n.call(this)})))},e}return t=c,(n=[{key:\"componentDidMount\",value:function(){this.props.autoMount&&this.renderComponent()}},{key:\"componentDidUpdate\",value:function(){this.props.autoMount&&this.renderComponent()}},{key:\"componentWillUnmount\",value:function(){this.props.autoDestroy&&this.removeContainer()}},{key:\"render\",value:function(){return this.props.children({renderComponent:this.renderComponent,removeContainer:this.removeContainer})}}])&&f(t.prototype,n),r&&f(t,r),c}(o.a.Component);m.propTypes={autoMount:l.a.bool,autoDestroy:l.a.bool,visible:l.a.bool,forceRender:l.a.bool,parent:l.a.any,getComponent:l.a.func.isRequired,getContainer:l.a.func.isRequired,children:l.a.func.isRequired},m.defaultProps={autoMount:!0,autoDestroy:!0,forceRender:!1}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return m}));var r=n(0),o=n.n(r),c=n(9),i=n.n(c),a=n(1),l=n.n(a);function u(e){return(u=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function s(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function f(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function p(e,t){return(p=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function h(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=v(e);if(t){var o=v(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return d(this,n)}}function d(e,t){return!t||\"object\"!==u(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function v(e){return(v=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var m=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&p(e,t)}(c,e);var t,n,r,o=h(c);function c(){return s(this,c),o.apply(this,arguments)}return t=c,(n=[{key:\"componentDidMount\",value:function(){this.createContainer()}},{key:\"componentDidUpdate\",value:function(e){var t=this.props.didUpdate;t&&t(e)}},{key:\"componentWillUnmount\",value:function(){this.removeContainer()}},{key:\"createContainer\",value:function(){this._container=this.props.getContainer(),this.forceUpdate()}},{key:\"removeContainer\",value:function(){this._container&&this._container.parentNode.removeChild(this._container)}},{key:\"render\",value:function(){return this._container?i.a.createPortal(this.props.children,this._container):null}}])&&f(t.prototype,n),r&&f(t,r),c}(o.a.Component);m.propTypes={getContainer:l.a.func.isRequired,children:l.a.node.isRequired,didUpdate:l.a.func}},function(e,t,n){\"use strict\";t.a={items_per_page:\"/ page\",jump_to:\"Go to\",jump_to_confirm:\"confirm\",page:\"\",prev_page:\"Previous Page\",next_page:\"Next Page\",prev_5:\"Previous 5 Pages\",next_5:\"Next 5 Pages\",prev_3:\"Previous 3 Pages\",next_3:\"Next 3 Pages\"}},function(e,t,n){\"use strict\";function r(){var e=[].slice.call(arguments,0);return 1===e.length?e[0]:function(){for(var t=0;t<e.length;t++)e[t]&&e[t].apply&&e[t].apply(this,arguments)}}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";var r=n(46),o=Object.prototype.toString,c=function(){return\"[object Arguments]\"===o.call(arguments)?function(e){return\"[object Arguments]\"===o.call(e)}:function(e){return Object(r.a)(\"callee\",e)}}();t.a=c},function(e,t,n){\"use strict\";function r(e){return\"[object String]\"===Object.prototype.toString.call(e)}n.d(t,\"a\",(function(){return r}))},function(e,t,n){var r=n(34),o=n(317),c=n(318),i=Math.max,a=Math.min;e.exports=function(e,t,n){var l,u,s,f,p,h,d=0,v=!1,m=!1,y=!0;if(\"function\"!=typeof e)throw new TypeError(\"Expected a function\");function b(t){var n=l,r=u;return l=u=void 0,d=t,f=e.apply(r,n)}function g(e){return d=e,p=setTimeout(z,t),v?b(e):f}function w(e){var n=e-h;return void 0===h||n>=t||n<0||m&&e-d>=s}function z(){var e=o();if(w(e))return O(e);p=setTimeout(z,function(e){var n=t-(e-h);return m?a(n,s-(e-d)):n}(e))}function O(e){return p=void 0,y&&l?b(e):(l=u=void 0,f)}function C(){var e=o(),n=w(e);if(l=arguments,u=this,h=e,n){if(void 0===p)return g(h);if(m)return clearTimeout(p),p=setTimeout(z,t),b(h)}return void 0===p&&(p=setTimeout(z,t)),f}return t=c(t)||0,r(n)&&(v=!!n.leading,s=(m=\"maxWait\"in n)?i(c(n.maxWait)||0,t):s,y=\"trailing\"in n?!!n.trailing:y),C.cancel=function(){void 0!==p&&clearTimeout(p),d=0,l=h=u=p=void 0},C.flush=function(){return void 0===p?f:O(o())},C}},function(e,t,n){\"use strict\";t.a={items_per_page:\"\\u6761/\\u9875\",jump_to:\"\\u8df3\\u81f3\",jump_to_confirm:\"\\u786e\\u5b9a\",page:\"\\u9875\",prev_page:\"\\u4e0a\\u4e00\\u9875\",next_page:\"\\u4e0b\\u4e00\\u9875\",prev_5:\"\\u5411\\u524d 5 \\u9875\",next_5:\"\\u5411\\u540e 5 \\u9875\",prev_3:\"\\u5411\\u524d 3 \\u9875\",next_3:\"\\u5411\\u540e 3 \\u9875\"}},function(e,t,n){\"use strict\";(function(e){var n=function(){if(\"undefined\"!==typeof Map)return Map;function e(e,t){var n=-1;return e.some((function(e,r){return e[0]===t&&(n=r,!0)})),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,\"size\",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(t){var n=e(this.__entries__,t),r=this.__entries__[n];return r&&r[1]},t.prototype.set=function(t,n){var r=e(this.__entries__,t);~r?this.__entries__[r][1]=n:this.__entries__.push([t,n])},t.prototype.delete=function(t){var n=this.__entries__,r=e(n,t);~r&&n.splice(r,1)},t.prototype.has=function(t){return!!~e(this.__entries__,t)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(e,t){void 0===t&&(t=null);for(var n=0,r=this.__entries__;n<r.length;n++){var o=r[n];e.call(t,o[1],o[0])}},t}()}(),r=\"undefined\"!==typeof window&&\"undefined\"!==typeof document&&window.document===document,o=\"undefined\"!==typeof e&&e.Math===Math?e:\"undefined\"!==typeof self&&self.Math===Math?self:\"undefined\"!==typeof window&&window.Math===Math?window:Function(\"return this\")(),c=\"function\"===typeof requestAnimationFrame?requestAnimationFrame.bind(o):function(e){return setTimeout((function(){return e(Date.now())}),1e3/60)};var i=[\"top\",\"right\",\"bottom\",\"left\",\"width\",\"height\",\"size\",\"weight\"],a=\"undefined\"!==typeof MutationObserver,l=function(){function e(){this.connected_=!1,this.mutationEventsAdded_=!1,this.mutationsObserver_=null,this.observers_=[],this.onTransitionEnd_=this.onTransitionEnd_.bind(this),this.refresh=function(e,t){var n=!1,r=!1,o=0;function i(){n&&(n=!1,e()),r&&l()}function a(){c(i)}function l(){var e=Date.now();if(n){if(e-o<2)return;r=!0}else n=!0,r=!1,setTimeout(a,t);o=e}return l}(this.refresh.bind(this),20)}return e.prototype.addObserver=function(e){~this.observers_.indexOf(e)||this.observers_.push(e),this.connected_||this.connect_()},e.prototype.removeObserver=function(e){var t=this.observers_,n=t.indexOf(e);~n&&t.splice(n,1),!t.length&&this.connected_&&this.disconnect_()},e.prototype.refresh=function(){this.updateObservers_()&&this.refresh()},e.prototype.updateObservers_=function(){var e=this.observers_.filter((function(e){return e.gatherActive(),e.hasActive()}));return e.forEach((function(e){return e.broadcastActive()})),e.length>0},e.prototype.connect_=function(){r&&!this.connected_&&(document.addEventListener(\"transitionend\",this.onTransitionEnd_),window.addEventListener(\"resize\",this.refresh),a?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener(\"DOMSubtreeModified\",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){r&&this.connected_&&(document.removeEventListener(\"transitionend\",this.onTransitionEnd_),window.removeEventListener(\"resize\",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener(\"DOMSubtreeModified\",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?\"\":t;i.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),u=function(e,t){for(var n=0,r=Object.keys(t);n<r.length;n++){var o=r[n];Object.defineProperty(e,o,{value:t[o],enumerable:!1,writable:!1,configurable:!0})}return e},s=function(e){return e&&e.ownerDocument&&e.ownerDocument.defaultView||o},f=y(0,0,0,0);function p(e){return parseFloat(e)||0}function h(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];return t.reduce((function(t,n){return t+p(e[\"border-\"+n+\"-width\"])}),0)}function d(e){var t=e.clientWidth,n=e.clientHeight;if(!t&&!n)return f;var r=s(e).getComputedStyle(e),o=function(e){for(var t={},n=0,r=[\"top\",\"right\",\"bottom\",\"left\"];n<r.length;n++){var o=r[n],c=e[\"padding-\"+o];t[o]=p(c)}return t}(r),c=o.left+o.right,i=o.top+o.bottom,a=p(r.width),l=p(r.height);if(\"border-box\"===r.boxSizing&&(Math.round(a+c)!==t&&(a-=h(r,\"left\",\"right\")+c),Math.round(l+i)!==n&&(l-=h(r,\"top\",\"bottom\")+i)),!function(e){return e===s(e).document.documentElement}(e)){var u=Math.round(a+c)-t,d=Math.round(l+i)-n;1!==Math.abs(u)&&(a-=u),1!==Math.abs(d)&&(l-=d)}return y(o.left,o.top,a,l)}var v=\"undefined\"!==typeof SVGGraphicsElement?function(e){return e instanceof s(e).SVGGraphicsElement}:function(e){return e instanceof s(e).SVGElement&&\"function\"===typeof e.getBBox};function m(e){return r?v(e)?function(e){var t=e.getBBox();return y(0,0,t.width,t.height)}(e):d(e):f}function y(e,t,n,r){return{x:e,y:t,width:n,height:r}}var b=function(){function e(e){this.broadcastWidth=0,this.broadcastHeight=0,this.contentRect_=y(0,0,0,0),this.target=e}return e.prototype.isActive=function(){var e=m(this.target);return this.contentRect_=e,e.width!==this.broadcastWidth||e.height!==this.broadcastHeight},e.prototype.broadcastRect=function(){var e=this.contentRect_;return this.broadcastWidth=e.width,this.broadcastHeight=e.height,e},e}(),g=function(e,t){var n=function(e){var t=e.x,n=e.y,r=e.width,o=e.height,c=\"undefined\"!==typeof DOMRectReadOnly?DOMRectReadOnly:Object,i=Object.create(c.prototype);return u(i,{x:t,y:n,width:r,height:o,top:n,right:t+r,bottom:o+n,left:t}),i}(t);u(this,{target:e,contentRect:n})},w=function(){function e(e,t,r){if(this.activeObservations_=[],this.observations_=new n,\"function\"!==typeof e)throw new TypeError(\"The callback provided as parameter 1 is not a function.\");this.callback_=e,this.controller_=t,this.callbackCtx_=r}return e.prototype.observe=function(e){if(!arguments.length)throw new TypeError(\"1 argument required, but only 0 present.\");if(\"undefined\"!==typeof Element&&Element instanceof Object){if(!(e instanceof s(e).Element))throw new TypeError('parameter 1 is not of type \"Element\".');var t=this.observations_;t.has(e)||(t.set(e,new b(e)),this.controller_.addObserver(this),this.controller_.refresh())}},e.prototype.unobserve=function(e){if(!arguments.length)throw new TypeError(\"1 argument required, but only 0 present.\");if(\"undefined\"!==typeof Element&&Element instanceof Object){if(!(e instanceof s(e).Element))throw new TypeError('parameter 1 is not of type \"Element\".');var t=this.observations_;t.has(e)&&(t.delete(e),t.size||this.controller_.removeObserver(this))}},e.prototype.disconnect=function(){this.clearActive(),this.observations_.clear(),this.controller_.removeObserver(this)},e.prototype.gatherActive=function(){var e=this;this.clearActive(),this.observations_.forEach((function(t){t.isActive()&&e.activeObservations_.push(t)}))},e.prototype.broadcastActive=function(){if(this.hasActive()){var e=this.callbackCtx_,t=this.activeObservations_.map((function(e){return new g(e.target,e.broadcastRect())}));this.callback_.call(e,t,e),this.clearActive()}},e.prototype.clearActive=function(){this.activeObservations_.splice(0)},e.prototype.hasActive=function(){return this.activeObservations_.length>0},e}(),z=\"undefined\"!==typeof WeakMap?new WeakMap:new n,O=function e(t){if(!(this instanceof e))throw new TypeError(\"Cannot call a class as a function.\");if(!arguments.length)throw new TypeError(\"1 argument required, but only 0 present.\");var n=l.getInstance(),r=new w(t,n,this);z.set(this,r)};[\"observe\",\"unobserve\",\"disconnect\"].forEach((function(e){O.prototype[e]=function(){var t;return(t=z.get(this))[e].apply(t,arguments)}}));var C=\"undefined\"!==typeof o.ResizeObserver?o.ResizeObserver:O;t.a=C}).call(this,n(78))},function(e,t,n){\"use strict\";e.exports=n(322)},function(e,t,n){\"use strict\";function r(e,t){var n;t=t||[];var r=(e=e||[]).length,o=t.length,c=[];for(n=0;n<r;)c[c.length]=e[n],n+=1;for(n=0;n<o;)c[c.length]=t[n],n+=1;return c}n.d(t,\"a\",(function(){return r}))},function(e,t,n){\"use strict\";var r=n(0),o=n.n(r),c=n(12),i=n(5),a=n.n(i),l=n(26),u=n.n(l),s=n(10),f=n.n(s),p=n(6),h=n.n(p),d=n(11),v=n.n(d),m=n(1),y=n.n(m),b=n(35),g={adjustX:1,adjustY:1},w=[0,0],z={left:{points:[\"cr\",\"cl\"],overflow:g,offset:[-4,0],targetOffset:w},right:{points:[\"cl\",\"cr\"],overflow:g,offset:[4,0],targetOffset:w},top:{points:[\"bc\",\"tc\"],overflow:g,offset:[0,-4],targetOffset:w},bottom:{points:[\"tc\",\"bc\"],overflow:g,offset:[0,4],targetOffset:w},topLeft:{points:[\"bl\",\"tl\"],overflow:g,offset:[0,-4],targetOffset:w},leftTop:{points:[\"tr\",\"tl\"],overflow:g,offset:[-4,0],targetOffset:w},topRight:{points:[\"br\",\"tr\"],overflow:g,offset:[0,-4],targetOffset:w},rightTop:{points:[\"tl\",\"tr\"],overflow:g,offset:[4,0],targetOffset:w},bottomRight:{points:[\"tr\",\"br\"],overflow:g,offset:[0,4],targetOffset:w},rightBottom:{points:[\"bl\",\"br\"],overflow:g,offset:[4,0],targetOffset:w},bottomLeft:{points:[\"tl\",\"bl\"],overflow:g,offset:[0,4],targetOffset:w},leftBottom:{points:[\"br\",\"bl\"],overflow:g,offset:[-4,0],targetOffset:w}},O=function(e){function t(){return f()(this,t),h()(this,e.apply(this,arguments))}return v()(t,e),t.prototype.componentDidUpdate=function(){var e=this.props.trigger;e&&e.forcePopupAlign()},t.prototype.render=function(){var e=this.props,t=e.overlay,n=e.prefixCls,r=e.id;return o.a.createElement(\"div\",{className:n+\"-inner\",id:r,role:\"tooltip\"},\"function\"===typeof t?t():t)},t}(o.a.Component);O.propTypes={prefixCls:y.a.string,overlay:y.a.oneOfType([y.a.node,y.a.func]).isRequired,id:y.a.string,trigger:y.a.any};var C=O,M=function(e){function t(){var n,r,c;f()(this,t);for(var i=arguments.length,a=Array(i),l=0;l<i;l++)a[l]=arguments[l];return n=r=h()(this,e.call.apply(e,[this].concat(a))),r.getPopupElement=function(){var e=r.props,t=e.arrowContent,n=e.overlay,c=e.prefixCls,i=e.id;return[o.a.createElement(\"div\",{className:c+\"-arrow\",key:\"arrow\"},t),o.a.createElement(C,{key:\"content\",trigger:r.trigger,prefixCls:c,id:i,overlay:n})]},r.saveTrigger=function(e){r.trigger=e},c=n,h()(r,c)}return v()(t,e),t.prototype.getPopupDomNode=function(){return this.trigger.getPopupDomNode()},t.prototype.render=function(){var e=this.props,t=e.overlayClassName,n=e.trigger,r=e.mouseEnterDelay,c=e.mouseLeaveDelay,i=e.overlayStyle,l=e.prefixCls,s=e.children,f=e.onVisibleChange,p=e.afterVisibleChange,h=e.transitionName,d=e.animation,v=e.placement,m=e.align,y=e.destroyTooltipOnHide,g=e.defaultVisible,w=e.getTooltipContainer,O=u()(e,[\"overlayClassName\",\"trigger\",\"mouseEnterDelay\",\"mouseLeaveDelay\",\"overlayStyle\",\"prefixCls\",\"children\",\"onVisibleChange\",\"afterVisibleChange\",\"transitionName\",\"animation\",\"placement\",\"align\",\"destroyTooltipOnHide\",\"defaultVisible\",\"getTooltipContainer\"]),C=a()({},O);return\"visible\"in this.props&&(C.popupVisible=this.props.visible),o.a.createElement(b.a,a()({popupClassName:t,ref:this.saveTrigger,prefixCls:l,popup:this.getPopupElement,action:n,builtinPlacements:z,popupPlacement:v,popupAlign:m,getPopupContainer:w,onPopupVisibleChange:f,afterPopupVisibleChange:p,popupTransitionName:h,popupAnimation:d,defaultPopupVisible:g,destroyPopupOnHide:y,mouseLeaveDelay:c,popupStyle:i,mouseEnterDelay:r},C),s)},t}(r.Component);M.propTypes={trigger:y.a.any,children:y.a.any,defaultVisible:y.a.bool,visible:y.a.bool,placement:y.a.string,transitionName:y.a.oneOfType([y.a.string,y.a.object]),animation:y.a.any,onVisibleChange:y.a.func,afterVisibleChange:y.a.func,overlay:y.a.oneOfType([y.a.node,y.a.func]).isRequired,overlayStyle:y.a.object,overlayClassName:y.a.string,prefixCls:y.a.string,mouseEnterDelay:y.a.number,mouseLeaveDelay:y.a.number,getTooltipContainer:y.a.func,destroyTooltipOnHide:y.a.bool,align:y.a.object,arrowContent:y.a.any,id:y.a.string},M.defaultProps={prefixCls:\"rc-tooltip\",mouseEnterDelay:0,destroyTooltipOnHide:!1,mouseLeaveDelay:.1,align:{},placement:\"right\",trigger:[\"hover\"],arrowContent:null};var S=M,_=n(3),x=n.n(_);function k(){return(k=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var H={adjustX:1,adjustY:1},E={adjustX:0,adjustY:0},P=[0,0];function V(e){return\"boolean\"===typeof e?e?H:E:k(k({},E),e)}var T=n(59);function L(e){return(L=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function j(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function N(e,t){return(N=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function D(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=I(e);if(t){var o=I(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return R(this,n)}}function R(e,t){return!t||\"object\"!==L(t)&&\"function\"!==typeof t?A(e):t}function A(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function I(e){return(I=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function F(){return(F=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function W(e){var t=e.type;if((!0===t.__ANT_BUTTON||!0===t.__ANT_SWITCH||!0===t.__ANT_CHECKBOX||\"button\"===e.type)&&e.props.disabled){var n=function(e,t){var n={},r=F({},e);return t.forEach((function(t){e&&t in e&&(n[t]=e[t],delete r[t])})),{picked:n,omitted:r}}(e.props.style,[\"position\",\"left\",\"right\",\"top\",\"bottom\",\"float\",\"display\",\"zIndex\"]),o=n.picked,c=n.omitted,i=F(F({display:\"inline-block\"},o),{cursor:\"not-allowed\",width:e.props.block?\"100%\":null}),a=F(F({},c),{pointerEvents:\"none\"}),l=r.cloneElement(e,{style:a,className:null});return r.createElement(\"span\",{style:i,className:e.props.className},l)}return e}var U=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&N(e,t)}(i,e);var t,n,o,c=D(i);function i(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=c.call(this,e)).onVisibleChange=function(e){var n=t.props.onVisibleChange;\"visible\"in t.props||t.setState({visible:!t.isNoTitle()&&e}),n&&!t.isNoTitle()&&n(e)},t.saveTooltip=function(e){t.tooltip=e},t.onPopupAlign=function(e,n){var r=t.getPlacements(),o=Object.keys(r).filter((function(e){return r[e].points[0]===n.points[0]&&r[e].points[1]===n.points[1]}))[0];if(o){var c=e.getBoundingClientRect(),i={top:\"50%\",left:\"50%\"};o.indexOf(\"top\")>=0||o.indexOf(\"Bottom\")>=0?i.top=\"\".concat(c.height-n.offset[1],\"px\"):(o.indexOf(\"Top\")>=0||o.indexOf(\"bottom\")>=0)&&(i.top=\"\".concat(-n.offset[1],\"px\")),o.indexOf(\"left\")>=0||o.indexOf(\"Right\")>=0?i.left=\"\".concat(c.width-n.offset[0],\"px\"):(o.indexOf(\"right\")>=0||o.indexOf(\"Left\")>=0)&&(i.left=\"\".concat(-n.offset[0],\"px\")),e.style.transformOrigin=\"\".concat(i.left,\" \").concat(i.top)}},t.renderTooltip=function(e){var n=e.getPopupContainer,o=e.getPrefixCls,c=A(t),i=c.props,a=c.state,l=i.prefixCls,u=i.openClassName,s=i.getPopupContainer,f=i.getTooltipContainer,p=i.children,h=o(\"tooltip\",l),d=a.visible;!(\"visible\"in i)&&t.isNoTitle()&&(d=!1);var v,m,y,b=W(r.isValidElement(p)?p:r.createElement(\"span\",null,p)),g=b.props,w=x()(g.className,(v={},m=u||\"\".concat(h,\"-open\"),y=!0,m in v?Object.defineProperty(v,m,{value:y,enumerable:!0,configurable:!0,writable:!0}):v[m]=y,v));return r.createElement(S,F({},t.props,{prefixCls:h,getTooltipContainer:s||f||n,ref:t.saveTooltip,builtinPlacements:t.getPlacements(),overlay:t.getOverlay(),visible:d,onVisibleChange:t.onVisibleChange,onPopupAlign:t.onPopupAlign}),d?r.cloneElement(b,{className:w}):b)},t.state={visible:!!e.visible||!!e.defaultVisible},t}return t=i,o=[{key:\"getDerivedStateFromProps\",value:function(e){return\"visible\"in e?{visible:e.visible}:null}}],(n=[{key:\"getPopupDomNode\",value:function(){return this.tooltip.getPopupDomNode()}},{key:\"getPlacements\",value:function(){var e=this.props,t=e.builtinPlacements,n=e.arrowPointAtCenter,r=e.autoAdjustOverflow;return t||function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.arrowWidth,n=void 0===t?5:t,r=e.horizontalArrowShift,o=void 0===r?16:r,c=e.verticalArrowShift,i=void 0===c?12:c,a=e.autoAdjustOverflow,l=void 0===a||a,u={left:{points:[\"cr\",\"cl\"],offset:[-4,0]},right:{points:[\"cl\",\"cr\"],offset:[4,0]},top:{points:[\"bc\",\"tc\"],offset:[0,-4]},bottom:{points:[\"tc\",\"bc\"],offset:[0,4]},topLeft:{points:[\"bl\",\"tc\"],offset:[-(o+n),-4]},leftTop:{points:[\"tr\",\"cl\"],offset:[-4,-(i+n)]},topRight:{points:[\"br\",\"tc\"],offset:[o+n,-4]},rightTop:{points:[\"tl\",\"cr\"],offset:[4,-(i+n)]},bottomRight:{points:[\"tr\",\"bc\"],offset:[o+n,4]},rightBottom:{points:[\"bl\",\"cr\"],offset:[4,i+n]},bottomLeft:{points:[\"tl\",\"bc\"],offset:[-(o+n),4]},leftBottom:{points:[\"br\",\"cl\"],offset:[-4,i+n]}};return Object.keys(u).forEach((function(t){u[t]=e.arrowPointAtCenter?k(k({},u[t]),{overflow:V(l),targetOffset:P}):k(k({},z[t]),{overflow:V(l)}),u[t].ignoreShake=!0})),u}({arrowPointAtCenter:n,verticalArrowShift:8,autoAdjustOverflow:r})}},{key:\"isNoTitle\",value:function(){var e=this.props,t=e.title,n=e.overlay;return!t&&!n&&0!==t}},{key:\"getOverlay\",value:function(){var e=this.props,t=e.title,n=e.overlay;return 0===t?t:n||t||\"\"}},{key:\"render\",value:function(){return r.createElement(T.a,null,this.renderTooltip)}}])&&j(t.prototype,n),o&&j(t,o),i}(r.Component);U.defaultProps={placement:\"top\",transitionName:\"zoom-big-fast\",mouseEnterDelay:.1,mouseLeaveDelay:.1,arrowPointAtCenter:!1,autoAdjustOverflow:!0},Object(c.polyfill)(U);t.a=U},function(e,t,n){\"use strict\";var r=n(23),o=n(57);function c(e){return null!=e&&\"function\"===typeof e[\"@@transducer/step\"]}function i(e,t,n){return function(){if(0===arguments.length)return n();var r=Array.prototype.slice.call(arguments,0),i=r.pop();if(!Object(o.a)(i)){for(var a=0;a<e.length;){if(\"function\"===typeof i[e[a]])return i[e[a]].apply(i,r);a+=1}if(c(i)){var l=t.apply(null,r);return l(i)}}return n.apply(this,arguments)}}var a=n(60),l=function(){return this.xf[\"@@transducer/init\"]()},u=function(e){return this.xf[\"@@transducer/result\"](e)},s=function(){function e(e,t){this.xf=t,this.f=e}return e.prototype[\"@@transducer/init\"]=l,e.prototype[\"@@transducer/result\"]=u,e.prototype[\"@@transducer/step\"]=function(e,t){return this.xf[\"@@transducer/step\"](e,this.f(t))},e}(),f=Object(r.a)((function(e,t){return new s(e,t)})),p=n(47),h=n(69),d=Object(r.a)(i([\"fantasy-land/map\",\"map\"],f,(function(e,t){switch(Object.prototype.toString.call(t)){case\"[object Function]\":return Object(p.a)(t.length,(function(){return e.call(this,t.apply(this,arguments))}));case\"[object Object]\":return Object(a.a)((function(n,r){return n[r]=e(t[r]),n}),{},Object(h.a)(t));default:return function(e,t){for(var n=0,r=t.length,o=Array(r);n<r;)o[n]=e(t[n]),n+=1;return o}(e,t)}})));t.a=d},function(e,t,n){\"use strict\";var r=n(0),o=n.n(r),c=n(1),i=n.n(c),a=n(8),l=n.n(a),u=n(15),s=n.n(u),f=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function p(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function h(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function d(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}var v=function(e){function t(){return h(this,t),d(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),f(t,[{key:\"shouldComponentUpdate\",value:function(e){return this.props.forceRender||!s()(this.props,e)}},{key:\"render\",value:function(){var e;if(this._isActived=this.props.forceRender||this._isActived||this.props.isActive,!this._isActived)return null;var t=this.props,n=t.prefixCls,r=t.isActive,c=t.children,i=t.destroyInactivePanel,a=t.forceRender,u=t.role,s=l()((p(e={},n+\"-content\",!0),p(e,n+\"-content-active\",r),p(e,n+\"-content-inactive\",!r),e)),f=a||r||!i?o.a.createElement(\"div\",{className:n+\"-content-box\"},c):null;return o.a.createElement(\"div\",{className:s,role:u},f)}}]),t}(r.Component);v.propTypes={prefixCls:i.a.string,isActive:i.a.bool,children:i.a.any,destroyInactivePanel:i.a.bool,forceRender:i.a.bool,role:i.a.string};var m=v,y=n(33),b=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function g(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function w(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function z(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}var O=function(e){function t(){var e,n,r;w(this,t);for(var o=arguments.length,c=Array(o),i=0;i<o;i++)c[i]=arguments[i];return n=r=z(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(c))),r.handleItemClick=function(){var e=r.props,t=e.onItemClick,n=e.panelKey;\"function\"===typeof t&&t(n)},r.handleKeyPress=function(e){\"Enter\"!==e.key&&13!==e.keyCode&&13!==e.which||r.handleItemClick()},z(r,n)}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),b(t,[{key:\"shouldComponentUpdate\",value:function(e){return!s()(this.props,e)}},{key:\"render\",value:function(){var e,t=this.props,n=t.className,r=t.id,c=t.style,i=t.prefixCls,a=t.header,u=t.headerClass,s=t.children,f=t.isActive,p=t.showArrow,h=t.destroyInactivePanel,d=t.disabled,v=t.accordion,b=t.forceRender,w=t.expandIcon,z=t.extra,O=l()(i+\"-header\",g({},u,u)),C=l()((g(e={},i+\"-item\",!0),g(e,i+\"-item-active\",f),g(e,i+\"-item-disabled\",d),e),n),M=o.a.createElement(\"i\",{className:\"arrow\"});return p&&\"function\"===typeof w&&(M=w(this.props)),o.a.createElement(\"div\",{className:C,style:c,id:r},o.a.createElement(\"div\",{className:O,onClick:this.handleItemClick,role:v?\"tab\":\"button\",tabIndex:d?-1:0,\"aria-expanded\":\"\"+f,onKeyPress:this.handleKeyPress},p&&M,a,z&&o.a.createElement(\"div\",{className:i+\"-extra\"},z)),o.a.createElement(y.a,{showProp:\"isActive\",exclusive:!0,component:\"\",animation:this.props.openAnimation},o.a.createElement(m,{prefixCls:i,isActive:f,destroyInactivePanel:h,forceRender:b,role:v?\"tabpanel\":null},s)))}}]),t}(r.Component);O.propTypes={className:i.a.oneOfType([i.a.string,i.a.object]),id:i.a.string,children:i.a.any,openAnimation:i.a.object,prefixCls:i.a.string,header:i.a.oneOfType([i.a.string,i.a.number,i.a.node]),headerClass:i.a.string,showArrow:i.a.bool,isActive:i.a.bool,onItemClick:i.a.func,style:i.a.object,destroyInactivePanel:i.a.bool,disabled:i.a.bool,accordion:i.a.bool,forceRender:i.a.bool,expandIcon:i.a.func,extra:i.a.node,panelKey:i.a.any},O.defaultProps={showArrow:!0,isActive:!1,destroyInactivePanel:!1,onItemClick:function(){},headerClass:\"\",forceRender:!1};var C=O,M=n(45);function S(e,t,n,r){var o=void 0;return Object(M.a)(e,n,{start:function(){t?(o=e.offsetHeight,e.style.height=0):e.style.height=e.offsetHeight+\"px\"},active:function(){e.style.height=(t?o:0)+\"px\"},end:function(){e.style.height=\"\",r()}})}var _=function(e){return{enter:function(t,n){return S(t,!0,e+\"-anim\",n)},leave:function(t,n){return S(t,!1,e+\"-anim\",n)}}},x=n(68),k=n(12),H=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function E(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function P(e){var t=e;return Array.isArray(t)||(t=t?[t]:[]),t.map((function(e){return String(e)}))}var V=function(e){function t(e){!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t);var n=function(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));T.call(n);var r=e.activeKey,o=e.defaultActiveKey;return\"activeKey\"in e&&(o=r),n.state={openAnimation:e.openAnimation||_(e.prefixCls),activeKey:P(o)},n}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),H(t,[{key:\"shouldComponentUpdate\",value:function(e,t){return!s()(this.props,e)||!s()(this.state,t)}},{key:\"render\",value:function(){var e,t=this.props,n=t.prefixCls,r=t.className,c=t.style,i=t.accordion,a=l()((E(e={},n,!0),E(e,r,!!r),e));return o.a.createElement(\"div\",{className:a,style:c,role:i?\"tablist\":null},this.getItems())}}],[{key:\"getDerivedStateFromProps\",value:function(e){var t={};return\"activeKey\"in e&&(t.activeKey=P(e.activeKey)),\"openAnimation\"in e&&(t.openAnimation=e.openAnimation),t.activeKey||t.openAnimation?t:null}}]),t}(r.Component),T=function(){var e=this;this.onClickItem=function(t){var n=e.state.activeKey;if(e.props.accordion)n=n[0]===t?[]:[t];else{var r=(n=[].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}(n))).indexOf(t);r>-1?n.splice(r,1):n.push(t)}e.setActiveKey(n)},this.getNewChild=function(t,n){if(!t)return null;var r=e.state.activeKey,c=e.props,i=c.prefixCls,a=c.accordion,l=c.destroyInactivePanel,u=c.expandIcon,s=t.key||String(n),f=t.props,p=f.header,h=f.headerClass,d=f.disabled,v={key:s,panelKey:s,header:p,headerClass:h,isActive:a?r[0]===s:r.indexOf(s)>-1,prefixCls:i,destroyInactivePanel:l,openAnimation:e.state.openAnimation,accordion:a,children:t.props.children,onItemClick:d?null:e.onClickItem,expandIcon:u};return\"string\"===typeof t.type?t:o.a.cloneElement(t,v)},this.getItems=function(){var t=e.props.children,n=Object(x.isFragment)(t)?t.props.children:t,c=r.Children.map(n,e.getNewChild);return Object(x.isFragment)(t)?o.a.createElement(o.a.Fragment,null,c):c},this.setActiveKey=function(t){\"activeKey\"in e.props||e.setState({activeKey:t}),e.props.onChange(e.props.accordion?t[0]:t)}};V.propTypes={children:i.a.any,prefixCls:i.a.string,activeKey:i.a.oneOfType([i.a.string,i.a.number,i.a.arrayOf(i.a.oneOfType([i.a.string,i.a.number]))]),defaultActiveKey:i.a.oneOfType([i.a.string,i.a.number,i.a.arrayOf(i.a.oneOfType([i.a.string,i.a.number]))]),openAnimation:i.a.object,onChange:i.a.func,accordion:i.a.bool,className:i.a.string,style:i.a.object,destroyInactivePanel:i.a.bool,expandIcon:i.a.func},V.defaultProps={prefixCls:\"rc-collapse\",onChange:function(){},accordion:!1,destroyInactivePanel:!1},V.Panel=C,Object(k.polyfill)(V);var L=V,j=(V.Panel,n(3)),N=n.n(j),D=n(59);function R(e){return(R=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function A(){return(A=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function F(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function W(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function U(e,t){return(U=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function K(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Y(e);if(t){var o=Y(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return B(this,n)}}function B(e,t){return!t||\"object\"!==R(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Y(e){return(Y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var q=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&U(e,t)}(i,e);var t,n,o,c=K(i);function i(){var e;return F(this,i),(e=c.apply(this,arguments)).renderCollapsePanel=function(t){var n=t.getPrefixCls,o=e.props,c=o.prefixCls,i=o.className,a=void 0===i?\"\":i,l=o.showArrow,u=void 0===l||l,s=n(\"collapse\",c),f=N()(I({},\"\".concat(s,\"-no-arrow\"),!u),a);return r.createElement(L.Panel,A({},e.props,{prefixCls:s,className:f}))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(D.a,null,this.renderCollapsePanel)}}])&&W(t.prototype,n),o&&W(t,o),i}(r.Component),G=n(13),$=n(19),Q=n.n($);function X(e,t,n){var r,o;return Object(M.a)(e,\"ant-motion-collapse-legacy\",{start:function(){t?(r=e.offsetHeight,e.style.height=\"0px\",e.style.opacity=\"0\"):(e.style.height=\"\".concat(e.offsetHeight,\"px\"),e.style.opacity=\"1\")},active:function(){o&&Q.a.cancel(o),o=Q()((function(){e.style.height=\"\".concat(t?r:0,\"px\"),e.style.opacity=t?\"1\":\"0\"}))},end:function(){o&&Q.a.cancel(o),e.style.height=\"\",e.style.opacity=\"\",n()}})}var Z={enter:function(e,t){return X(e,!0,t)},leave:function(e,t){return X(e,!1,t)},appear:function(e,t){return X(e,!0,t)}};function J(e){return(J=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function ee(){return(ee=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function te(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ne(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function re(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function oe(e,t){return(oe=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function ce(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=ae(e);if(t){var o=ae(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return ie(this,n)}}function ie(e,t){return!t||\"object\"!==J(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function ae(e){return(ae=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var le=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&oe(e,t)}(i,e);var t,n,o,c=ce(i);function i(){var e;return ne(this,i),(e=c.apply(this,arguments)).renderExpandIcon=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=arguments.length>1?arguments[1]:void 0,o=e.props.expandIcon,c=o?o(t):r.createElement(G.a,{type:\"right\",rotate:t.isActive?90:void 0});return r.isValidElement(c)?r.cloneElement(c,{className:N()(c.props.className,\"\".concat(n,\"-arrow\"))}):c},e.renderCollapse=function(t){var n,o=t.getPrefixCls,c=e.props,i=c.prefixCls,a=c.className,l=void 0===a?\"\":a,u=c.bordered,s=c.expandIconPosition,f=o(\"collapse\",i),p=N()((te(n={},\"\".concat(f,\"-borderless\"),!u),te(n,\"\".concat(f,\"-icon-position-\").concat(s),!0),n),l);return r.createElement(L,ee({},e.props,{expandIcon:function(t){return e.renderExpandIcon(t,f)},prefixCls:f,className:p}))},e}return t=i,(n=[{key:\"render\",value:function(){return r.createElement(D.a,null,this.renderCollapse)}}])&&re(t.prototype,n),o&&re(t,o),i}(r.Component);le.Panel=q,le.defaultProps={bordered:!0,openAnimation:ee(ee({},Z),{appear:function(){}}),expandIconPosition:\"left\"};t.a=le},function(e,t,n){var r=n(50);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&\"function\"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if(\"function\"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&\"function\"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError(\"Can't convert object to primitive value\")}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError(\"Can't call method on  \"+e);return e}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(105)(\"keys\"),o=n(76);e.exports=function(e){return r[e]||(r[e]=o(e))}},function(e,t,n){var r=n(37),o=n(36),c=\"__core-js_shared__\",i=o[c]||(o[c]={});(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})(\"versions\",[]).push({version:r.version,mode:n(75)?\"pure\":\"global\",copyright:\"\\xa9 2020 Denis Pushkarev (zloirock.ru)\"})},function(e,t){e.exports=\"constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf\".split(\",\")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var r=n(102);e.exports=function(e){return Object(r(e))}},function(e,t){e.exports={}},function(e,t,n){var r=n(62),o=n(187),c=n(106),i=n(104)(\"IE_PROTO\"),a=function(){},l=function(){var e,t=n(125)(\"iframe\"),r=c.length;for(t.style.display=\"none\",n(188).appendChild(t),t.src=\"javascript:\",(e=t.contentWindow.document).open(),e.write(\"<script>document.F=Object<\\/script>\"),e.close(),l=e.F;r--;)delete l.prototype[c[r]];return l()};e.exports=Object.create||function(e,t){var n;return null!==e?(a.prototype=r(e),n=new a,a.prototype=null,n[i]=e):n=l(),void 0===t?n:o(n,t)}},function(e,t,n){var r=n(42).f,o=n(43),c=n(52)(\"toStringTag\");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,c)&&r(e,c,{configurable:!0,value:t})}},function(e,t,n){t.f=n(52)},function(e,t,n){var r=n(36),o=n(37),c=n(75),i=n(112),a=n(42).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=c?{}:r.Symbol||{});\"_\"==e.charAt(0)||e in t||a(t,e,{value:i.f(e)})}},function(e,t,n){var r=n(243),o=n(249);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},function(e,t,n){var r=n(64),o=n(34);e.exports=function(e){if(!o(e))return!1;var t=r(e);return\"[object Function]\"==t||\"[object GeneratorFunction]\"==t||\"[object AsyncFunction]\"==t||\"[object Proxy]\"==t}},function(e,t,n){var r=n(44).Symbol;e.exports=r},function(e,t,n){var r=n(142);e.exports=function(e,t,n){\"__proto__\"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},function(e,t,n){var r=n(115),o=n(146);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0}),t.default=function(e,t,n,r){function o(t){var r=new c.default(t);n.call(e,r)}if(e.addEventListener){var i=function(){var n=!1;return\"object\"===typeof r?n=r.capture||!1:\"boolean\"===typeof r&&(n=r),e.addEventListener(t,o,r||!1),{v:{remove:function(){e.removeEventListener(t,o,n)}}}}();if(\"object\"===typeof i)return i.v}else if(e.attachEvent)return e.attachEvent(\"on\"+t,o),{remove:function(){e.detachEvent(\"on\"+t,o)}}};var r,o=n(213),c=(r=o)&&r.__esModule?r:{default:r};e.exports=t.default},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var r=\"0 0 1024 1024\",o=\"64 64 896 896\",c=\"fill\",i=\"outline\",a=\"twotone\";function l(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];return{tag:\"svg\",attrs:{viewBox:e,focusable:!1},children:t.map((function(e){return Array.isArray(e)?{tag:\"path\",attrs:{fill:e[0],d:e[1]}}:{tag:\"path\",attrs:{d:e}}}))}}function u(e,t,n){return{name:e,theme:t,icon:n}}t.AccountBookFill=u(\"account-book\",c,l(o,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zM648.3 426.8l-87.7 161.1h45.7c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4v29.7h63.4c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4V752c0 5.5-4.5 10-10 10h-41.3c-5.5 0-10-4.5-10-10v-51.8h-63.1c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h63.1v-29.7h-63.1c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h45.2l-88-161.1c-2.6-4.8-.9-10.9 4-13.6 1.5-.8 3.1-1.2 4.8-1.2h46c3.8 0 7.2 2.1 8.9 5.5l72.9 144.3 73.2-144.3a10 10 0 0 1 8.9-5.5h45c5.5 0 10 4.5 10 10 .1 1.7-.3 3.3-1.1 4.8z\")),t.AlertFill=u(\"alert\",c,l(o,\"M512 244c176.18 0 319 142.82 319 319v233a32 32 0 0 1-32 32H225a32 32 0 0 1-32-32V563c0-176.18 142.82-319 319-319zM484 68h56a8 8 0 0 1 8 8v96a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8V76a8 8 0 0 1 8-8zM177.25 191.66a8 8 0 0 1 11.32 0l67.88 67.88a8 8 0 0 1 0 11.31l-39.6 39.6a8 8 0 0 1-11.31 0l-67.88-67.88a8 8 0 0 1 0-11.31l39.6-39.6zm669.6 0l39.6 39.6a8 8 0 0 1 0 11.3l-67.88 67.9a8 8 0 0 1-11.32 0l-39.6-39.6a8 8 0 0 1 0-11.32l67.89-67.88a8 8 0 0 1 11.31 0zM192 892h640a32 32 0 0 1 32 32v24a8 8 0 0 1-8 8H168a8 8 0 0 1-8-8v-24a32 32 0 0 1 32-32zm148-317v253h64V575h-64z\")),t.AlipaySquareFill=u(\"alipay-square\",c,l(o,\"M308.6 545.7c-19.8 2-57.1 10.7-77.4 28.6-61 53-24.5 150 99 150 71.8 0 143.5-45.7 199.8-119-80.2-38.9-148.1-66.8-221.4-59.6zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm29.4 663.2S703 689.4 598.7 639.5C528.8 725.2 438.6 777.3 345 777.3c-158.4 0-212.1-138.1-137.2-229 16.3-19.8 44.2-38.7 87.3-49.4 67.5-16.5 175 10.3 275.7 43.4 18.1-33.3 33.4-69.9 44.7-108.9H305.1V402h160v-56.2H271.3v-31.3h193.8v-80.1s0-13.5 13.7-13.5H557v93.6h191.7v31.3H557.1V402h156.4c-15 61.1-37.7 117.4-66.2 166.8 47.5 17.1 90.1 33.3 121.8 43.9 114.3 38.2 140.2 40.2 140.2 40.2v122.3z\")),t.AliwangwangFill=u(\"aliwangwang\",c,l(o,\"M868.2 377.4c-18.9-45.1-46.3-85.6-81.2-120.6a377.26 377.26 0 0 0-120.5-81.2A375.65 375.65 0 0 0 519 145.8c-41.9 0-82.9 6.7-121.9 20C306 123.3 200.8 120 170.6 120c-2.2 0-7.4 0-9.4.2-11.9.4-22.8 6.5-29.2 16.4-6.5 9.9-7.7 22.4-3.4 33.5l64.3 161.6a378.59 378.59 0 0 0-52.8 193.2c0 51.4 10 101 29.8 147.6 18.9 45 46.2 85.6 81.2 120.5 34.7 34.8 75.4 62.1 120.5 81.2C418.3 894 467.9 904 519 904c51.3 0 100.9-10 147.7-29.8 44.9-18.9 85.5-46.3 120.4-81.2 34.7-34.8 62.1-75.4 81.2-120.6a376.5 376.5 0 0 0 29.8-147.6c-.2-51.2-10.1-100.8-29.9-147.4zm-325.2 79c0 20.4-16.6 37.1-37.1 37.1-20.4 0-37.1-16.7-37.1-37.1v-55.1c0-20.4 16.6-37.1 37.1-37.1 20.4 0 37.1 16.6 37.1 37.1v55.1zm175.2 0c0 20.4-16.6 37.1-37.1 37.1S644 476.8 644 456.4v-55.1c0-20.4 16.7-37.1 37.1-37.1 20.4 0 37.1 16.6 37.1 37.1v55.1z\")),t.AlipayCircleFill=u(\"alipay-circle\",c,l(o,\"M308.6 545.7c-19.8 2-57.1 10.7-77.4 28.6-61 53-24.5 150 99 150 71.8 0 143.5-45.7 199.8-119-80.2-38.9-148.1-66.8-221.4-59.6zm460.5 67c100.1 33.4 154.7 43 166.7 44.8A445.9 445.9 0 0 0 960 512c0-247.4-200.6-448-448-448S64 264.6 64 512s200.6 448 448 448c155.9 0 293.2-79.7 373.5-200.5-75.6-29.8-213.6-85-286.8-120.1-69.9 85.7-160.1 137.8-253.7 137.8-158.4 0-212.1-138.1-137.2-229 16.3-19.8 44.2-38.7 87.3-49.4 67.5-16.5 175 10.3 275.7 43.4 18.1-33.3 33.4-69.9 44.7-108.9H305.1V402h160v-56.2H271.3v-31.3h193.8v-80.1s0-13.5 13.7-13.5H557v93.6h191.7v31.3H557.1V402h156.4c-15 61.1-37.7 117.4-66.2 166.8 47.5 17.1 90.1 33.3 121.8 43.9z\")),t.AmazonCircleFill=u(\"amazon-circle\",c,l(o,\"M485 467.5c-11.6 4.9-20.9 12.2-27.8 22-6.9 9.8-10.4 21.6-10.4 35.5 0 17.8 7.5 31.5 22.4 41.2 14.1 9.1 28.9 11.4 44.4 6.8 17.9-5.2 30-17.9 36.4-38.1 3-9.3 4.5-19.7 4.5-31.3v-50.2c-12.6.4-24.4 1.6-35.5 3.7-11.1 2.1-22.4 5.6-34 10.4zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm35.8 262.7c-7.2-10.9-20.1-16.4-38.7-16.4-1.3 0-3 .1-5.3.3-2.2.2-6.6 1.5-12.9 3.7a79.4 79.4 0 0 0-17.9 9.1c-5.5 3.8-11.5 10-18 18.4-6.4 8.5-11.5 18.4-15.3 29.8l-94-8.4c0-12.4 2.4-24.7 7-36.9 4.7-12.2 11.8-23.9 21.4-35 9.6-11.2 21.1-21 34.5-29.4 13.4-8.5 29.6-15.2 48.4-20.3 18.9-5.1 39.1-7.6 60.9-7.6 21.3 0 40.6 2.6 57.8 7.7 17.2 5.2 31.1 11.5 41.4 19.1a117 117 0 0 1 25.9 25.7c6.9 9.6 11.7 18.5 14.4 26.7 2.7 8.2 4 15.7 4 22.8v182.5c0 6.4 1.4 13 4.3 19.8 2.9 6.8 6.3 12.8 10.2 18 3.9 5.2 7.9 9.9 12 14.3 4.1 4.3 7.6 7.7 10.6 9.9l4.1 3.4-72.5 69.4c-8.5-7.7-16.9-15.4-25.2-23.4-8.3-8-14.5-14-18.5-18.1l-6.1-6.2c-2.4-2.3-5-5.7-8-10.2-8.1 12.2-18.5 22.8-31.1 31.8-12.7 9-26.3 15.6-40.7 19.7-14.5 4.1-29.4 6.5-44.7 7.1-15.3.6-30-1.5-43.9-6.5-13.9-5-26.5-11.7-37.6-20.3-11.1-8.6-19.9-20.2-26.5-35-6.6-14.8-9.9-31.5-9.9-50.4 0-17.4 3-33.3 8.9-47.7 6-14.5 13.6-26.5 23-36.1 9.4-9.6 20.7-18.2 34-25.7s26.4-13.4 39.2-17.7c12.8-4.2 26.6-7.8 41.5-10.7 14.9-2.9 27.6-4.8 38.2-5.7 10.6-.9 21.2-1.6 31.8-2v-39.4c0-13.5-2.3-23.5-6.7-30.1zm180.5 379.6c-2.8 3.3-7.5 7.8-14.1 13.5s-16.8 12.7-30.5 21.1c-13.7 8.4-28.8 16-45 22.9-16.3 6.9-36.3 12.9-60.1 18-23.7 5.1-48.2 7.6-73.3 7.6-25.4 0-50.7-3.2-76.1-9.6-25.4-6.4-47.6-14.3-66.8-23.7-19.1-9.4-37.6-20.2-55.1-32.2-17.6-12.1-31.7-22.9-42.4-32.5-10.6-9.6-19.6-18.7-26.8-27.1-1.7-1.9-2.8-3.6-3.2-5.1-.4-1.5-.3-2.8.3-3.7.6-.9 1.5-1.6 2.6-2.2a7.42 7.42 0 0 1 7.4.8c40.9 24.2 72.9 41.3 95.9 51.4 82.9 36.4 168 45.7 255.3 27.9 40.5-8.3 82.1-22.2 124.9-41.8 3.2-1.2 6-1.5 8.3-.9 2.3.6 3.5 2.4 3.5 5.4 0 2.8-1.6 6.3-4.8 10.2zm59.9-29c-1.8 11.1-4.9 21.6-9.1 31.8-7.2 17.1-16.3 30-27.1 38.4-3.6 2.9-6.4 3.8-8.3 2.8-1.9-1-1.9-3.5 0-7.4 4.5-9.3 9.2-21.8 14.2-37.7 5-15.8 5.7-26 2.1-30.5-1.1-1.5-2.7-2.6-5-3.6-2.2-.9-5.1-1.5-8.6-1.9s-6.7-.6-9.4-.8c-2.8-.2-6.5-.2-11.2 0-4.7.2-8 .4-10.1.6a874.4 874.4 0 0 1-17.1 1.5c-1.3.2-2.7.4-4.1.5-1.5.1-2.7.2-3.5.3l-2.7.3c-1 .1-1.7.2-2.2.2h-3.2l-1-.2-.6-.5-.5-.9c-1.3-3.3 3.7-7.4 15-12.4s22.3-8.1 32.9-9.3c9.8-1.5 21.3-1.5 34.5-.3s21.3 3.7 24.3 7.4c2.3 3.5 2.5 10.7.7 21.7z\")),t.AndroidFill=u(\"android\",c,l(o,\"M270.1 741.7c0 23.4 19.1 42.5 42.6 42.5h48.7v120.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V784.1h85v120.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V784.1h48.7c23.5 0 42.6-19.1 42.6-42.5V346.4h-486v395.3zm357.1-600.1l44.9-65c2.6-3.8 2-8.9-1.5-11.4-3.5-2.4-8.5-1.2-11.1 2.6l-46.6 67.6c-30.7-12.1-64.9-18.8-100.8-18.8-35.9 0-70.1 6.7-100.8 18.8l-46.6-67.5c-2.6-3.8-7.6-5.1-11.1-2.6-3.5 2.4-4.1 7.4-1.5 11.4l44.9 65c-71.4 33.2-121.4 96.1-127.8 169.6h486c-6.6-73.6-56.7-136.5-128-169.7zM409.5 244.1a26.9 26.9 0 1 1 26.9-26.9 26.97 26.97 0 0 1-26.9 26.9zm208.4 0a26.9 26.9 0 1 1 26.9-26.9 26.97 26.97 0 0 1-26.9 26.9zm223.4 100.7c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V400.1c.1-30.6-24.3-55.3-54.6-55.3zm-658.6 0c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4 30.2 0 54.6-24.8 54.6-55.4V400.1c0-30.6-24.5-55.3-54.6-55.3z\")),t.AmazonSquareFill=u(\"amazon-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM547.8 326.7c-7.2-10.9-20.1-16.4-38.7-16.4-1.3 0-3 .1-5.3.3-2.2.2-6.6 1.5-12.9 3.7a79.4 79.4 0 0 0-17.9 9.1c-5.5 3.8-11.5 10-18 18.4-6.4 8.5-11.5 18.4-15.3 29.8l-94-8.4c0-12.4 2.4-24.7 7-36.9s11.8-23.9 21.4-35c9.6-11.2 21.1-21 34.5-29.4 13.4-8.5 29.6-15.2 48.4-20.3 18.9-5.1 39.1-7.6 60.9-7.6 21.3 0 40.6 2.6 57.8 7.7 17.2 5.2 31.1 11.5 41.4 19.1a117 117 0 0 1 25.9 25.7c6.9 9.6 11.7 18.5 14.4 26.7 2.7 8.2 4 15.7 4 22.8v182.5c0 6.4 1.4 13 4.3 19.8 2.9 6.8 6.3 12.8 10.2 18 3.9 5.2 7.9 9.9 12 14.3 4.1 4.3 7.6 7.7 10.6 9.9l4.1 3.4-72.5 69.4c-8.5-7.7-16.9-15.4-25.2-23.4-8.3-8-14.5-14-18.5-18.1l-6.1-6.2c-2.4-2.3-5-5.7-8-10.2-8.1 12.2-18.5 22.8-31.1 31.8-12.7 9-26.3 15.6-40.7 19.7-14.5 4.1-29.4 6.5-44.7 7.1-15.3.6-30-1.5-43.9-6.5-13.9-5-26.5-11.7-37.6-20.3-11.1-8.6-19.9-20.2-26.5-35-6.6-14.8-9.9-31.5-9.9-50.4 0-17.4 3-33.3 8.9-47.7 6-14.5 13.6-26.5 23-36.1 9.4-9.6 20.7-18.2 34-25.7s26.4-13.4 39.2-17.7c12.8-4.2 26.6-7.8 41.5-10.7 14.9-2.9 27.6-4.8 38.2-5.7 10.6-.9 21.2-1.6 31.8-2v-39.4c0-13.5-2.3-23.5-6.7-30.1zm180.5 379.6c-2.8 3.3-7.5 7.8-14.1 13.5s-16.8 12.7-30.5 21.1c-13.7 8.4-28.8 16-45 22.9-16.3 6.9-36.3 12.9-60.1 18-23.7 5.1-48.2 7.6-73.3 7.6-25.4 0-50.7-3.2-76.1-9.6-25.4-6.4-47.6-14.3-66.8-23.7-19.1-9.4-37.6-20.2-55.1-32.2-17.6-12.1-31.7-22.9-42.4-32.5-10.6-9.6-19.6-18.7-26.8-27.1-1.7-1.9-2.8-3.6-3.2-5.1-.4-1.5-.3-2.8.3-3.7.6-.9 1.5-1.6 2.6-2.2a7.42 7.42 0 0 1 7.4.8c40.9 24.2 72.9 41.3 95.9 51.4 82.9 36.4 168 45.7 255.3 27.9 40.5-8.3 82.1-22.2 124.9-41.8 3.2-1.2 6-1.5 8.3-.9 2.3.6 3.5 2.4 3.5 5.4 0 2.8-1.6 6.3-4.8 10.2zm59.9-29c-1.8 11.1-4.9 21.6-9.1 31.8-7.2 17.1-16.3 30-27.1 38.4-3.6 2.9-6.4 3.8-8.3 2.8-1.9-1-1.9-3.5 0-7.4 4.5-9.3 9.2-21.8 14.2-37.7 5-15.8 5.7-26 2.1-30.5-1.1-1.5-2.7-2.6-5-3.6-2.2-.9-5.1-1.5-8.6-1.9s-6.7-.6-9.4-.8c-2.8-.2-6.5-.2-11.2 0-4.7.2-8 .4-10.1.6a874.4 874.4 0 0 1-17.1 1.5c-1.3.2-2.7.4-4.1.5-1.5.1-2.7.2-3.5.3l-2.7.3c-1 .1-1.7.2-2.2.2h-3.2l-1-.2-.6-.5-.5-.9c-1.3-3.3 3.7-7.4 15-12.4s22.3-8.1 32.9-9.3c9.8-1.5 21.3-1.5 34.5-.3s21.3 3.7 24.3 7.4c2.3 3.5 2.5 10.7.7 21.7zM485 467.5c-11.6 4.9-20.9 12.2-27.8 22-6.9 9.8-10.4 21.6-10.4 35.5 0 17.8 7.5 31.5 22.4 41.2 14.1 9.1 28.9 11.4 44.4 6.8 17.9-5.2 30-17.9 36.4-38.1 3-9.3 4.5-19.7 4.5-31.3v-50.2c-12.6.4-24.4 1.6-35.5 3.7-11.1 2.1-22.4 5.6-34 10.4z\")),t.ApiFill=u(\"api\",c,l(o,\"M917.7 148.8l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 0 0-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 0 0 0 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM578.9 546.7a8.03 8.03 0 0 0-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 0 0-11.3 0L363 475.3l-43-43a7.85 7.85 0 0 0-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2c-68.9 68.9-77 175.7-24.3 253.5l-76.1 76.1a8.03 8.03 0 0 0 0 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2z\")),t.AppstoreFill=u(\"appstore\",c,l(o,\"M864 144H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm0 400H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zM464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm0 400H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16z\")),t.AudioFill=u(\"audio\",c,l(o,\"M512 624c93.9 0 170-75.2 170-168V232c0-92.8-76.1-168-170-168s-170 75.2-170 168v224c0 92.8 76.1 168 170 168zm330-170c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 140.3-113.7 254-254 254S258 594.3 258 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 168.7 126.6 307.9 290 327.6V884H326.7c-13.7 0-24.7 14.3-24.7 32v36c0 4.4 2.8 8 6.2 8h407.6c3.4 0 6.2-3.6 6.2-8v-36c0-17.7-11-32-24.7-32H548V782.1c165.3-18 294-158 294-328.1z\")),t.AppleFill=u(\"apple\",c,l(o,\"M747.4 535.7c-.4-68.2 30.5-119.6 92.9-157.5-34.9-50-87.7-77.5-157.3-82.8-65.9-5.2-138 38.4-164.4 38.4-27.9 0-91.7-36.6-141.9-36.6C273.1 298.8 163 379.8 163 544.6c0 48.7 8.9 99 26.7 150.8 23.8 68.2 109.6 235.3 199.1 232.6 46.8-1.1 79.9-33.2 140.8-33.2 59.1 0 89.7 33.2 141.9 33.2 90.3-1.3 167.9-153.2 190.5-221.6-121.1-57.1-114.6-167.2-114.6-170.7zm-105.1-305c50.7-60.2 46.1-115 44.6-134.7-44.8 2.6-96.6 30.5-126.1 64.8-32.5 36.8-51.6 82.3-47.5 133.6 48.4 3.7 92.6-21.2 129-63.7z\")),t.BackwardFill=u(\"backward\",c,l(r,\"M485.6 249.9L198.2 498c-8.3 7.1-8.3 20.8 0 27.9l287.4 248.2c10.7 9.2 26.4.9 26.4-14V263.8c0-14.8-15.7-23.2-26.4-13.9zm320 0L518.2 498a18.6 18.6 0 0 0-6.2 14c0 5.2 2.1 10.4 6.2 14l287.4 248.2c10.7 9.2 26.4.9 26.4-14V263.8c0-14.8-15.7-23.2-26.4-13.9z\")),t.BankFill=u(\"bank\",c,l(o,\"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 0 0-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM381 836H264V462h117v374zm189 0H453V462h117v374zm190 0H642V462h118v374z\")),t.BehanceCircleFill=u(\"behance-circle\",c,l(o,\"M420.3 470.3c8.7-6.3 12.9-16.7 12.9-31 .3-6.8-1.1-13.5-4.1-19.6-2.7-4.9-6.7-9-11.6-11.9a44.8 44.8 0 0 0-16.6-6c-6.4-1.2-12.9-1.8-19.3-1.7h-70.3v79.7h76.1c13.1.1 24.2-3.1 32.9-9.5zm11.8 72c-9.8-7.5-22.9-11.2-39.2-11.2h-81.8v94h80.2c7.5 0 14.4-.7 21.1-2.1a50.5 50.5 0 0 0 17.8-7.2c5.1-3.3 9.2-7.8 12.3-13.6 3-5.8 4.5-13.2 4.5-22.1 0-17.7-5-30.2-14.9-37.8zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm86.5 286.9h138.4v33.7H598.5v-33.7zM512 628.8a89.52 89.52 0 0 1-27 31c-11.8 8.2-24.9 14.2-38.8 17.7a167.4 167.4 0 0 1-44.6 5.7H236V342.1h161c16.3 0 31.1 1.5 44.6 4.3 13.4 2.8 24.8 7.6 34.4 14.1 9.5 6.5 17 15.2 22.3 26 5.2 10.7 7.9 24.1 7.9 40 0 17.2-3.9 31.4-11.7 42.9-7.9 11.5-19.3 20.8-34.8 28.1 21.1 6 36.6 16.7 46.8 31.7 10.4 15.2 15.5 33.4 15.5 54.8 0 17.4-3.3 32.3-10 44.8zM790.8 576H612.4c0 19.4 6.7 38 16.8 48 10.2 9.9 24.8 14.9 43.9 14.9 13.8 0 25.5-3.5 35.5-10.4 9.9-6.9 15.9-14.2 18.1-21.8h59.8c-9.6 29.7-24.2 50.9-44 63.7-19.6 12.8-43.6 19.2-71.5 19.2-19.5 0-37-3.2-52.7-9.3-15.1-5.9-28.7-14.9-39.9-26.5a121.2 121.2 0 0 1-25.1-41.2c-6.1-16.9-9.1-34.7-8.9-52.6 0-18.5 3.1-35.7 9.1-51.7 11.5-31.1 35.4-56 65.9-68.9 16.3-6.8 33.8-10.2 51.5-10 21 0 39.2 4 55 12.2a111.6 111.6 0 0 1 38.6 32.8c10.1 13.7 17.2 29.3 21.7 46.9 4.3 17.3 5.8 35.5 4.6 54.7zm-122-95.6c-10.8 0-19.9 1.9-26.9 5.6-7 3.7-12.8 8.3-17.2 13.6a48.4 48.4 0 0 0-9.1 17.4c-1.6 5.3-2.7 10.7-3.1 16.2H723c-1.6-17.3-7.6-30.1-15.6-39.1-8.4-8.9-21.9-13.7-38.6-13.7z\")),t.BellFill=u(\"bell\",c,l(o,\"M816 768h-24V428c0-141.1-104.3-257.8-240-277.2V112c0-22.1-17.9-40-40-40s-40 17.9-40 40v38.8C336.3 170.2 232 286.9 232 428v340h-24c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h216c0 61.8 50.2 112 112 112s112-50.2 112-112h216c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM512 888c-26.5 0-48-21.5-48-48h96c0 26.5-21.5 48-48 48z\")),t.BehanceSquareFill=u(\"behance-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM598.5 350.9h138.4v33.7H598.5v-33.7zM512 628.8a89.52 89.52 0 0 1-27 31c-11.8 8.2-24.9 14.2-38.8 17.7a167.4 167.4 0 0 1-44.6 5.7H236V342.1h161c16.3 0 31.1 1.5 44.6 4.3 13.4 2.8 24.8 7.6 34.4 14.1 9.5 6.5 17 15.2 22.3 26 5.2 10.7 7.9 24.1 7.9 40 0 17.2-3.9 31.4-11.7 42.9-7.9 11.5-19.3 20.8-34.8 28.1 21.1 6 36.6 16.7 46.8 31.7 10.4 15.2 15.5 33.4 15.5 54.8 0 17.4-3.3 32.3-10 44.8zM790.8 576H612.4c0 19.4 6.7 38 16.8 48 10.2 9.9 24.8 14.9 43.9 14.9 13.8 0 25.5-3.5 35.5-10.4 9.9-6.9 15.9-14.2 18.1-21.8h59.8c-9.6 29.7-24.2 50.9-44 63.7-19.6 12.8-43.6 19.2-71.5 19.2-19.5 0-37-3.2-52.7-9.3-15.1-5.9-28.7-14.9-39.9-26.5a121.2 121.2 0 0 1-25.1-41.2c-6.1-16.9-9.1-34.7-8.9-52.6 0-18.5 3.1-35.7 9.1-51.7 11.5-31.1 35.4-56 65.9-68.9 16.3-6.8 33.8-10.2 51.5-10 21 0 39.2 4 55 12.2a111.6 111.6 0 0 1 38.6 32.8c10.1 13.7 17.2 29.3 21.7 46.9 4.3 17.3 5.8 35.5 4.6 54.7zm-122-95.6c-10.8 0-19.9 1.9-26.9 5.6-7 3.7-12.8 8.3-17.2 13.6a48.4 48.4 0 0 0-9.1 17.4c-1.6 5.3-2.7 10.7-3.1 16.2H723c-1.6-17.3-7.6-30.1-15.6-39.1-8.4-8.9-21.9-13.7-38.6-13.7zm-248.5-10.1c8.7-6.3 12.9-16.7 12.9-31 .3-6.8-1.1-13.5-4.1-19.6-2.7-4.9-6.7-9-11.6-11.9a44.8 44.8 0 0 0-16.6-6c-6.4-1.2-12.9-1.8-19.3-1.7h-70.3v79.7h76.1c13.1.1 24.2-3.1 32.9-9.5zm11.8 72c-9.8-7.5-22.9-11.2-39.2-11.2h-81.8v94h80.2c7.5 0 14.4-.7 21.1-2.1s12.7-3.8 17.8-7.2c5.1-3.3 9.2-7.8 12.3-13.6 3-5.8 4.5-13.2 4.5-22.1 0-17.7-5-30.2-14.9-37.8z\")),t.BookFill=u(\"book\",c,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zM668 345.9L621.5 312 572 347.4V124h96v221.9z\")),t.BoxPlotFill=u(\"box-plot\",c,l(o,\"M952 224h-52c-4.4 0-8 3.6-8 8v248h-92V304c0-4.4-3.6-8-8-8H448v432h344c4.4 0 8-3.6 8-8V548h92v244c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zm-728 80v176h-92V232c0-4.4-3.6-8-8-8H72c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V548h92v172c0 4.4 3.6 8 8 8h152V296H232c-4.4 0-8 3.6-8 8z\")),t.BugFill=u(\"bug\",c,l(o,\"M304 280h416c4.4 0 8-3.6 8-8 0-40-8.8-76.7-25.9-108.1a184.31 184.31 0 0 0-74-74C596.7 72.8 560 64 520 64h-16c-40 0-76.7 8.8-108.1 25.9a184.31 184.31 0 0 0-74 74C304.8 195.3 296 232 296 272c0 4.4 3.6 8 8 8z\",\"M940 512H792V412c76.8 0 139-62.2 139-139 0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8a63 63 0 0 1-63 63H232a63 63 0 0 1-63-63c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 76.8 62.2 139 139 139v100H84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h148v96c0 6.5.2 13 .7 19.3C164.1 728.6 116 796.7 116 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-44.2 23.9-82.9 59.6-103.7a273 273 0 0 0 22.7 49c24.3 41.5 59 76.2 100.5 100.5 28.9 16.9 61 28.8 95.3 34.5 4.4 0 8-3.6 8-8V484c0-4.4 3.6-8 8-8h60c4.4 0 8 3.6 8 8v464.2c0 4.4 3.6 8 8 8 34.3-5.7 66.4-17.6 95.3-34.5a281.38 281.38 0 0 0 123.2-149.5A120.4 120.4 0 0 1 836 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-79.3-48.1-147.4-116.7-176.7.4-6.4.7-12.8.7-19.3v-96h148c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.CalculatorFill=u(\"calculator\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM440.2 765h-50.8c-2.2 0-4.5-1.1-5.9-2.9L348 718.6l-35.5 43.5a7.38 7.38 0 0 1-5.9 2.9h-50.8c-6.6 0-10.2-7.9-5.8-13.1l62.7-76.8-61.2-74.9c-4.3-5.2-.7-13.1 5.9-13.1h50.9c2.2 0 4.5 1.1 5.9 2.9l34 41.6 34-41.6c1.5-1.9 3.6-2.9 5.9-2.9h50.8c6.6 0 10.2 7.9 5.9 13.1L383.5 675l62.7 76.8c4.2 5.3.6 13.2-6 13.2zm7.8-382c0 2.2-1.4 4-3.2 4H376v68.7c0 1.9-1.8 3.3-4 3.3h-48c-2.2 0-4-1.4-4-3.2V387h-68.8c-1.8 0-3.2-1.8-3.2-4v-48c0-2.2 1.4-4 3.2-4H320v-68.8c0-1.8 1.8-3.2 4-3.2h48c2.2 0 4 1.4 4 3.2V331h68.7c1.9 0 3.3 1.8 3.3 4v48zm328 369c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48zm0-104c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48zm0-265c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48z\")),t.BulbFill=u(\"bulb\",c,l(o,\"M348 676.1C250 619.4 184 513.4 184 392c0-181.1 146.9-328 328-328s328 146.9 328 328c0 121.4-66 227.4-164 284.1V792c0 17.7-14.3 32-32 32H380c-17.7 0-32-14.3-32-32V676.1zM392 888h240c4.4 0 8 3.6 8 8v32c0 17.7-14.3 32-32 32H416c-17.7 0-32-14.3-32-32v-32c0-4.4 3.6-8 8-8z\")),t.BuildFill=u(\"build\",c,l(o,\"M916 210H376c-17.7 0-32 14.3-32 32v236H108c-17.7 0-32 14.3-32 32v272c0 17.7 14.3 32 32 32h540c17.7 0 32-14.3 32-32V546h236c17.7 0 32-14.3 32-32V242c0-17.7-14.3-32-32-32zM612 746H412V546h200v200zm268-268H680V278h200v200z\")),t.CalendarFill=u(\"calendar\",c,l(o,\"M112 880c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V460H112v420zm768-696H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v176h800V216c0-17.7-14.3-32-32-32z\")),t.CameraFill=u(\"camera\",c,l(o,\"M864 260H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 260H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V340c0-44.2-35.8-80-80-80zM512 716c-88.4 0-160-71.6-160-160s71.6-160 160-160 160 71.6 160 160-71.6 160-160 160zm-96-160a96 96 0 1 0 192 0 96 96 0 1 0-192 0z\")),t.CarFill=u(\"car\",c,l(o,\"M959 413.4L935.3 372a8 8 0 0 0-10.9-2.9l-50.7 29.6-78.3-216.2a63.9 63.9 0 0 0-60.9-44.4H301.2c-34.7 0-65.5 22.4-76.2 55.5l-74.6 205.2-50.8-29.6a8 8 0 0 0-10.9 2.9L65 413.4c-2.2 3.8-.9 8.6 2.9 10.8l60.4 35.2-14.5 40c-1.2 3.2-1.8 6.6-1.8 10v348.2c0 15.7 11.8 28.4 26.3 28.4h67.6c12.3 0 23-9.3 25.6-22.3l7.7-37.7h545.6l7.7 37.7c2.7 13 13.3 22.3 25.6 22.3h67.6c14.5 0 26.3-12.7 26.3-28.4V509.4c0-3.4-.6-6.8-1.8-10l-14.5-40 60.3-35.2a8 8 0 0 0 3-10.8zM264 621c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm388 75c0 4.4-3.6 8-8 8H380c-4.4 0-8-3.6-8-8v-84c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v36h168v-36c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v84zm108-75c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zM220 418l72.7-199.9.5-1.3.4-1.3c1.1-3.3 4.1-5.5 7.6-5.5h427.6l75.4 208H220z\")),t.CaretDownFill=u(\"caret-down\",c,l(r,\"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z\")),t.CaretLeftFill=u(\"caret-left\",c,l(r,\"M689 165.1L308.2 493.5c-10.9 9.4-10.9 27.5 0 37L689 858.9c14.2 12.2 35 1.2 35-18.5V183.6c0-19.7-20.8-30.7-35-18.5z\")),t.CaretRightFill=u(\"caret-right\",c,l(r,\"M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z\")),t.CarryOutFill=u(\"carry-out\",c,l(o,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zM694.5 432.7L481.9 725.4a16.1 16.1 0 0 1-26 0l-126.4-174c-3.8-5.3 0-12.7 6.5-12.7h55.2c5.1 0 10 2.5 13 6.6l64.7 89 150.9-207.8c3-4.1 7.8-6.6 13-6.6H688c6.5.1 10.3 7.5 6.5 12.8z\")),t.CaretUpFill=u(\"caret-up\",c,l(r,\"M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z\")),t.CheckCircleFill=u(\"check-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z\")),t.CheckSquareFill=u(\"check-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM695.5 365.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L308.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H689c6.5 0 10.3 7.4 6.5 12.7z\")),t.ChromeFill=u(\"chrome\",c,l(o,\"M371.8 512c0 77.5 62.7 140.2 140.2 140.2S652.2 589.5 652.2 512 589.5 371.8 512 371.8 371.8 434.4 371.8 512zM900 362.4l-234.3 12.1c63.6 74.3 64.6 181.5 11.1 263.7l-188 289.2c78 4.2 158.4-12.9 231.2-55.2 180-104 253-322.1 180-509.8zM320.3 591.9L163.8 284.1A415.35 415.35 0 0 0 96 512c0 208 152.3 380.3 351.4 410.8l106.9-209.4c-96.6 18.2-189.9-34.8-234-121.5zm218.5-285.5l344.4 18.1C848 254.7 792.6 194 719.8 151.7 653.9 113.6 581.5 95.5 510.5 96c-122.5.5-242.2 55.2-322.1 154.5l128.2 196.9c32-91.9 124.8-146.7 222.2-141z\")),t.CiCircleFill=u(\"ci-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-63.6 656c-103 0-162.4-68.6-162.4-182.6v-49C286 373.5 345.4 304 448.3 304c88.3 0 152.3 56.9 152.3 138.1 0 2.4-2 4.4-4.4 4.4h-52.6c-4.2 0-7.6-3.2-8-7.4-4-46.1-37.6-77.6-87-77.6-61.1 0-95.6 45.4-95.6 126.9v49.3c0 80.3 34.5 125.1 95.6 125.1 49.3 0 82.8-29.5 87-72.4.4-4.1 3.8-7.3 8-7.3h52.7c2.4 0 4.4 2 4.4 4.4 0 77.4-64.3 132.5-152.3 132.5zM738 704.1c0 4.4-3.6 8-8 8h-50.4c-4.4 0-8-3.6-8-8V319.9c0-4.4 3.6-8 8-8H730c4.4 0 8 3.6 8 8v384.2z\")),t.ClockCircleFill=u(\"clock-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm176.5 585.7l-28.6 39a7.99 7.99 0 0 1-11.2 1.7L483.3 569.8a7.92 7.92 0 0 1-3.3-6.5V288c0-4.4 3.6-8 8-8h48.1c4.4 0 8 3.6 8 8v247.5l142.6 103.1c3.6 2.5 4.4 7.5 1.8 11.1z\")),t.CloseCircleFill=u(\"close-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 0 1-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z\")),t.CloudFill=u(\"cloud\",c,l(o,\"M811.4 418.7C765.6 297.9 648.9 212 512.2 212S258.8 297.8 213 418.6C127.3 441.1 64 519.1 64 612c0 110.5 89.5 200 199.9 200h496.2C870.5 812 960 722.5 960 612c0-92.7-63.1-170.7-148.6-193.3z\")),t.CloseSquareFill=u(\"close-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM676.1 657.9c4.4 5.2.7 13.1-6.1 13.1h-58.9c-4.7 0-9.2-2.1-12.3-5.7L512 561.8l-86.8 103.5c-3 3.6-7.5 5.7-12.3 5.7H354c-6.8 0-10.5-7.9-6.1-13.1L470.2 512 347.9 366.1A7.95 7.95 0 0 1 354 353h58.9c4.7 0 9.2 2.1 12.3 5.7L512 462.2l86.8-103.5c3-3.6 7.5-5.7 12.3-5.7H670c6.8 0 10.5 7.9 6.1 13.1L553.8 512l122.3 145.9z\")),t.CodeSandboxSquareFill=u(\"code-sandbox-square\",c,l(o,\"M307.9 536.7l87.6 49.9V681l96.7 55.9V524.8L307.9 418.4zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM755.7 653.2L512 794 268.3 653.2V371.8l110-63.6-.4-.2h.2L512 231l134 77h-.2l-.3.2 110.1 63.6v281.4zm-223.9 83.7l97.3-56.2v-94.1l87-49.5V418.5L531.8 525zm-20-352L418 331l-91.1 52.6 185.2 107 185.2-106.9-91.4-52.8z\")),t.CodeSandboxCircleFill=u(\"code-sandbox-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm243.7 589.2L512 794 268.3 653.2V371.8l110-63.6-.4-.2h.2L512 231l134 77h-.2l-.3.2 110.1 63.6v281.4zM307.9 536.7l87.6 49.9V681l96.7 55.9V524.8L307.9 418.4zm203.9-151.8L418 331l-91.1 52.6 185.2 107 185.2-106.9-91.4-52.8zm20 352l97.3-56.2v-94.1l87-49.5V418.5L531.8 525z\")),t.CodeFill=u(\"code\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM513.1 518.1l-192 161c-5.2 4.4-13.1.7-13.1-6.1v-62.7c0-2.3 1.1-4.6 2.9-6.1L420.7 512l-109.8-92.2a7.63 7.63 0 0 1-2.9-6.1V351c0-6.8 7.9-10.5 13.1-6.1l192 160.9c3.9 3.2 3.9 9.1 0 12.3zM716 673c0 4.4-3.4 8-7.5 8h-185c-4.1 0-7.5-3.6-7.5-8v-48c0-4.4 3.4-8 7.5-8h185c4.1 0 7.5 3.6 7.5 8v48z\")),t.CompassFill=u(\"compass\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM327.3 702.4c-2 .9-4.4 0-5.3-2.1-.4-1-.4-2.2 0-3.2l98.7-225.5 132.1 132.1-225.5 98.7zm375.1-375.1l-98.7 225.5-132.1-132.1L697.1 322c2-.9 4.4 0 5.3 2.1.4 1 .4 2.1 0 3.2z\")),t.CodepenCircleFill=u(\"codepen-circle\",c,l(o,\"M488.1 414.7V303.4L300.9 428l83.6 55.8zm254.1 137.7v-79.8l-59.8 39.9zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm278 533c0 1.1-.1 2.1-.2 3.1 0 .4-.1.7-.2 1a14.16 14.16 0 0 1-.8 3.2c-.2.6-.4 1.2-.6 1.7-.2.4-.4.8-.5 1.2-.3.5-.5 1.1-.8 1.6-.2.4-.4.7-.7 1.1-.3.5-.7 1-1 1.5-.3.4-.5.7-.8 1-.4.4-.8.9-1.2 1.3-.3.3-.6.6-1 .9-.4.4-.9.8-1.4 1.1-.4.3-.7.6-1.1.8-.1.1-.3.2-.4.3L525.2 786c-4 2.7-8.6 4-13.2 4-4.7 0-9.3-1.4-13.3-4L244.6 616.9c-.1-.1-.3-.2-.4-.3l-1.1-.8c-.5-.4-.9-.7-1.3-1.1-.3-.3-.6-.6-1-.9-.4-.4-.8-.8-1.2-1.3a7 7 0 0 1-.8-1c-.4-.5-.7-1-1-1.5-.2-.4-.5-.7-.7-1.1-.3-.5-.6-1.1-.8-1.6-.2-.4-.4-.8-.5-1.2-.2-.6-.4-1.2-.6-1.7-.1-.4-.3-.8-.4-1.2-.2-.7-.3-1.3-.4-2-.1-.3-.1-.7-.2-1-.1-1-.2-2.1-.2-3.1V427.9c0-1 .1-2.1.2-3.1.1-.3.1-.7.2-1a14.16 14.16 0 0 1 .8-3.2c.2-.6.4-1.2.6-1.7.2-.4.4-.8.5-1.2.2-.5.5-1.1.8-1.6.2-.4.4-.7.7-1.1.6-.9 1.2-1.7 1.8-2.5.4-.4.8-.9 1.2-1.3.3-.3.6-.6 1-.9.4-.4.9-.8 1.3-1.1.4-.3.7-.6 1.1-.8.1-.1.3-.2.4-.3L498.7 239c8-5.3 18.5-5.3 26.5 0l254.1 169.1c.1.1.3.2.4.3l1.1.8 1.4 1.1c.3.3.6.6 1 .9.4.4.8.8 1.2 1.3.7.8 1.3 1.6 1.8 2.5.2.4.5.7.7 1.1.3.5.6 1 .8 1.6.2.4.4.8.5 1.2.2.6.4 1.2.6 1.7.1.4.3.8.4 1.2.2.7.3 1.3.4 2 .1.3.1.7.2 1 .1 1 .2 2.1.2 3.1V597zm-254.1 13.3v111.3L723.1 597l-83.6-55.8zM281.8 472.6v79.8l59.8-39.9zM512 456.1l-84.5 56.4 84.5 56.4 84.5-56.4zM723.1 428L535.9 303.4v111.3l103.6 69.1zM384.5 541.2L300.9 597l187.2 124.6V610.3l-103.6-69.1z\")),t.CodepenSquareFill=u(\"codepen-square\",c,l(o,\"M723.1 428L535.9 303.4v111.3l103.6 69.1zM512 456.1l-84.5 56.4 84.5 56.4 84.5-56.4zm23.9 154.2v111.3L723.1 597l-83.6-55.8zm-151.4-69.1L300.9 597l187.2 124.6V610.3l-103.6-69.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-90 485c0 1.1-.1 2.1-.2 3.1 0 .4-.1.7-.2 1a14.16 14.16 0 0 1-.8 3.2c-.2.6-.4 1.2-.6 1.7-.2.4-.4.8-.5 1.2-.3.5-.5 1.1-.8 1.6-.2.4-.4.7-.7 1.1-.3.5-.7 1-1 1.5-.3.4-.5.7-.8 1-.4.4-.8.9-1.2 1.3-.3.3-.6.6-1 .9-.4.4-.9.8-1.4 1.1-.4.3-.7.6-1.1.8-.1.1-.3.2-.4.3L525.2 786c-4 2.7-8.6 4-13.2 4-4.7 0-9.3-1.4-13.3-4L244.6 616.9c-.1-.1-.3-.2-.4-.3l-1.1-.8c-.5-.4-.9-.7-1.3-1.1-.3-.3-.6-.6-1-.9-.4-.4-.8-.8-1.2-1.3a7 7 0 0 1-.8-1c-.4-.5-.7-1-1-1.5-.2-.4-.5-.7-.7-1.1-.3-.5-.6-1.1-.8-1.6-.2-.4-.4-.8-.5-1.2-.2-.6-.4-1.2-.6-1.7-.1-.4-.3-.8-.4-1.2-.2-.7-.3-1.3-.4-2-.1-.3-.1-.7-.2-1-.1-1-.2-2.1-.2-3.1V427.9c0-1 .1-2.1.2-3.1.1-.3.1-.7.2-1a14.16 14.16 0 0 1 .8-3.2c.2-.6.4-1.2.6-1.7.2-.4.4-.8.5-1.2.2-.5.5-1.1.8-1.6.2-.4.4-.7.7-1.1.6-.9 1.2-1.7 1.8-2.5.4-.4.8-.9 1.2-1.3.3-.3.6-.6 1-.9.4-.4.9-.8 1.3-1.1.4-.3.7-.6 1.1-.8.1-.1.3-.2.4-.3L498.7 239c8-5.3 18.5-5.3 26.5 0l254.1 169.1c.1.1.3.2.4.3l1.1.8 1.4 1.1c.3.3.6.6 1 .9.4.4.8.8 1.2 1.3.7.8 1.3 1.6 1.8 2.5.2.4.5.7.7 1.1.3.5.6 1 .8 1.6.2.4.4.8.5 1.2.2.6.4 1.2.6 1.7.1.4.3.8.4 1.2.2.7.3 1.3.4 2 .1.3.1.7.2 1 .1 1 .2 2.1.2 3.1V597zm-47.8-44.6v-79.8l-59.8 39.9zm-460.4-79.8v79.8l59.8-39.9zm206.3-57.9V303.4L300.9 428l83.6 55.8z\")),t.ContactsFill=u(\"contacts\",c,l(o,\"M928 224H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zM661 736h-43.9c-4.2 0-7.6-3.3-7.9-7.5-3.8-50.6-46-90.5-97.2-90.5s-93.4 40-97.2 90.5c-.3 4.2-3.7 7.5-7.9 7.5H363a8 8 0 0 1-8-8.4c2.8-53.3 32-99.7 74.6-126.1a111.8 111.8 0 0 1-29.1-75.5c0-61.9 49.9-112 111.4-112 61.5 0 111.4 50.1 111.4 112 0 29.1-11 55.5-29.1 75.5 42.7 26.5 71.8 72.8 74.6 126.1.4 4.6-3.2 8.4-7.8 8.4zM512 474c-28.5 0-51.7 23.3-51.7 52s23.2 52 51.7 52c28.5 0 51.7-23.3 51.7-52s-23.2-52-51.7-52z\")),t.ControlFill=u(\"control\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM404 683v77c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-77c-41.7-13.6-72-52.8-72-99s30.3-85.5 72-99V264c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v221c41.7 13.6 72 52.8 72 99s-30.3 85.5-72 99zm279.6-143.9c.2 0 .3-.1.4-.1v221c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V539c.2 0 .3.1.4.1-42-13.4-72.4-52.7-72.4-99.1 0-46.4 30.4-85.7 72.4-99.1-.2 0-.3.1-.4.1v-77c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v77c-.2 0-.3-.1-.4-.1 42 13.4 72.4 52.7 72.4 99.1 0 46.4-30.4 85.7-72.4 99.1zM616 440a36 36 0 1 0 72 0 36 36 0 1 0-72 0zM403.4 566.5l-1.5-2.4c0-.1-.1-.1-.1-.2l-.9-1.2c-.1-.1-.2-.2-.2-.3-1-1.3-2-2.5-3.2-3.6l-.2-.2c-.4-.4-.8-.8-1.2-1.1-.8-.8-1.7-1.5-2.6-2.1h-.1l-1.2-.9c-.1-.1-.3-.2-.4-.3-1.2-.8-2.5-1.6-3.9-2.2-.2-.1-.5-.2-.7-.4-.4-.2-.7-.3-1.1-.5-.3-.1-.7-.3-1-.4-.5-.2-1-.4-1.5-.5-.4-.1-.9-.3-1.3-.4l-.9-.3-1.4-.3c-.2-.1-.5-.1-.7-.2-.7-.1-1.4-.3-2.1-.4-.2 0-.4 0-.6-.1-.6-.1-1.1-.1-1.7-.2-.2 0-.4 0-.7-.1-.8 0-1.5-.1-2.3-.1s-1.5 0-2.3.1c-.2 0-.4 0-.7.1-.6 0-1.2.1-1.7.2-.2 0-.4 0-.6.1-.7.1-1.4.2-2.1.4-.2.1-.5.1-.7.2l-1.4.3-.9.3c-.4.1-.9.3-1.3.4-.5.2-1 .4-1.5.5-.3.1-.7.3-1 .4-.4.2-.7.3-1.1.5-.2.1-.5.2-.7.4-1.3.7-2.6 1.4-3.9 2.2-.1.1-.3.2-.4.3l-1.2.9h-.1c-.9.7-1.8 1.4-2.6 2.1-.4.4-.8.7-1.2 1.1l-.2.2a54.8 54.8 0 0 0-3.2 3.6c-.1.1-.2.2-.2.3l-.9 1.2c0 .1-.1.1-.1.2l-1.5 2.4c-.1.2-.2.3-.3.5-2.7 5.1-4.3 10.9-4.3 17s1.6 12 4.3 17c.1.2.2.3.3.5l1.5 2.4c0 .1.1.1.1.2l.9 1.2c.1.1.2.2.2.3 1 1.3 2 2.5 3.2 3.6l.2.2c.4.4.8.8 1.2 1.1.8.8 1.7 1.5 2.6 2.1h.1l1.2.9c.1.1.3.2.4.3 1.2.8 2.5 1.6 3.9 2.2.2.1.5.2.7.4.4.2.7.3 1.1.5.3.1.7.3 1 .4.5.2 1 .4 1.5.5.4.1.9.3 1.3.4l.9.3 1.4.3c.2.1.5.1.7.2.7.1 1.4.3 2.1.4.2 0 .4 0 .6.1.6.1 1.1.1 1.7.2.2 0 .4 0 .7.1.8 0 1.5.1 2.3.1s1.5 0 2.3-.1c.2 0 .4 0 .7-.1.6 0 1.2-.1 1.7-.2.2 0 .4 0 .6-.1.7-.1 1.4-.2 2.1-.4.2-.1.5-.1.7-.2l1.4-.3.9-.3c.4-.1.9-.3 1.3-.4.5-.2 1-.4 1.5-.5.3-.1.7-.3 1-.4.4-.2.7-.3 1.1-.5.2-.1.5-.2.7-.4 1.3-.7 2.6-1.4 3.9-2.2.1-.1.3-.2.4-.3l1.2-.9h.1c.9-.7 1.8-1.4 2.6-2.1.4-.4.8-.7 1.2-1.1l.2-.2c1.1-1.1 2.2-2.4 3.2-3.6.1-.1.2-.2.2-.3l.9-1.2c0-.1.1-.1.1-.2l1.5-2.4c.1-.2.2-.3.3-.5 2.7-5.1 4.3-10.9 4.3-17s-1.6-12-4.3-17c-.1-.2-.2-.4-.3-.5z\")),t.ContainerFill=u(\"container\",c,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v529c0-.6.4-1 1-1h219.3l5.2 24.7C397.6 708.5 450.8 752 512 752s114.4-43.5 126.4-103.3l5.2-24.7H863c.6 0 1 .4 1 1V96c0-17.7-14.3-32-32-32zM712 493c0 4.4-3.6 8-8 8H320c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h384c4.4 0 8 3.6 8 8v48zm0-160c0 4.4-3.6 8-8 8H320c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h384c4.4 0 8 3.6 8 8v48zm151 354H694.1c-11.6 32.8-32 62.3-59.1 84.7-34.5 28.6-78.2 44.3-123 44.3s-88.5-15.8-123-44.3a194.02 194.02 0 0 1-59.1-84.7H161c-.6 0-1-.4-1-1v242c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V686c0 .6-.4 1-1 1z\")),t.CopyFill=u(\"copy\",c,l(o,\"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM382 896h-.2L232 746.2v-.2h150v150z\")),t.CopyrightCircleFill=u(\"copyright-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm5.4 670c-110 0-173.4-73.2-173.4-194.9v-52.3C344 364.2 407.4 290 517.3 290c94.3 0 162.7 60.7 162.7 147.4 0 2.6-2.1 4.7-4.7 4.7h-56.7c-4.2 0-7.6-3.2-8-7.4-4-49.5-40-83.4-93-83.4-65.3 0-102.1 48.5-102.1 135.5v52.6c0 85.7 36.9 133.6 102.1 133.6 52.8 0 88.7-31.7 93-77.8.4-4.1 3.8-7.3 8-7.3h56.8c2.6 0 4.7 2.1 4.7 4.7 0 82.6-68.7 141.4-162.7 141.4z\")),t.CreditCardFill=u(\"credit-card\",c,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v160h896V192c0-17.7-14.3-32-32-32zM64 832c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V440H64v392zm579-184c0-4.4 3.6-8 8-8h165c4.4 0 8 3.6 8 8v72c0 4.4-3.6 8-8 8H651c-4.4 0-8-3.6-8-8v-72z\")),t.CrownFill=u(\"crown\",c,l(o,\"M899.6 276.5L705 396.4 518.4 147.5a8.06 8.06 0 0 0-12.9 0L319 396.4 124.3 276.5c-5.7-3.5-13.1 1.2-12.2 7.9L188.5 865c1.1 7.9 7.9 14 16 14h615.1c8 0 14.9-6 15.9-14l76.4-580.6c.8-6.7-6.5-11.4-12.3-7.9zM512 734.2c-62.1 0-112.6-50.5-112.6-112.6S449.9 509 512 509s112.6 50.5 112.6 112.6S574.1 734.2 512 734.2zm0-160.9c-26.6 0-48.2 21.6-48.2 48.3 0 26.6 21.6 48.3 48.2 48.3s48.2-21.6 48.2-48.3c0-26.6-21.6-48.3-48.2-48.3z\")),t.CustomerServiceFill=u(\"customer-service\",c,l(o,\"M512 128c-212.1 0-384 171.9-384 384v360c0 13.3 10.7 24 24 24h184c35.3 0 64-28.7 64-64V624c0-35.3-28.7-64-64-64H200v-48c0-172.3 139.7-312 312-312s312 139.7 312 312v48H688c-35.3 0-64 28.7-64 64v208c0 35.3 28.7 64 64 64h184c13.3 0 24-10.7 24-24V512c0-212.1-171.9-384-384-384z\")),t.DashboardFill=u(\"dashboard\",c,l(o,\"M924.8 385.6a446.7 446.7 0 0 0-96-142.4 446.7 446.7 0 0 0-142.4-96C631.1 123.8 572.5 112 512 112s-119.1 11.8-174.4 35.2a446.7 446.7 0 0 0-142.4 96 446.7 446.7 0 0 0-96 142.4C75.8 440.9 64 499.5 64 560c0 132.7 58.3 257.7 159.9 343.1l1.7 1.4c5.8 4.8 13.1 7.5 20.6 7.5h531.7c7.5 0 14.8-2.7 20.6-7.5l1.7-1.4C901.7 817.7 960 692.7 960 560c0-60.5-11.9-119.1-35.2-174.4zM482 232c0-4.4 3.6-8 8-8h44c4.4 0 8 3.6 8 8v80c0 4.4-3.6 8-8 8h-44c-4.4 0-8-3.6-8-8v-80zM270 582c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8v-44c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v44zm90.7-204.5l-31.1 31.1a8.03 8.03 0 0 1-11.3 0L261.7 352a8.03 8.03 0 0 1 0-11.3l31.1-31.1c3.1-3.1 8.2-3.1 11.3 0l56.6 56.6c3.1 3.1 3.1 8.2 0 11.3zm291.1 83.6l-84.5 84.5c5 18.7.2 39.4-14.5 54.1a55.95 55.95 0 0 1-79.2 0 55.95 55.95 0 0 1 0-79.2 55.87 55.87 0 0 1 54.1-14.5l84.5-84.5c3.1-3.1 8.2-3.1 11.3 0l28.3 28.3c3.1 3.1 3.1 8.1 0 11.3zm43-52.4l-31.1-31.1a8.03 8.03 0 0 1 0-11.3l56.6-56.6c3.1-3.1 8.2-3.1 11.3 0l31.1 31.1c3.1 3.1 3.1 8.2 0 11.3l-56.6 56.6a8.03 8.03 0 0 1-11.3 0zM846 582c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8v-44c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v44z\")),t.DeleteFill=u(\"delete\",c,l(o,\"M864 256H736v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zm-200 0H360v-72h304v72z\")),t.DiffFill=u(\"diff\",c,l(o,\"M854.2 306.6L611.3 72.9c-6-5.7-13.9-8.9-22.2-8.9H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h277l219 210.6V824c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V329.6c0-8.7-3.5-17-9.8-23zM553.4 201.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V397.3c0-8.5-3.4-16.6-9.4-22.6L553.4 201.4zM568 753c0 3.8-3.4 7-7.5 7h-225c-4.1 0-7.5-3.2-7.5-7v-42c0-3.8 3.4-7 7.5-7h225c4.1 0 7.5 3.2 7.5 7v42zm0-220c0 3.8-3.4 7-7.5 7H476v84.9c0 3.9-3.1 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V540h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.9 3.4-7 7.5-7H420v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.9 0 7 3.2 7 7.1V484h84.5c4.1 0 7.5 3.1 7.5 7v42z\")),t.DingtalkCircleFill=u(\"dingtalk-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm227 385.3c-1 4.2-3.5 10.4-7 17.8h.1l-.4.7c-20.3 43.1-73.1 127.7-73.1 127.7s-.1-.2-.3-.5l-15.5 26.8h74.5L575.1 810l32.3-128h-58.6l20.4-84.7c-16.5 3.9-35.9 9.4-59 16.8 0 0-31.2 18.2-89.9-35 0 0-39.6-34.7-16.6-43.4 9.8-3.7 47.4-8.4 77-12.3 40-5.4 64.6-8.2 64.6-8.2S422 517 392.7 512.5c-29.3-4.6-66.4-53.1-74.3-95.8 0 0-12.2-23.4 26.3-12.3 38.5 11.1 197.9 43.2 197.9 43.2s-207.4-63.3-221.2-78.7c-13.8-15.4-40.6-84.2-37.1-126.5 0 0 1.5-10.5 12.4-7.7 0 0 153.3 69.7 258.1 107.9 104.8 37.9 195.9 57.3 184.2 106.7z\")),t.DatabaseFill=u(\"database\",c,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v224h704V96c0-17.7-14.3-32-32-32zM288 232c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zM160 928c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V704H160v224zm128-136c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zM160 640h704V384H160v256zm128-168c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\")),t.DingtalkSquareFill=u(\"dingtalk-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM739 449.3c-1 4.2-3.5 10.4-7 17.8h.1l-.4.7c-20.3 43.1-73.1 127.7-73.1 127.7s-.1-.2-.3-.5l-15.5 26.8h74.5L575.1 810l32.3-128h-58.6l20.4-84.7c-16.5 3.9-35.9 9.4-59 16.8 0 0-31.2 18.2-89.9-35 0 0-39.6-34.7-16.6-43.4 9.8-3.7 47.4-8.4 77-12.3 40-5.4 64.6-8.2 64.6-8.2S422 517 392.7 512.5c-29.3-4.6-66.4-53.1-74.3-95.8 0 0-12.2-23.4 26.3-12.3 38.5 11.1 197.9 43.2 197.9 43.2s-207.4-63.3-221.2-78.7c-13.8-15.4-40.6-84.2-37.1-126.5 0 0 1.5-10.5 12.4-7.7 0 0 153.3 69.7 258.1 107.9 104.8 37.9 195.9 57.3 184.2 106.7z\")),t.DislikeFill=u(\"dislike\",c,l(o,\"M885.9 490.3c3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-51.6-30.7-98.1-78.3-118.4a66.1 66.1 0 0 0-26.5-5.4H273v428h.3l85.8 310.8C372.9 889 418.9 924 470.9 924c29.7 0 57.4-11.8 77.9-33.4 20.5-21.5 31-49.7 29.5-79.4l-6-122.9h239.9c12.1 0 23.9-3.2 34.3-9.3 40.4-23.5 65.5-66.1 65.5-111 0-28.3-9.3-55.5-26.1-77.7zM112 132v364c0 17.7 14.3 32 32 32h65V100h-65c-17.7 0-32 14.3-32 32z\")),t.DollarCircleFill=u(\"dollar-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm22.3 665.2l.2 31.7c0 4.4-3.6 8.1-8 8.1h-28.4c-4.4 0-8-3.6-8-8v-31.4C401.3 723 359.5 672.4 355 617.4c-.4-4.7 3.3-8.7 8-8.7h46.2c3.9 0 7.3 2.8 7.9 6.6 5.1 31.7 29.8 55.4 74.1 61.3V533.9l-24.7-6.3c-52.3-12.5-102.1-45.1-102.1-112.7 0-72.9 55.4-112.1 126.2-119v-33c0-4.4 3.6-8 8-8h28.1c4.4 0 8 3.6 8 8v32.7c68.5 6.9 119.9 46.9 125.9 109.2.5 4.7-3.2 8.8-8 8.8h-44.9c-4 0-7.4-3-7.9-6.9-4-29.2-27.4-53-65.5-58.2v134.3l25.4 5.9c64.8 16 108.9 47 108.9 116.4 0 75.3-56 117.3-134.3 124.1zM426.6 410.3c0 25.4 15.7 45.1 49.5 57.3 4.7 1.9 9.4 3.4 15 5v-124c-36.9 4.7-64.5 25.4-64.5 61.7zm116.5 135.2c-2.8-.6-5.6-1.3-8.8-2.2V677c42.6-3.8 72-27.2 72-66.4 0-30.7-15.9-50.7-63.2-65.1z\")),t.DownCircleFill=u(\"down-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm184.5 353.7l-178 246a7.95 7.95 0 0 1-12.9 0l-178-246c-3.8-5.3 0-12.7 6.5-12.7H381c10.2 0 19.9 4.9 25.9 13.2L512 563.6l105.2-145.4c6-8.3 15.6-13.2 25.9-13.2H690c6.5 0 10.3 7.4 6.5 12.7z\")),t.DownSquareFill=u(\"down-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM696.5 412.7l-178 246a7.95 7.95 0 0 1-12.9 0l-178-246c-3.8-5.3 0-12.7 6.5-12.7H381c10.2 0 19.9 4.9 25.9 13.2L512 558.6l105.2-145.4c6-8.3 15.6-13.2 25.9-13.2H690c6.5 0 10.3 7.4 6.5 12.7z\")),t.DribbbleCircleFill=u(\"dribbble-circle\",c,l(o,\"M675.1 328.3a245.2 245.2 0 0 0-220.8-55.1c6.8 9.1 51.5 69.9 91.8 144 87.5-32.8 124.5-82.6 129-88.9zM554 552.8c-138.7 48.3-188.6 144.6-193 153.6 41.7 32.5 94.1 51.9 151 51.9 34.1 0 66.6-6.9 96.1-19.5-3.7-21.6-17.9-96.8-52.5-186.6l-1.6.6zm47.7-11.9c32.2 88.4 45.3 160.4 47.8 175.4 55.2-37.3 94.5-96.4 105.4-164.9-8.4-2.6-76.1-22.8-153.2-10.5zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 736c-158.8 0-288-129.2-288-288s129.2-288 288-288 288 129.2 288 288-129.2 288-288 288zm53.1-346.2c5.7 11.7 11.2 23.6 16.3 35.6 1.8 4.2 3.6 8.4 5.3 12.7 81.8-10.3 163.2 6.2 171.3 7.9-.5-58.1-21.3-111.4-55.5-153.3-5.3 7.1-46.5 60-137.4 97.1zM498.6 432c-40.8-72.5-84.7-133.4-91.2-142.3-68.8 32.5-120.3 95.9-136.2 172.2 11 .2 112.4.7 227.4-29.9zm30.6 82.5c3.2-1 6.4-2 9.7-2.9-6.2-14-12.9-28-19.9-41.7-122.8 36.8-242.1 35.2-252.8 35-.1 2.5-.1 5-.1 7.5 0 63.2 23.9 120.9 63.2 164.5 5.5-9.6 73-121.4 199.9-162.4z\")),t.DribbbleSquareFill=u(\"dribbble-square\",c,l(o,\"M498.6 432c-40.8-72.5-84.7-133.4-91.2-142.3-68.8 32.5-120.3 95.9-136.2 172.2 11 .2 112.4.7 227.4-29.9zm66.5 21.8c5.7 11.7 11.2 23.6 16.3 35.6 1.8 4.2 3.6 8.4 5.3 12.7 81.8-10.3 163.2 6.2 171.3 7.9-.5-58.1-21.3-111.4-55.5-153.3-5.3 7.1-46.5 60-137.4 97.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM512 800c-158.8 0-288-129.2-288-288s129.2-288 288-288 288 129.2 288 288-129.2 288-288 288zm89.7-259.1c32.2 88.4 45.3 160.4 47.8 175.4 55.2-37.3 94.5-96.4 105.4-164.9-8.4-2.6-76.1-22.8-153.2-10.5zm-72.5-26.4c3.2-1 6.4-2 9.7-2.9-6.2-14-12.9-28-19.9-41.7-122.8 36.8-242.1 35.2-252.8 35-.1 2.5-.1 5-.1 7.5 0 63.2 23.9 120.9 63.2 164.5 5.5-9.6 73-121.4 199.9-162.4zm145.9-186.2a245.2 245.2 0 0 0-220.8-55.1c6.8 9.1 51.5 69.9 91.8 144 87.5-32.8 124.5-82.6 129-88.9zM554 552.8c-138.7 48.3-188.6 144.6-193 153.6 41.7 32.5 94.1 51.9 151 51.9 34.1 0 66.6-6.9 96.1-19.5-3.7-21.6-17.9-96.8-52.5-186.6l-1.6.6z\")),t.DropboxCircleFill=u(\"dropbox-circle\",c,l(o,\"M663.8 455.5zm-151.5-93.8l-151.8 93.8 151.8 93.9 151.5-93.9zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm151.2 595.5L512.6 750l-151-90.5v-33.1l45.4 29.4 105.6-87.7 105.6 87.7 45.1-29.4v33.1zm-45.6-22.4l-105.3-87.7L407 637.1l-151-99.2 104.5-82.4L256 371.2 407 274l105.3 87.7L617.6 274 768 372.1l-104.2 83.5L768 539l-150.4 98.1z\")),t.DropboxSquareFill=u(\"dropbox-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM663.2 659.5L512.6 750l-151-90.5v-33.1l45.4 29.4 105.6-87.7 105.6 87.7 45.1-29.4v33.1zm-45.6-22.4l-105.3-87.7L407 637.1l-151-99.2 104.5-82.4L256 371.2 407 274l105.3 87.7L617.6 274 768 372.1l-104.2 83.5L768 539l-150.4 98.1zM512.3 361.7l-151.8 93.8 151.8 93.9 151.5-93.9zm151.5 93.8z\")),t.EnvironmentFill=u(\"environment\",c,l(o,\"M512 327c-29.9 0-58 11.6-79.2 32.8A111.6 111.6 0 0 0 400 439c0 29.9 11.7 58 32.8 79.2A111.6 111.6 0 0 0 512 551c29.9 0 58-11.7 79.2-32.8C612.4 497 624 468.9 624 439c0-29.9-11.6-58-32.8-79.2S541.9 327 512 327zm342.6-37.9a362.49 362.49 0 0 0-79.9-115.7 370.83 370.83 0 0 0-118.2-77.8C610.7 76.6 562.1 67 512 67c-50.1 0-98.7 9.6-144.5 28.5-44.3 18.3-84 44.5-118.2 77.8A363.6 363.6 0 0 0 169.4 289c-19.5 45-29.4 92.8-29.4 142 0 70.6 16.9 140.9 50.1 208.7 26.7 54.5 64 107.6 111 158.1 80.3 86.2 164.5 138.9 188.4 153a43.9 43.9 0 0 0 22.4 6.1c7.8 0 15.5-2 22.4-6.1 23.9-14.1 108.1-66.8 188.4-153 47-50.4 84.3-103.6 111-158.1C867.1 572 884 501.8 884 431.1c0-49.2-9.9-97-29.4-142zM512 615c-97.2 0-176-78.8-176-176s78.8-176 176-176 176 78.8 176 176-78.8 176-176 176z\")),t.EditFill=u(\"edit\",c,l(o,\"M880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32zm-622.3-84c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 0 0 0-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 0 0 9.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9z\")),t.ExclamationCircleFill=u(\"exclamation-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.EuroCircleFill=u(\"euro-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm63.5 375.8c4.4 0 8 3.6 8 8V475c0 4.4-3.6 8-8 8h-136c-.3 4.4-.3 9.1-.3 13.8v36h136.2c4.4 0 8 3.6 8 8V568c0 4.4-3.6 8-8 8H444.9c15.3 62 61.3 98.6 129.8 98.6 19.9 0 37.1-1.2 51.8-4.1 4.9-1 9.5 2.8 9.5 7.8v42.8c0 3.8-2.7 7-6.4 7.8-15.9 3.4-34.3 5.1-55.3 5.1-109.8 0-183-58.8-200.2-158H344c-4.4 0-8-3.6-8-8v-27.2c0-4.4 3.6-8 8-8h26.1v-36.9c0-4.4 0-8.8.3-12.8H344c-4.4 0-8-3.6-8-8v-27.2c0-4.4 3.6-8 8-8h31.7c19.7-94.2 92-149.9 198.6-149.9 20.9 0 39.4 1.9 55.3 5.4 3.7.8 6.3 4 6.3 7.8V346h.1c0 5.1-4.6 8.8-9.6 7.8-14.7-2.9-31.8-4.4-51.7-4.4-65.4 0-110.4 33.5-127.6 90.4h128.4z\")),t.ExperimentFill=u(\"experiment\",c,l(o,\"M218.9 636.3l42.6 26.6c.1.1.3.2.4.3l12.7 8 .3.3a186.9 186.9 0 0 0 94.1 25.1c44.9 0 87.2-15.7 121-43.8a256.27 256.27 0 0 1 164.9-59.9c52.3 0 102.2 15.7 144.6 44.5l7.9 5-111.6-289V179.8h63.5c4.4 0 8-3.6 8-8V120c0-4.4-3.6-8-8-8H264.7c-4.4 0-8 3.6-8 8v51.9c0 4.4 3.6 8 8 8h63.5v173.6L218.9 636.3zm333-203.1c22 0 39.9 17.9 39.9 39.9S573.9 513 551.9 513 512 495.1 512 473.1s17.9-39.9 39.9-39.9zM878 825.1l-29.9-77.4-85.7-53.5-.1.1c-.7-.5-1.5-1-2.2-1.5l-8.1-5-.3-.3c-29-17.5-62.3-26.8-97-26.8-44.9 0-87.2 15.7-121 43.8a256.27 256.27 0 0 1-164.9 59.9c-53 0-103.5-16.1-146.2-45.6l-28.9-18.1L146 825.1c-2.8 7.4-4.3 15.2-4.3 23 0 35.2 28.6 63.8 63.8 63.8h612.9c7.9 0 15.7-1.5 23-4.3a63.6 63.6 0 0 0 36.6-82.5z\")),t.EyeInvisibleFill=u(\"eye-invisible\",c,l(o,\"M508 624a112 112 0 0 0 112-112c0-3.28-.15-6.53-.43-9.74L498.26 623.57c3.21.28 6.45.43 9.74.43zm370.72-458.44L836 122.88a8 8 0 0 0-11.31 0L715.37 232.23Q624.91 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q56.7 119.43 136.55 191.45L112.56 835a8 8 0 0 0 0 11.31L155.25 889a8 8 0 0 0 11.31 0l712.16-712.12a8 8 0 0 0 0-11.32zM332 512a176 176 0 0 1 258.88-155.28l-48.62 48.62a112.08 112.08 0 0 0-140.92 140.92l-48.62 48.62A175.09 175.09 0 0 1 332 512z\",\"M942.2 486.2Q889.4 375 816.51 304.85L672.37 449A176.08 176.08 0 0 1 445 676.37L322.74 798.63Q407.82 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5z\")),t.EyeFill=u(\"eye\",c,l(o,\"M396 512a112 112 0 1 0 224 0 112 112 0 1 0-224 0zm546.2-25.8C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 0 0 0 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM508 688c-97.2 0-176-78.8-176-176s78.8-176 176-176 176 78.8 176 176-78.8 176-176 176z\")),t.FacebookFill=u(\"facebook\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-92.4 233.5h-63.9c-50.1 0-59.8 23.8-59.8 58.8v77.1h119.6l-15.6 120.7h-104V912H539.2V602.2H434.9V481.4h104.3v-89c0-103.3 63.1-159.6 155.3-159.6 44.2 0 82.1 3.3 93.2 4.8v107.9z\")),t.FastBackwardFill=u(\"fast-backward\",c,l(r,\"M517.6 273.5L230.2 499.3a16.14 16.14 0 0 0 0 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm320 0L550.2 499.3a16.14 16.14 0 0 0 0 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm-620-25.5h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z\")),t.FastForwardFill=u(\"fast-forward\",c,l(r,\"M793.8 499.3L506.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.6c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8a16.14 16.14 0 0 0 0-25.4zm-320 0L186.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.5c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8c4.1-3.2 6.2-8 6.2-12.7 0-4.6-2.1-9.4-6.2-12.6zM857.6 248h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z\")),t.FileAddFill=u(\"file-add\",c,l(o,\"M480 580H372a8 8 0 0 0-8 8v48a8 8 0 0 0 8 8h108v108a8 8 0 0 0 8 8h48a8 8 0 0 0 8-8V644h108a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8H544V472a8 8 0 0 0-8-8h-48a8 8 0 0 0-8 8v108zm374.6-291.3c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2z\")),t.FileExcelFill=u(\"file-excel\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM575.34 477.84l-61.22 102.3L452.3 477.8a12 12 0 0 0-10.27-5.79h-38.44a12 12 0 0 0-6.4 1.85 12 12 0 0 0-3.75 16.56l82.34 130.42-83.45 132.78a12 12 0 0 0-1.84 6.39 12 12 0 0 0 12 12h34.46a12 12 0 0 0 10.21-5.7l62.7-101.47 62.3 101.45a12 12 0 0 0 10.23 5.72h37.48a12 12 0 0 0 6.48-1.9 12 12 0 0 0 3.62-16.58l-83.83-130.55 85.3-132.47a12 12 0 0 0 1.9-6.5 12 12 0 0 0-12-12h-35.7a12 12 0 0 0-10.29 5.84z\")),t.FileExclamationFill=u(\"file-exclamation\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM512 784a40 40 0 1 0 0-80 40 40 0 0 0 0 80zm32-152V448a8 8 0 0 0-8-8h-48a8 8 0 0 0-8 8v184a8 8 0 0 0 8 8h48a8 8 0 0 0 8-8z\")),t.FileImageFill=u(\"file-image\",c,l(o,\"M854.6 288.7L639.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.6-9.4-22.6zM400 402c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zm296 294H328c-6.7 0-10.4-7.7-6.3-12.9l99.8-127.2a8 8 0 0 1 12.6 0l41.1 52.4 77.8-99.2a8 8 0 0 1 12.6 0l136.5 174c4.3 5.2.5 12.9-6.1 12.9zm-94-370V137.8L790.2 326H602z\")),t.FileMarkdownFill=u(\"file-markdown\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM426.13 600.93l59.11 132.97a16 16 0 0 0 14.62 9.5h24.06a16 16 0 0 0 14.63-9.51l59.1-133.35V758a16 16 0 0 0 16.01 16H641a16 16 0 0 0 16-16V486a16 16 0 0 0-16-16h-34.75a16 16 0 0 0-14.67 9.62L512.1 662.2l-79.48-182.59a16 16 0 0 0-14.67-9.61H383a16 16 0 0 0-16 16v272a16 16 0 0 0 16 16h27.13a16 16 0 0 0 16-16V600.93z\")),t.FilePdfFill=u(\"file-pdf\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM633.22 637.26c-15.18-.5-31.32.67-49.65 2.96-24.3-14.99-40.66-35.58-52.28-65.83l1.07-4.38 1.24-5.18c4.3-18.13 6.61-31.36 7.3-44.7.52-10.07-.04-19.36-1.83-27.97-3.3-18.59-16.45-29.46-33.02-30.13-15.45-.63-29.65 8-33.28 21.37-5.91 21.62-2.45 50.07 10.08 98.59-15.96 38.05-37.05 82.66-51.2 107.54-18.89 9.74-33.6 18.6-45.96 28.42-16.3 12.97-26.48 26.3-29.28 40.3-1.36 6.49.69 14.97 5.36 21.92 5.3 7.88 13.28 13 22.85 13.74 24.15 1.87 53.83-23.03 86.6-79.26 3.29-1.1 6.77-2.26 11.02-3.7l11.9-4.02c7.53-2.54 12.99-4.36 18.39-6.11 23.4-7.62 41.1-12.43 57.2-15.17 27.98 14.98 60.32 24.8 82.1 24.8 17.98 0 30.13-9.32 34.52-23.99 3.85-12.88.8-27.82-7.48-36.08-8.56-8.41-24.3-12.43-45.65-13.12zM385.23 765.68v-.36l.13-.34a54.86 54.86 0 0 1 5.6-10.76c4.28-6.58 10.17-13.5 17.47-20.87 3.92-3.95 8-7.8 12.79-12.12 1.07-.96 7.91-7.05 9.19-8.25l11.17-10.4-8.12 12.93c-12.32 19.64-23.46 33.78-33 43-3.51 3.4-6.6 5.9-9.1 7.51a16.43 16.43 0 0 1-2.61 1.42c-.41.17-.77.27-1.13.3a2.2 2.2 0 0 1-1.12-.15 2.07 2.07 0 0 1-1.27-1.91zM511.17 547.4l-2.26 4-1.4-4.38c-3.1-9.83-5.38-24.64-6.01-38-.72-15.2.49-24.32 5.29-24.32 6.74 0 9.83 10.8 10.07 27.05.22 14.28-2.03 29.14-5.7 35.65zm-5.81 58.46l1.53-4.05 2.09 3.8c11.69 21.24 26.86 38.96 43.54 51.31l3.6 2.66-4.39.9c-16.33 3.38-31.54 8.46-52.34 16.85 2.17-.88-21.62 8.86-27.64 11.17l-5.25 2.01 2.8-4.88c12.35-21.5 23.76-47.32 36.05-79.77zm157.62 76.26c-7.86 3.1-24.78.33-54.57-12.39l-7.56-3.22 8.2-.6c23.3-1.73 39.8-.45 49.42 3.07 4.1 1.5 6.83 3.39 8.04 5.55a4.64 4.64 0 0 1-1.36 6.31 6.7 6.7 0 0 1-2.17 1.28z\")),t.FilePptFill=u(\"file-ppt\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM468.53 760v-91.54h59.27c60.57 0 100.2-39.65 100.2-98.12 0-58.22-39.58-98.34-99.98-98.34H424a12 12 0 0 0-12 12v276a12 12 0 0 0 12 12h32.53a12 12 0 0 0 12-12zm0-139.33h34.9c47.82 0 67.19-12.93 67.19-50.33 0-32.05-18.12-50.12-49.87-50.12h-52.22v100.45z\")),t.FileTextFill=u(\"file-text\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM320 482a8 8 0 0 0-8 8v48a8 8 0 0 0 8 8h384a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8H320zm0 136a8 8 0 0 0-8 8v48a8 8 0 0 0 8 8h184a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8H320z\")),t.FileWordFill=u(\"file-word\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM512 566.1l52.81 197a12 12 0 0 0 11.6 8.9h31.77a12 12 0 0 0 11.6-8.88l74.37-276a12 12 0 0 0 .4-3.12 12 12 0 0 0-12-12h-35.57a12 12 0 0 0-11.7 9.31l-45.78 199.1-49.76-199.32A12 12 0 0 0 528.1 472h-32.2a12 12 0 0 0-11.64 9.1L434.6 680.01 388.5 481.3a12 12 0 0 0-11.68-9.29h-35.39a12 12 0 0 0-3.11.41 12 12 0 0 0-8.47 14.7l74.17 276A12 12 0 0 0 415.6 772h31.99a12 12 0 0 0 11.59-8.9l52.81-197z\")),t.FileUnknownFill=u(\"file-unknown\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM402 549c0 5.4 4.4 9.5 9.8 9.5h32.4c5.4 0 9.8-4.2 9.8-9.4 0-28.2 25.8-51.6 58-51.6s58 23.4 58 51.5c0 25.3-21 47.2-49.3 50.9-19.3 2.8-34.5 20.3-34.7 40.1v32c0 5.5 4.5 10 10 10h32c5.5 0 10-4.5 10-10v-12.2c0-6 4-11.5 9.7-13.3 44.6-14.4 75-54 74.3-98.9-.8-55.5-49.2-100.8-108.5-101.6-61.4-.7-111.5 45.6-111.5 103zm110 227a32 32 0 1 0 0-64 32 32 0 0 0 0 64z\")),t.FileZipFill=u(\"file-zip\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2zM296 136v64h64v-64h-64zm64 64v64h64v-64h-64zm-64 64v64h64v-64h-64zm64 64v64h64v-64h-64zm-64 64v64h64v-64h-64zm64 64v64h64v-64h-64zm-64 64v64h64v-64h-64zm0 64v160h128V584H296zm48 48h32v64h-32v-64z\")),t.FileFill=u(\"file\",c,l(o,\"M854.6 288.7c6 6 9.4 14.1 9.4 22.6V928c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32h424.7c8.5 0 16.7 3.4 22.7 9.4l215.2 215.3zM790.2 326L602 137.8V326h188.2z\")),t.FilterFill=u(\"filter\",c,l(o,\"M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z\")),t.FireFill=u(\"fire\",c,l(o,\"M834.1 469.2A347.49 347.49 0 0 0 751.2 354l-29.1-26.7a8.09 8.09 0 0 0-13 3.3l-13 37.3c-8.1 23.4-23 47.3-44.1 70.8-1.4 1.5-3 1.9-4.1 2-1.1.1-2.8-.1-4.3-1.5-1.4-1.2-2.1-3-2-4.8 3.7-60.2-14.3-128.1-53.7-202C555.3 171 510 123.1 453.4 89.7l-41.3-24.3c-5.4-3.2-12.3 1-12 7.3l2.2 48c1.5 32.8-2.3 61.8-11.3 85.9-11 29.5-26.8 56.9-47 81.5a295.64 295.64 0 0 1-47.5 46.1 352.6 352.6 0 0 0-100.3 121.5A347.75 347.75 0 0 0 160 610c0 47.2 9.3 92.9 27.7 136a349.4 349.4 0 0 0 75.5 110.9c32.4 32 70 57.2 111.9 74.7C418.5 949.8 464.5 959 512 959s93.5-9.2 136.9-27.3A348.6 348.6 0 0 0 760.8 857c32.4-32 57.8-69.4 75.5-110.9a344.2 344.2 0 0 0 27.7-136c0-48.8-10-96.2-29.9-140.9z\")),t.FlagFill=u(\"flag\",c,l(o,\"M880 305H624V192c0-17.7-14.3-32-32-32H184v-40c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v784c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V640h248v113c0 17.7 14.3 32 32 32h416c17.7 0 32-14.3 32-32V337c0-17.7-14.3-32-32-32z\")),t.FolderAddFill=u(\"folder-add\",c,l(o,\"M880 298.4H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM632 577c0 3.8-3.4 7-7.5 7H540v84.9c0 3.9-3.2 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V584h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.8 3.4-7 7.5-7H484v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.8 0 7 3.2 7 7.1V528h84.5c4.1 0 7.5 3.2 7.5 7v42z\")),t.FolderFill=u(\"folder\",c,l(o,\"M880 298.4H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32z\")),t.FolderOpenFill=u(\"folder-open\",c,l(o,\"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 0 0-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zm-180 0H238c-13 0-24.8 7.9-29.7 20L136 643.2V256h188.5l119.6 114.4H748V444z\")),t.ForwardFill=u(\"forward\",c,l(r,\"M825.8 498L538.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L825.8 526c8.3-7.2 8.3-20.8 0-28zm-320 0L218.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L505.8 526c4.1-3.6 6.2-8.8 6.2-14 0-5.2-2.1-10.4-6.2-14z\")),t.FrownFill=u(\"frown\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm376 272h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 636.1 562.5 597 512 597s-92.1 39.1-95.8 88.6c-.3 4.2-3.9 7.4-8.1 7.4H360a8 8 0 0 1-8-8.4c4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6a8 8 0 0 1-8 8.4zm24-224a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.FundFill=u(\"fund\",c,l(o,\"M926 164H94c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V196c0-17.7-14.3-32-32-32zm-92.3 194.4l-297 297.2a8.03 8.03 0 0 1-11.3 0L410.9 541.1 238.4 713.7a8.03 8.03 0 0 1-11.3 0l-36.8-36.8a8.03 8.03 0 0 1 0-11.3l214.9-215c3.1-3.1 8.2-3.1 11.3 0L531 565l254.5-254.6c3.1-3.1 8.2-3.1 11.3 0l36.8 36.8c3.2 3 3.2 8.1.1 11.2z\")),t.FunnelPlotFill=u(\"funnel-plot\",c,l(o,\"M336.7 586h350.6l84.9-148H251.8zm543.4-432H143.9c-24.5 0-39.8 26.7-27.5 48L215 374h594l98.7-172c12.2-21.3-3.1-48-27.6-48zM349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V650H349v188z\")),t.GiftFill=u(\"gift\",c,l(o,\"M160 894c0 17.7 14.3 32 32 32h286V550H160v344zm386 32h286c17.7 0 32-14.3 32-32V550H546v376zm334-616H732.4c13.6-21.4 21.6-46.8 21.6-74 0-76.1-61.9-138-138-138-41.4 0-78.7 18.4-104 47.4-25.3-29-62.6-47.4-104-47.4-76.1 0-138 61.9-138 138 0 27.2 7.9 52.6 21.6 74H144c-17.7 0-32 14.3-32 32v140h366V310h68v172h366V342c0-17.7-14.3-32-32-32zm-402-4h-70c-38.6 0-70-31.4-70-70s31.4-70 70-70 70 31.4 70 70v70zm138 0h-70v-70c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70z\")),t.GithubFill=u(\"github\",c,l(o,\"M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0 1 38.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z\")),t.GitlabFill=u(\"gitlab\",c,l(o,\"M910.5 553.2l-109-370.8c-6.8-20.4-23.1-34.1-44.9-34.1s-39.5 12.3-46.3 32.7l-72.2 215.4H386.2L314 181.1c-6.8-20.4-24.5-32.7-46.3-32.7s-39.5 13.6-44.9 34.1L113.9 553.2c-4.1 13.6 1.4 28.6 12.3 36.8l385.4 289 386.7-289c10.8-8.1 16.3-23.1 12.2-36.8z\")),t.GoldenFill=u(\"golden\",c,l(o,\"M905.9 806.7l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H596.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.3-.7 7.3-4.8 6.6-9.2zm-470.2-248c-.6-3.9-4-6.7-7.9-6.7H166.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248zM342 472h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H382.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8z\")),t.GoogleCircleFill=u(\"google-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm167 633.6C638.4 735 583 757 516.9 757c-95.7 0-178.5-54.9-218.8-134.9C281.5 589 272 551.6 272 512s9.5-77 26.1-110.1c40.3-80.1 123.1-135 218.8-135 66 0 121.4 24.3 163.9 63.8L610.6 401c-25.4-24.3-57.7-36.6-93.6-36.6-63.8 0-117.8 43.1-137.1 101-4.9 14.7-7.7 30.4-7.7 46.6s2.8 31.9 7.7 46.6c19.3 57.9 73.3 101 137 101 33 0 61-8.7 82.9-23.4 26-17.4 43.2-43.3 48.9-74H516.9v-94.8h230.7c2.9 16.1 4.4 32.8 4.4 50.1 0 74.7-26.7 137.4-73 180.1z\")),t.GooglePlusCircleFill=u(\"google-plus-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm36.5 558.8c-43.9 61.8-132.1 79.8-200.9 53.3-69-26.3-118-99.2-112.1-173.5 1.5-90.9 85.2-170.6 176.1-167.5 43.6-2 84.6 16.9 118 43.6-14.3 16.2-29 31.8-44.8 46.3-40.1-27.7-97.2-35.6-137.3-3.6-57.4 39.7-60 133.4-4.8 176.1 53.7 48.7 155.2 24.5 170.1-50.1-33.6-.5-67.4 0-101-1.1-.1-20.1-.2-40.1-.1-60.2 56.2-.2 112.5-.3 168.8.2 3.3 47.3-3 97.5-32 136.5zM791 536.5c-16.8.2-33.6.3-50.4.4-.2 16.8-.3 33.6-.3 50.4H690c-.2-16.8-.2-33.5-.3-50.3-16.8-.2-33.6-.3-50.4-.5v-50.1c16.8-.2 33.6-.3 50.4-.3.1-16.8.3-33.6.4-50.4h50.2l.3 50.4c16.8.2 33.6.2 50.4.3v50.1z\")),t.GooglePlusSquareFill=u(\"google-plus-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM548.5 622.8c-43.9 61.8-132.1 79.8-200.9 53.3-69-26.3-118-99.2-112.1-173.5 1.5-90.9 85.2-170.6 176.1-167.5 43.6-2 84.6 16.9 118 43.6-14.3 16.2-29 31.8-44.8 46.3-40.1-27.7-97.2-35.6-137.3-3.6-57.4 39.7-60 133.4-4.8 176.1 53.7 48.7 155.2 24.5 170.1-50.1-33.6-.5-67.4 0-101-1.1-.1-20.1-.2-40.1-.1-60.2 56.2-.2 112.5-.3 168.8.2 3.3 47.3-3 97.5-32 136.5zM791 536.5c-16.8.2-33.6.3-50.4.4-.2 16.8-.3 33.6-.3 50.4H690c-.2-16.8-.2-33.5-.3-50.3-16.8-.2-33.6-.3-50.4-.5v-50.1c16.8-.2 33.6-.3 50.4-.3.1-16.8.3-33.6.4-50.4h50.2l.3 50.4c16.8.2 33.6.2 50.4.3v50.1z\")),t.GoogleSquareFill=u(\"google-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM679 697.6C638.4 735 583 757 516.9 757c-95.7 0-178.5-54.9-218.8-134.9A245.02 245.02 0 0 1 272 512c0-39.6 9.5-77 26.1-110.1 40.3-80.1 123.1-135 218.8-135 66 0 121.4 24.3 163.9 63.8L610.6 401c-25.4-24.3-57.7-36.6-93.6-36.6-63.8 0-117.8 43.1-137.1 101-4.9 14.7-7.7 30.4-7.7 46.6s2.8 31.9 7.7 46.6c19.3 57.9 73.3 101 137 101 33 0 61-8.7 82.9-23.4 26-17.4 43.2-43.3 48.9-74H516.9v-94.8h230.7c2.9 16.1 4.4 32.8 4.4 50.1 0 74.7-26.7 137.4-73 180.1z\")),t.HddFill=u(\"hdd\",c,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v224h704V96c0-17.7-14.3-32-32-32zM456 216c0 4.4-3.6 8-8 8H264c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zM160 928c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V704H160v224zm576-136c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zM160 640h704V384H160v256zm96-152c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H264c-4.4 0-8-3.6-8-8v-48z\")),t.HeartFill=u(\"heart\",c,l(o,\"M923 283.6a260.04 260.04 0 0 0-56.9-82.8 264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9 0 33.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 101.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 20.3-103.3.1-35.3-7-69.6-20.9-101.9z\")),t.HighlightFill=u(\"highlight\",c,l(o,\"M957.6 507.4L603.2 158.2a7.9 7.9 0 0 0-11.2 0L353.3 393.4a8.03 8.03 0 0 0-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 0 0-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0 0 11.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0 0 11.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2z\")),t.HomeFill=u(\"home\",c,l(o,\"M946.5 505L534.6 93.4a31.93 31.93 0 0 0-45.2 0L77.5 505c-12 12-18.8 28.3-18.8 45.3 0 35.3 28.7 64 64 64h43.4V908c0 17.7 14.3 32 32 32H448V716h112v224h265.9c17.7 0 32-14.3 32-32V614.3h43.4c17 0 33.3-6.7 45.3-18.8 24.9-25 24.9-65.5-.1-90.5z\")),t.HourglassFill=u(\"hourglass\",c,l(o,\"M742 318V184h86c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H196c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h86v134c0 81.5 42.4 153.2 106.4 194-64 40.8-106.4 112.5-106.4 194v134h-86c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h632c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-86V706c0-81.5-42.4-153.2-106.4-194 64-40.8 106.4-112.5 106.4-194z\")),t.Html5Fill=u(\"html5\",c,l(o,\"M145.2 96l66 746.6L512 928l299.6-85.4L878.9 96H145.2zm595 177.1l-4.8 47.2-1.7 19.5H382.3l8.2 94.2h335.1l-3.3 24.3-21.2 242.2-1.7 16.2-187 51.6v.3h-1.2l-.3.1v-.1h-.1l-188.6-52L310.8 572h91.1l6.5 73.2 102.4 27.7h.4l102-27.6 11.4-118.6H510.9v-.1H306l-22.8-253.5-1.7-24.3h460.3l-1.6 24.3z\")),t.IdcardFill=u(\"idcard\",c,l(o,\"M373 411c-28.5 0-51.7 23.3-51.7 52s23.2 52 51.7 52 51.7-23.3 51.7-52-23.2-52-51.7-52zm555-251H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zM608 420c0-4.4 1-8 2.3-8h123.4c1.3 0 2.3 3.6 2.3 8v48c0 4.4-1 8-2.3 8H610.3c-1.3 0-2.3-3.6-2.3-8v-48zm-86 253h-43.9c-4.2 0-7.6-3.3-7.9-7.5-3.8-50.5-46-90.5-97.2-90.5s-93.4 40-97.2 90.5c-.3 4.2-3.7 7.5-7.9 7.5H224a8 8 0 0 1-8-8.4c2.8-53.3 32-99.7 74.6-126.1a111.8 111.8 0 0 1-29.1-75.5c0-61.9 49.9-112 111.4-112s111.4 50.1 111.4 112c0 29.1-11 55.5-29.1 75.5 42.7 26.5 71.8 72.8 74.6 126.1.4 4.6-3.2 8.4-7.8 8.4zm278.9-53H615.1c-3.9 0-7.1-3.6-7.1-8v-48c0-4.4 3.2-8 7.1-8h185.7c3.9 0 7.1 3.6 7.1 8v48h.1c0 4.4-3.2 8-7.1 8z\")),t.IeCircleFill=u(\"ie-circle\",c,l(o,\"M693.6 284.4c-24 0-51.1 11.7-72.6 22 46.3 18 86 57.3 112.3 99.6 7.1-18.9 14.6-47.9 14.6-67.9 0-32-22.8-53.7-54.3-53.7zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm253.9 492.9H437.1c0 100.4 144.3 136 196.8 47.4h120.8c-32.6 91.7-119.7 146-216.8 146-35.1 0-70.3-.1-101.7-15.6-87.4 44.5-180.3 56.6-180.3-42 0-45.8 23.2-107.1 44-145C335 484 381.3 422.8 435.6 374.5c-43.7 18.9-91.1 66.3-122 101.2 25.9-112.8 129.5-193.6 237.1-186.5 130-59.8 209.7-34.1 209.7 38.6 0 27.4-10.6 63.3-21.4 87.9 25.2 45.5 33.3 97.6 26.9 141.2zM540.5 399.1c-53.7 0-102 39.7-104 94.9h208c-2-55.1-50.6-94.9-104-94.9zM320.6 602.9c-73 152.4 11.5 172.2 100.3 123.3-46.6-27.5-82.6-72.2-100.3-123.3z\")),t.IeSquareFill=u(\"ie-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM765.9 556.9H437.1c0 100.4 144.3 136 196.8 47.4h120.8c-32.6 91.7-119.7 146-216.8 146-35.1 0-70.3-.1-101.7-15.6-87.4 44.5-180.3 56.6-180.3-42 0-45.8 23.2-107.1 44-145C335 484 381.3 422.8 435.6 374.5c-43.7 18.9-91.1 66.3-122 101.2 25.9-112.8 129.5-193.6 237.1-186.5 130-59.8 209.7-34.1 209.7 38.6 0 27.4-10.6 63.3-21.4 87.9 25.2 45.5 33.3 97.6 26.9 141.2zm-72.3-272.5c-24 0-51.1 11.7-72.6 22 46.3 18 86 57.3 112.3 99.6 7.1-18.9 14.6-47.9 14.6-67.9 0-32-22.8-53.7-54.3-53.7zM540.5 399.1c-53.7 0-102 39.7-104 94.9h208c-2-55.1-50.6-94.9-104-94.9zM320.6 602.9c-73 152.4 11.5 172.2 100.3 123.3-46.6-27.5-82.6-72.2-100.3-123.3z\")),t.InfoCircleFill=u(\"info-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.InstagramFill=u(\"instagram\",c,l(o,\"M512 378.7c-73.4 0-133.3 59.9-133.3 133.3S438.6 645.3 512 645.3 645.3 585.4 645.3 512 585.4 378.7 512 378.7zM911.8 512c0-55.2.5-109.9-2.6-165-3.1-64-17.7-120.8-64.5-167.6-46.9-46.9-103.6-61.4-167.6-64.5-55.2-3.1-109.9-2.6-165-2.6-55.2 0-109.9-.5-165 2.6-64 3.1-120.8 17.7-167.6 64.5C132.6 226.3 118.1 283 115 347c-3.1 55.2-2.6 109.9-2.6 165s-.5 109.9 2.6 165c3.1 64 17.7 120.8 64.5 167.6 46.9 46.9 103.6 61.4 167.6 64.5 55.2 3.1 109.9 2.6 165 2.6 55.2 0 109.9.5 165-2.6 64-3.1 120.8-17.7 167.6-64.5 46.9-46.9 61.4-103.6 64.5-167.6 3.2-55.1 2.6-109.8 2.6-165zM512 717.1c-113.5 0-205.1-91.6-205.1-205.1S398.5 306.9 512 306.9 717.1 398.5 717.1 512 625.5 717.1 512 717.1zm213.5-370.7c-26.5 0-47.9-21.4-47.9-47.9s21.4-47.9 47.9-47.9 47.9 21.4 47.9 47.9a47.84 47.84 0 0 1-47.9 47.9z\")),t.InsuranceFill=u(\"insurance\",c,l(o,\"M519.9 358.8h97.9v41.6h-97.9zm347-188.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM411.3 656h-.2c0 4.4-3.6 8-8 8h-37.3c-4.4 0-8-3.6-8-8V471.4c-7.7 9.2-15.4 17.9-23.1 26a6.04 6.04 0 0 1-10.2-2.4l-13.2-43.5c-.6-2-.2-4.1 1.2-5.6 37-43.4 64.7-95.1 82.2-153.6 1.1-3.5 5-5.3 8.4-3.7l38.6 18.3c2.7 1.3 4.1 4.4 3.2 7.2a429.2 429.2 0 0 1-33.6 79V656zm296.5-49.2l-26.3 35.3a5.92 5.92 0 0 1-8.9.7c-30.6-29.3-56.8-65.2-78.1-106.9V656c0 4.4-3.6 8-8 8h-36.2c-4.4 0-8-3.6-8-8V536c-22 44.7-49 80.8-80.6 107.6a5.9 5.9 0 0 1-8.9-1.4L430 605.7a6 6 0 0 1 1.6-8.1c28.6-20.3 51.9-45.2 71-76h-55.1c-4.4 0-8-3.6-8-8V478c0-4.4 3.6-8 8-8h94.9v-18.6h-65.9c-4.4 0-8-3.6-8-8V316c0-4.4 3.6-8 8-8h184.7c4.4 0 8 3.6 8 8v127.2c0 4.4-3.6 8-8 8h-66.7v18.6h98.8c4.4 0 8 3.6 8 8v35.6c0 4.4-3.6 8-8 8h-59c18.1 29.1 41.8 54.3 72.3 76.9 2.6 2.1 3.2 5.9 1.2 8.5z\")),t.InteractionFill=u(\"interaction\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM726 585.7c0 55.3-44.7 100.1-99.7 100.1H420.6v53.4c0 5.7-6.5 8.8-10.9 5.3l-109.1-85.7c-3.5-2.7-3.5-8 0-10.7l109.1-85.7c4.4-3.5 10.9-.3 10.9 5.3v53.4h205.7c19.6 0 35.5-16 35.5-35.6v-78.9c0-3.7 3-6.8 6.8-6.8h50.7c3.7 0 6.8 3 6.8 6.8v79.1zm-2.6-209.9l-109.1 85.7c-4.4 3.5-10.9.3-10.9-5.3v-53.4H397.7c-19.6 0-35.5 16-35.5 35.6v78.9c0 3.7-3 6.8-6.8 6.8h-50.7c-3.7 0-6.8-3-6.8-6.8v-78.9c0-55.3 44.7-100.1 99.7-100.1h205.7v-53.4c0-5.7 6.5-8.8 10.9-5.3l109.1 85.7c3.6 2.5 3.6 7.8.1 10.5z\")),t.InterationFill=u(\"interation\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM726 585.7c0 55.3-44.7 100.1-99.7 100.1H420.6v53.4c0 5.7-6.5 8.8-10.9 5.3l-109.1-85.7c-3.5-2.7-3.5-8 0-10.7l109.1-85.7c4.4-3.5 10.9-.3 10.9 5.3v53.4h205.7c19.6 0 35.5-16 35.5-35.6v-78.9c0-3.7 3-6.8 6.8-6.8h50.7c3.7 0 6.8 3 6.8 6.8v79.1zm-2.6-209.9l-109.1 85.7c-4.4 3.5-10.9.3-10.9-5.3v-53.4H397.7c-19.6 0-35.5 16-35.5 35.6v78.9c0 3.7-3 6.8-6.8 6.8h-50.7c-3.7 0-6.8-3-6.8-6.8v-78.9c0-55.3 44.7-100.1 99.7-100.1h205.7v-53.4c0-5.7 6.5-8.8 10.9-5.3l109.1 85.7c3.6 2.5 3.6 7.8.1 10.5z\")),t.LayoutFill=u(\"layout\",c,l(o,\"M384 912h496c17.7 0 32-14.3 32-32V340H384v572zm496-800H384v164h528V144c0-17.7-14.3-32-32-32zm-768 32v736c0 17.7 14.3 32 32 32h176V112H144c-17.7 0-32 14.3-32 32z\")),t.LeftCircleFill=u(\"left-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm104 316.9c0 10.2-4.9 19.9-13.2 25.9L457.4 512l145.4 105.2c8.3 6 13.2 15.6 13.2 25.9V690c0 6.5-7.4 10.3-12.7 6.5l-246-178a7.95 7.95 0 0 1 0-12.9l246-178a8 8 0 0 1 12.7 6.5v46.8z\")),t.LeftSquareFill=u(\"left-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM624 380.9c0 10.2-4.9 19.9-13.2 25.9L465.4 512l145.4 105.2c8.3 6 13.2 15.6 13.2 25.9V690c0 6.5-7.4 10.3-12.7 6.5l-246-178a7.95 7.95 0 0 1 0-12.9l246-178c5.3-3.8 12.7 0 12.7 6.5v46.8z\")),t.LikeFill=u(\"like\",c,l(o,\"M885.9 533.7c16.8-22.2 26.1-49.4 26.1-77.7 0-44.9-25.1-87.4-65.5-111.1a67.67 67.67 0 0 0-34.3-9.3H572.4l6-122.9c1.4-29.7-9.1-57.9-29.5-79.4A106.62 106.62 0 0 0 471 99.9c-52 0-98 35-111.8 85.1l-85.9 311h-.3v428h472.3c9.2 0 18.2-1.8 26.5-5.4 47.6-20.3 78.3-66.8 78.3-118.4 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7-.2-12.6-2-25.1-5.6-37.1zM112 528v364c0 17.7 14.3 32 32 32h65V496h-65c-17.7 0-32 14.3-32 32z\")),t.LockFill=u(\"lock\",c,l(o,\"M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM540 701v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53a48.01 48.01 0 1 1 56 0zm152-237H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224z\")),t.LinkedinFill=u(\"linkedin\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM349.3 793.7H230.6V411.9h118.7v381.8zm-59.3-434a68.8 68.8 0 1 1 68.8-68.8c-.1 38-30.9 68.8-68.8 68.8zm503.7 434H675.1V608c0-44.3-.8-101.2-61.7-101.2-61.7 0-71.2 48.2-71.2 98v188.9H423.7V411.9h113.8v52.2h1.6c15.8-30 54.5-61.7 112.3-61.7 120.2 0 142.3 79.1 142.3 181.9v209.4z\")),t.MailFill=u(\"mail\",c,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-80.8 108.9L531.7 514.4c-7.8 6.1-18.7 6.1-26.5 0L189.6 268.9A7.2 7.2 0 0 1 194 256h648.8a7.2 7.2 0 0 1 4.4 12.9z\")),t.MedicineBoxFill=u(\"medicine-box\",c,l(o,\"M839.2 278.1a32 32 0 0 0-30.4-22.1H736V144c0-17.7-14.3-32-32-32H320c-17.7 0-32 14.3-32 32v112h-72.8a31.9 31.9 0 0 0-30.4 22.1L112 502v378c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V502l-72.8-223.9zM660 628c0 4.4-3.6 8-8 8H544v108c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V636H372c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h108V464c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v108h108c4.4 0 8 3.6 8 8v48zm4-372H360v-72h304v72z\")),t.MediumCircleFill=u(\"medium-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm256 253.7l-40.8 39.1c-3.6 2.7-5.3 7.1-4.6 11.4v287.7c-.7 4.4 1 8.8 4.6 11.4l40 39.1v8.7H566.4v-8.3l41.3-40.1c4.1-4.1 4.1-5.3 4.1-11.4V422.5l-115 291.6h-15.5L347.5 422.5V618c-1.2 8.2 1.7 16.5 7.5 22.4l53.8 65.1v8.7H256v-8.7l53.8-65.1a26.1 26.1 0 0 0 7-22.4V392c.7-6.3-1.7-12.4-6.5-16.7l-47.8-57.6V309H411l114.6 251.5 100.9-251.3H768v8.5z\")),t.MediumSquareFill=u(\"medium-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM768 317.7l-40.8 39.1c-3.6 2.7-5.3 7.1-4.6 11.4v287.7c-.7 4.4 1 8.8 4.6 11.4l40 39.1v8.7H566.4v-8.3l41.3-40.1c4.1-4.1 4.1-5.3 4.1-11.4V422.5l-115 291.6h-15.5L347.5 422.5V618c-1.2 8.2 1.7 16.5 7.5 22.4l53.8 65.1v8.7H256v-8.7l53.8-65.1a26.1 26.1 0 0 0 7-22.4V392c.7-6.3-1.7-12.4-6.5-16.7l-47.8-57.6V309H411l114.6 251.5 100.9-251.3H768v8.5z\")),t.MehFill=u(\"meh\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm384 200c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48zm16-152a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.MessageFill=u(\"message\",c,l(o,\"M924.3 338.4a447.57 447.57 0 0 0-96.1-143.3 443.09 443.09 0 0 0-143-96.3A443.91 443.91 0 0 0 512 64h-2c-60.5.3-119 12.3-174.1 35.9a444.08 444.08 0 0 0-141.7 96.5 445 445 0 0 0-95 142.8A449.89 449.89 0 0 0 65 514.1c.3 69.4 16.9 138.3 47.9 199.9v152c0 25.4 20.6 46 45.9 46h151.8a447.72 447.72 0 0 0 199.5 48h2.1c59.8 0 117.7-11.6 172.3-34.3A443.2 443.2 0 0 0 827 830.5c41.2-40.9 73.6-88.7 96.3-142 23.5-55.2 35.5-113.9 35.8-174.5.2-60.9-11.6-120-34.8-175.6zM312.4 560c-26.4 0-47.9-21.5-47.9-48s21.5-48 47.9-48 47.9 21.5 47.9 48-21.4 48-47.9 48zm199.6 0c-26.4 0-47.9-21.5-47.9-48s21.5-48 47.9-48 47.9 21.5 47.9 48-21.5 48-47.9 48zm199.6 0c-26.4 0-47.9-21.5-47.9-48s21.5-48 47.9-48 47.9 21.5 47.9 48-21.5 48-47.9 48z\")),t.MinusCircleFill=u(\"minus-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm192 472c0 4.4-3.6 8-8 8H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h368c4.4 0 8 3.6 8 8v48z\")),t.MinusSquareFill=u(\"minus-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h368c4.4 0 8 3.6 8 8v48z\")),t.MobileFill=u(\"mobile\",c,l(o,\"M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64zM512 824c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40z\")),t.MoneyCollectFill=u(\"money-collect\",c,l(o,\"M911.5 699.7a8 8 0 0 0-10.3-4.8L840 717.2V179c0-37.6-30.4-68-68-68H252c-37.6 0-68 30.4-68 68v538.2l-61.3-22.3c-.9-.3-1.8-.5-2.7-.5-4.4 0-8 3.6-8 8V762c0 3.3 2.1 6.3 5.3 7.5L501 909.1c7.1 2.6 14.8 2.6 21.9 0l383.8-139.5c3.2-1.2 5.3-4.2 5.3-7.5v-59.6c0-1-.2-1.9-.5-2.8zm-243.8-377L564 514.3h57.6c4.4 0 8 3.6 8 8v27.1c0 4.4-3.6 8-8 8h-76.3v39h76.3c4.4 0 8 3.6 8 8v27.1c0 4.4-3.6 8-8 8h-76.3V703c0 4.4-3.6 8-8 8h-49.9c-4.4 0-8-3.6-8-8v-63.4h-76c-4.4 0-8-3.6-8-8v-27.1c0-4.4 3.6-8 8-8h76v-39h-76c-4.4 0-8-3.6-8-8v-27.1c0-4.4 3.6-8 8-8h57L356.5 322.8c-2.1-3.8-.7-8.7 3.2-10.8 1.2-.7 2.5-1 3.8-1h55.7a8 8 0 0 1 7.1 4.4L511 484.2h3.3L599 315.4c1.3-2.7 4.1-4.4 7.1-4.4h54.5c4.4 0 8 3.6 8.1 7.9 0 1.3-.4 2.6-1 3.8z\")),t.PauseCircleFill=u(\"pause-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-80 600c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V360c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v304zm224 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V360c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v304z\")),t.PayCircleFill=u(\"pay-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm166.6 246.8L567.5 515.6h62c4.4 0 8 3.6 8 8v29.9c0 4.4-3.6 8-8 8h-82V603h82c4.4 0 8 3.6 8 8v29.9c0 4.4-3.6 8-8 8h-82V717c0 4.4-3.6 8-8 8h-54.3c-4.4 0-8-3.6-8-8v-68.1h-81.7c-4.4 0-8-3.6-8-8V611c0-4.4 3.6-8 8-8h81.7v-41.5h-81.7c-4.4 0-8-3.6-8-8v-29.9c0-4.4 3.6-8 8-8h61.4L345.4 310.8a8.07 8.07 0 0 1 7-11.9h60.7c3 0 5.8 1.7 7.1 4.4l90.6 180h3.4l90.6-180a8 8 0 0 1 7.1-4.4h59.5c4.4 0 8 3.6 8 8 .2 1.4-.2 2.7-.8 3.9z\")),t.NotificationFill=u(\"notification\",c,l(o,\"M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.6c-3.7 11.6-5.6 23.9-5.6 36.4 0 65.9 53.8 119.5 120 119.5 55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3 16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112zM344 762.3c-26.5 0-48-21.4-48-47.8 0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1z\")),t.PhoneFill=u(\"phone\",c,l(o,\"M885.6 230.2L779.1 123.8a80.83 80.83 0 0 0-57.3-23.8c-21.7 0-42.1 8.5-57.4 23.8L549.8 238.4a80.83 80.83 0 0 0-23.8 57.3c0 21.7 8.5 42.1 23.8 57.4l83.8 83.8A393.82 393.82 0 0 1 553.1 553 395.34 395.34 0 0 1 437 633.8L353.2 550a80.83 80.83 0 0 0-57.3-23.8c-21.7 0-42.1 8.5-57.4 23.8L123.8 664.5a80.89 80.89 0 0 0-23.8 57.4c0 21.7 8.5 42.1 23.8 57.4l106.3 106.3c24.4 24.5 58.1 38.4 92.7 38.4 7.3 0 14.3-.6 21.2-1.8 134.8-22.2 268.5-93.9 376.4-201.7C828.2 612.8 899.8 479.2 922.3 344c6.8-41.3-6.9-83.8-36.7-113.8z\")),t.PictureFill=u(\"picture\",c,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zM338 304c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm513.9 437.1a8.11 8.11 0 0 1-5.2 1.9H177.2c-4.4 0-8-3.6-8-8 0-1.9.7-3.7 1.9-5.2l170.3-202c2.8-3.4 7.9-3.8 11.3-1 .3.3.7.6 1 1l99.4 118 158.1-187.5c2.8-3.4 7.9-3.8 11.3-1 .3.3.7.6 1 1l229.6 271.6c2.6 3.3 2.2 8.4-1.2 11.2z\")),t.PieChartFill=u(\"pie-chart\",c,l(o,\"M863.1 518.5H505.5V160.9c0-4.4-3.6-8-8-8h-26a398.57 398.57 0 0 0-282.5 117 397.47 397.47 0 0 0-85.6 127C82.6 446.2 72 498.5 72 552.5S82.6 658.7 103.4 708c20.1 47.5 48.9 90.3 85.6 127 36.7 36.7 79.4 65.5 127 85.6a396.64 396.64 0 0 0 155.6 31.5 398.57 398.57 0 0 0 282.5-117c36.7-36.7 65.5-79.4 85.6-127a396.64 396.64 0 0 0 31.5-155.6v-26c-.1-4.4-3.7-8-8.1-8zM951 463l-2.6-28.2c-8.5-92-49.3-178.8-115.1-244.3A398.5 398.5 0 0 0 588.4 75.6L560.1 73c-4.7-.4-8.7 3.2-8.7 7.9v383.7c0 4.4 3.6 8 8 8l383.6-1c4.7-.1 8.4-4 8-8.6z\")),t.PlayCircleFill=u(\"play-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm144.1 454.9L437.7 677.8a8.02 8.02 0 0 1-12.7-6.5V353.7a8 8 0 0 1 12.7-6.5L656.1 506a7.9 7.9 0 0 1 0 12.9z\")),t.PlaySquareFill=u(\"play-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM641.7 520.8L442.3 677.6c-7.4 5.8-18.3.6-18.3-8.8V355.3c0-9.4 10.9-14.7 18.3-8.8l199.4 156.7a11.2 11.2 0 0 1 0 17.6z\")),t.PlusCircleFill=u(\"plus-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm192 472c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z\")),t.PlusSquareFill=u(\"plus-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM704 536c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z\")),t.PoundCircleFill=u(\"pound-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm146 658c0 4.4-3.6 8-8 8H376.2c-4.4 0-8-3.6-8-8v-38.5c0-3.7 2.5-6.9 6.1-7.8 44-10.9 72.8-49 72.8-94.2 0-14.7-2.5-29.4-5.9-44.2H374c-4.4 0-8-3.6-8-8v-30c0-4.4 3.6-8 8-8h53.7c-7.8-25.1-14.6-50.7-14.6-77.1 0-75.8 58.6-120.3 151.5-120.3 26.5 0 51.4 5.5 70.3 12.7 3.1 1.2 5.2 4.2 5.2 7.5v39.5a8 8 0 0 1-10.6 7.6c-17.9-6.4-39-10.5-60.4-10.5-53.3 0-87.3 26.6-87.3 70.2 0 24.7 6.2 47.9 13.4 70.5h112c4.4 0 8 3.6 8 8v30c0 4.4-3.6 8-8 8h-98.6c3.1 13.2 5.3 26.9 5.3 41 0 40.7-16.5 73.9-43.9 91.1v4.7h180c4.4 0 8 3.6 8 8V722z\")),t.PrinterFill=u(\"printer\",c,l(o,\"M732 120c0-4.4-3.6-8-8-8H300c-4.4 0-8 3.6-8 8v148h440V120zm120 212H172c-44.2 0-80 35.8-80 80v328c0 17.7 14.3 32 32 32h168v132c0 4.4 3.6 8 8 8h424c4.4 0 8-3.6 8-8V772h168c17.7 0 32-14.3 32-32V412c0-44.2-35.8-80-80-80zM664 844H360V568h304v276zm164-360c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-40c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v40z\")),t.ProfileFill=u(\"profile\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM380 696c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm0-144c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm0-144c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm304 272c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm0-144c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm0-144c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48z\")),t.ProjectFill=u(\"project\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM368 744c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v464zm192-280c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v184zm192 72c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v256z\")),t.PushpinFill=u(\"pushpin\",c,l(o,\"M878.3 392.1L631.9 145.7c-6.5-6.5-15-9.7-23.5-9.7s-17 3.2-23.5 9.7L423.8 306.9c-12.2-1.4-24.5-2-36.8-2-73.2 0-146.4 24.1-206.5 72.3-15.4 12.3-16.6 35.4-2.7 49.4l181.7 181.7-215.4 215.2a15.8 15.8 0 0 0-4.6 9.8l-3.4 37.2c-.9 9.4 6.6 17.4 15.9 17.4.5 0 1 0 1.5-.1l37.2-3.4c3.7-.3 7.2-2 9.8-4.6l215.4-215.4 181.7 181.7c6.5 6.5 15 9.7 23.5 9.7 9.7 0 19.3-4.2 25.9-12.4 56.3-70.3 79.7-158.3 70.2-243.4l161.1-161.1c12.9-12.8 12.9-33.8 0-46.8z\")),t.PropertySafetyFill=u(\"property-safety\",c,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM648.3 332.8l-87.7 161.1h45.7c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4v29.7h63.4c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4V658c0 5.5-4.5 10-10 10h-41.3c-5.5 0-10-4.5-10-10v-51.8h-63.1c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h63.1v-29.7h-63.1c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h45.2l-88-161.1c-2.6-4.8-.9-10.9 4-13.6 1.5-.8 3.1-1.2 4.8-1.2h46c3.8 0 7.2 2.1 8.9 5.5l72.9 144.3 73.2-144.3a10 10 0 0 1 8.9-5.5h45c5.5 0 10 4.5 10 10 .1 1.7-.3 3.3-1.1 4.8z\")),t.QqCircleFill=u(\"qq-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm210.5 612.4c-11.5 1.4-44.9-52.7-44.9-52.7 0 31.3-16.2 72.2-51.1 101.8 16.9 5.2 54.9 19.2 45.9 34.4-7.3 12.3-125.6 7.9-159.8 4-34.2 3.8-152.5 8.3-159.8-4-9.1-15.2 28.9-29.2 45.8-34.4-35-29.5-51.1-70.4-51.1-101.8 0 0-33.4 54.1-44.9 52.7-5.4-.7-12.4-29.6 9.4-99.7 10.3-33 22-60.5 40.2-105.8-3.1-116.9 45.3-215 160.4-215 113.9 0 163.3 96.1 160.4 215 18.1 45.2 29.9 72.8 40.2 105.8 21.7 70.1 14.6 99.1 9.3 99.7z\")),t.QqSquareFill=u(\"qq-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM722.5 676.4c-11.5 1.4-44.9-52.7-44.9-52.7 0 31.3-16.2 72.2-51.1 101.8 16.9 5.2 54.9 19.2 45.9 34.4-7.3 12.3-125.6 7.9-159.8 4-34.2 3.8-152.5 8.3-159.8-4-9.1-15.2 28.9-29.2 45.8-34.4-35-29.5-51.1-70.4-51.1-101.8 0 0-33.4 54.1-44.9 52.7-5.4-.7-12.4-29.6 9.4-99.7 10.3-33 22-60.5 40.2-105.8-3.1-116.9 45.3-215 160.4-215 113.9 0 163.3 96.1 160.4 215 18.1 45.2 29.9 72.8 40.2 105.8 21.7 70.1 14.6 99.1 9.3 99.7z\")),t.QuestionCircleFill=u(\"question-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 708c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm62.9-219.5a48.3 48.3 0 0 0-30.9 44.8V620c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-21.5c0-23.1 6.7-45.9 19.9-64.9 12.9-18.6 30.9-32.8 52.1-40.9 34-13.1 56-41.6 56-72.7 0-44.1-43.1-80-96-80s-96 35.9-96 80v7.6c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V420c0-39.3 17.2-76 48.4-103.3C430.4 290.4 470 276 512 276s81.6 14.5 111.6 40.7C654.8 344 672 380.7 672 420c0 57.8-38.1 109.8-97.1 132.5z\")),t.ReadFill=u(\"read\",c,l(o,\"M928 161H699.2c-49.1 0-97.1 14.1-138.4 40.7L512 233l-48.8-31.3A255.2 255.2 0 0 0 324.8 161H96c-17.7 0-32 14.3-32 32v568c0 17.7 14.3 32 32 32h228.8c49.1 0 97.1 14.1 138.4 40.7l44.4 28.6c1.3.8 2.8 1.3 4.3 1.3s3-.4 4.3-1.3l44.4-28.6C602 807.1 650.1 793 699.2 793H928c17.7 0 32-14.3 32-32V193c0-17.7-14.3-32-32-32zM404 553.5c0 4.1-3.2 7.5-7.1 7.5H211.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm0-140c0 4.1-3.2 7.5-7.1 7.5H211.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm416 140c0 4.1-3.2 7.5-7.1 7.5H627.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45zm0-140c0 4.1-3.2 7.5-7.1 7.5H627.1c-3.9 0-7.1-3.4-7.1-7.5v-45c0-4.1 3.2-7.5 7.1-7.5h185.7c3.9 0 7.1 3.4 7.1 7.5v45z\")),t.ReconciliationFill=u(\"reconciliation\",c,l(o,\"M676 623c-18.8 0-34 15.2-34 34s15.2 34 34 34 34-15.2 34-34-15.2-34-34-34zm204-455H668c0-30.9-25.1-56-56-56h-80c-30.9 0-56 25.1-56 56H264c-17.7 0-32 14.3-32 32v200h-88c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h336c17.7 0 32-14.3 32-32v-16h368c17.7 0 32-14.3 32-32V200c0-17.7-14.3-32-32-32zM448 848H176V616h272v232zm0-296H176v-88h272v88zm20-272v-48h72v-56h64v56h72v48H468zm180 168v56c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-56c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8zm28 301c-50.8 0-92-41.2-92-92s41.2-92 92-92 92 41.2 92 92-41.2 92-92 92zm92-245c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-96c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v96zm-92 61c-50.8 0-92 41.2-92 92s41.2 92 92 92 92-41.2 92-92-41.2-92-92-92zm0 126c-18.8 0-34-15.2-34-34s15.2-34 34-34 34 15.2 34 34-15.2 34-34 34z\")),t.RedEnvelopeFill=u(\"red-envelope\",c,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zM647 470.4l-87.2 161h45.9c4.6 0 8.4 3.8 8.4 8.4v25.1c0 4.6-3.8 8.4-8.4 8.4h-63.3v28.6h63.3c4.6 0 8.4 3.8 8.4 8.4v25c.2 4.6-3.6 8.5-8.2 8.5h-63.3v49.9c0 4.6-3.8 8.4-8.4 8.4h-43.7c-4.6 0-8.4-3.8-8.4-8.4v-49.9h-63c-4.6 0-8.4-3.8-8.4-8.4v-25.1c0-4.6 3.8-8.4 8.4-8.4h63v-28.6h-63c-4.6 0-8.4-3.8-8.4-8.4v-25.1c0-4.6 3.8-8.4 8.4-8.4h45.4l-87.5-161c-2.2-4.1-.7-9.1 3.4-11.4 1.3-.6 2.6-1 3.9-1h48.8c3.2 0 6.1 1.8 7.5 4.6l71.9 141.8 71.9-141.9a8.5 8.5 0 0 1 7.5-4.6h47.8c4.6 0 8.4 3.8 8.4 8.4-.1 1.5-.5 2.9-1.1 4.1zM512.6 323L289 148h446L512.6 323z\")),t.RedditCircleFill=u(\"reddit-circle\",c,l(o,\"M584 548a36 36 0 1 0 72 0 36 36 0 1 0-72 0zm144-108a35.9 35.9 0 0 0-32.5 20.6c18.8 14.3 34.4 30.7 45.9 48.8A35.98 35.98 0 0 0 728 440zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm245 477.9c4.6 13.5 7 27.6 7 42.1 0 99.4-112.8 180-252 180s-252-80.6-252-180c0-14.5 2.4-28.6 7-42.1A72.01 72.01 0 0 1 296 404c27.1 0 50.6 14.9 62.9 37 36.2-19.8 80.2-32.8 128.1-36.1l58.4-131.1c4.3-9.8 15.2-14.8 25.5-11.8l91.6 26.5a54.03 54.03 0 0 1 101.6 25.6c0 29.8-24.2 54-54 54-23.5 0-43.5-15.1-50.9-36.1L577 308.3l-43 96.5c49.1 3 94.2 16.1 131.2 36.3 12.3-22.1 35.8-37 62.9-37 39.8 0 72 32.2 72 72-.1 29.3-17.8 54.6-43.1 65.8zm-171.3 83c-14.9 11.7-44.3 24.3-73.7 24.3s-58.9-12.6-73.7-24.3c-9.3-7.3-22.7-5.7-30 3.6-7.3 9.3-5.7 22.7 3.6 30 25.7 20.4 65 33.5 100.1 33.5 35.1 0 74.4-13.1 100.2-33.5 9.3-7.3 10.9-20.8 3.6-30a21.46 21.46 0 0 0-30.1-3.6zM296 440a35.98 35.98 0 0 0-13.4 69.4c11.5-18.1 27.1-34.5 45.9-48.8A35.9 35.9 0 0 0 296 440zm72 108a36 36 0 1 0 72 0 36 36 0 1 0-72 0z\")),t.RedditSquareFill=u(\"reddit-square\",c,l(o,\"M296 440a35.98 35.98 0 0 0-13.4 69.4c11.5-18.1 27.1-34.5 45.9-48.8A35.9 35.9 0 0 0 296 440zm289.7 184.9c-14.9 11.7-44.3 24.3-73.7 24.3s-58.9-12.6-73.7-24.3c-9.3-7.3-22.7-5.7-30 3.6-7.3 9.3-5.7 22.7 3.6 30 25.7 20.4 65 33.5 100.1 33.5 35.1 0 74.4-13.1 100.2-33.5 9.3-7.3 10.9-20.8 3.6-30a21.46 21.46 0 0 0-30.1-3.6zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM757 541.9c4.6 13.5 7 27.6 7 42.1 0 99.4-112.8 180-252 180s-252-80.6-252-180c0-14.5 2.4-28.6 7-42.1A72.01 72.01 0 0 1 296 404c27.1 0 50.6 14.9 62.9 37 36.2-19.8 80.2-32.8 128.1-36.1l58.4-131.1c4.3-9.8 15.2-14.8 25.5-11.8l91.6 26.5a54.03 54.03 0 0 1 101.6 25.6c0 29.8-24.2 54-54 54-23.5 0-43.5-15.1-50.9-36.1L577 308.3l-43 96.5c49.1 3 94.2 16.1 131.2 36.3 12.3-22.1 35.8-37 62.9-37 39.8 0 72 32.2 72 72-.1 29.3-17.8 54.6-43.1 65.8zM584 548a36 36 0 1 0 72 0 36 36 0 1 0-72 0zm144-108a35.9 35.9 0 0 0-32.5 20.6c18.8 14.3 34.4 30.7 45.9 48.8A35.98 35.98 0 0 0 728 440zM368 548a36 36 0 1 0 72 0 36 36 0 1 0-72 0z\")),t.RestFill=u(\"rest\",c,l(o,\"M832 256h-28.1l-35.7-120.9c-4-13.7-16.5-23.1-30.7-23.1h-451c-14.3 0-26.8 9.4-30.7 23.1L220.1 256H192c-17.7 0-32 14.3-32 32v28c0 4.4 3.6 8 8 8h45.8l47.7 558.7a32 32 0 0 0 31.9 29.3h429.2a32 32 0 0 0 31.9-29.3L802.2 324H856c4.4 0 8-3.6 8-8v-28c0-17.7-14.3-32-32-32zM508 704c-79.5 0-144-64.5-144-144s64.5-144 144-144 144 64.5 144 144-64.5 144-144 144zM291 256l22.4-76h397.2l22.4 76H291zm137 304a80 80 0 1 0 160 0 80 80 0 1 0-160 0z\")),t.RightCircleFill=u(\"right-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm154.7 454.5l-246 178c-5.3 3.8-12.7 0-12.7-6.5v-46.9c0-10.2 4.9-19.9 13.2-25.9L566.6 512 421.2 406.8c-8.3-6-13.2-15.6-13.2-25.9V334c0-6.5 7.4-10.3 12.7-6.5l246 178c4.4 3.2 4.4 9.8 0 13z\")),t.RocketFill=u(\"rocket\",c,l(o,\"M864 736c0-111.6-65.4-208-160-252.9V317.3c0-15.1-5.3-29.7-15.1-41.2L536.5 95.4C530.1 87.8 521 84 512 84s-18.1 3.8-24.5 11.4L335.1 276.1a63.97 63.97 0 0 0-15.1 41.2v165.8C225.4 528 160 624.4 160 736h156.5c-2.3 7.2-3.5 15-3.5 23.8 0 22.1 7.6 43.7 21.4 60.8a97.2 97.2 0 0 0 43.1 30.6c23.1 54 75.6 88.8 134.5 88.8 29.1 0 57.3-8.6 81.4-24.8 23.6-15.8 41.9-37.9 53-64a97 97 0 0 0 43.1-30.5 97.52 97.52 0 0 0 21.4-60.8c0-8.4-1.1-16.4-3.1-23.8L864 736zM512 352a48.01 48.01 0 0 1 0 96 48.01 48.01 0 0 1 0-96zm116.1 432.2c-5.2 3-11.2 4.2-17.1 3.4l-19.5-2.4-2.8 19.4c-5.4 37.9-38.4 66.5-76.7 66.5s-71.3-28.6-76.7-66.5l-2.8-19.5-19.5 2.5a27.7 27.7 0 0 1-17.1-3.5c-8.7-5-14.1-14.3-14.1-24.4 0-10.6 5.9-19.4 14.6-23.8h231.3c8.8 4.5 14.6 13.3 14.6 23.8-.1 10.2-5.5 19.6-14.2 24.5z\")),t.RightSquareFill=u(\"right-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM658.7 518.5l-246 178c-5.3 3.8-12.7 0-12.7-6.5v-46.9c0-10.2 4.9-19.9 13.2-25.9L558.6 512 413.2 406.8c-8.3-6-13.2-15.6-13.2-25.9V334c0-6.5 7.4-10.3 12.7-6.5l246 178c4.4 3.2 4.4 9.8 0 13z\")),t.SafetyCertificateFill=u(\"safety-certificate\",c,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM694.5 340.7L481.9 633.4a16.1 16.1 0 0 1-26 0l-126.4-174c-3.8-5.3 0-12.7 6.5-12.7h55.2c5.1 0 10 2.5 13 6.6l64.7 89 150.9-207.8c3-4.1 7.8-6.6 13-6.6H688c6.5.1 10.3 7.5 6.5 12.8z\")),t.SaveFill=u(\"save\",c,l(o,\"M893.3 293.3L730.7 130.7c-12-12-28.3-18.7-45.3-18.7H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 176h256v112H384V176zm128 554c-79.5 0-144-64.5-144-144s64.5-144 144-144 144 64.5 144 144-64.5 144-144 144zm0-224c-44.2 0-80 35.8-80 80s35.8 80 80 80 80-35.8 80-80-35.8-80-80-80z\")),t.ScheduleFill=u(\"schedule\",c,l(o,\"M928 224H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zM424 688c0 4.4-3.6 8-8 8H232c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm0-136c0 4.4-3.6 8-8 8H232c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm374.5-91.3l-165 228.7a15.9 15.9 0 0 1-25.8 0L493.5 531.2c-3.8-5.3 0-12.7 6.5-12.7h54.9c5.1 0 9.9 2.5 12.9 6.6l52.8 73.1 103.7-143.7c3-4.2 7.8-6.6 12.9-6.6H792c6.5.1 10.3 7.5 6.5 12.8z\")),t.SecurityScanFill=u(\"security-scan\",c,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM626.8 554c-48.5 48.5-123 55.2-178.6 20.1l-77.5 77.5a8.03 8.03 0 0 1-11.3 0l-34-34a8.03 8.03 0 0 1 0-11.3l77.5-77.5c-35.1-55.7-28.4-130.1 20.1-178.6 56.3-56.3 147.5-56.3 203.8 0 56.3 56.3 56.3 147.5 0 203.8zm-158.54-45.27a80.1 80.1 0 1 0 113.27-113.28 80.1 80.1 0 1 0-113.27 113.28z\")),t.SettingFill=u(\"setting\",c,l(o,\"M512.5 390.6c-29.9 0-57.9 11.6-79.1 32.8-21.1 21.2-32.8 49.2-32.8 79.1 0 29.9 11.7 57.9 32.8 79.1 21.2 21.1 49.2 32.8 79.1 32.8 29.9 0 57.9-11.7 79.1-32.8 21.1-21.2 32.8-49.2 32.8-79.1 0-29.9-11.7-57.9-32.8-79.1a110.96 110.96 0 0 0-79.1-32.8zm412.3 235.5l-65.4-55.9c3.1-19 4.7-38.4 4.7-57.7s-1.6-38.8-4.7-57.7l65.4-55.9a32.03 32.03 0 0 0 9.3-35.2l-.9-2.6a442.5 442.5 0 0 0-79.6-137.7l-1.8-2.1a32.12 32.12 0 0 0-35.1-9.5l-81.2 28.9c-30-24.6-63.4-44-99.6-57.5l-15.7-84.9a32.05 32.05 0 0 0-25.8-25.7l-2.7-.5c-52-9.4-106.8-9.4-158.8 0l-2.7.5a32.05 32.05 0 0 0-25.8 25.7l-15.8 85.3a353.44 353.44 0 0 0-98.9 57.3l-81.8-29.1a32 32 0 0 0-35.1 9.5l-1.8 2.1a445.93 445.93 0 0 0-79.6 137.7l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.2 56.5c-3.1 18.8-4.6 38-4.6 57 0 19.2 1.5 38.4 4.6 57l-66 56.5a32.03 32.03 0 0 0-9.3 35.2l.9 2.6c18.1 50.3 44.8 96.8 79.6 137.7l1.8 2.1a32.12 32.12 0 0 0 35.1 9.5l81.8-29.1c29.8 24.5 63 43.9 98.9 57.3l15.8 85.3a32.05 32.05 0 0 0 25.8 25.7l2.7.5a448.27 448.27 0 0 0 158.8 0l2.7-.5a32.05 32.05 0 0 0 25.8-25.7l15.7-84.9c36.2-13.6 69.6-32.9 99.6-57.5l81.2 28.9a32 32 0 0 0 35.1-9.5l1.8-2.1c34.8-41.1 61.5-87.4 79.6-137.7l.9-2.6c4.3-12.4.6-26.3-9.5-35zm-412.3 52.2c-97.1 0-175.8-78.7-175.8-175.8s78.7-175.8 175.8-175.8 175.8 78.7 175.8 175.8-78.7 175.8-175.8 175.8z\")),t.ShopFill=u(\"shop\",c,l(o,\"M882 272.1V144c0-17.7-14.3-32-32-32H174c-17.7 0-32 14.3-32 32v128.1c-16.7 1-30 14.9-30 31.9v131.7a177 177 0 0 0 14.4 70.4c4.3 10.2 9.6 19.8 15.6 28.9v345c0 17.6 14.3 32 32 32h274V736h128v176h274c17.7 0 32-14.3 32-32V535a175 175 0 0 0 15.6-28.9c9.5-22.3 14.4-46 14.4-70.4V304c0-17-13.3-30.9-30-31.9zm-72 568H640V704c0-17.7-14.3-32-32-32H416c-17.7 0-32 14.3-32 32v136.1H214V597.9c2.9 1.4 5.9 2.8 9 4 22.3 9.4 46 14.1 70.4 14.1s48-4.7 70.4-14.1c13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1 24.4 0 48-4.7 70.4-14.1 13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1 24.4 0 48-4.7 70.4-14.1 3-1.3 6-2.6 9-4v242.2zm0-568.1H214v-88h596v88z\")),t.ShoppingFill=u(\"shopping\",c,l(o,\"M832 312H696v-16c0-101.6-82.4-184-184-184s-184 82.4-184 184v16H192c-17.7 0-32 14.3-32 32v536c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V344c0-17.7-14.3-32-32-32zm-208 0H400v-16c0-61.9 50.1-112 112-112s112 50.1 112 112v16z\")),t.SketchCircleFill=u(\"sketch-circle\",c,l(o,\"M582.3 625.6l147.9-166.3h-63.4zm90-202.3h62.5l-92.1-115.1zm-274.7 36L512 684.5l114.4-225.2zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm286.7 380.2L515.8 762.3c-1 1.1-2.4 1.7-3.8 1.7s-2.8-.6-3.8-1.7L225.3 444.2a5.14 5.14 0 0 1-.2-6.6L365.6 262c1-1.2 2.4-1.9 4-1.9h284.6c1.6 0 3 .7 4 1.9l140.5 175.6a4.9 4.9 0 0 1 0 6.6zm-190.5-20.9L512 326.1l-96.2 97.2zM420.3 301.1l-23.1 89.8 88.8-89.8zm183.4 0H538l88.8 89.8zm-222.4 7.1l-92.1 115.1h62.5zm-87.5 151.1l147.9 166.3-84.5-166.3z\")),t.SketchSquareFill=u(\"sketch-square\",c,l(o,\"M608.2 423.3L512 326.1l-96.2 97.2zm-25.9 202.3l147.9-166.3h-63.4zm90-202.3h62.5l-92.1-115.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-81.3 332.2L515.8 762.3c-1 1.1-2.4 1.7-3.8 1.7s-2.8-.6-3.8-1.7L225.3 444.2a5.14 5.14 0 0 1-.2-6.6L365.6 262c1-1.2 2.4-1.9 4-1.9h284.6c1.6 0 3 .7 4 1.9l140.5 175.6a4.9 4.9 0 0 1 0 6.6zm-401.1 15.1L512 684.5l114.4-225.2zm-16.3-151.1l-92.1 115.1h62.5zm-87.5 151.1l147.9 166.3-84.5-166.3zm126.5-158.2l-23.1 89.8 88.8-89.8zm183.4 0H538l88.8 89.8z\")),t.SkinFill=u(\"skin\",c,l(o,\"M870 126H663.8c-17.4 0-32.9 11.9-37 29.3C614.3 208.1 567 246 512 246s-102.3-37.9-114.8-90.7a37.93 37.93 0 0 0-37-29.3H154a44 44 0 0 0-44 44v252a44 44 0 0 0 44 44h75v388a44 44 0 0 0 44 44h478a44 44 0 0 0 44-44V466h75a44 44 0 0 0 44-44V170a44 44 0 0 0-44-44z\")),t.SlackCircleFill=u(\"slack-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM361.5 580.2c0 27.8-22.5 50.4-50.3 50.4a50.35 50.35 0 0 1-50.3-50.4c0-27.8 22.5-50.4 50.3-50.4h50.3v50.4zm134 134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V580.2c0-27.8 22.5-50.4 50.3-50.4a50.35 50.35 0 0 1 50.3 50.4v134.4zm-50.2-218.4h-134c-27.8 0-50.3-22.6-50.3-50.4 0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4-.1 27.9-22.6 50.4-50.3 50.4zm0-134.4c-13.3 0-26.1-5.3-35.6-14.8S395 324.8 395 311.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v50.4h-50.3zm83.7-50.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V311.4zM579.3 765c-27.8 0-50.3-22.6-50.3-50.4v-50.4h50.3c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm134-134.4h-134c-13.3 0-26.1-5.3-35.6-14.8S529 593.6 529 580.2c0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm0-134.4H663v-50.4c0-27.8 22.5-50.4 50.3-50.4s50.3 22.6 50.3 50.4c0 27.8-22.5 50.4-50.3 50.4z\")),t.SlackSquareFill=u(\"slack-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM529 311.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V311.4zM361.5 580.2c0 27.8-22.5 50.4-50.3 50.4a50.35 50.35 0 0 1-50.3-50.4c0-27.8 22.5-50.4 50.3-50.4h50.3v50.4zm134 134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V580.2c0-27.8 22.5-50.4 50.3-50.4a50.35 50.35 0 0 1 50.3 50.4v134.4zm-50.2-218.4h-134c-27.8 0-50.3-22.6-50.3-50.4 0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4-.1 27.9-22.6 50.4-50.3 50.4zm0-134.4c-13.3 0-26.1-5.3-35.6-14.8S395 324.8 395 311.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v50.4h-50.3zm134 403.2c-27.8 0-50.3-22.6-50.3-50.4v-50.4h50.3c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm134-134.4h-134a50.35 50.35 0 0 1-50.3-50.4c0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm0-134.4H663v-50.4c0-27.8 22.5-50.4 50.3-50.4s50.3 22.6 50.3 50.4c0 27.8-22.5 50.4-50.3 50.4z\")),t.SkypeFill=u(\"skype\",c,l(o,\"M883.7 578.6c4.1-22.5 6.3-45.5 6.3-68.5 0-51-10-100.5-29.7-147-19-45-46.3-85.4-81-120.1a375.79 375.79 0 0 0-120.1-80.9c-46.6-19.7-96-29.7-147-29.7-24 0-48.1 2.3-71.5 6.8A225.1 225.1 0 0 0 335.6 113c-59.7 0-115.9 23.3-158.1 65.5A222.25 222.25 0 0 0 112 336.6c0 38 9.8 75.4 28.1 108.4-3.7 21.4-5.7 43.3-5.7 65.1 0 51 10 100.5 29.7 147 19 45 46.2 85.4 80.9 120.1 34.7 34.7 75.1 61.9 120.1 80.9 46.6 19.7 96 29.7 147 29.7 22.2 0 44.4-2 66.2-5.9 33.5 18.9 71.3 29 110 29 59.7 0 115.9-23.2 158.1-65.5 42.3-42.2 65.5-98.4 65.5-158.1.1-38-9.7-75.5-28.2-108.7zm-370 162.9c-134.2 0-194.2-66-194.2-115.4 0-25.4 18.7-43.1 44.5-43.1 57.4 0 42.6 82.5 149.7 82.5 54.9 0 85.2-29.8 85.2-60.3 0-18.3-9-38.7-45.2-47.6l-119.4-29.8c-96.1-24.1-113.6-76.1-113.6-124.9 0-101.4 95.5-139.5 185.2-139.5 82.6 0 180 45.7 180 106.5 0 26.1-22.6 41.2-48.4 41.2-49 0-40-67.8-138.7-67.8-49 0-76.1 22.2-76.1 53.9s38.7 41.8 72.3 49.5l88.4 19.6c96.8 21.6 121.3 78.1 121.3 131.3 0 82.3-63.3 143.9-191 143.9z\")),t.SlidersFill=u(\"sliders\",c,l(o,\"M904 296h-66v-96c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v96h-66c-4.4 0-8 3.6-8 8v416c0 4.4 3.6 8 8 8h66v96c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-96h66c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8zm-584-72h-66v-56c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v56h-66c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h66v56c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-56h66c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zm292 180h-66V232c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v172h-66c-4.4 0-8 3.6-8 8v200c0 4.4 3.6 8 8 8h66v172c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V620h66c4.4 0 8-3.6 8-8V412c0-4.4-3.6-8-8-8z\")),t.SmileFill=u(\"smile\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm224 272c-85.5 0-155.6-67.3-160-151.6a8 8 0 0 1 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 589.9 461.5 629 512 629s92.1-39.1 95.8-88.6c.3-4.2 3.9-7.4 8.1-7.4H664a8 8 0 0 1 8 8.4C667.6 625.7 597.5 693 512 693zm176-224a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.SnippetsFill=u(\"snippets\",c,l(o,\"M832 112H724V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H500V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H320c-17.7 0-32 14.3-32 32v120h-96c-17.7 0-32 14.3-32 32v632c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32v-96h96c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM664 486H514V336h.2L664 485.8v.2zm128 274h-56V456L544 264H360v-80h68v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h152v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h68v576z\")),t.SoundFill=u(\"sound\",c,l(o,\"M892.1 737.8l-110.3-63.7a15.9 15.9 0 0 0-21.7 5.9l-19.9 34.5c-4.4 7.6-1.8 17.4 5.8 21.8L856.3 800a15.9 15.9 0 0 0 21.7-5.9l19.9-34.5c4.4-7.6 1.7-17.4-5.8-21.8zM760 344a15.9 15.9 0 0 0 21.7 5.9L892 286.2c7.6-4.4 10.2-14.2 5.8-21.8L878 230a15.9 15.9 0 0 0-21.7-5.9L746 287.8a15.99 15.99 0 0 0-5.8 21.8L760 344zm174 132H806c-8.8 0-16 7.2-16 16v40c0 8.8 7.2 16 16 16h128c8.8 0 16-7.2 16-16v-40c0-8.8-7.2-16-16-16zM625.9 115c-5.9 0-11.9 1.6-17.4 5.3L254 352H90c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h164l354.5 231.7c5.5 3.6 11.6 5.3 17.4 5.3 16.7 0 32.1-13.3 32.1-32.1V147.1c0-18.8-15.4-32.1-32.1-32.1z\")),t.StarFill=u(\"star\",c,l(o,\"M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 0 0 .6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0 0 46.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3z\")),t.StepBackwardFill=u(\"step-backward\",c,l(r,\"M347.6 528.95l383.2 301.02c14.25 11.2 35.2 1.1 35.2-16.95V210.97c0-18.05-20.95-28.14-35.2-16.94L347.6 495.05a21.53 21.53 0 0 0 0 33.9M330 864h-64a8 8 0 0 1-8-8V168a8 8 0 0 1 8-8h64a8 8 0 0 1 8 8v688a8 8 0 0 1-8 8\")),t.StepForwardFill=u(\"step-forward\",c,l(r,\"M676.4 528.95L293.2 829.97c-14.25 11.2-35.2 1.1-35.2-16.95V210.97c0-18.05 20.95-28.14 35.2-16.94l383.2 301.02a21.53 21.53 0 0 1 0 33.9M694 864h64a8 8 0 0 0 8-8V168a8 8 0 0 0-8-8h-64a8 8 0 0 0-8 8v688a8 8 0 0 0 8 8\")),t.StopFill=u(\"stop\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm234.8 736.5L223.5 277.2c16-19.7 34-37.7 53.7-53.7l523.3 523.3c-16 19.6-34 37.7-53.7 53.7z\")),t.SwitcherFill=u(\"switcher\",c,l(o,\"M752 240H144c-17.7 0-32 14.3-32 32v608c0 17.7 14.3 32 32 32h608c17.7 0 32-14.3 32-32V272c0-17.7-14.3-32-32-32zM596 606c0 4.4-3.6 8-8 8H308c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h280c4.4 0 8 3.6 8 8v48zm284-494H264c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h576v576c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V144c0-17.7-14.3-32-32-32z\")),t.TabletFill=u(\"tablet\",c,l(o,\"M800 64H224c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h576c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64zM512 824c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40z\")),t.TagFill=u(\"tag\",c,l(o,\"M938 458.8l-29.6-312.6c-1.5-16.2-14.4-29-30.6-30.6L565.2 86h-.4c-3.2 0-5.7 1-7.6 2.9L88.9 557.2a9.96 9.96 0 0 0 0 14.1l363.8 363.8c1.9 1.9 4.4 2.9 7.1 2.9s5.2-1 7.1-2.9l468.3-468.3c2-2.1 3-5 2.8-8zM699 387c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z\")),t.TagsFill=u(\"tags\",c,l(o,\"M483.2 790.3L861.4 412c1.7-1.7 2.5-4 2.3-6.3l-25.5-301.4c-.7-7.8-6.8-13.9-14.6-14.6L522.2 64.3c-2.3-.2-4.7.6-6.3 2.3L137.7 444.8a8.03 8.03 0 0 0 0 11.3l334.2 334.2c3.1 3.2 8.2 3.2 11.3 0zm122.7-533.4c18.7-18.7 49.1-18.7 67.9 0 18.7 18.7 18.7 49.1 0 67.9-18.7 18.7-49.1 18.7-67.9 0-18.7-18.7-18.7-49.1 0-67.9zm283.8 282.9l-39.6-39.5a8.03 8.03 0 0 0-11.3 0l-362 361.3-237.6-237a8.03 8.03 0 0 0-11.3 0l-39.6 39.5a8.03 8.03 0 0 0 0 11.3l243.2 242.8 39.6 39.5c3.1 3.1 8.2 3.1 11.3 0l407.3-406.6c3.1-3.1 3.1-8.2 0-11.3z\")),t.TaobaoCircleFill=u(\"taobao-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM315.7 291.5c27.3 0 49.5 22.1 49.5 49.4s-22.1 49.4-49.5 49.4a49.4 49.4 0 1 1 0-98.8zM366.9 578c-13.6 42.3-10.2 26.7-64.4 144.5l-78.5-49s87.7-79.8 105.6-116.2c19.2-38.4-21.1-58.9-21.1-58.9l-60.2-37.5 32.7-50.2c45.4 33.7 48.7 36.6 79.2 67.2 23.8 23.9 20.7 56.8 6.7 100.1zm427.2 55c-15.3 143.8-202.4 90.3-202.4 90.3l10.2-41.1 43.3 9.3c80 5 72.3-64.9 72.3-64.9V423c.6-77.3-72.6-85.4-204.2-38.3l30.6 8.3c-2.5 9-12.5 23.2-25.2 38.6h176v35.6h-99.1v44.5h98.7v35.7h-98.7V622c14.9-4.8 28.6-11.5 40.5-20.5l-8.7-32.5 46.5-14.4 38.8 94.9-57.3 23.9-10.2-37.8c-25.6 19.5-78.8 48-171.8 45.4-99.2 2.6-73.7-112-73.7-112l2.5-1.3H472c-.5 14.7-6.6 38.7 1.7 51.8 6.8 10.8 24.2 12.6 35.3 13.1 1.3.1 2.6.1 3.9.1v-85.3h-101v-35.7h101v-44.5H487c-22.7 24.1-43.5 44.1-43.5 44.1l-30.6-26.7c21.7-22.9 43.3-59.1 56.8-83.2-10.9 4.4-22 9.2-33.6 14.2-11.2 14.3-24.2 29-38.7 43.5.5.8-50-28.4-50-28.4 52.2-44.4 81.4-139.9 81.4-139.9l72.5 20.4s-5.9 14-18.4 35.6c290.3-82.3 307.4 50.5 307.4 50.5s19.1 91.8 3.8 235.7z\")),t.TaobaoSquareFill=u(\"taobao-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM315.7 291.5c27.3 0 49.5 22.1 49.5 49.4s-22.1 49.4-49.5 49.4a49.4 49.4 0 1 1 0-98.8zM366.9 578c-13.6 42.3-10.2 26.7-64.4 144.5l-78.5-49s87.7-79.8 105.6-116.2c19.2-38.4-21.1-58.9-21.1-58.9l-60.2-37.5 32.7-50.2c45.4 33.7 48.7 36.6 79.2 67.2 23.8 23.9 20.7 56.8 6.7 100.1zm427.2 55c-15.3 143.8-202.4 90.3-202.4 90.3l10.2-41.1 43.3 9.3c80 5 72.3-64.9 72.3-64.9V423c.6-77.3-72.6-85.4-204.2-38.3l30.6 8.3c-2.5 9-12.5 23.2-25.2 38.6h176v35.6h-99.1v44.5h98.7v35.7h-98.7V622c14.9-4.8 28.6-11.5 40.5-20.5l-8.7-32.5 46.5-14.4 38.8 94.9-57.3 23.9-10.2-37.8c-25.6 19.5-78.8 48-171.8 45.4-99.2 2.6-73.7-112-73.7-112l2.5-1.3H472c-.5 14.7-6.6 38.7 1.7 51.8 6.8 10.8 24.2 12.6 35.3 13.1 1.3.1 2.6.1 3.9.1v-85.3h-101v-35.7h101v-44.5H487c-22.7 24.1-43.5 44.1-43.5 44.1l-30.6-26.7c21.7-22.9 43.3-59.1 56.8-83.2-10.9 4.4-22 9.2-33.6 14.2-11.2 14.3-24.2 29-38.7 43.5.5.8-50-28.4-50-28.4 52.2-44.4 81.4-139.9 81.4-139.9l72.5 20.4s-5.9 14-18.4 35.6c290.3-82.3 307.4 50.5 307.4 50.5s19.1 91.8 3.8 235.7z\")),t.ToolFill=u(\"tool\",c,l(o,\"M865.3 244.7c-.3-.3-61.1 59.8-182.1 180.6l-84.9-84.9 180.9-180.9c-95.2-57.3-217.5-42.6-296.8 36.7A244.42 244.42 0 0 0 419 432l1.8 6.7-283.5 283.4c-6.2 6.2-6.2 16.4 0 22.6l141.4 141.4c6.2 6.2 16.4 6.2 22.6 0l283.3-283.3 6.7 1.8c83.7 22.3 173.6-.9 236-63.3 79.4-79.3 94.1-201.6 38-296.6z\")),t.ThunderboltFill=u(\"thunderbolt\",c,l(o,\"M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7z\")),t.TrademarkCircleFill=u(\"trademark-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm164.7 660.2c-1.1.5-2.3.8-3.5.8h-62c-3.1 0-5.9-1.8-7.2-4.6l-74.6-159.2h-88.7V717c0 4.4-3.6 8-8 8H378c-4.4 0-8-3.6-8-8V307c0-4.4 3.6-8 8-8h155.6c98.8 0 144.2 59.9 144.2 131.1 0 70.2-43.6 106.4-78.4 119.2l80.8 164.2c2.1 3.9.4 8.7-3.5 10.7zM523.9 357h-83.4v148H522c53 0 82.8-25.6 82.8-72.4 0-50.3-32.9-75.6-80.9-75.6z\")),t.TwitterCircleFill=u(\"twitter-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm215.3 337.7c.3 4.7.3 9.6.3 14.4 0 146.8-111.8 315.9-316.1 315.9-63 0-121.4-18.3-170.6-49.8 9 1 17.6 1.4 26.8 1.4 52 0 99.8-17.6 137.9-47.4-48.8-1-89.8-33-103.8-77 17.1 2.5 32.5 2.5 50.1-2a111 111 0 0 1-88.9-109v-1.4c14.7 8.3 32 13.4 50.1 14.1a111.13 111.13 0 0 1-49.5-92.4c0-20.7 5.4-39.6 15.1-56a315.28 315.28 0 0 0 229 116.1C492 353.1 548.4 292 616.2 292c32 0 60.8 13.4 81.1 35 25.1-4.7 49.1-14.1 70.5-26.7-8.3 25.7-25.7 47.4-48.8 61.1 22.4-2.4 44-8.6 64-17.3-15.1 22.2-34 41.9-55.7 57.6z\")),t.TrophyFill=u(\"trophy\",c,l(o,\"M868 160h-92v-40c0-4.4-3.6-8-8-8H256c-4.4 0-8 3.6-8 8v40h-92a44 44 0 0 0-44 44v148c0 81.7 60 149.6 138.2 162C265.6 630.2 359 721.8 476 734.5v105.2H280c-17.7 0-32 14.3-32 32V904c0 4.4 3.6 8 8 8h512c4.4 0 8-3.6 8-8v-32.3c0-17.7-14.3-32-32-32H548V734.5C665 721.8 758.4 630.2 773.8 514 852 501.6 912 433.7 912 352V204a44 44 0 0 0-44-44zM248 439.6c-37.1-11.9-64-46.7-64-87.6V232h64v207.6zM840 352c0 41-26.9 75.8-64 87.6V232h64v120z\")),t.TwitterSquareFill=u(\"twitter-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM727.3 401.7c.3 4.7.3 9.6.3 14.4 0 146.8-111.8 315.9-316.1 315.9-63 0-121.4-18.3-170.6-49.8 9 1 17.6 1.4 26.8 1.4 52 0 99.8-17.6 137.9-47.4-48.8-1-89.8-33-103.8-77 17.1 2.5 32.5 2.5 50.1-2a111 111 0 0 1-88.9-109v-1.4c14.7 8.3 32 13.4 50.1 14.1a111.13 111.13 0 0 1-49.5-92.4c0-20.7 5.4-39.6 15.1-56a315.28 315.28 0 0 0 229 116.1C492 353.1 548.4 292 616.2 292c32 0 60.8 13.4 81.1 35 25.1-4.7 49.1-14.1 70.5-26.7-8.3 25.7-25.7 47.4-48.8 61.1 22.4-2.4 44-8.6 64-17.3-15.1 22.2-34 41.9-55.7 57.6z\")),t.UnlockFill=u(\"unlock\",c,l(o,\"M832 464H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v68c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-68c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM540 701v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53a48.01 48.01 0 1 1 56 0z\")),t.UpCircleFill=u(\"up-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm178 555h-46.9c-10.2 0-19.9-4.9-25.9-13.2L512 460.4 406.8 605.8c-6 8.3-15.6 13.2-25.9 13.2H334c-6.5 0-10.3-7.4-6.5-12.7l178-246c3.2-4.4 9.7-4.4 12.9 0l178 246c3.9 5.3.1 12.7-6.4 12.7z\")),t.UpSquareFill=u(\"up-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM690 624h-46.9c-10.2 0-19.9-4.9-25.9-13.2L512 465.4 406.8 610.8c-6 8.3-15.6 13.2-25.9 13.2H334c-6.5 0-10.3-7.4-6.5-12.7l178-246c3.2-4.4 9.7-4.4 12.9 0l178 246c3.9 5.3.1 12.7-6.4 12.7z\")),t.UsbFill=u(\"usb\",c,l(o,\"M408 312h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm352 120V144c0-17.7-14.3-32-32-32H296c-17.7 0-32 14.3-32 32v288c-66.2 0-120 52.1-120 116v356c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8V548c0-63.9-53.8-116-120-116zm-72 0H336V184h352v248zM568 312h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\")),t.WalletFill=u(\"wallet\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-32 464H528V448h320v128zm-268-64a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.VideoCameraFill=u(\"video-camera\",c,l(o,\"M912 302.3L784 376V224c0-35.3-28.7-64-64-64H128c-35.3 0-64 28.7-64 64v576c0 35.3 28.7 64 64 64h592c35.3 0 64-28.7 64-64V648l128 73.7c21.3 12.3 48-3.1 48-27.6V330c0-24.6-26.7-40-48-27.7zM328 352c0 4.4-3.6 8-8 8H208c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h112c4.4 0 8 3.6 8 8v48zm560 273l-104-59.8V458.9L888 399v226z\")),t.WarningFill=u(\"warning\",c,l(o,\"M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zM480 416c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v184c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V416zm32 352a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.WeiboCircleFill=u(\"weibo-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-44.4 672C353.1 736 236 680.4 236 588.9c0-47.8 30.2-103.1 82.3-155.3 69.5-69.6 150.6-101.4 181.1-70.8 13.5 13.5 14.8 36.8 6.1 64.6-4.5 14 13.1 6.3 13.1 6.3 56.2-23.6 105.2-25 123.1.7 9.6 13.7 8.6 32.8-.2 55.1-4.1 10.2 1.3 11.8 9 14.1 31.7 9.8 66.9 33.6 66.9 75.5.2 69.5-99.7 156.9-249.8 156.9zm207.3-290.8a34.9 34.9 0 0 0-7.2-34.1 34.68 34.68 0 0 0-33.1-10.7 18.24 18.24 0 0 1-7.6-35.7c24.1-5.1 50.1 2.3 67.7 21.9 17.7 19.6 22.4 46.3 14.9 69.8a18.13 18.13 0 0 1-22.9 11.7 18.18 18.18 0 0 1-11.8-22.9zm106 34.3s0 .1 0 0a21.1 21.1 0 0 1-26.6 13.7 21.19 21.19 0 0 1-13.6-26.7c11-34.2 4-73.2-21.7-101.8a104.04 104.04 0 0 0-98.9-32.1 21.14 21.14 0 0 1-25.1-16.3 21.07 21.07 0 0 1 16.2-25.1c49.4-10.5 102.8 4.8 139.1 45.1 36.3 40.2 46.1 95.1 30.6 143.2zm-334.5 6.1c-91.4 9-160.7 65.1-154.7 125.2 5.9 60.1 84.8 101.5 176.2 92.5 91.4-9.1 160.7-65.1 154.7-125.3-5.9-60.1-84.8-101.5-176.2-92.4zm80.2 141.7c-18.7 42.3-72.3 64.8-117.8 50.1-43.9-14.2-62.5-57.7-43.3-96.8 18.9-38.4 68-60.1 111.5-48.8 45 11.7 68 54.2 49.6 95.5zm-93-32.2c-14.2-5.9-32.4.2-41.2 13.9-8.8 13.8-4.7 30.2 9.3 36.6 14.3 6.5 33.2.3 42-13.8 8.8-14.3 4.2-30.6-10.1-36.7zm34.9-14.5c-5.4-2.2-12.2.5-15.4 5.8-3.1 5.4-1.4 11.5 4.1 13.8 5.5 2.3 12.6-.3 15.8-5.8 3-5.6 1-11.8-4.5-13.8z\")),t.WechatFill=u(\"wechat\",c,l(o,\"M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6a21.5 21.5 0 0 1 9.1 17.6c0 2.4-.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-.1 17.8-.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8zm-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1zm-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1zm586.8 415.6c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7a9 9 0 0 0 6.4-2.6 9 9 0 0 0 2.6-6.4c0-2.2-.9-4.4-1.4-6.6-.3-1.2-7.6-28.3-12.2-45.3-.5-1.9-.9-3.8-.9-5.7.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9zm179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9a36.08 36.08 0 0 1-36 35.9z\")),t.WindowsFill=u(\"windows\",c,l(o,\"M523.8 191.4v288.9h382V128.1zm0 642.2l382 62.2v-352h-382zM120.1 480.2H443V201.9l-322.9 53.5zm0 290.4L443 823.2V543.8H120.1z\")),t.YahooFill=u(\"yahoo\",c,l(o,\"M937.3 231H824.7c-15.5 0-27.7 12.6-27.1 28.1l13.1 366h84.4l65.4-366.4c2.7-15.2-7.8-27.7-23.2-27.7zm-77.4 450.4h-14.1c-27.1 0-49.2 22.2-49.2 49.3v14.1c0 27.1 22.2 49.3 49.2 49.3h14.1c27.1 0 49.2-22.2 49.2-49.3v-14.1c0-27.1-22.2-49.3-49.2-49.3zM402.6 231C216.2 231 65 357 65 512.5S216.2 794 402.6 794s337.6-126 337.6-281.5S589.1 231 402.6 231zm225.2 225.2h-65.3L458.9 559.8v65.3h84.4v56.3H318.2v-56.3h84.4v-65.3L242.9 399.9h-37v-56.3h168.5v56.3h-37l93.4 93.5 28.1-28.1V400h168.8v56.2z\")),t.WeiboSquareFill=u(\"weibo-square\",c,l(o,\"M433.6 595.1c-14.2-5.9-32.4.2-41.2 13.9-8.8 13.8-4.7 30.2 9.3 36.6 14.3 6.5 33.2.3 42-13.8 8.8-14.3 4.2-30.6-10.1-36.7zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM467.6 736C353.1 736 236 680.4 236 588.9c0-47.8 30.2-103.1 82.3-155.3 69.5-69.6 150.6-101.4 181.1-70.8 13.5 13.5 14.8 36.8 6.1 64.6-4.5 14 13.1 6.3 13.1 6.3 56.2-23.6 105.2-25 123.1.7 9.6 13.7 8.6 32.8-.2 55.1-4.1 10.2 1.3 11.8 9 14.1 31.7 9.8 66.9 33.6 66.9 75.5.2 69.5-99.7 156.9-249.8 156.9zm207.3-290.8a34.9 34.9 0 0 0-7.2-34.1 34.68 34.68 0 0 0-33.1-10.7 18.24 18.24 0 0 1-7.6-35.7c24.1-5.1 50.1 2.3 67.7 21.9 17.7 19.6 22.4 46.3 14.9 69.8a18.13 18.13 0 0 1-22.9 11.7 18.18 18.18 0 0 1-11.8-22.9zm106 34.3s0 .1 0 0a21.1 21.1 0 0 1-26.6 13.7 21.19 21.19 0 0 1-13.6-26.7c11-34.2 4-73.2-21.7-101.8a104.04 104.04 0 0 0-98.9-32.1 21.14 21.14 0 0 1-25.1-16.3 21.07 21.07 0 0 1 16.2-25.1c49.4-10.5 102.8 4.8 139.1 45.1 36.3 40.2 46.1 95.1 30.6 143.2zm-334.5 6.1c-91.4 9-160.7 65.1-154.7 125.2 5.9 60.1 84.8 101.5 176.2 92.5 91.4-9.1 160.7-65.1 154.7-125.3-5.9-60.1-84.8-101.5-176.2-92.4zm80.2 141.7c-18.7 42.3-72.3 64.8-117.8 50.1-43.9-14.2-62.5-57.7-43.3-96.8 18.9-38.4 68-60.1 111.5-48.8 45 11.7 68 54.2 49.6 95.5zm-58.1-46.7c-5.4-2.2-12.2.5-15.4 5.8-3.1 5.4-1.4 11.5 4.1 13.8 5.5 2.3 12.6-.3 15.8-5.8 3-5.6 1-11.8-4.5-13.8z\")),t.YuqueFill=u(\"yuque\",c,l(o,\"M854.6 370.6c-9.9-39.4 9.9-102.2 73.4-124.4l-67.9-3.6s-25.7-90-143.6-98c-117.9-8.1-195-3-195-3s87.4 55.6 52.4 154.7c-25.6 52.5-65.8 95.6-108.8 144.7-1.3 1.3-2.5 2.6-3.5 3.7C319.4 605 96 860 96 860c245.9 64.4 410.7-6.3 508.2-91.1 20.5-.2 35.9-.3 46.3-.3 135.8 0 250.6-117.6 245.9-248.4-3.2-89.9-31.9-110.2-41.8-149.6z\")),t.YoutubeFill=u(\"youtube\",c,l(o,\"M941.3 296.1a112.3 112.3 0 0 0-79.2-79.3C792.2 198 512 198 512 198s-280.2 0-350.1 18.7A112.12 112.12 0 0 0 82.7 296C64 366 64 512 64 512s0 146 18.7 215.9c10.3 38.6 40.7 69 79.2 79.3C231.8 826 512 826 512 826s280.2 0 350.1-18.8c38.6-10.3 68.9-40.7 79.2-79.3C960 658 960 512 960 512s0-146-18.7-215.9zM423 646V378l232 133-232 135z\")),t.ZhihuSquareFill=u(\"zhihu-square\",c,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM432.3 592.8l71 80.7c9.2 33-3.3 63.1-3.3 63.1l-95.7-111.9v-.1c-8.9 29-20.1 57.3-33.3 84.7-22.6 45.7-55.2 54.7-89.5 57.7-34.4 3-23.3-5.3-23.3-5.3 68-55.5 78-87.8 96.8-123.1 11.9-22.3 20.4-64.3 25.3-96.8H264.1s4.8-31.2 19.2-41.7h101.6c.6-15.3-1.3-102.8-2-131.4h-49.4c-9.2 45-41 56.7-48.1 60.1-7 3.4-23.6 7.1-21.1 0 2.6-7.1 27-46.2 43.2-110.7 16.3-64.6 63.9-62 63.9-62-12.8 22.5-22.4 73.6-22.4 73.6h159.7c10.1 0 10.6 39 10.6 39h-90.8c-.7 22.7-2.8 83.8-5 131.4H519s12.2 15.4 12.2 41.7h-110l-.1 1.5c-1.5 20.4-6.3 43.9-12.9 67.6l24.1-18.1zm335.5 116h-87.6l-69.5 46.6-16.4-46.6h-40.1V321.5h213.6v387.3zM408.2 611s0-.1 0 0zm216 94.3l56.8-38.1h45.6-.1V364.7H596.7v302.5h14.1z\")),t.ZhihuCircleFill=u(\"zhihu-circle\",c,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-90.7 477.8l-.1 1.5c-1.5 20.4-6.3 43.9-12.9 67.6l24-18.1 71 80.7c9.2 33-3.3 63.1-3.3 63.1l-95.7-111.9v-.1c-8.9 29-20.1 57.3-33.3 84.7-22.6 45.7-55.2 54.7-89.5 57.7-34.4 3-23.3-5.3-23.3-5.3 68-55.5 78-87.8 96.8-123.1 11.9-22.3 20.4-64.3 25.3-96.8H264.1s4.8-31.2 19.2-41.7h101.6c.6-15.3-1.3-102.8-2-131.4h-49.4c-9.2 45-41 56.7-48.1 60.1-7 3.4-23.6 7.1-21.1 0 2.6-7.1 27-46.2 43.2-110.7 16.3-64.6 63.9-62 63.9-62-12.8 22.5-22.4 73.6-22.4 73.6h159.7c10.1 0 10.6 39 10.6 39h-90.8c-.7 22.7-2.8 83.8-5 131.4H519s12.2 15.4 12.2 41.7H421.3zm346.5 167h-87.6l-69.5 46.6-16.4-46.6h-40.1V321.5h213.6v387.3zM408.2 611s0-.1 0 0zm216 94.3l56.8-38.1h45.6-.1V364.7H596.7v302.5h14.1z\")),t.AccountBookOutline=u(\"account-book\",i,l(o,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v584zM639.5 414h-45c-3 0-5.8 1.7-7.1 4.4L514 563.8h-2.8l-73.4-145.4a8 8 0 0 0-7.1-4.4h-46c-1.3 0-2.7.3-3.8 1-3.9 2.1-5.3 7-3.2 10.9l89.3 164h-48.6c-4.4 0-8 3.6-8 8v21.3c0 4.4 3.6 8 8 8h65.1v33.7h-65.1c-4.4 0-8 3.6-8 8v21.3c0 4.4 3.6 8 8 8h65.1V752c0 4.4 3.6 8 8 8h41.3c4.4 0 8-3.6 8-8v-53.8h65.4c4.4 0 8-3.6 8-8v-21.3c0-4.4-3.6-8-8-8h-65.4v-33.7h65.4c4.4 0 8-3.6 8-8v-21.3c0-4.4-3.6-8-8-8h-49.1l89.3-164.1c.6-1.2 1-2.5 1-3.8.1-4.4-3.4-8-7.9-8z\")),t.AlertOutline=u(\"alert\",i,l(o,\"M193 796c0 17.7 14.3 32 32 32h574c17.7 0 32-14.3 32-32V563c0-176.2-142.8-319-319-319S193 386.8 193 563v233zm72-233c0-136.4 110.6-247 247-247s247 110.6 247 247v193H404V585c0-5.5-4.5-10-10-10h-44c-5.5 0-10 4.5-10 10v171h-75V563zm-48.1-252.5l39.6-39.6c3.1-3.1 3.1-8.2 0-11.3l-67.9-67.9a8.03 8.03 0 0 0-11.3 0l-39.6 39.6a8.03 8.03 0 0 0 0 11.3l67.9 67.9c3.1 3.1 8.1 3.1 11.3 0zm669.6-79.2l-39.6-39.6a8.03 8.03 0 0 0-11.3 0l-67.9 67.9a8.03 8.03 0 0 0 0 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l67.9-67.9c3.1-3.2 3.1-8.2 0-11.3zM832 892H192c-17.7 0-32 14.3-32 32v24c0 4.4 3.6 8 8 8h688c4.4 0 8-3.6 8-8v-24c0-17.7-14.3-32-32-32zM484 180h56c4.4 0 8-3.6 8-8V76c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v96c0 4.4 3.6 8 8 8z\")),t.AlipayCircleOutline=u(\"alipay-circle\",i,l(o,\"M308.6 545.7c-19.8 2-57.1 10.7-77.4 28.6-61 53-24.5 150 99 150 71.8 0 143.5-45.7 199.8-119-80.2-38.9-148.1-66.8-221.4-59.6zm460.5 67c100.1 33.4 154.7 43 166.7 44.8A445.9 445.9 0 0 0 960 512c0-247.4-200.6-448-448-448S64 264.6 64 512s200.6 448 448 448c155.9 0 293.2-79.7 373.5-200.5-75.6-29.8-213.6-85-286.8-120.1-69.9 85.7-160.1 137.8-253.7 137.8-158.4 0-212.1-138.1-137.2-229 16.3-19.8 44.2-38.7 87.3-49.4 67.5-16.5 175 10.3 275.7 43.4 18.1-33.3 33.4-69.9 44.7-108.9H305.1V402h160v-56.2H271.3v-31.3h193.8v-80.1s0-13.5 13.7-13.5H557v93.6h191.7v31.3H557.1V402h156.4c-15 61.1-37.7 117.4-66.2 166.8 47.5 17.1 90.1 33.3 121.8 43.9z\")),t.AliwangwangOutline=u(\"aliwangwang\",i,l(o,\"M868.2 377.4c-18.9-45.1-46.3-85.6-81.2-120.6a377.26 377.26 0 0 0-120.5-81.2A375.65 375.65 0 0 0 519 145.8c-41.9 0-82.9 6.7-121.9 20C306 123.3 200.8 120 170.6 120c-2.2 0-7.4 0-9.4.2-11.9.4-22.8 6.5-29.2 16.4-6.5 9.9-7.7 22.4-3.4 33.5l64.3 161.6a378.59 378.59 0 0 0-52.8 193.2c0 51.4 10 101 29.8 147.6 18.9 45 46.2 85.6 81.2 120.5 34.7 34.8 75.4 62.1 120.5 81.2C418.3 894 467.9 904 519 904c51.3 0 100.9-10.1 147.7-29.8 44.9-18.9 85.5-46.3 120.4-81.2 34.7-34.8 62.1-75.4 81.2-120.6a376.5 376.5 0 0 0 29.8-147.6c-.2-51.2-10.1-100.8-29.9-147.4zm-66.4 266.5a307.08 307.08 0 0 1-65.9 98c-28.4 28.5-61.3 50.7-97.7 65.9h-.1c-38 16-78.3 24.2-119.9 24.2a306.51 306.51 0 0 1-217.5-90.2c-28.4-28.5-50.6-61.4-65.8-97.8v-.1c-16-37.8-24.1-78.2-24.1-119.9 0-55.4 14.8-109.7 42.8-157l13.2-22.1-9.5-23.9L206 192c14.9.6 35.9 2.1 59.7 5.6 43.8 6.5 82.5 17.5 114.9 32.6l19 8.9 19.9-6.8c31.5-10.8 64.8-16.2 98.9-16.2a306.51 306.51 0 0 1 217.5 90.2c28.4 28.5 50.6 61.4 65.8 97.8l.1.1.1.1c16 37.6 24.1 78 24.2 119.8-.1 41.7-8.3 82-24.3 119.8zM681.1 364.2c-20.4 0-37.1 16.7-37.1 37.1v55.1c0 20.4 16.6 37.1 37.1 37.1s37.1-16.7 37.1-37.1v-55.1c0-20.5-16.7-37.1-37.1-37.1zm-175.2 0c-20.5 0-37.1 16.7-37.1 37.1v55.1c0 20.4 16.7 37.1 37.1 37.1 20.5 0 37.1-16.7 37.1-37.1v-55.1c0-20.5-16.7-37.1-37.1-37.1z\")),t.AndroidOutline=u(\"android\",i,l(o,\"M448.3 225.2c-18.6 0-32 13.4-32 31.9s13.5 31.9 32 31.9c18.6 0 32-13.4 32-31.9.1-18.4-13.4-31.9-32-31.9zm393.9 96.4c-13.8-13.8-32.7-21.5-53.2-21.5-3.9 0-7.4.4-10.7 1v-1h-3.6c-5.5-30.6-18.6-60.5-38.1-87.4-18.7-25.7-43-47.9-70.8-64.9l25.1-35.8v-3.3c0-.8.4-2.3.7-3.8.6-2.4 1.4-5.5 1.4-8.9 0-18.5-13.5-31.9-32-31.9-9.8 0-19.5 5.7-25.9 15.4l-29.3 42.1c-30-9.8-62.4-15-93.8-15-31.3 0-63.7 5.2-93.8 15L389 79.4c-6.6-9.6-16.1-15.4-26-15.4-18.6 0-32 13.4-32 31.9 0 6.2 2.5 12.8 6.7 17.4l22.6 32.3c-28.7 17-53.5 39.4-72.2 65.1-19.4 26.9-32 56.8-36.7 87.4h-5.5v1c-3.2-.6-6.7-1-10.7-1-20.3 0-39.2 7.5-53.1 21.3-13.8 13.8-21.5 32.6-21.5 53v235c0 20.3 7.5 39.1 21.4 52.9 13.8 13.8 32.8 21.5 53.2 21.5 3.9 0 7.4-.4 10.7-1v93.5c0 29.2 23.9 53.1 53.2 53.1H331v58.3c0 20.3 7.5 39.1 21.4 52.9 13.8 13.8 32.8 21.5 53.2 21.5 20.3 0 39.2-7.5 53.1-21.3 13.8-13.8 21.5-32.6 21.5-53v-58.2H544v58.1c0 20.3 7.5 39.1 21.4 52.9 13.8 13.8 32.8 21.5 53.2 21.5 20.4 0 39.2-7.5 53.1-21.6 13.8-13.8 21.5-32.6 21.5-53v-58.2h31.9c29.3 0 53.2-23.8 53.2-53.1v-91.4c3.2.6 6.7 1 10.7 1 20.3 0 39.2-7.5 53.1-21.3 13.8-13.8 21.5-32.6 21.5-53v-235c-.1-20.3-7.6-39-21.4-52.9zM246 609.6c0 6.8-3.9 10.6-10.7 10.6-6.8 0-10.7-3.8-10.7-10.6V374.5c0-6.8 3.9-10.6 10.7-10.6 6.8 0 10.7 3.8 10.7 10.6v235.1zm131.1-396.8c37.5-27.3 85.3-42.3 135-42.3s97.5 15.1 135 42.5c32.4 23.7 54.2 54.2 62.7 87.5H314.4c8.5-33.4 30.5-64 62.7-87.7zm39.3 674.7c-.6 5.6-4.4 8.7-10.5 8.7-6.8 0-10.7-3.8-10.7-10.6v-58.2h21.2v60.1zm202.3 8.7c-6.8 0-10.7-3.8-10.7-10.6v-58.2h21.2v60.1c-.6 5.6-4.3 8.7-10.5 8.7zm95.8-132.6H309.9V364h404.6v399.6zm85.2-154c0 6.8-3.9 10.6-10.7 10.6-6.8 0-10.7-3.8-10.7-10.6V374.5c0-6.8 3.9-10.6 10.7-10.6 6.8 0 10.7 3.8 10.7 10.6v235.1zM576.1 225.2c-18.6 0-32 13.4-32 31.9s13.5 31.9 32 31.9c18.6 0 32.1-13.4 32.1-32-.1-18.6-13.4-31.8-32.1-31.8z\")),t.ApiOutline=u(\"api\",i,l(o,\"M917.7 148.8l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 0 0-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 0 0 0 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM769.1 441.7l-59.4 59.4-186.8-186.8 59.4-59.4c24.9-24.9 58.1-38.7 93.4-38.7 35.3 0 68.4 13.7 93.4 38.7 24.9 24.9 38.7 58.1 38.7 93.4 0 35.3-13.8 68.4-38.7 93.4zm-190.2 105a8.03 8.03 0 0 0-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 0 0-11.3 0L363 475.3l-43-43a7.85 7.85 0 0 0-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2c-68.9 69-77 175.7-24.3 253.5l-76.1 76.1a8.03 8.03 0 0 0 0 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2zM441.7 769.1a131.32 131.32 0 0 1-93.4 38.7c-35.3 0-68.4-13.7-93.4-38.7a131.32 131.32 0 0 1-38.7-93.4c0-35.3 13.7-68.4 38.7-93.4l59.4-59.4 186.8 186.8-59.4 59.4z\")),t.AppstoreOutline=u(\"appstore\",i,l(o,\"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z\")),t.AudioOutline=u(\"audio\",i,l(o,\"M842 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 140.3-113.7 254-254 254S258 594.3 258 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 168.7 126.6 307.9 290 327.6V884H326.7c-13.7 0-24.7 14.3-24.7 32v36c0 4.4 2.8 8 6.2 8h407.6c3.4 0 6.2-3.6 6.2-8v-36c0-17.7-11-32-24.7-32H548V782.1c165.3-18 294-158 294-328.1zM512 624c93.9 0 170-75.2 170-168V232c0-92.8-76.1-168-170-168s-170 75.2-170 168v224c0 92.8 76.1 168 170 168zm-94-392c0-50.6 41.9-92 94-92s94 41.4 94 92v224c0 50.6-41.9 92-94 92s-94-41.4-94-92V232z\")),t.AppleOutline=u(\"apple\",i,l(o,\"M747.4 535.7c-.4-68.2 30.5-119.6 92.9-157.5-34.9-50-87.7-77.5-157.3-82.8-65.9-5.2-138 38.4-164.4 38.4-27.9 0-91.7-36.6-141.9-36.6C273.1 298.8 163 379.8 163 544.6c0 48.7 8.9 99 26.7 150.8 23.8 68.2 109.6 235.3 199.1 232.6 46.8-1.1 79.9-33.2 140.8-33.2 59.1 0 89.7 33.2 141.9 33.2 90.3-1.3 167.9-153.2 190.5-221.6-121.1-57.1-114.6-167.2-114.6-170.7zm-10.6 267c-14.3 19.9-28.7 35.6-41.9 45.7-10.5 8-18.6 11.4-24 11.6-9-.1-17.7-2.3-34.7-8.8-1.2-.5-2.5-1-4.2-1.6l-4.4-1.7c-17.4-6.7-27.8-10.3-41.1-13.8-18.6-4.8-37.1-7.4-56.9-7.4-20.2 0-39.2 2.5-58.1 7.2-13.9 3.5-25.6 7.4-42.7 13.8-.7.3-8.1 3.1-10.2 3.9-3.5 1.3-6.2 2.3-8.7 3.2-10.4 3.6-17 5.1-22.9 5.2-.7 0-1.3-.1-1.8-.2-1.1-.2-2.5-.6-4.1-1.3-4.5-1.8-9.9-5.1-16-9.8-14-10.9-29.4-28-45.1-49.9-27.5-38.6-53.5-89.8-66-125.7-15.4-44.8-23-87.7-23-128.6 0-60.2 17.8-106 48.4-137.1 26.3-26.6 61.7-41.5 97.8-42.3 5.9.1 14.5 1.5 25.4 4.5 8.6 2.3 18 5.4 30.7 9.9 3.8 1.4 16.9 6.1 18.5 6.7 7.7 2.8 13.5 4.8 19.2 6.6 18.2 5.8 32.3 9 47.6 9 15.5 0 28.8-3.3 47.7-9.8 7.1-2.4 32.9-12 37.5-13.6 25.6-9.1 44.5-14 60.8-15.2 4.8-.4 9.1-.4 13.2-.1 22.7 1.8 42.1 6.3 58.6 13.8-37.6 43.4-57 96.5-56.9 158.4-.3 14.7.9 31.7 5.1 51.8 6.4 30.5 18.6 60.7 37.9 89 14.7 21.5 32.9 40.9 54.7 57.8-11.5 23.7-25.6 48.2-40.4 68.8zm-94.5-572c50.7-60.2 46.1-115 44.6-134.7-44.8 2.6-96.6 30.5-126.1 64.8-32.5 36.8-51.6 82.3-47.5 133.6 48.4 3.7 92.6-21.2 129-63.7z\")),t.BackwardOutline=u(\"backward\",i,l(r,\"M485.6 249.9L198.2 498c-8.3 7.1-8.3 20.8 0 27.9l287.4 248.2c10.7 9.2 26.4.9 26.4-14V263.8c0-14.8-15.7-23.2-26.4-13.9zm320 0L518.2 498a18.6 18.6 0 0 0-6.2 14c0 5.2 2.1 10.4 6.2 14l287.4 248.2c10.7 9.2 26.4.9 26.4-14V263.8c0-14.8-15.7-23.2-26.4-13.9z\")),t.BankOutline=u(\"bank\",i,l(o,\"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 0 0-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM512 196.7l271.1 197.2H240.9L512 196.7zM264 462h117v374H264V462zm189 0h117v374H453V462zm307 374H642V462h118v374z\")),t.BellOutline=u(\"bell\",i,l(o,\"M816 768h-24V428c0-141.1-104.3-257.7-240-277.1V112c0-22.1-17.9-40-40-40s-40 17.9-40 40v38.9c-135.7 19.4-240 136-240 277.1v340h-24c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h216c0 61.8 50.2 112 112 112s112-50.2 112-112h216c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM512 888c-26.5 0-48-21.5-48-48h96c0 26.5-21.5 48-48 48zM304 768V428c0-55.6 21.6-107.8 60.9-147.1S456.4 220 512 220c55.6 0 107.8 21.6 147.1 60.9S720 372.4 720 428v340H304z\")),t.BehanceSquareOutline=u(\"behance-square\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM598.5 350.9h138.4v33.7H598.5v-33.7zM512 628.8a89.52 89.52 0 0 1-27 31c-11.8 8.2-24.9 14.2-38.8 17.7a167.4 167.4 0 0 1-44.6 5.7H236V342.1h161c16.3 0 31.1 1.5 44.6 4.3 13.4 2.8 24.8 7.6 34.4 14.1 9.5 6.5 17 15.2 22.3 26 5.2 10.7 7.9 24.1 7.9 40 0 17.2-3.9 31.4-11.7 42.9-7.9 11.5-19.3 20.8-34.8 28.1 21.1 6 36.6 16.7 46.8 31.7 10.4 15.2 15.5 33.4 15.5 54.8 0 17.4-3.3 32.3-10 44.8zM790.8 576H612.4c0 19.4 6.7 38 16.8 48 10.2 9.9 24.8 14.9 43.9 14.9 13.8 0 25.5-3.5 35.5-10.4 9.9-6.9 15.9-14.2 18.1-21.8h59.8c-9.6 29.7-24.2 50.9-44 63.7-19.6 12.8-43.6 19.2-71.5 19.2-19.5 0-37-3.2-52.7-9.3-15.1-5.9-28.7-14.9-39.9-26.5a121.2 121.2 0 0 1-25.1-41.2c-6.1-16.9-9.1-34.7-8.9-52.6 0-18.5 3.1-35.7 9.1-51.7 11.5-31.1 35.4-56 65.9-68.9 16.3-6.8 33.8-10.2 51.5-10 21 0 39.2 4 55 12.2a111.6 111.6 0 0 1 38.6 32.8c10.1 13.7 17.2 29.3 21.7 46.9 4.3 17.3 5.8 35.5 4.6 54.7zm-122-95.6c-10.8 0-19.9 1.9-26.9 5.6-7 3.7-12.8 8.3-17.2 13.6a48.4 48.4 0 0 0-9.1 17.4c-1.6 5.3-2.7 10.7-3.1 16.2H723c-1.6-17.3-7.6-30.1-15.6-39.1-8.4-8.9-21.9-13.7-38.6-13.7zm-248.5-10.1c8.7-6.3 12.9-16.7 12.9-31 .3-6.8-1.1-13.5-4.1-19.6-2.7-4.9-6.7-9-11.6-11.9a44.8 44.8 0 0 0-16.6-6c-6.4-1.2-12.9-1.8-19.3-1.7h-70.3v79.7h76.1c13.1.1 24.2-3.1 32.9-9.5zm11.8 72c-9.8-7.5-22.9-11.2-39.2-11.2h-81.8v94h80.2c7.5 0 14.4-.7 21.1-2.1s12.7-3.8 17.8-7.2c5.1-3.3 9.2-7.8 12.3-13.6 3-5.8 4.5-13.2 4.5-22.1 0-17.7-5-30.2-14.9-37.8z\")),t.BookOutline=u(\"book\",i,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-260 72h96v209.9L621.5 312 572 347.4V136zm220 752H232V136h280v296.9c0 3.3 1 6.6 3 9.3a15.9 15.9 0 0 0 22.3 3.7l83.8-59.9 81.4 59.4c2.7 2 6 3.1 9.4 3.1 8.8 0 16-7.2 16-16V136h64v752z\")),t.BoxPlotOutline=u(\"box-plot\",i,l(o,\"M952 224h-52c-4.4 0-8 3.6-8 8v248h-92V304c0-4.4-3.6-8-8-8H232c-4.4 0-8 3.6-8 8v176h-92V232c0-4.4-3.6-8-8-8H72c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V548h92v172c0 4.4 3.6 8 8 8h560c4.4 0 8-3.6 8-8V548h92v244c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zM296 368h88v288h-88V368zm432 288H448V368h280v288z\")),t.BulbOutline=u(\"bulb\",i,l(o,\"M632 888H392c-4.4 0-8 3.6-8 8v32c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-32c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-328 328 0 121.4 66 227.4 164 284.1V792c0 17.7 14.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98-56.7 164-162.7 164-284.1 0-181.1-146.9-328-328-328zm127.9 549.8L604 634.6V752H420V634.6l-35.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4 114.6-256 256-256s256 114.6 256 256c0 92.5-49.4 176.3-128.1 221.8z\")),t.BugOutline=u(\"bug\",i,l(o,\"M304 280h56c4.4 0 8-3.6 8-8 0-28.3 5.9-53.2 17.1-73.5 10.6-19.4 26-34.8 45.4-45.4C450.9 142 475.7 136 504 136h16c28.3 0 53.2 5.9 73.5 17.1 19.4 10.6 34.8 26 45.4 45.4C650 218.9 656 243.7 656 272c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-40-8.8-76.7-25.9-108.1a184.31 184.31 0 0 0-74-74C596.7 72.8 560 64 520 64h-16c-40 0-76.7 8.8-108.1 25.9a184.31 184.31 0 0 0-74 74C304.8 195.3 296 232 296 272c0 4.4 3.6 8 8 8z\",\"M940 512H792V412c76.8 0 139-62.2 139-139 0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8a63 63 0 0 1-63 63H232a63 63 0 0 1-63-63c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 76.8 62.2 139 139 139v100H84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h148v96c0 6.5.2 13 .7 19.3C164.1 728.6 116 796.7 116 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-44.2 23.9-82.9 59.6-103.7a273 273 0 0 0 22.7 49c24.3 41.5 59 76.2 100.5 100.5S460.5 960 512 960s99.8-13.9 141.3-38.2a281.38 281.38 0 0 0 123.2-149.5A120 120 0 0 1 836 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-79.3-48.1-147.4-116.7-176.7.4-6.4.7-12.8.7-19.3v-96h148c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM716 680c0 36.8-9.7 72-27.8 102.9-17.7 30.3-43 55.6-73.3 73.3C584 874.3 548.8 884 512 884s-72-9.7-102.9-27.8c-30.3-17.7-55.6-43-73.3-73.3A202.75 202.75 0 0 1 308 680V412h408v268z\")),t.CalculatorOutline=u(\"calculator\",i,l(o,\"M251.2 387H320v68.8c0 1.8 1.8 3.2 4 3.2h48c2.2 0 4-1.4 4-3.3V387h68.8c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H376v-68.8c0-1.8-1.8-3.2-4-3.2h-48c-2.2 0-4 1.4-4 3.2V331h-68.8c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm328 0h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm0 265h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm0 104h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm-195.7-81l61.2-74.9c4.3-5.2.7-13.1-5.9-13.1H388c-2.3 0-4.5 1-5.9 2.9l-34 41.6-34-41.6a7.85 7.85 0 0 0-5.9-2.9h-50.9c-6.6 0-10.2 7.9-5.9 13.1l61.2 74.9-62.7 76.8c-4.4 5.2-.8 13.1 5.8 13.1h50.8c2.3 0 4.5-1 5.9-2.9l35.5-43.5 35.5 43.5c1.5 1.8 3.7 2.9 5.9 2.9h50.8c6.6 0 10.2-7.9 5.9-13.1L383.5 675zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-36 732H180V180h664v664z\")),t.BuildOutline=u(\"build\",i,l(o,\"M916 210H376c-17.7 0-32 14.3-32 32v236H108c-17.7 0-32 14.3-32 32v272c0 17.7 14.3 32 32 32h540c17.7 0 32-14.3 32-32V546h236c17.7 0 32-14.3 32-32V242c0-17.7-14.3-32-32-32zm-504 68h200v200H412V278zm-68 468H144V546h200v200zm268 0H412V546h200v200zm268-268H680V278h200v200z\")),t.CalendarOutline=u(\"calendar\",i,l(o,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z\")),t.CameraOutline=u(\"camera\",i,l(o,\"M864 248H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 248H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V328c0-44.2-35.8-80-80-80zm8 536c0 4.4-3.6 8-8 8H160c-4.4 0-8-3.6-8-8V328c0-4.4 3.6-8 8-8h186.7l17.1-47.8 22.9-64.2h250.5l22.9 64.2 17.1 47.8H864c4.4 0 8 3.6 8 8v456zM512 384c-88.4 0-160 71.6-160 160s71.6 160 160 160 160-71.6 160-160-71.6-160-160-160zm0 256c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96z\")),t.CarOutline=u(\"car\",i,l(o,\"M380 704h264c4.4 0 8-3.6 8-8v-84c0-4.4-3.6-8-8-8h-40c-4.4 0-8 3.6-8 8v36H428v-36c0-4.4-3.6-8-8-8h-40c-4.4 0-8 3.6-8 8v84c0 4.4 3.6 8 8 8zm340-123a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm239-167.6L935.3 372a8 8 0 0 0-10.9-2.9l-50.7 29.6-78.3-216.2a63.9 63.9 0 0 0-60.9-44.4H301.2c-34.7 0-65.5 22.4-76.2 55.5l-74.6 205.2-50.8-29.6a8 8 0 0 0-10.9 2.9L65 413.4c-2.2 3.8-.9 8.6 2.9 10.8l60.4 35.2-14.5 40c-1.2 3.2-1.8 6.6-1.8 10v348.2c0 15.7 11.8 28.4 26.3 28.4h67.6c12.3 0 23-9.3 25.6-22.3l7.7-37.7h545.6l7.7 37.7c2.7 13 13.3 22.3 25.6 22.3h67.6c14.5 0 26.3-12.7 26.3-28.4V509.4c0-3.4-.6-6.8-1.8-10l-14.5-40 60.3-35.2a8 8 0 0 0 3-10.8zM840 517v237H184V517l15.6-43h624.8l15.6 43zM292.7 218.1l.5-1.3.4-1.3c1.1-3.3 4.1-5.5 7.6-5.5h427.6l75.4 208H220l72.7-199.9zM224 581a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.CaretDownOutline=u(\"caret-down\",i,l(r,\"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z\")),t.CaretLeftOutline=u(\"caret-left\",i,l(r,\"M689 165.1L308.2 493.5c-10.9 9.4-10.9 27.5 0 37L689 858.9c14.2 12.2 35 1.2 35-18.5V183.6c0-19.7-20.8-30.7-35-18.5z\")),t.CaretRightOutline=u(\"caret-right\",i,l(r,\"M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z\")),t.CarryOutOutline=u(\"carry-out\",i,l(o,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v584zM688 420h-55.2c-5.1 0-10 2.5-13 6.6L468.9 634.4l-64.7-89c-3-4.1-7.8-6.6-13-6.6H336c-6.5 0-10.3 7.4-6.5 12.7l126.4 174a16.1 16.1 0 0 0 26 0l212.6-292.7c3.8-5.4 0-12.8-6.5-12.8z\")),t.CheckCircleOutline=u(\"check-circle\",i,l(o,\"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0 0 51.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.CaretUpOutline=u(\"caret-up\",i,l(r,\"M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z\")),t.CheckSquareOutline=u(\"check-square\",i,l(o,\"M433.1 657.7a31.8 31.8 0 0 0 51.7 0l210.6-292c3.8-5.3 0-12.7-6.5-12.7H642c-10.2 0-19.9 4.9-25.9 13.3L459 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H315c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.ChromeOutline=u(\"chrome\",i,l(o,\"M928 512.3v-.3c0-229.8-186.2-416-416-416S96 282.2 96 512v.4c0 229.8 186.2 416 416 416s416-186.2 416-416v-.3.2zm-6.7-74.6l.6 3.3-.6-3.3zM676.7 638.2c53.5-82.2 52.5-189.4-11.1-263.7l162.4-8.4c20.5 44.4 32 93.8 32 145.9 0 185.2-144.6 336.6-327.1 347.4l143.8-221.2zM512 652.3c-77.5 0-140.2-62.7-140.2-140.2 0-77.7 62.7-140.2 140.2-140.2S652.2 434.5 652.2 512 589.5 652.3 512 652.3zm369.2-331.7l-3-5.7 3 5.7zM512 164c121.3 0 228.2 62.1 290.4 156.2l-263.6-13.9c-97.5-5.7-190.2 49.2-222.3 141.1L227.8 311c63.1-88.9 166.9-147 284.2-147zM102.5 585.8c26 145 127.1 264 261.6 315.1C229.6 850 128.5 731 102.5 585.8zM164 512c0-55.9 13.2-108.7 36.6-155.5l119.7 235.4c44.1 86.7 137.4 139.7 234 121.6l-74 145.1C302.9 842.5 164 693.5 164 512zm324.7 415.4c4 .2 8 .4 12 .5-4-.2-8-.3-12-.5z\")),t.ClockCircleOutline=u(\"clock-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\")),t.CloseCircleOutline=u(\"close-circle\",i,l(o,\"M685.4 354.8c0-4.4-3.6-8-8-8l-66 .3L512 465.6l-99.3-118.4-66.1-.3c-4.4 0-8 3.5-8 8 0 1.9.7 3.7 1.9 5.2l130.1 155L340.5 670a8.32 8.32 0 0 0-1.9 5.2c0 4.4 3.6 8 8 8l66.1-.3L512 564.4l99.3 118.4 66 .3c4.4 0 8-3.5 8-8 0-1.9-.7-3.7-1.9-5.2L553.5 515l130.1-155c1.2-1.4 1.8-3.3 1.8-5.2z\",\"M512 65C264.6 65 64 265.6 64 513s200.6 448 448 448 448-200.6 448-448S759.4 65 512 65zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.CloudOutline=u(\"cloud\",i,l(o,\"M811.4 418.7C765.6 297.9 648.9 212 512.2 212S258.8 297.8 213 418.6C127.3 441.1 64 519.1 64 612c0 110.5 89.5 200 199.9 200h496.2C870.5 812 960 722.5 960 612c0-92.7-63.1-170.7-148.6-193.3zm36.3 281a123.07 123.07 0 0 1-87.6 36.3H263.9c-33.1 0-64.2-12.9-87.6-36.3A123.3 123.3 0 0 1 140 612c0-28 9.1-54.3 26.2-76.3a125.7 125.7 0 0 1 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4a245.6 245.6 0 0 1 52.4-49.9c41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10c54.3 14.5 92.1 63.8 92.1 120 0 33.1-12.9 64.3-36.3 87.7z\")),t.CloseSquareOutline=u(\"close-square\",i,l(o,\"M354 671h58.9c4.7 0 9.2-2.1 12.3-5.7L512 561.8l86.8 103.5c3 3.6 7.5 5.7 12.3 5.7H670c6.8 0 10.5-7.9 6.1-13.1L553.8 512l122.4-145.9c4.4-5.2.7-13.1-6.1-13.1h-58.9c-4.7 0-9.2 2.1-12.3 5.7L512 462.2l-86.8-103.5c-3-3.6-7.5-5.7-12.3-5.7H354c-6.8 0-10.5 7.9-6.1 13.1L470.2 512 347.9 657.9A7.95 7.95 0 0 0 354 671z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.CodeOutline=u(\"code\",i,l(o,\"M516 673c0 4.4 3.4 8 7.5 8h185c4.1 0 7.5-3.6 7.5-8v-48c0-4.4-3.4-8-7.5-8h-185c-4.1 0-7.5 3.6-7.5 8v48zm-194.9 6.1l192-161c3.8-3.2 3.8-9.1 0-12.3l-192-160.9A7.95 7.95 0 0 0 308 351v62.7c0 2.4 1 4.6 2.9 6.1L420.7 512l-109.8 92.2a8.1 8.1 0 0 0-2.9 6.1V673c0 6.8 7.9 10.5 13.1 6.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.CodepenCircleOutline=u(\"codepen-circle\",i,l(o,\"M488.1 414.7V303.4L300.9 428l83.6 55.8zm254.1 137.7v-79.8l-59.8 39.9zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm278 533c0 1.1-.1 2.1-.2 3.1 0 .4-.1.7-.2 1a14.16 14.16 0 0 1-.8 3.2c-.2.6-.4 1.2-.6 1.7-.2.4-.4.8-.5 1.2-.3.5-.5 1.1-.8 1.6-.2.4-.4.7-.7 1.1-.3.5-.7 1-1 1.5-.3.4-.5.7-.8 1-.4.4-.8.9-1.2 1.3-.3.3-.6.6-1 .9-.4.4-.9.8-1.4 1.1-.4.3-.7.6-1.1.8-.1.1-.3.2-.4.3L525.2 786c-4 2.7-8.6 4-13.2 4-4.7 0-9.3-1.4-13.3-4L244.6 616.9c-.1-.1-.3-.2-.4-.3l-1.1-.8c-.5-.4-.9-.7-1.3-1.1-.3-.3-.6-.6-1-.9-.4-.4-.8-.8-1.2-1.3a7 7 0 0 1-.8-1c-.4-.5-.7-1-1-1.5-.2-.4-.5-.7-.7-1.1-.3-.5-.6-1.1-.8-1.6-.2-.4-.4-.8-.5-1.2-.2-.6-.4-1.2-.6-1.7-.1-.4-.3-.8-.4-1.2-.2-.7-.3-1.3-.4-2-.1-.3-.1-.7-.2-1-.1-1-.2-2.1-.2-3.1V427.9c0-1 .1-2.1.2-3.1.1-.3.1-.7.2-1a14.16 14.16 0 0 1 .8-3.2c.2-.6.4-1.2.6-1.7.2-.4.4-.8.5-1.2.2-.5.5-1.1.8-1.6.2-.4.4-.7.7-1.1.6-.9 1.2-1.7 1.8-2.5.4-.4.8-.9 1.2-1.3.3-.3.6-.6 1-.9.4-.4.9-.8 1.3-1.1.4-.3.7-.6 1.1-.8.1-.1.3-.2.4-.3L498.7 239c8-5.3 18.5-5.3 26.5 0l254.1 169.1c.1.1.3.2.4.3l1.1.8 1.4 1.1c.3.3.6.6 1 .9.4.4.8.8 1.2 1.3.7.8 1.3 1.6 1.8 2.5.2.4.5.7.7 1.1.3.5.6 1 .8 1.6.2.4.4.8.5 1.2.2.6.4 1.2.6 1.7.1.4.3.8.4 1.2.2.7.3 1.3.4 2 .1.3.1.7.2 1 .1 1 .2 2.1.2 3.1V597zm-254.1 13.3v111.3L723.1 597l-83.6-55.8zM281.8 472.6v79.8l59.8-39.9zM512 456.1l-84.5 56.4 84.5 56.4 84.5-56.4zM723.1 428L535.9 303.4v111.3l103.6 69.1zM384.5 541.2L300.9 597l187.2 124.6V610.3l-103.6-69.1z\")),t.CompassOutline=u(\"compass\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm198.4-588.1a32 32 0 0 0-24.5.5L414.9 415 296.4 686c-3.6 8.2-3.6 17.5 0 25.7 3.4 7.8 9.7 13.9 17.7 17 3.8 1.5 7.7 2.2 11.7 2.2 4.4 0 8.7-.9 12.8-2.7l271-118.6 118.5-271a32.06 32.06 0 0 0-17.7-42.7zM576.8 534.4l26.2 26.2-42.4 42.4-26.2-26.2L380 644.4 447.5 490 422 464.4l42.4-42.4 25.5 25.5L644.4 380l-67.6 154.4zM464.4 422L422 464.4l25.5 25.6 86.9 86.8 26.2 26.2 42.4-42.4-26.2-26.2-86.8-86.9z\")),t.ContactsOutline=u(\"contacts\",i,l(o,\"M594.3 601.5a111.8 111.8 0 0 0 29.1-75.5c0-61.9-49.9-112-111.4-112s-111.4 50.1-111.4 112c0 29.1 11 55.5 29.1 75.5a158.09 158.09 0 0 0-74.6 126.1 8 8 0 0 0 8 8.4H407c4.2 0 7.6-3.3 7.9-7.5 3.8-50.6 46-90.5 97.2-90.5s93.4 40 97.2 90.5c.3 4.2 3.7 7.5 7.9 7.5H661a8 8 0 0 0 8-8.4c-2.8-53.3-32-99.7-74.7-126.1zM512 578c-28.5 0-51.7-23.3-51.7-52s23.2-52 51.7-52 51.7 23.3 51.7 52-23.2 52-51.7 52zm416-354H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zm-40 568H136V296h120v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h120v496z\")),t.ContainerOutline=u(\"container\",i,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V687h97.9c11.6 32.8 32 62.3 59.1 84.7 34.5 28.5 78.2 44.3 123 44.3s88.5-15.7 123-44.3c27.1-22.4 47.5-51.9 59.1-84.7H792v-63H643.6l-5.2 24.7C626.4 708.5 573.2 752 512 752s-114.4-43.5-126.5-103.3l-5.2-24.7H232V136h560v752zM320 341h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0 160h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\")),t.ControlOutline=u(\"control\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656zM340 683v77c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-77c-10.1 3.3-20.8 5-32 5s-21.9-1.8-32-5zm64-198V264c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v221c10.1-3.3 20.8-5 32-5s21.9 1.8 32 5zm-64 198c10.1 3.3 20.8 5 32 5s21.9-1.8 32-5c41.8-13.5 72-52.7 72-99s-30.2-85.5-72-99c-10.1-3.3-20.8-5-32-5s-21.9 1.8-32 5c-41.8 13.5-72 52.7-72 99s30.2 85.5 72 99zm.1-115.7c.3-.6.7-1.2 1-1.8v-.1l1.2-1.8c.1-.2.2-.3.3-.5.3-.5.7-.9 1-1.4.1-.1.2-.3.3-.4.5-.6.9-1.1 1.4-1.6l.3-.3 1.2-1.2.4-.4c.5-.5 1-.9 1.6-1.4.6-.5 1.1-.9 1.7-1.3.2-.1.3-.2.5-.3.5-.3.9-.7 1.4-1 .1-.1.3-.2.4-.3.6-.4 1.2-.7 1.9-1.1.1-.1.3-.1.4-.2.5-.3 1-.5 1.6-.8l.6-.3c.7-.3 1.3-.6 2-.8.7-.3 1.4-.5 2.1-.7.2-.1.4-.1.6-.2.6-.2 1.1-.3 1.7-.4.2 0 .3-.1.5-.1.7-.2 1.5-.3 2.2-.4.2 0 .3 0 .5-.1.6-.1 1.2-.1 1.8-.2h.6c.8 0 1.5-.1 2.3-.1s1.5 0 2.3.1h.6c.6 0 1.2.1 1.8.2.2 0 .3 0 .5.1.7.1 1.5.2 2.2.4.2 0 .3.1.5.1.6.1 1.2.3 1.7.4.2.1.4.1.6.2.7.2 1.4.4 2.1.7.7.2 1.3.5 2 .8l.6.3c.5.2 1.1.5 1.6.8.1.1.3.1.4.2.6.3 1.3.7 1.9 1.1.1.1.3.2.4.3.5.3 1 .6 1.4 1 .2.1.3.2.5.3.6.4 1.2.9 1.7 1.3s1.1.9 1.6 1.4l.4.4 1.2 1.2.3.3c.5.5 1 1.1 1.4 1.6.1.1.2.3.3.4.4.4.7.9 1 1.4.1.2.2.3.3.5l1.2 1.8s0 .1.1.1a36.18 36.18 0 0 1 5.1 18.5c0 6-1.5 11.7-4.1 16.7-.3.6-.7 1.2-1 1.8 0 0 0 .1-.1.1l-1.2 1.8c-.1.2-.2.3-.3.5-.3.5-.7.9-1 1.4-.1.1-.2.3-.3.4-.5.6-.9 1.1-1.4 1.6l-.3.3-1.2 1.2-.4.4c-.5.5-1 .9-1.6 1.4-.6.5-1.1.9-1.7 1.3-.2.1-.3.2-.5.3-.5.3-.9.7-1.4 1-.1.1-.3.2-.4.3-.6.4-1.2.7-1.9 1.1-.1.1-.3.1-.4.2-.5.3-1 .5-1.6.8l-.6.3c-.7.3-1.3.6-2 .8-.7.3-1.4.5-2.1.7-.2.1-.4.1-.6.2-.6.2-1.1.3-1.7.4-.2 0-.3.1-.5.1-.7.2-1.5.3-2.2.4-.2 0-.3 0-.5.1-.6.1-1.2.1-1.8.2h-.6c-.8 0-1.5.1-2.3.1s-1.5 0-2.3-.1h-.6c-.6 0-1.2-.1-1.8-.2-.2 0-.3 0-.5-.1-.7-.1-1.5-.2-2.2-.4-.2 0-.3-.1-.5-.1-.6-.1-1.2-.3-1.7-.4-.2-.1-.4-.1-.6-.2-.7-.2-1.4-.4-2.1-.7-.7-.2-1.3-.5-2-.8l-.6-.3c-.5-.2-1.1-.5-1.6-.8-.1-.1-.3-.1-.4-.2-.6-.3-1.3-.7-1.9-1.1-.1-.1-.3-.2-.4-.3-.5-.3-1-.6-1.4-1-.2-.1-.3-.2-.5-.3-.6-.4-1.2-.9-1.7-1.3s-1.1-.9-1.6-1.4l-.4-.4-1.2-1.2-.3-.3c-.5-.5-1-1.1-1.4-1.6-.1-.1-.2-.3-.3-.4-.4-.4-.7-.9-1-1.4-.1-.2-.2-.3-.3-.5l-1.2-1.8v-.1c-.4-.6-.7-1.2-1-1.8-2.6-5-4.1-10.7-4.1-16.7s1.5-11.7 4.1-16.7zM620 539v221c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V539c-10.1 3.3-20.8 5-32 5s-21.9-1.8-32-5zm64-198v-77c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v77c10.1-3.3 20.8-5 32-5s21.9 1.8 32 5zm-64 198c10.1 3.3 20.8 5 32 5s21.9-1.8 32-5c41.8-13.5 72-52.7 72-99s-30.2-85.5-72-99c-10.1-3.3-20.8-5-32-5s-21.9 1.8-32 5c-41.8 13.5-72 52.7-72 99s30.2 85.5 72 99zm.1-115.7c.3-.6.7-1.2 1-1.8v-.1l1.2-1.8c.1-.2.2-.3.3-.5.3-.5.7-.9 1-1.4.1-.1.2-.3.3-.4.5-.6.9-1.1 1.4-1.6l.3-.3 1.2-1.2.4-.4c.5-.5 1-.9 1.6-1.4.6-.5 1.1-.9 1.7-1.3.2-.1.3-.2.5-.3.5-.3.9-.7 1.4-1 .1-.1.3-.2.4-.3.6-.4 1.2-.7 1.9-1.1.1-.1.3-.1.4-.2.5-.3 1-.5 1.6-.8l.6-.3c.7-.3 1.3-.6 2-.8.7-.3 1.4-.5 2.1-.7.2-.1.4-.1.6-.2.6-.2 1.1-.3 1.7-.4.2 0 .3-.1.5-.1.7-.2 1.5-.3 2.2-.4.2 0 .3 0 .5-.1.6-.1 1.2-.1 1.8-.2h.6c.8 0 1.5-.1 2.3-.1s1.5 0 2.3.1h.6c.6 0 1.2.1 1.8.2.2 0 .3 0 .5.1.7.1 1.5.2 2.2.4.2 0 .3.1.5.1.6.1 1.2.3 1.7.4.2.1.4.1.6.2.7.2 1.4.4 2.1.7.7.2 1.3.5 2 .8l.6.3c.5.2 1.1.5 1.6.8.1.1.3.1.4.2.6.3 1.3.7 1.9 1.1.1.1.3.2.4.3.5.3 1 .6 1.4 1 .2.1.3.2.5.3.6.4 1.2.9 1.7 1.3s1.1.9 1.6 1.4l.4.4 1.2 1.2.3.3c.5.5 1 1.1 1.4 1.6.1.1.2.3.3.4.4.4.7.9 1 1.4.1.2.2.3.3.5l1.2 1.8v.1a36.18 36.18 0 0 1 5.1 18.5c0 6-1.5 11.7-4.1 16.7-.3.6-.7 1.2-1 1.8v.1l-1.2 1.8c-.1.2-.2.3-.3.5-.3.5-.7.9-1 1.4-.1.1-.2.3-.3.4-.5.6-.9 1.1-1.4 1.6l-.3.3-1.2 1.2-.4.4c-.5.5-1 .9-1.6 1.4-.6.5-1.1.9-1.7 1.3-.2.1-.3.2-.5.3-.5.3-.9.7-1.4 1-.1.1-.3.2-.4.3-.6.4-1.2.7-1.9 1.1-.1.1-.3.1-.4.2-.5.3-1 .5-1.6.8l-.6.3c-.7.3-1.3.6-2 .8-.7.3-1.4.5-2.1.7-.2.1-.4.1-.6.2-.6.2-1.1.3-1.7.4-.2 0-.3.1-.5.1-.7.2-1.5.3-2.2.4-.2 0-.3 0-.5.1-.6.1-1.2.1-1.8.2h-.6c-.8 0-1.5.1-2.3.1s-1.5 0-2.3-.1h-.6c-.6 0-1.2-.1-1.8-.2-.2 0-.3 0-.5-.1-.7-.1-1.5-.2-2.2-.4-.2 0-.3-.1-.5-.1-.6-.1-1.2-.3-1.7-.4-.2-.1-.4-.1-.6-.2-.7-.2-1.4-.4-2.1-.7-.7-.2-1.3-.5-2-.8l-.6-.3c-.5-.2-1.1-.5-1.6-.8-.1-.1-.3-.1-.4-.2-.6-.3-1.3-.7-1.9-1.1-.1-.1-.3-.2-.4-.3-.5-.3-1-.6-1.4-1-.2-.1-.3-.2-.5-.3-.6-.4-1.2-.9-1.7-1.3s-1.1-.9-1.6-1.4l-.4-.4-1.2-1.2-.3-.3c-.5-.5-1-1.1-1.4-1.6-.1-.1-.2-.3-.3-.4-.4-.4-.7-.9-1-1.4-.1-.2-.2-.3-.3-.5l-1.2-1.8v-.1c-.4-.6-.7-1.2-1-1.8-2.6-5-4.1-10.7-4.1-16.7s1.5-11.7 4.1-16.7z\")),t.CopyOutline=u(\"copy\",i,l(o,\"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z\")),t.CreditCardOutline=u(\"credit-card\",i,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-792 72h752v120H136V232zm752 560H136V440h752v352zm-237-64h165c4.4 0 8-3.6 8-8v-72c0-4.4-3.6-8-8-8H651c-4.4 0-8 3.6-8 8v72c0 4.4 3.6 8 8 8z\")),t.CrownOutline=u(\"crown\",i,l(o,\"M899.6 276.5L705 396.4 518.4 147.5a8.06 8.06 0 0 0-12.9 0L319 396.4 124.3 276.5c-5.7-3.5-13.1 1.2-12.2 7.9L188.5 865c1.1 7.9 7.9 14 16 14h615.1c8 0 14.9-6 15.9-14l76.4-580.6c.8-6.7-6.5-11.4-12.3-7.9zm-126 534.1H250.3l-53.8-409.4 139.8 86.1L512 252.9l175.7 234.4 139.8-86.1-53.9 409.4zM512 509c-62.1 0-112.6 50.5-112.6 112.6S449.9 734.2 512 734.2s112.6-50.5 112.6-112.6S574.1 509 512 509zm0 160.9c-26.6 0-48.2-21.6-48.2-48.3 0-26.6 21.6-48.3 48.2-48.3s48.2 21.6 48.2 48.3c0 26.6-21.6 48.3-48.2 48.3z\")),t.CustomerServiceOutline=u(\"customer-service\",i,l(o,\"M512 128c-212.1 0-384 171.9-384 384v360c0 13.3 10.7 24 24 24h184c35.3 0 64-28.7 64-64V624c0-35.3-28.7-64-64-64H200v-48c0-172.3 139.7-312 312-312s312 139.7 312 312v48H688c-35.3 0-64 28.7-64 64v208c0 35.3 28.7 64 64 64h184c13.3 0 24-10.7 24-24V512c0-212.1-171.9-384-384-384zM328 632v192H200V632h128zm496 192H696V632h128v192z\")),t.DashboardOutline=u(\"dashboard\",i,l(o,\"M924.8 385.6a446.7 446.7 0 0 0-96-142.4 446.7 446.7 0 0 0-142.4-96C631.1 123.8 572.5 112 512 112s-119.1 11.8-174.4 35.2a446.7 446.7 0 0 0-142.4 96 446.7 446.7 0 0 0-96 142.4C75.8 440.9 64 499.5 64 560c0 132.7 58.3 257.7 159.9 343.1l1.7 1.4c5.8 4.8 13.1 7.5 20.6 7.5h531.7c7.5 0 14.8-2.7 20.6-7.5l1.7-1.4C901.7 817.7 960 692.7 960 560c0-60.5-11.9-119.1-35.2-174.4zM761.4 836H262.6A371.12 371.12 0 0 1 140 560c0-99.4 38.7-192.8 109-263 70.3-70.3 163.7-109 263-109 99.4 0 192.8 38.7 263 109 70.3 70.3 109 163.7 109 263 0 105.6-44.5 205.5-122.6 276zM623.5 421.5a8.03 8.03 0 0 0-11.3 0L527.7 506c-18.7-5-39.4-.2-54.1 14.5a55.95 55.95 0 0 0 0 79.2 55.95 55.95 0 0 0 79.2 0 55.87 55.87 0 0 0 14.5-54.1l84.5-84.5c3.1-3.1 3.1-8.2 0-11.3l-28.3-28.3zM490 320h44c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8h-44c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8zm260 218v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8zm12.7-197.2l-31.1-31.1a8.03 8.03 0 0 0-11.3 0l-56.6 56.6a8.03 8.03 0 0 0 0 11.3l31.1 31.1c3.1 3.1 8.2 3.1 11.3 0l56.6-56.6c3.1-3.1 3.1-8.2 0-11.3zm-458.6-31.1a8.03 8.03 0 0 0-11.3 0l-31.1 31.1a8.03 8.03 0 0 0 0 11.3l56.6 56.6c3.1 3.1 8.2 3.1 11.3 0l31.1-31.1c3.1-3.1 3.1-8.2 0-11.3l-56.6-56.6zM262 530h-80c-4.4 0-8 3.6-8 8v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8z\")),t.DeleteOutline=u(\"delete\",i,l(o,\"M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z\")),t.DiffOutline=u(\"diff\",i,l(o,\"M476 399.1c0-3.9-3.1-7.1-7-7.1h-42c-3.8 0-7 3.2-7 7.1V484h-84.5c-4.1 0-7.5 3.1-7.5 7v42c0 3.8 3.4 7 7.5 7H420v84.9c0 3.9 3.2 7.1 7 7.1h42c3.9 0 7-3.2 7-7.1V540h84.5c4.1 0 7.5-3.2 7.5-7v-42c0-3.9-3.4-7-7.5-7H476v-84.9zM560.5 704h-225c-4.1 0-7.5 3.2-7.5 7v42c0 3.8 3.4 7 7.5 7h225c4.1 0 7.5-3.2 7.5-7v-42c0-3.8-3.4-7-7.5-7zm-7.1-502.6c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V397.3c0-8.5-3.4-16.6-9.4-22.6L553.4 201.4zM664 888H232V264h282.2L664 413.8V888zm190.2-581.4L611.3 72.9c-6-5.7-13.9-8.9-22.2-8.9H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h277l219 210.6V824c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V329.6c0-8.7-3.5-17-9.8-23z\")),t.DatabaseOutline=u(\"database\",i,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208zM304 240a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0 272a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0 272a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.DislikeOutline=u(\"dislike\",i,l(o,\"M885.9 490.3c3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-51.6-30.7-98.1-78.3-118.4a66.1 66.1 0 0 0-26.5-5.4H144c-17.7 0-32 14.3-32 32v364c0 17.7 14.3 32 32 32h129.3l85.8 310.8C372.9 889 418.9 924 470.9 924c29.7 0 57.4-11.8 77.9-33.4 20.5-21.5 31-49.7 29.5-79.4l-6-122.9h239.9c12.1 0 23.9-3.2 34.3-9.3 40.4-23.5 65.5-66.1 65.5-111 0-28.3-9.3-55.5-26.1-77.7zM184 456V172h81v284h-81zm627.2 160.4H496.8l9.6 198.4c.6 11.9-4.7 23.1-14.6 30.5-6.1 4.5-13.6 6.8-21.1 6.7a44.28 44.28 0 0 1-42.2-32.3L329 459.2V172h415.4a56.85 56.85 0 0 1 33.6 51.8c0 9.7-2.3 18.9-6.9 27.3l-13.9 25.4 21.9 19a56.76 56.76 0 0 1 19.6 43c0 9.7-2.3 18.9-6.9 27.3l-13.9 25.4 21.9 19a56.76 56.76 0 0 1 19.6 43c0 9.7-2.3 18.9-6.9 27.3l-14 25.5 21.9 19a56.76 56.76 0 0 1 19.6 43c0 19.1-11 37.5-28.8 48.4z\")),t.DownCircleOutline=u(\"down-circle\",i,l(o,\"M690 405h-46.9c-10.2 0-19.9 4.9-25.9 13.2L512 563.6 406.8 418.2c-6-8.3-15.6-13.2-25.9-13.2H334c-6.5 0-10.3 7.4-6.5 12.7l178 246c3.2 4.4 9.7 4.4 12.9 0l178-246c3.9-5.3.1-12.7-6.4-12.7z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.DownSquareOutline=u(\"down-square\",i,l(o,\"M505.5 658.7c3.2 4.4 9.7 4.4 12.9 0l178-246c3.8-5.3 0-12.7-6.5-12.7H643c-10.2 0-19.9 4.9-25.9 13.2L512 558.6 406.8 413.2c-6-8.3-15.6-13.2-25.9-13.2H334c-6.5 0-10.3 7.4-6.5 12.7l178 246z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.DribbbleSquareOutline=u(\"dribbble-square\",i,l(o,\"M498.6 432c-40.8-72.5-84.7-133.4-91.2-142.3-68.8 32.5-120.3 95.9-136.2 172.2 11 .2 112.4.7 227.4-29.9zm66.5 21.8c5.7 11.7 11.2 23.6 16.3 35.6 1.8 4.2 3.6 8.4 5.3 12.7 81.8-10.3 163.2 6.2 171.3 7.9-.5-58.1-21.3-111.4-55.5-153.3-5.3 7.1-46.5 60-137.4 97.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM512 800c-158.8 0-288-129.2-288-288s129.2-288 288-288 288 129.2 288 288-129.2 288-288 288zm89.7-259.1c32.2 88.4 45.3 160.4 47.8 175.4 55.2-37.3 94.5-96.4 105.4-164.9-8.4-2.6-76.1-22.8-153.2-10.5zm-72.5-26.4c3.2-1 6.4-2 9.7-2.9-6.2-14-12.9-28-19.9-41.7-122.8 36.8-242.1 35.2-252.8 35-.1 2.5-.1 5-.1 7.5 0 63.2 23.9 120.9 63.2 164.5 5.5-9.6 73-121.4 199.9-162.4zm145.9-186.2a245.2 245.2 0 0 0-220.8-55.1c6.8 9.1 51.5 69.9 91.8 144 87.5-32.8 124.5-82.6 129-88.9zM554 552.8c-138.7 48.3-188.6 144.6-193 153.6 41.7 32.5 94.1 51.9 151 51.9 34.1 0 66.6-6.9 96.1-19.5-3.7-21.6-17.9-96.8-52.5-186.6l-1.6.6z\")),t.EnvironmentOutline=u(\"environment\",i,l(o,\"M854.6 289.1a362.49 362.49 0 0 0-79.9-115.7 370.83 370.83 0 0 0-118.2-77.8C610.7 76.6 562.1 67 512 67c-50.1 0-98.7 9.6-144.5 28.5-44.3 18.3-84 44.5-118.2 77.8A363.6 363.6 0 0 0 169.4 289c-19.5 45-29.4 92.8-29.4 142 0 70.6 16.9 140.9 50.1 208.7 26.7 54.5 64 107.6 111 158.1 80.3 86.2 164.5 138.9 188.4 153a43.9 43.9 0 0 0 22.4 6.1c7.8 0 15.5-2 22.4-6.1 23.9-14.1 108.1-66.8 188.4-153 47-50.4 84.3-103.6 111-158.1C867.1 572 884 501.8 884 431.1c0-49.2-9.9-97-29.4-142zM512 880.2c-65.9-41.9-300-207.8-300-449.1 0-77.9 31.1-151.1 87.6-206.3C356.3 169.5 431.7 139 512 139s155.7 30.5 212.4 85.9C780.9 280 812 353.2 812 431.1c0 241.3-234.1 407.2-300 449.1zm0-617.2c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 0 1 512 551c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 0 1 400 439c0-29.9 11.7-58 32.8-79.2C454 338.6 482.1 327 512 327c29.9 0 58 11.6 79.2 32.8C612.4 381 624 409.1 624 439c0 29.9-11.6 58-32.8 79.2z\")),t.EditOutline=u(\"edit\",i,l(o,\"M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 0 0 0-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 0 0 9.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z\")),t.ExclamationCircleOutline=u(\"exclamation-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\"M464 688a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z\")),t.ExperimentOutline=u(\"experiment\",i,l(o,\"M512 472a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 0 1-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z\")),t.EyeInvisibleOutline=u(\"eye-invisible\",i,l(o,\"M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5zm-63.57-320.64L836 122.88a8 8 0 0 0-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 0 0 0 11.31L155.17 889a8 8 0 0 0 11.31 0l712.15-712.12a8 8 0 0 0 0-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 0 0-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 0 1 146.2-106.69L401.31 546.2A112 112 0 0 1 396 512z\",\"M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 0 0 227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 0 1-112 112z\")),t.EyeOutline=u(\"eye\",i,l(o,\"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 0 0 0 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z\")),t.FacebookOutline=u(\"facebook\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-32 736H663.9V602.2h104l15.6-120.7H663.9v-77.1c0-35 9.7-58.8 59.8-58.8h63.9v-108c-11.1-1.5-49-4.8-93.2-4.8-92.2 0-155.3 56.3-155.3 159.6v89H434.9v120.7h104.3V848H176V176h672v672z\")),t.FastBackwardOutline=u(\"fast-backward\",i,l(r,\"M517.6 273.5L230.2 499.3a16.14 16.14 0 0 0 0 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm320 0L550.2 499.3a16.14 16.14 0 0 0 0 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm-620-25.5h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z\")),t.FastForwardOutline=u(\"fast-forward\",i,l(r,\"M793.8 499.3L506.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.6c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8a16.14 16.14 0 0 0 0-25.4zm-320 0L186.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.5c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8c4.1-3.2 6.2-8 6.2-12.7 0-4.6-2.1-9.4-6.2-12.6zM857.6 248h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z\")),t.FileAddOutline=u(\"file-add\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM544 472c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v108H372c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h108v108c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V644h108c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V472z\")),t.FileExcelOutline=u(\"file-excel\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM514.1 580.1l-61.8-102.4c-2.2-3.6-6.1-5.8-10.3-5.8h-38.4c-2.3 0-4.5.6-6.4 1.9-5.6 3.5-7.3 10.9-3.7 16.6l82.3 130.4-83.4 132.8a12.04 12.04 0 0 0 10.2 18.4h34.5c4.2 0 8-2.2 10.2-5.7L510 664.8l62.3 101.4c2.2 3.6 6.1 5.7 10.2 5.7H620c2.3 0 4.5-.7 6.5-1.9 5.6-3.6 7.2-11 3.6-16.6l-84-130.4 85.3-132.5a12.04 12.04 0 0 0-10.1-18.5h-35.7c-4.2 0-8.1 2.2-10.3 5.8l-61.2 102.3z\")),t.FileExclamationOutline=u(\"file-exclamation\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM472 744a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm16-104h48c4.4 0 8-3.6 8-8V448c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8z\")),t.FileImageOutline=u(\"file-image\",i,l(o,\"M553.1 509.1l-77.8 99.2-41.1-52.4a8 8 0 0 0-12.6 0l-99.8 127.2a7.98 7.98 0 0 0 6.3 12.9H696c6.7 0 10.4-7.7 6.3-12.9l-136.5-174a8.1 8.1 0 0 0-12.7 0zM360 442a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm494.6-153.4L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494z\")),t.FileMarkdownOutline=u(\"file-markdown\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM429 481.2c-1.9-4.4-6.2-7.2-11-7.2h-35c-6.6 0-12 5.4-12 12v272c0 6.6 5.4 12 12 12h27.1c6.6 0 12-5.4 12-12V582.1l66.8 150.2a12 12 0 0 0 11 7.1H524c4.7 0 9-2.8 11-7.1l66.8-150.6V758c0 6.6 5.4 12 12 12H641c6.6 0 12-5.4 12-12V486c0-6.6-5.4-12-12-12h-34.7c-4.8 0-9.1 2.8-11 7.2l-83.1 191-83.2-191z\")),t.FilePptOutline=u(\"file-ppt\",i,l(o,\"M424 476c-4.4 0-8 3.6-8 8v276c0 4.4 3.6 8 8 8h32.5c4.4 0 8-3.6 8-8v-95.5h63.3c59.4 0 96.2-38.9 96.2-94.1 0-54.5-36.3-94.3-96-94.3H424zm150.6 94.3c0 43.4-26.5 54.3-71.2 54.3h-38.9V516.2h56.2c33.8 0 53.9 19.7 53.9 54.1zm280-281.7L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494z\")),t.FileTextOutline=u(\"file-text\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z\")),t.FilePdfOutline=u(\"file-pdf\",i,l(o,\"M531.3 574.4l.3-1.4c5.8-23.9 13.1-53.7 7.4-80.7-3.8-21.3-19.5-29.6-32.9-30.2-15.8-.7-29.9 8.3-33.4 21.4-6.6 24-.7 56.8 10.1 98.6-13.6 32.4-35.3 79.5-51.2 107.5-29.6 15.3-69.3 38.9-75.2 68.7-1.2 5.5.2 12.5 3.5 18.8 3.7 7 9.6 12.4 16.5 15 3 1.1 6.6 2 10.8 2 17.6 0 46.1-14.2 84.1-79.4 5.8-1.9 11.8-3.9 17.6-5.9 27.2-9.2 55.4-18.8 80.9-23.1 28.2 15.1 60.3 24.8 82.1 24.8 21.6 0 30.1-12.8 33.3-20.5 5.6-13.5 2.9-30.5-6.2-39.6-13.2-13-45.3-16.4-95.3-10.2-24.6-15-40.7-35.4-52.4-65.8zM421.6 726.3c-13.9 20.2-24.4 30.3-30.1 34.7 6.7-12.3 19.8-25.3 30.1-34.7zm87.6-235.5c5.2 8.9 4.5 35.8.5 49.4-4.9-19.9-5.6-48.1-2.7-51.4.8.1 1.5.7 2.2 2zm-1.6 120.5c10.7 18.5 24.2 34.4 39.1 46.2-21.6 4.9-41.3 13-58.9 20.2-4.2 1.7-8.3 3.4-12.3 5 13.3-24.1 24.4-51.4 32.1-71.4zm155.6 65.5c.1.2.2.5-.4.9h-.2l-.2.3c-.8.5-9 5.3-44.3-8.6 40.6-1.9 45 7.3 45.1 7.4zm191.4-388.2L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494z\")),t.FileZipOutline=u(\"file-zip\",i,l(o,\"M296 392h64v64h-64zm0 190v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zm-16-302h64v64h-64zm-64-64h64v64h-64zm64 192h64v64h-64zm0-256h64v64h-64zm494.6 88.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h64v64h64v-64h174v216a42 42 0 0 0 42 42h216v494z\")),t.FileOutline=u(\"file\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494z\")),t.FilterOutline=u(\"filter\",i,l(o,\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z\")),t.FileWordOutline=u(\"file-word\",i,l(o,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM528.1 472h-32.2c-5.5 0-10.3 3.7-11.6 9.1L434.6 680l-46.1-198.7c-1.3-5.4-6.1-9.3-11.7-9.3h-35.4a12.02 12.02 0 0 0-11.6 15.1l74.2 276c1.4 5.2 6.2 8.9 11.6 8.9h32c5.4 0 10.2-3.6 11.6-8.9l52.8-197 52.8 197c1.4 5.2 6.2 8.9 11.6 8.9h31.8c5.4 0 10.2-3.6 11.6-8.9l74.4-276a12.04 12.04 0 0 0-11.6-15.1H647c-5.6 0-10.4 3.9-11.7 9.3l-45.8 199.1-49.8-199.3c-1.3-5.4-6.1-9.1-11.6-9.1z\")),t.FireOutline=u(\"fire\",i,l(o,\"M834.1 469.2A347.49 347.49 0 0 0 751.2 354l-29.1-26.7a8.09 8.09 0 0 0-13 3.3l-13 37.3c-8.1 23.4-23 47.3-44.1 70.8-1.4 1.5-3 1.9-4.1 2-1.1.1-2.8-.1-4.3-1.5-1.4-1.2-2.1-3-2-4.8 3.7-60.2-14.3-128.1-53.7-202C555.3 171 510 123.1 453.4 89.7l-41.3-24.3c-5.4-3.2-12.3 1-12 7.3l2.2 48c1.5 32.8-2.3 61.8-11.3 85.9-11 29.5-26.8 56.9-47 81.5a295.64 295.64 0 0 1-47.5 46.1 352.6 352.6 0 0 0-100.3 121.5A347.75 347.75 0 0 0 160 610c0 47.2 9.3 92.9 27.7 136a349.4 349.4 0 0 0 75.5 110.9c32.4 32 70 57.2 111.9 74.7C418.5 949.8 464.5 959 512 959s93.5-9.2 136.9-27.3A348.6 348.6 0 0 0 760.8 857c32.4-32 57.8-69.4 75.5-110.9a344.2 344.2 0 0 0 27.7-136c0-48.8-10-96.2-29.9-140.9zM713 808.5c-53.7 53.2-125 82.4-201 82.4s-147.3-29.2-201-82.4c-53.5-53.1-83-123.5-83-198.4 0-43.5 9.8-85.2 29.1-124 18.8-37.9 46.8-71.8 80.8-97.9a349.6 349.6 0 0 0 58.6-56.8c25-30.5 44.6-64.5 58.2-101a240 240 0 0 0 12.1-46.5c24.1 22.2 44.3 49 61.2 80.4 33.4 62.6 48.8 118.3 45.8 165.7a74.01 74.01 0 0 0 24.4 59.8 73.36 73.36 0 0 0 53.4 18.8c19.7-1 37.8-9.7 51-24.4 13.3-14.9 24.8-30.1 34.4-45.6 14 17.9 25.7 37.4 35 58.4 15.9 35.8 24 73.9 24 113.1 0 74.9-29.5 145.4-83 198.4z\")),t.FileUnknownOutline=u(\"file-unknown\",i,l(o,\"M854.6 288.7L639.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.6-9.4-22.6zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM402 549c0 5.4 4.4 9.5 9.8 9.5h32.4c5.4 0 9.8-4.2 9.8-9.4 0-28.2 25.8-51.6 58-51.6s58 23.4 58 51.5c0 25.3-21 47.2-49.3 50.9-19.3 2.8-34.5 20.3-34.7 40.1v32c0 5.5 4.5 10 10 10h32c5.5 0 10-4.5 10-10v-12.2c0-6 4-11.5 9.7-13.3 44.6-14.4 75-54 74.3-98.9-.8-55.5-49.2-100.8-108.5-101.6-61.4-.7-111.5 45.6-111.5 103zm78 195a32 32 0 1 0 64 0 32 32 0 1 0-64 0z\")),t.FlagOutline=u(\"flag\",i,l(o,\"M880 305H624V192c0-17.7-14.3-32-32-32H184v-40c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v784c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V640h248v113c0 17.7 14.3 32 32 32h416c17.7 0 32-14.3 32-32V337c0-17.7-14.3-32-32-32zM184 568V232h368v336H184zm656 145H504v-73h112c4.4 0 8-3.6 8-8V377h216v336z\")),t.FolderAddOutline=u(\"folder-add\",i,l(o,\"M484 443.1V528h-84.5c-4.1 0-7.5 3.1-7.5 7v42c0 3.8 3.4 7 7.5 7H484v84.9c0 3.9 3.2 7.1 7 7.1h42c3.9 0 7-3.2 7-7.1V584h84.5c4.1 0 7.5-3.2 7.5-7v-42c0-3.9-3.4-7-7.5-7H540v-84.9c0-3.9-3.1-7.1-7-7.1h-42c-3.8 0-7 3.2-7 7.1zm396-144.7H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z\")),t.FolderOutline=u(\"folder\",i,l(o,\"M880 298.4H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z\")),t.FolderOpenOutline=u(\"folder-open\",i,l(o,\"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 0 0-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z\")),t.ForwardOutline=u(\"forward\",i,l(r,\"M825.8 498L538.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L825.8 526c8.3-7.2 8.3-20.8 0-28zm-320 0L218.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L505.8 526c4.1-3.6 6.2-8.8 6.2-14 0-5.2-2.1-10.4-6.2-14z\")),t.FrownOutline=u(\"frown\",i,l(o,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 0 0 8 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 0 0 8-8.4C667.6 600.3 597.5 533 512 533z\")),t.FundOutline=u(\"fund\",i,l(o,\"M926 164H94c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V196c0-17.7-14.3-32-32-32zm-40 632H134V236h752v560zm-658.9-82.3c3.1 3.1 8.2 3.1 11.3 0l172.5-172.5 114.4 114.5c3.1 3.1 8.2 3.1 11.3 0l297-297.2c3.1-3.1 3.1-8.2 0-11.3l-36.8-36.8a8.03 8.03 0 0 0-11.3 0L531 565 416.6 450.5a8.03 8.03 0 0 0-11.3 0l-214.9 215a8.03 8.03 0 0 0 0 11.3l36.7 36.9z\")),t.FunnelPlotOutline=u(\"funnel-plot\",i,l(o,\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 607.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V607.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V650h182.9v148zm9.6-226.6l-8.4 14.6H419.3l-8.4-14.6L334.4 438h355.2L613 571.4zM726.3 374H297.7l-85-148h598.6l-85 148z\")),t.GiftOutline=u(\"gift\",i,l(o,\"M880 310H732.4c13.6-21.4 21.6-46.8 21.6-74 0-76.1-61.9-138-138-138-41.4 0-78.7 18.4-104 47.4-25.3-29-62.6-47.4-104-47.4-76.1 0-138 61.9-138 138 0 27.2 7.9 52.6 21.6 74H144c-17.7 0-32 14.3-32 32v200c0 4.4 3.6 8 8 8h40v344c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V550h40c4.4 0 8-3.6 8-8V342c0-17.7-14.3-32-32-32zm-334-74c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70h-70v-70zm-138-70c38.6 0 70 31.4 70 70v70h-70c-38.6 0-70-31.4-70-70s31.4-70 70-70zM180 482V378h298v104H180zm48 68h250v308H228V550zm568 308H546V550h250v308zm48-376H546V378h298v104z\")),t.GithubOutline=u(\"github\",i,l(o,\"M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0 1 38.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z\")),t.GitlabOutline=u(\"gitlab\",i,l(o,\"M913.9 552.2L805 181.4v-.1c-7.6-22.9-25.7-36.5-48.3-36.5-23.4 0-42.5 13.5-49.7 35.2l-71.4 213H388.8l-71.4-213c-7.2-21.7-26.3-35.2-49.7-35.2-23.1 0-42.5 14.8-48.4 36.6L110.5 552.2c-4.4 14.7 1.2 31.4 13.5 40.7l368.5 276.4c2.6 3.6 6.2 6.3 10.4 7.8l8.6 6.4 8.5-6.4c4.9-1.7 9-4.7 11.9-8.9l368.4-275.4c12.4-9.2 18-25.9 13.6-40.6zM751.7 193.4c1-1.8 2.9-1.9 3.5-1.9 1.1 0 2.5.3 3.4 3L818 394.3H684.5l67.2-200.9zm-487.4 1c.9-2.6 2.3-2.9 3.4-2.9 2.7 0 2.9.1 3.4 1.7l67.3 201.2H206.5l57.8-200zM158.8 558.7l28.2-97.3 202.4 270.2-230.6-172.9zm73.9-116.4h122.1l90.8 284.3-212.9-284.3zM512.9 776L405.7 442.3H620L512.9 776zm157.9-333.7h119.5L580 723.1l90.8-280.8zm-40.7 293.9l207.3-276.7 29.5 99.2-236.8 177.5z\")),t.HeartOutline=u(\"heart\",i,l(o,\"M923 283.6a260.04 260.04 0 0 0-56.9-82.8 264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9 0 33.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 101.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 20.3-103.3.1-35.3-7-69.6-20.9-101.9zM512 814.8S156 586.7 156 385.5C156 283.6 240.3 201 344.3 201c73.1 0 136.5 40.8 167.7 100.4C543.2 241.8 606.6 201 679.7 201c104 0 188.3 82.6 188.3 184.5 0 201.2-356 429.3-356 429.3z\")),t.HddOutline=u(\"hdd\",i,l(o,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208zM496 208H312c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 544h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H312c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm328 244a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.HighlightOutline=u(\"highlight\",i,l(o,\"M957.6 507.4L603.2 158.2a7.9 7.9 0 0 0-11.2 0L353.3 393.4a8.03 8.03 0 0 0-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 0 0-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0 0 11.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0 0 11.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z\")),t.HomeOutline=u(\"home\",i,l(o,\"M946.5 505L560.1 118.8l-25.9-25.9a31.5 31.5 0 0 0-44.4 0L77.5 505a63.9 63.9 0 0 0-18.8 46c.4 35.2 29.7 63.3 64.9 63.3h42.5V940h691.8V614.3h43.4c17.1 0 33.2-6.7 45.3-18.8a63.6 63.6 0 0 0 18.7-45.3c0-17-6.7-33.1-18.8-45.2zM568 868H456V664h112v204zm217.9-325.7V868H632V640c0-22.1-17.9-40-40-40H432c-22.1 0-40 17.9-40 40v228H238.1V542.3h-96l370-369.7 23.1 23.1L882 542.3h-96.1z\")),t.HourglassOutline=u(\"hourglass\",i,l(o,\"M742 318V184h86c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H196c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h86v134c0 81.5 42.4 153.2 106.4 194-64 40.8-106.4 112.5-106.4 194v134h-86c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h632c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-86V706c0-81.5-42.4-153.2-106.4-194 64-40.8 106.4-112.5 106.4-194zm-72 388v134H354V706c0-42.2 16.4-81.9 46.3-111.7C430.1 564.4 469.8 548 512 548s81.9 16.4 111.7 46.3C653.6 624.1 670 663.8 670 706zm0-388c0 42.2-16.4 81.9-46.3 111.7C593.9 459.6 554.2 476 512 476s-81.9-16.4-111.7-46.3A156.63 156.63 0 0 1 354 318V184h316v134z\")),t.Html5Outline=u(\"html5\",i,l(o,\"M145 96l66 746.6L511.8 928l299.6-85.4L878.7 96H145zm610.9 700.6l-244.1 69.6-245.2-69.6-56.7-641.2h603.8l-57.8 641.2zM281 249l1.7 24.3 22.7 253.5h206.5v-.1h112.9l-11.4 118.5L511 672.9v.2h-.8l-102.4-27.7-6.5-73.2h-91l11.3 144.7 188.6 52h1.7v-.4l187.7-51.7 1.7-16.3 21.2-242.2 3.2-24.3H511v.2H389.9l-8.2-94.2h352.1l1.7-19.5 4.8-47.2L742 249H511z\")),t.IdcardOutline=u(\"idcard\",i,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136V232h752v560zM610.3 476h123.4c1.3 0 2.3-3.6 2.3-8v-48c0-4.4-1-8-2.3-8H610.3c-1.3 0-2.3 3.6-2.3 8v48c0 4.4 1 8 2.3 8zm4.8 144h185.7c3.9 0 7.1-3.6 7.1-8v-48c0-4.4-3.2-8-7.1-8H615.1c-3.9 0-7.1 3.6-7.1 8v48c0 4.4 3.2 8 7.1 8zM224 673h43.9c4.2 0 7.6-3.3 7.9-7.5 3.8-50.5 46-90.5 97.2-90.5s93.4 40 97.2 90.5c.3 4.2 3.7 7.5 7.9 7.5H522a8 8 0 0 0 8-8.4c-2.8-53.3-32-99.7-74.6-126.1a111.8 111.8 0 0 0 29.1-75.5c0-61.9-49.9-112-111.4-112s-111.4 50.1-111.4 112c0 29.1 11 55.5 29.1 75.5a158.09 158.09 0 0 0-74.6 126.1c-.4 4.6 3.2 8.4 7.8 8.4zm149-262c28.5 0 51.7 23.3 51.7 52s-23.2 52-51.7 52-51.7-23.3-51.7-52 23.2-52 51.7-52z\")),t.InfoCircleOutline=u(\"info-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\")),t.InstagramOutline=u(\"instagram\",i,l(o,\"M512 306.9c-113.5 0-205.1 91.6-205.1 205.1S398.5 717.1 512 717.1 717.1 625.5 717.1 512 625.5 306.9 512 306.9zm0 338.4c-73.4 0-133.3-59.9-133.3-133.3S438.6 378.7 512 378.7 645.3 438.6 645.3 512 585.4 645.3 512 645.3zm213.5-394.6c-26.5 0-47.9 21.4-47.9 47.9s21.4 47.9 47.9 47.9 47.9-21.3 47.9-47.9a47.84 47.84 0 0 0-47.9-47.9zM911.8 512c0-55.2.5-109.9-2.6-165-3.1-64-17.7-120.8-64.5-167.6-46.9-46.9-103.6-61.4-167.6-64.5-55.2-3.1-109.9-2.6-165-2.6-55.2 0-109.9-.5-165 2.6-64 3.1-120.8 17.7-167.6 64.5C132.6 226.3 118.1 283 115 347c-3.1 55.2-2.6 109.9-2.6 165s-.5 109.9 2.6 165c3.1 64 17.7 120.8 64.5 167.6 46.9 46.9 103.6 61.4 167.6 64.5 55.2 3.1 109.9 2.6 165 2.6 55.2 0 109.9.5 165-2.6 64-3.1 120.8-17.7 167.6-64.5 46.9-46.9 61.4-103.6 64.5-167.6 3.2-55.1 2.6-109.8 2.6-165zm-88 235.8c-7.3 18.2-16.1 31.8-30.2 45.8-14.1 14.1-27.6 22.9-45.8 30.2C695.2 844.7 570.3 840 512 840c-58.3 0-183.3 4.7-235.9-16.1-18.2-7.3-31.8-16.1-45.8-30.2-14.1-14.1-22.9-27.6-30.2-45.8C179.3 695.2 184 570.3 184 512c0-58.3-4.7-183.3 16.1-235.9 7.3-18.2 16.1-31.8 30.2-45.8s27.6-22.9 45.8-30.2C328.7 179.3 453.7 184 512 184s183.3-4.7 235.9 16.1c18.2 7.3 31.8 16.1 45.8 30.2 14.1 14.1 22.9 27.6 30.2 45.8C844.7 328.7 840 453.7 840 512c0 58.3 4.7 183.2-16.2 235.8z\")),t.InsuranceOutline=u(\"insurance\",i,l(o,\"M441.6 306.8L403 288.6a6.1 6.1 0 0 0-8.4 3.7c-17.5 58.5-45.2 110.1-82.2 153.6a6.05 6.05 0 0 0-1.2 5.6l13.2 43.5c1.3 4.4 7 5.7 10.2 2.4 7.7-8.1 15.4-16.9 23.1-26V656c0 4.4 3.6 8 8 8H403c4.4 0 8-3.6 8-8V393.1a429.2 429.2 0 0 0 33.6-79c1-2.9-.3-6-3-7.3zm26.8 9.2v127.2c0 4.4 3.6 8 8 8h65.9v18.6h-94.9c-4.4 0-8 3.6-8 8v35.6c0 4.4 3.6 8 8 8h55.1c-19.1 30.8-42.4 55.7-71 76a6 6 0 0 0-1.6 8.1l22.8 36.5c1.9 3.1 6.2 3.8 8.9 1.4 31.6-26.8 58.7-62.9 80.6-107.6v120c0 4.4 3.6 8 8 8h36.2c4.4 0 8-3.6 8-8V536c21.3 41.7 47.5 77.5 78.1 106.9 2.6 2.5 6.8 2.1 8.9-.7l26.3-35.3c2-2.7 1.4-6.5-1.2-8.4-30.5-22.6-54.2-47.8-72.3-76.9h59c4.4 0 8-3.6 8-8V478c0-4.4-3.6-8-8-8h-98.8v-18.6h66.7c4.4 0 8-3.6 8-8V316c0-4.4-3.6-8-8-8H476.4c-4.4 0-8 3.6-8 8zm51.5 42.8h97.9v41.6h-97.9v-41.6zm347-188.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z\")),t.InteractionOutline=u(\"interaction\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656zM304.8 524h50.7c3.7 0 6.8-3 6.8-6.8v-78.9c0-19.7 15.9-35.6 35.5-35.6h205.7v53.4c0 5.7 6.5 8.8 10.9 5.3l109.1-85.7c3.5-2.7 3.5-8 0-10.7l-109.1-85.7c-4.4-3.5-10.9-.3-10.9 5.3V338H397.7c-55.1 0-99.7 44.8-99.7 100.1V517c0 4 3 7 6.8 7zm-4.2 134.9l109.1 85.7c4.4 3.5 10.9.3 10.9-5.3v-53.4h205.7c55.1 0 99.7-44.8 99.7-100.1v-78.9c0-3.7-3-6.8-6.8-6.8h-50.7c-3.7 0-6.8 3-6.8 6.8v78.9c0 19.7-15.9 35.6-35.5 35.6H420.6V568c0-5.7-6.5-8.8-10.9-5.3l-109.1 85.7c-3.5 2.5-3.5 7.8 0 10.5z\")),t.InterationOutline=u(\"interation\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656zM304.8 524h50.7c3.7 0 6.8-3 6.8-6.8v-78.9c0-19.7 15.9-35.6 35.5-35.6h205.7v53.4c0 5.7 6.5 8.8 10.9 5.3l109.1-85.7c3.5-2.7 3.5-8 0-10.7l-109.1-85.7c-4.4-3.5-10.9-.3-10.9 5.3V338H397.7c-55.1 0-99.7 44.8-99.7 100.1V517c0 4 3 7 6.8 7zm-4.2 134.9l109.1 85.7c4.4 3.5 10.9.3 10.9-5.3v-53.4h205.7c55.1 0 99.7-44.8 99.7-100.1v-78.9c0-3.7-3-6.8-6.8-6.8h-50.7c-3.7 0-6.8 3-6.8 6.8v78.9c0 19.7-15.9 35.6-35.5 35.6H420.6V568c0-5.7-6.5-8.8-10.9-5.3l-109.1 85.7c-3.5 2.5-3.5 7.8 0 10.5z\")),t.LayoutOutline=u(\"layout\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-696 72h136v656H184V184zm656 656H384V384h456v456zM384 320V184h456v136H384z\")),t.LeftCircleOutline=u(\"left-circle\",i,l(o,\"M603.3 327.5l-246 178a7.95 7.95 0 0 0 0 12.9l246 178c5.3 3.8 12.7 0 12.7-6.5V643c0-10.2-4.9-19.9-13.2-25.9L457.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.LeftSquareOutline=u(\"left-square\",i,l(o,\"M365.3 518.5l246 178c5.3 3.8 12.7 0 12.7-6.5v-46.9c0-10.2-4.9-19.9-13.2-25.9L465.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5l-246 178a8.05 8.05 0 0 0 0 13z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.LikeOutline=u(\"like\",i,l(o,\"M885.9 533.7c16.8-22.2 26.1-49.4 26.1-77.7 0-44.9-25.1-87.4-65.5-111.1a67.67 67.67 0 0 0-34.3-9.3H572.4l6-122.9c1.4-29.7-9.1-57.9-29.5-79.4A106.62 106.62 0 0 0 471 99.9c-52 0-98 35-111.8 85.1l-85.9 311H144c-17.7 0-32 14.3-32 32v364c0 17.7 14.3 32 32 32h601.3c9.2 0 18.2-1.8 26.5-5.4 47.6-20.3 78.3-66.8 78.3-118.4 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7-.2-12.6-2-25.1-5.6-37.1zM184 852V568h81v284h-81zm636.4-353l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.2 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.2 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 22.4-13.2 42.6-33.6 51.8H329V564.8l99.5-360.5a44.1 44.1 0 0 1 42.2-32.3c7.6 0 15.1 2.2 21.1 6.7 9.9 7.4 15.2 18.6 14.6 30.5l-9.6 198.4h314.4C829 418.5 840 436.9 840 456c0 16.5-7.2 32.1-19.6 43z\")),t.LinkedinOutline=u(\"linkedin\",i,l(o,\"M847.7 112H176.3c-35.5 0-64.3 28.8-64.3 64.3v671.4c0 35.5 28.8 64.3 64.3 64.3h671.4c35.5 0 64.3-28.8 64.3-64.3V176.3c0-35.5-28.8-64.3-64.3-64.3zm0 736c-447.8-.1-671.7-.2-671.7-.3.1-447.8.2-671.7.3-671.7 447.8.1 671.7.2 671.7.3-.1 447.8-.2 671.7-.3 671.7zM230.6 411.9h118.7v381.8H230.6zm59.4-52.2c37.9 0 68.8-30.8 68.8-68.8a68.8 68.8 0 1 0-137.6 0c-.1 38 30.7 68.8 68.8 68.8zm252.3 245.1c0-49.8 9.5-98 71.2-98 60.8 0 61.7 56.9 61.7 101.2v185.7h118.6V584.3c0-102.8-22.2-181.9-142.3-181.9-57.7 0-96.4 31.7-112.3 61.7h-1.6v-52.2H423.7v381.8h118.6V604.8z\")),t.LockOutline=u(\"lock\",i,l(o,\"M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240zm460 600H232V536h560v304zM484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53a48.01 48.01 0 1 0-56 0z\")),t.MedicineBoxOutline=u(\"medicine-box\",i,l(o,\"M839.2 278.1a32 32 0 0 0-30.4-22.1H736V144c0-17.7-14.3-32-32-32H320c-17.7 0-32 14.3-32 32v112h-72.8a31.9 31.9 0 0 0-30.4 22.1L112 502v378c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V502l-72.8-223.9zM360 184h304v72H360v-72zm480 656H184V513.4L244.3 328h535.4L840 513.4V840zM652 572H544V464c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v108H372c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h108v108c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V636h108c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\")),t.MehOutline=u(\"meh\",i,l(o,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM664 565H360c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\")),t.MailOutline=u(\"mail\",i,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 110.8V792H136V270.8l-27.6-21.5 39.3-50.5 42.8 33.3h643.1l42.8-33.3 39.3 50.5-27.7 21.5zM833.6 232L512 482 190.4 232l-42.8-33.3-39.3 50.5 27.6 21.5 341.6 265.6a55.99 55.99 0 0 0 68.7 0L888 270.8l27.6-21.5-39.3-50.5-42.7 33.2z\")),t.MessageOutline=u(\"message\",i,l(o,\"M464 512a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm200 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm-400 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm661.2-173.6c-22.6-53.7-55-101.9-96.3-143.3a444.35 444.35 0 0 0-143.3-96.3C630.6 75.7 572.2 64 512 64h-2c-60.6.3-119.3 12.3-174.5 35.9a445.35 445.35 0 0 0-142 96.5c-40.9 41.3-73 89.3-95.2 142.8-23 55.4-34.6 114.3-34.3 174.9A449.4 449.4 0 0 0 112 714v152a46 46 0 0 0 46 46h152.1A449.4 449.4 0 0 0 510 960h2.1c59.9 0 118-11.6 172.7-34.3a444.48 444.48 0 0 0 142.8-95.2c41.3-40.9 73.8-88.7 96.5-142 23.6-55.2 35.6-113.9 35.9-174.5.3-60.9-11.5-120-34.8-175.6zm-151.1 438C704 845.8 611 884 512 884h-1.7c-60.3-.3-120.2-15.3-173.1-43.5l-8.4-4.5H188V695.2l-4.5-8.4C155.3 633.9 140.3 574 140 513.7c-.4-99.7 37.7-193.3 107.6-263.8 69.8-70.5 163.1-109.5 262.8-109.9h1.7c50 0 98.5 9.7 144.2 28.9 44.6 18.7 84.6 45.6 119 80 34.3 34.3 61.3 74.4 80 119 19.4 46.2 29.1 95.2 28.9 145.8-.6 99.6-39.7 192.9-110.1 262.7z\")),t.MinusCircleOutline=u(\"minus-circle\",i,l(o,\"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.MinusSquareOutline=u(\"minus-square\",i,l(o,\"M328 544h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.MobileOutline=u(\"mobile\",i,l(o,\"M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64zm-8 824H288V134h448v752zM472 784a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.MoneyCollectOutline=u(\"money-collect\",i,l(o,\"M911.5 700.7a8 8 0 0 0-10.3-4.8L840 718.2V180c0-37.6-30.4-68-68-68H252c-37.6 0-68 30.4-68 68v538.2l-61.3-22.3c-.9-.3-1.8-.5-2.7-.5-4.4 0-8 3.6-8 8V763c0 3.3 2.1 6.3 5.3 7.5L501 910.1c7.1 2.6 14.8 2.6 21.9 0l383.8-139.5c3.2-1.2 5.3-4.2 5.3-7.5v-59.6c0-1-.2-1.9-.5-2.8zM512 837.5l-256-93.1V184h512v560.4l-256 93.1zM660.6 312h-54.5c-3 0-5.8 1.7-7.1 4.4l-84.7 168.8H511l-84.7-168.8a8 8 0 0 0-7.1-4.4h-55.7c-1.3 0-2.6.3-3.8 1-3.9 2.1-5.3 7-3.2 10.8l103.9 191.6h-57c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76v39h-76c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76V704c0 4.4 3.6 8 8 8h49.9c4.4 0 8-3.6 8-8v-63.5h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8h-76.3v-39h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8H564l103.7-191.6c.6-1.2 1-2.5 1-3.8-.1-4.3-3.7-7.9-8.1-7.9z\")),t.PauseCircleOutline=u(\"pause-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm-88-532h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8zm224 0h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8z\")),t.PayCircleOutline=u(\"pay-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm159.6-585h-59.5c-3 0-5.8 1.7-7.1 4.4l-90.6 180H511l-90.6-180a8 8 0 0 0-7.1-4.4h-60.7c-1.3 0-2.6.3-3.8 1-3.9 2.1-5.3 7-3.2 10.9L457 515.7h-61.4c-4.4 0-8 3.6-8 8v29.9c0 4.4 3.6 8 8 8h81.7V603h-81.7c-4.4 0-8 3.6-8 8v29.9c0 4.4 3.6 8 8 8h81.7V717c0 4.4 3.6 8 8 8h54.3c4.4 0 8-3.6 8-8v-68.1h82c4.4 0 8-3.6 8-8V611c0-4.4-3.6-8-8-8h-82v-41.5h82c4.4 0 8-3.6 8-8v-29.9c0-4.4-3.6-8-8-8h-62l111.1-204.8c.6-1.2 1-2.5 1-3.8-.1-4.4-3.7-8-8.1-8z\")),t.NotificationOutline=u(\"notification\",i,l(o,\"M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.7c-3.7 11.6-5.7 23.9-5.7 36.4 0 65.9 53.8 119.5 120 119.5 55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3 16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112zM344 762.3c-26.5 0-48-21.4-48-47.8 0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1zm496 58.4L318.8 611.3l-12.9-5.2H184V417.9h121.9l12.9-5.2L840 203.3v617.4z\")),t.PhoneOutline=u(\"phone\",i,l(o,\"M877.1 238.7L770.6 132.3c-13-13-30.4-20.3-48.8-20.3s-35.8 7.2-48.8 20.3L558.3 246.8c-13 13-20.3 30.5-20.3 48.9 0 18.5 7.2 35.8 20.3 48.9l89.6 89.7a405.46 405.46 0 0 1-86.4 127.3c-36.7 36.9-79.6 66-127.2 86.6l-89.6-89.7c-13-13-30.4-20.3-48.8-20.3a68.2 68.2 0 0 0-48.8 20.3L132.3 673c-13 13-20.3 30.5-20.3 48.9 0 18.5 7.2 35.8 20.3 48.9l106.4 106.4c22.2 22.2 52.8 34.9 84.2 34.9 6.5 0 12.8-.5 19.2-1.6 132.4-21.8 263.8-92.3 369.9-198.3C818 606 888.4 474.6 910.4 342.1c6.3-37.6-6.3-76.3-33.3-103.4zm-37.6 91.5c-19.5 117.9-82.9 235.5-178.4 331s-213 158.9-330.9 178.4c-14.8 2.5-30-2.5-40.8-13.2L184.9 721.9 295.7 611l119.8 120 .9.9 21.6-8a481.29 481.29 0 0 0 285.7-285.8l8-21.6-120.8-120.7 110.8-110.9 104.5 104.5c10.8 10.8 15.8 26 13.3 40.8z\")),t.PictureOutline=u(\"picture\",i,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136v-39.9l138.5-164.3 150.1 178L658.1 489 888 761.6V792zm0-129.8L664.2 396.8c-3.2-3.8-9-3.8-12.2 0L424.6 666.4l-144-170.7c-3.2-3.8-9-3.8-12.2 0L136 652.7V232h752v430.2zM304 456a88 88 0 1 0 0-176 88 88 0 0 0 0 176zm0-116c15.5 0 28 12.5 28 28s-12.5 28-28 28-28-12.5-28-28 12.5-28 28-28z\")),t.PieChartOutline=u(\"pie-chart\",i,l(o,\"M864 518H506V160c0-4.4-3.6-8-8-8h-26a398.46 398.46 0 0 0-282.8 117.1 398.19 398.19 0 0 0-85.7 127.1A397.61 397.61 0 0 0 72 552a398.46 398.46 0 0 0 117.1 282.8c36.7 36.7 79.5 65.6 127.1 85.7A397.61 397.61 0 0 0 472 952a398.46 398.46 0 0 0 282.8-117.1c36.7-36.7 65.6-79.5 85.7-127.1A397.61 397.61 0 0 0 872 552v-26c0-4.4-3.6-8-8-8zM705.7 787.8A331.59 331.59 0 0 1 470.4 884c-88.1-.4-170.9-34.9-233.2-97.2C174.5 724.1 140 640.7 140 552c0-88.7 34.5-172.1 97.2-234.8 54.6-54.6 124.9-87.9 200.8-95.5V586h364.3c-7.7 76.3-41.3 147-96.6 201.8zM952 462.4l-2.6-28.2c-8.5-92.1-49.4-179-115.2-244.6A399.4 399.4 0 0 0 589 74.6L560.7 72c-4.7-.4-8.7 3.2-8.7 7.9V464c0 4.4 3.6 8 8 8l384-1c4.7 0 8.4-4 8-8.6zm-332.2-58.2V147.6a332.24 332.24 0 0 1 166.4 89.8c45.7 45.6 77 103.6 90 166.1l-256.4.7z\")),t.PlaySquareOutline=u(\"play-square\",i,l(o,\"M442.3 677.6l199.4-156.7a11.3 11.3 0 0 0 0-17.7L442.3 346.4c-7.4-5.8-18.3-.6-18.3 8.8v313.5c0 9.4 10.9 14.7 18.3 8.9z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.PlayCircleOutline=u(\"play-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\"M719.4 499.1l-296.1-215A15.9 15.9 0 0 0 398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 0 0 0-25.8zm-257.6 134V390.9L628.5 512 461.8 633.1z\")),t.PlusCircleOutline=u(\"plus-circle\",i,l(o,\"M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.PrinterOutline=u(\"printer\",i,l(o,\"M820 436h-40c-4.4 0-8 3.6-8 8v40c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-40c0-4.4-3.6-8-8-8zm32-104H732V120c0-4.4-3.6-8-8-8H300c-4.4 0-8 3.6-8 8v212H172c-44.2 0-80 35.8-80 80v328c0 17.7 14.3 32 32 32h168v132c0 4.4 3.6 8 8 8h424c4.4 0 8-3.6 8-8V772h168c17.7 0 32-14.3 32-32V412c0-44.2-35.8-80-80-80zM360 180h304v152H360V180zm304 664H360V568h304v276zm200-140H732V500H292v204H160V412c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4 12 12v292z\")),t.PlusSquareOutline=u(\"plus-square\",i,l(o,\"M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.ProfileOutline=u(\"profile\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656zM492 400h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0 144h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0 144h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zM340 368a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0 144a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0 144a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.ProjectOutline=u(\"project\",i,l(o,\"M280 752h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8zm192-280h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8zm192 72h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v256c0 4.4 3.6 8 8 8zm216-432H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.PushpinOutline=u(\"pushpin\",i,l(o,\"M878.3 392.1L631.9 145.7c-6.5-6.5-15-9.7-23.5-9.7s-17 3.2-23.5 9.7L423.8 306.9c-12.2-1.4-24.5-2-36.8-2-73.2 0-146.4 24.1-206.5 72.3a33.23 33.23 0 0 0-2.7 49.4l181.7 181.7-215.4 215.2a15.8 15.8 0 0 0-4.6 9.8l-3.4 37.2c-.9 9.4 6.6 17.4 15.9 17.4.5 0 1 0 1.5-.1l37.2-3.4c3.7-.3 7.2-2 9.8-4.6l215.4-215.4 181.7 181.7c6.5 6.5 15 9.7 23.5 9.7 9.7 0 19.3-4.2 25.9-12.4 56.3-70.3 79.7-158.3 70.2-243.4l161.1-161.1c12.9-12.8 12.9-33.8 0-46.8zM666.2 549.3l-24.5 24.5 3.8 34.4a259.92 259.92 0 0 1-30.4 153.9L262 408.8c12.9-7.1 26.3-13.1 40.3-17.9 27.2-9.4 55.7-14.1 84.7-14.1 9.6 0 19.3.5 28.9 1.6l34.4 3.8 24.5-24.5L608.5 224 800 415.5 666.2 549.3z\")),t.PropertySafetyOutline=u(\"property-safety\",i,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6zM430.5 318h-46c-1.7 0-3.3.4-4.8 1.2a10.1 10.1 0 0 0-4 13.6l88 161.1h-45.2c-5.5 0-10 4.5-10 10v21.3c0 5.5 4.5 10 10 10h63.1v29.7h-63.1c-5.5 0-10 4.5-10 10v21.3c0 5.5 4.5 10 10 10h63.1V658c0 5.5 4.5 10 10 10h41.3c5.5 0 10-4.5 10-10v-51.8h63.4c5.5 0 10-4.5 10-10v-21.3c0-5.5-4.5-10-10-10h-63.4v-29.7h63.4c5.5 0 10-4.5 10-10v-21.3c0-5.5-4.5-10-10-10h-45.7l87.7-161.1a10.05 10.05 0 0 0-8.8-14.8h-45c-3.8 0-7.2 2.1-8.9 5.5l-73.2 144.3-72.9-144.3c-1.7-3.4-5.2-5.5-9-5.5z\")),t.QuestionCircleOutline=u(\"question-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\"M623.6 316.7C593.6 290.4 554 276 512 276s-81.6 14.5-111.6 40.7C369.2 344 352 380.7 352 420v7.6c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V420c0-44.1 43.1-80 96-80s96 35.9 96 80c0 31.1-22 59.6-56.1 72.7-21.2 8.1-39.2 22.3-52.1 40.9-13.1 19-19.9 41.8-19.9 64.9V620c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-22.7a48.3 48.3 0 0 1 30.9-44.8c59-22.7 97.1-74.7 97.1-132.5.1-39.3-17.1-76-48.3-103.3zM472 732a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.ReadOutline=u(\"read\",i,l(o,\"M928 161H699.2c-49.1 0-97.1 14.1-138.4 40.7L512 233l-48.8-31.3A255.2 255.2 0 0 0 324.8 161H96c-17.7 0-32 14.3-32 32v568c0 17.7 14.3 32 32 32h228.8c49.1 0 97.1 14.1 138.4 40.7l44.4 28.6c1.3.8 2.8 1.3 4.3 1.3s3-.4 4.3-1.3l44.4-28.6C602 807.1 650.1 793 699.2 793H928c17.7 0 32-14.3 32-32V193c0-17.7-14.3-32-32-32zM324.8 721H136V233h188.8c35.4 0 69.8 10.1 99.5 29.2l48.8 31.3 6.9 4.5v462c-47.6-25.6-100.8-39-155.2-39zm563.2 0H699.2c-54.4 0-107.6 13.4-155.2 39V298l6.9-4.5 48.8-31.3c29.7-19.1 64.1-29.2 99.5-29.2H888v488zM396.9 361H211.1c-3.9 0-7.1 3.4-7.1 7.5v45c0 4.1 3.2 7.5 7.1 7.5h185.7c3.9 0 7.1-3.4 7.1-7.5v-45c.1-4.1-3.1-7.5-7-7.5zm223.1 7.5v45c0 4.1 3.2 7.5 7.1 7.5h185.7c3.9 0 7.1-3.4 7.1-7.5v-45c0-4.1-3.2-7.5-7.1-7.5H627.1c-3.9 0-7.1 3.4-7.1 7.5zM396.9 501H211.1c-3.9 0-7.1 3.4-7.1 7.5v45c0 4.1 3.2 7.5 7.1 7.5h185.7c3.9 0 7.1-3.4 7.1-7.5v-45c.1-4.1-3.1-7.5-7-7.5zm416 0H627.1c-3.9 0-7.1 3.4-7.1 7.5v45c0 4.1 3.2 7.5 7.1 7.5h185.7c3.9 0 7.1-3.4 7.1-7.5v-45c.1-4.1-3.1-7.5-7-7.5z\")),t.ReconciliationOutline=u(\"reconciliation\",i,l(o,\"M676 565c-50.8 0-92 41.2-92 92s41.2 92 92 92 92-41.2 92-92-41.2-92-92-92zm0 126c-18.8 0-34-15.2-34-34s15.2-34 34-34 34 15.2 34 34-15.2 34-34 34zm204-523H668c0-30.9-25.1-56-56-56h-80c-30.9 0-56 25.1-56 56H264c-17.7 0-32 14.3-32 32v200h-88c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h336c17.7 0 32-14.3 32-32v-16h368c17.7 0 32-14.3 32-32V200c0-17.7-14.3-32-32-32zm-412 64h72v-56h64v56h72v48H468v-48zm-20 616H176V616h272v232zm0-296H176v-88h272v88zm392 240H512V432c0-17.7-14.3-32-32-32H304V240h100v104h336V240h100v552zM704 408v96c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-96c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zM592 512h48c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8z\")),t.RedEnvelopeOutline=u(\"red-envelope\",i,l(o,\"M440.6 462.6a8.38 8.38 0 0 0-7.5-4.6h-48.8c-1.3 0-2.6.4-3.9 1a8.4 8.4 0 0 0-3.4 11.4l87.4 161.1H419c-4.6 0-8.4 3.8-8.4 8.4V665c0 4.6 3.8 8.4 8.4 8.4h63V702h-63c-4.6 0-8.4 3.8-8.4 8.4v25.1c0 4.6 3.8 8.4 8.4 8.4h63v49.9c0 4.6 3.8 8.4 8.4 8.4h43.7c4.6 0 8.4-3.8 8.4-8.4v-49.9h63.3c4.7 0 8.4-3.8 8.2-8.5v-25c0-4.6-3.8-8.4-8.4-8.4h-63.3v-28.6h63.3c4.6 0 8.4-3.8 8.4-8.4v-25.1c0-4.6-3.8-8.4-8.4-8.4h-45.9l87.2-161a8.45 8.45 0 0 0-7.4-12.4h-47.8c-3.1 0-6 1.8-7.5 4.6l-71.9 141.9-71.7-142zM832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V193.1l260.3 204.1c11.6 9.1 27.9 9.1 39.5 0L792 193.1V888zm0-751.3h-31.7L512 331.3 263.7 136.7H232v-.7h560v.7z\")),t.RestOutline=u(\"rest\",i,l(o,\"M508 704c79.5 0 144-64.5 144-144s-64.5-144-144-144-144 64.5-144 144 64.5 144 144 144zm0-224c44.2 0 80 35.8 80 80s-35.8 80-80 80-80-35.8-80-80 35.8-80 80-80z\",\"M832 256h-28.1l-35.7-120.9c-4-13.7-16.5-23.1-30.7-23.1h-451c-14.3 0-26.8 9.4-30.7 23.1L220.1 256H192c-17.7 0-32 14.3-32 32v28c0 4.4 3.6 8 8 8h45.8l47.7 558.7a32 32 0 0 0 31.9 29.3h429.2a32 32 0 0 0 31.9-29.3L802.2 324H856c4.4 0 8-3.6 8-8v-28c0-17.7-14.3-32-32-32zm-518.6-76h397.2l22.4 76H291l22.4-76zm376.2 664H326.4L282 324h451.9l-44.3 520z\")),t.RightCircleOutline=u(\"right-circle\",i,l(o,\"M666.7 505.5l-246-178A8 8 0 0 0 408 334v46.9c0 10.2 4.9 19.9 13.2 25.9L566.6 512 421.2 617.2c-8.3 6-13.2 15.6-13.2 25.9V690c0 6.5 7.4 10.3 12.7 6.5l246-178c4.4-3.2 4.4-9.8 0-13z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.RocketOutline=u(\"rocket\",i,l(o,\"M864 736c0-111.6-65.4-208-160-252.9V317.3c0-15.1-5.3-29.7-15.1-41.2L536.5 95.4C530.1 87.8 521 84 512 84s-18.1 3.8-24.5 11.4L335.1 276.1a63.97 63.97 0 0 0-15.1 41.2v165.8C225.4 528 160 624.4 160 736h156.5c-2.3 7.2-3.5 15-3.5 23.8 0 22.1 7.6 43.7 21.4 60.8a97.2 97.2 0 0 0 43.1 30.6c23.1 54 75.6 88.8 134.5 88.8 29.1 0 57.3-8.6 81.4-24.8 23.6-15.8 41.9-37.9 53-64a97 97 0 0 0 43.1-30.5 97.52 97.52 0 0 0 21.4-60.8c0-8.4-1.1-16.4-3.1-23.8H864zM762.3 621.4c9.4 14.6 17 30.3 22.5 46.6H700V558.7a211.6 211.6 0 0 1 62.3 62.7zM388 483.1V318.8l124-147 124 147V668H388V483.1zM239.2 668c5.5-16.3 13.1-32 22.5-46.6 16.3-25.2 37.5-46.5 62.3-62.7V668h-84.8zm388.9 116.2c-5.2 3-11.2 4.2-17.1 3.4l-19.5-2.4-2.8 19.4c-5.4 37.9-38.4 66.5-76.7 66.5-38.3 0-71.3-28.6-76.7-66.5l-2.8-19.5-19.5 2.5a27.7 27.7 0 0 1-17.1-3.5c-8.7-5-14.1-14.3-14.1-24.4 0-10.6 5.9-19.4 14.6-23.8h231.3c8.8 4.5 14.6 13.3 14.6 23.8-.1 10.2-5.5 19.6-14.2 24.5zM464 400a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\")),t.RightSquareOutline=u(\"right-square\",i,l(o,\"M412.7 696.5l246-178c4.4-3.2 4.4-9.7 0-12.9l-246-178c-5.3-3.8-12.7 0-12.7 6.5V381c0 10.2 4.9 19.9 13.2 25.9L558.6 512 413.2 617.2c-8.3 6-13.2 15.6-13.2 25.9V690c0 6.5 7.4 10.3 12.7 6.5z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.SafetyCertificateOutline=u(\"safety-certificate\",i,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6zm-405.8-201c-3-4.1-7.8-6.6-13-6.6H336c-6.5 0-10.3 7.4-6.5 12.7l126.4 174a16.1 16.1 0 0 0 26 0l212.6-292.7c3.8-5.3 0-12.7-6.5-12.7h-55.2c-5.1 0-10 2.5-13 6.6L468.9 542.4l-64.7-89.1z\")),t.ScheduleOutline=u(\"schedule\",i,l(o,\"M928 224H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zm-40 568H136V296h120v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h120v496zM416 496H232c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm0 136H232c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm308.2-177.4L620.6 598.3l-52.8-73.1c-3-4.2-7.8-6.6-12.9-6.6H500c-6.5 0-10.3 7.4-6.5 12.7l114.1 158.2a15.9 15.9 0 0 0 25.8 0l165-228.7c3.8-5.3 0-12.7-6.5-12.7H737c-5-.1-9.8 2.4-12.8 6.5z\")),t.SaveOutline=u(\"save\",i,l(o,\"M893.3 293.3L730.7 130.7c-7.5-7.5-16.7-13-26.7-16V112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 184h256v104H384V184zm456 656H184V184h136v136c0 17.7 14.3 32 32 32h320c17.7 0 32-14.3 32-32V205.8l136 136V840zM512 442c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm0 224c-44.2 0-80-35.8-80-80s35.8-80 80-80 80 35.8 80 80-35.8 80-80 80z\")),t.SecurityScanOutline=u(\"security-scan\",i,l(o,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6zM402.9 528.8l-77.5 77.5a8.03 8.03 0 0 0 0 11.3l34 34c3.1 3.1 8.2 3.1 11.3 0l77.5-77.5c55.7 35.1 130.1 28.4 178.6-20.1 56.3-56.3 56.3-147.5 0-203.8-56.3-56.3-147.5-56.3-203.8 0-48.5 48.5-55.2 123-20.1 178.6zm65.4-133.3c31.3-31.3 82-31.3 113.2 0 31.3 31.3 31.3 82 0 113.2-31.3 31.3-82 31.3-113.2 0s-31.3-81.9 0-113.2z\")),t.SettingOutline=u(\"setting\",i,l(o,\"M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 0 0 9.3-35.2l-.9-2.6a443.74 443.74 0 0 0-79.7-137.9l-1.8-2.1a32.12 32.12 0 0 0-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 0 0-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 0 0-25.8 25.7l-15.8 85.4a351.86 351.86 0 0 0-99 57.4l-81.9-29.1a32 32 0 0 0-35.1 9.5l-1.8 2.1a446.02 446.02 0 0 0-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 0 0-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0 0 35.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0 0 25.8 25.7l2.7.5a449.4 449.4 0 0 0 159 0l2.7-.5a32.05 32.05 0 0 0 25.8-25.7l15.7-85a350 350 0 0 0 99.7-57.6l81.3 28.9a32 32 0 0 0 35.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 0 1-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 0 1-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 0 1 512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 0 1 400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 0 1 624 502c0 29.9-11.7 58-32.8 79.2z\")),t.ShoppingOutline=u(\"shopping\",i,l(o,\"M832 312H696v-16c0-101.6-82.4-184-184-184s-184 82.4-184 184v16H192c-17.7 0-32 14.3-32 32v536c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V344c0-17.7-14.3-32-32-32zm-432-16c0-61.9 50.1-112 112-112s112 50.1 112 112v16H400v-16zm392 544H232V384h96v88c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-88h224v88c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-88h96v456z\")),t.SkinOutline=u(\"skin\",i,l(o,\"M870 126H663.8c-17.4 0-32.9 11.9-37 29.3C614.3 208.1 567 246 512 246s-102.3-37.9-114.8-90.7a37.93 37.93 0 0 0-37-29.3H154a44 44 0 0 0-44 44v252a44 44 0 0 0 44 44h75v388a44 44 0 0 0 44 44h478a44 44 0 0 0 44-44V466h75a44 44 0 0 0 44-44V170a44 44 0 0 0-44-44zm-28 268H723v432H301V394H182V198h153.3c28.2 71.2 97.5 120 176.7 120s148.5-48.8 176.7-120H842v196z\")),t.SkypeOutline=u(\"skype\",i,l(o,\"M883.7 578.6c4.1-22.5 6.3-45.5 6.3-68.5 0-51-10-100.5-29.7-147-19-45-46.3-85.4-81-120.1a375.79 375.79 0 0 0-120.1-80.9c-46.6-19.7-96-29.7-147-29.7-24 0-48.1 2.3-71.5 6.8A225.1 225.1 0 0 0 335.6 113c-59.7 0-115.9 23.3-158.1 65.5A222.25 222.25 0 0 0 112 336.6c0 38 9.8 75.4 28.1 108.4-3.7 21.4-5.7 43.3-5.7 65.1 0 51 10 100.5 29.7 147 19 45 46.2 85.4 80.9 120.1 34.7 34.7 75.1 61.9 120.1 80.9 46.6 19.7 96 29.7 147 29.7 22.2 0 44.4-2 66.2-5.9 33.5 18.9 71.3 29 110 29 59.7 0 115.9-23.2 158.1-65.5 42.3-42.2 65.5-98.4 65.5-158.1.1-38-9.7-75.5-28.2-108.7zm-88.1 216C766.9 823.4 729 839 688.4 839c-26.1 0-51.8-6.8-74.6-19.7l-22.5-12.7-25.5 4.5c-17.8 3.2-35.8 4.8-53.6 4.8-41.4 0-81.3-8.1-119.1-24.1-36.3-15.3-69-37.3-97.2-65.5a304.29 304.29 0 0 1-65.5-97.1c-16-37.7-24-77.6-24-119 0-17.4 1.6-35.2 4.6-52.8l4.4-25.1L203 410a151.02 151.02 0 0 1-19.1-73.4c0-40.6 15.7-78.5 44.4-107.2C257.1 200.7 295 185 335.6 185a153 153 0 0 1 71.4 17.9l22.4 11.8 24.8-4.8c18.9-3.6 38.4-5.5 58-5.5 41.4 0 81.3 8.1 119 24 36.5 15.4 69.1 37.4 97.2 65.5 28.2 28.1 50.2 60.8 65.6 97.2 16 37.7 24 77.6 24 119 0 18.4-1.7 37-5.1 55.5l-4.7 25.5 12.6 22.6c12.6 22.5 19.2 48 19.2 73.7 0 40.7-15.7 78.5-44.4 107.2zM583.4 466.2L495 446.6c-33.6-7.7-72.3-17.8-72.3-49.5s27.1-53.9 76.1-53.9c98.7 0 89.7 67.8 138.7 67.8 25.8 0 48.4-15.2 48.4-41.2 0-60.8-97.4-106.5-180-106.5-89.7 0-185.2 38.1-185.2 139.5 0 48.8 17.4 100.8 113.6 124.9l119.4 29.8c36.1 8.9 45.2 29.2 45.2 47.6 0 30.5-30.3 60.3-85.2 60.3-107.2 0-92.3-82.5-149.7-82.5-25.8 0-44.5 17.8-44.5 43.1 0 49.4 60 115.4 194.2 115.4 127.7 0 191-61.5 191-144 0-53.1-24.5-109.6-121.3-131.2z\")),t.SlackSquareOutline=u(\"slack-square\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM529 311.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V311.4zM361.5 580.2c0 27.8-22.5 50.4-50.3 50.4a50.35 50.35 0 0 1-50.3-50.4c0-27.8 22.5-50.4 50.3-50.4h50.3v50.4zm134 134.4c0 27.8-22.5 50.4-50.3 50.4-27.8 0-50.3-22.6-50.3-50.4V580.2c0-27.8 22.5-50.4 50.3-50.4a50.35 50.35 0 0 1 50.3 50.4v134.4zm-50.2-218.4h-134c-27.8 0-50.3-22.6-50.3-50.4 0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4-.1 27.9-22.6 50.4-50.3 50.4zm0-134.4c-13.3 0-26.1-5.3-35.6-14.8S395 324.8 395 311.4c0-27.8 22.5-50.4 50.3-50.4 27.8 0 50.3 22.6 50.3 50.4v50.4h-50.3zm134 403.2c-27.8 0-50.3-22.6-50.3-50.4v-50.4h50.3c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm134-134.4h-134a50.35 50.35 0 0 1-50.3-50.4c0-27.8 22.5-50.4 50.3-50.4h134c27.8 0 50.3 22.6 50.3 50.4 0 27.8-22.5 50.4-50.3 50.4zm0-134.4H663v-50.4c0-27.8 22.5-50.4 50.3-50.4s50.3 22.6 50.3 50.4c0 27.8-22.5 50.4-50.3 50.4z\")),t.SlidersOutline=u(\"sliders\",i,l(o,\"M320 224h-66v-56c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v56h-66c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h66v56c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-56h66c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zm-60 508h-80V292h80v440zm644-436h-66v-96c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v96h-66c-4.4 0-8 3.6-8 8v416c0 4.4 3.6 8 8 8h66v96c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-96h66c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8zm-60 364h-80V364h80v296zM612 404h-66V232c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v172h-66c-4.4 0-8 3.6-8 8v200c0 4.4 3.6 8 8 8h66v172c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V620h66c4.4 0 8-3.6 8-8V412c0-4.4-3.6-8-8-8zm-60 145a3 3 0 0 1-3 3h-74a3 3 0 0 1-3-3v-74a3 3 0 0 1 3-3h74a3 3 0 0 1 3 3v74z\")),t.SmileOutline=u(\"smile\",i,l(o,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 0 0-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 0 0-8-8.4z\")),t.SnippetsOutline=u(\"snippets\",i,l(o,\"M832 112H724V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H500V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H320c-17.7 0-32 14.3-32 32v120h-96c-17.7 0-32 14.3-32 32v632c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32v-96h96c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM664 888H232V336h218v174c0 22.1 17.9 40 40 40h174v338zm0-402H514V336h.2L664 485.8v.2zm128 274h-56V456L544 264H360v-80h68v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h152v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h68v576z\")),t.SoundOutline=u(\"sound\",i,l(o,\"M625.9 115c-5.9 0-11.9 1.6-17.4 5.3L254 352H90c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h164l354.5 231.7c5.5 3.6 11.6 5.3 17.4 5.3 16.7 0 32.1-13.3 32.1-32.1V147.1c0-18.8-15.4-32.1-32.1-32.1zM586 803L293.4 611.7l-18-11.7H146V424h129.4l17.9-11.7L586 221v582zm348-327H806c-8.8 0-16 7.2-16 16v40c0 8.8 7.2 16 16 16h128c8.8 0 16-7.2 16-16v-40c0-8.8-7.2-16-16-16zm-41.9 261.8l-110.3-63.7a15.9 15.9 0 0 0-21.7 5.9l-19.9 34.5c-4.4 7.6-1.8 17.4 5.8 21.8L856.3 800a15.9 15.9 0 0 0 21.7-5.9l19.9-34.5c4.4-7.6 1.7-17.4-5.8-21.8zM760 344a15.9 15.9 0 0 0 21.7 5.9L892 286.2c7.6-4.4 10.2-14.2 5.8-21.8L878 230a15.9 15.9 0 0 0-21.7-5.9L746 287.8a15.99 15.99 0 0 0-5.8 21.8L760 344z\")),t.StarOutline=u(\"star\",i,l(o,\"M908.1 353.1l-253.9-36.9L540.7 86.1c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L369.8 316.2l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 0 0 .6 45.3l183.7 179.1-43.4 252.9a31.95 31.95 0 0 0 46.4 33.7L512 754l227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3zM664.8 561.6l36.1 210.3L512 672.7 323.1 772l36.1-210.3-152.8-149L417.6 382 512 190.7 606.4 382l211.2 30.7-152.8 148.9z\")),t.StepBackwardOutline=u(\"step-backward\",i,l(r,\"M347.6 528.95l383.2 301.02c14.25 11.2 35.2 1.1 35.2-16.95V210.97c0-18.05-20.95-28.14-35.2-16.94L347.6 495.05a21.53 21.53 0 0 0 0 33.9M330 864h-64a8 8 0 0 1-8-8V168a8 8 0 0 1 8-8h64a8 8 0 0 1 8 8v688a8 8 0 0 1-8 8\")),t.StepForwardOutline=u(\"step-forward\",i,l(r,\"M676.4 528.95L293.2 829.97c-14.25 11.2-35.2 1.1-35.2-16.95V210.97c0-18.05 20.95-28.14 35.2-16.94l383.2 301.02a21.53 21.53 0 0 1 0 33.9M694 864h64a8 8 0 0 0 8-8V168a8 8 0 0 0-8-8h-64a8 8 0 0 0-8 8v688a8 8 0 0 0 8 8\")),t.StopOutline=u(\"stop\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884zm288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z\")),t.SwitcherOutline=u(\"switcher\",i,l(o,\"M752 240H144c-17.7 0-32 14.3-32 32v608c0 17.7 14.3 32 32 32h608c17.7 0 32-14.3 32-32V272c0-17.7-14.3-32-32-32zm-40 600H184V312h528v528zm168-728H264c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h576v576c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V144c0-17.7-14.3-32-32-32zM300 550h296v64H300z\")),t.TagOutline=u(\"tag\",i,l(o,\"M938 458.8l-29.6-312.6c-1.5-16.2-14.4-29-30.6-30.6L565.2 86h-.4c-3.2 0-5.7 1-7.6 2.9L88.9 557.2a9.96 9.96 0 0 0 0 14.1l363.8 363.8c1.9 1.9 4.4 2.9 7.1 2.9s5.2-1 7.1-2.9l468.3-468.3c2-2.1 3-5 2.8-8zM459.7 834.7L189.3 564.3 589 164.6 836 188l23.4 247-399.7 399.7zM680 256c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88zm0 120c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z\")),t.TabletOutline=u(\"tablet\",i,l(o,\"M800 64H224c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h576c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64zm-8 824H232V136h560v752zM472 784a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.ShopOutline=u(\"shop\",i,l(o,\"M882 272.1V144c0-17.7-14.3-32-32-32H174c-17.7 0-32 14.3-32 32v128.1c-16.7 1-30 14.9-30 31.9v131.7a177 177 0 0 0 14.4 70.4c4.3 10.2 9.6 19.8 15.6 28.9v345c0 17.6 14.3 32 32 32h676c17.7 0 32-14.3 32-32V535a175 175 0 0 0 15.6-28.9c9.5-22.3 14.4-46 14.4-70.4V304c0-17-13.3-30.9-30-31.9zM214 184h596v88H214v-88zm362 656.1H448V736h128v104.1zm234 0H640V704c0-17.7-14.3-32-32-32H416c-17.7 0-32 14.3-32 32v136.1H214V597.9c2.9 1.4 5.9 2.8 9 4 22.3 9.4 46 14.1 70.4 14.1s48-4.7 70.4-14.1c13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1 24.4 0 48-4.7 70.4-14.1 13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1 24.4 0 48-4.7 70.4-14.1 3-1.3 6-2.6 9-4v242.2zm30-404.4c0 59.8-49 108.3-109.3 108.3-40.8 0-76.4-22.1-95.2-54.9-2.9-5-8.1-8.1-13.9-8.1h-.6c-5.7 0-11 3.1-13.9 8.1A109.24 109.24 0 0 1 512 544c-40.7 0-76.2-22-95-54.7-3-5.1-8.4-8.3-14.3-8.3s-11.4 3.2-14.3 8.3a109.63 109.63 0 0 1-95.1 54.7C233 544 184 495.5 184 435.7v-91.2c0-.3.2-.5.5-.5h655c.3 0 .5.2.5.5v91.2z\")),t.TagsOutline=u(\"tags\",i,l(o,\"M483.2 790.3L861.4 412c1.7-1.7 2.5-4 2.3-6.3l-25.5-301.4c-.7-7.8-6.8-13.9-14.6-14.6L522.2 64.3c-2.3-.2-4.7.6-6.3 2.3L137.7 444.8a8.03 8.03 0 0 0 0 11.3l334.2 334.2c3.1 3.2 8.2 3.2 11.3 0zm62.6-651.7l224.6 19 19 224.6L477.5 694 233.9 450.5l311.9-311.9zm60.16 186.23a48 48 0 1 0 67.88-67.89 48 48 0 1 0-67.88 67.89zM889.7 539.8l-39.6-39.5a8.03 8.03 0 0 0-11.3 0l-362 361.3-237.6-237a8.03 8.03 0 0 0-11.3 0l-39.6 39.5a8.03 8.03 0 0 0 0 11.3l243.2 242.8 39.6 39.5c3.1 3.1 8.2 3.1 11.3 0l407.3-406.6c3.1-3.1 3.1-8.2 0-11.3z\")),t.TaobaoCircleOutline=u(\"taobao-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zM315.7 291.5c27.3 0 49.5 22.1 49.5 49.4s-22.1 49.4-49.5 49.4a49.4 49.4 0 1 1 0-98.8zM366.9 578c-13.6 42.3-10.2 26.7-64.4 144.5l-78.5-49s87.7-79.8 105.6-116.2c19.2-38.4-21.1-58.9-21.1-58.9l-60.2-37.5 32.7-50.2c45.4 33.7 48.7 36.6 79.2 67.2 23.8 23.9 20.7 56.8 6.7 100.1zm427.2 55c-15.3 143.8-202.4 90.3-202.4 90.3l10.2-41.1 43.3 9.3c80 5 72.3-64.9 72.3-64.9V423c.6-77.3-72.6-85.4-204.2-38.3l30.6 8.3c-2.5 9-12.5 23.2-25.2 38.6h176v35.6h-99.1v44.5h98.7v35.7h-98.7V622c14.9-4.8 28.6-11.5 40.5-20.5l-8.7-32.5 46.5-14.4 38.8 94.9-57.3 23.9-10.2-37.8c-25.6 19.5-78.8 48-171.8 45.4-99.2 2.6-73.7-112-73.7-112l2.5-1.3H472c-.5 14.7-6.6 38.7 1.7 51.8 6.8 10.8 24.2 12.6 35.3 13.1 1.3.1 2.6.1 3.9.1v-85.3h-101v-35.7h101v-44.5H487c-22.7 24.1-43.5 44.1-43.5 44.1l-30.6-26.7c21.7-22.9 43.3-59.1 56.8-83.2-10.9 4.4-22 9.2-33.6 14.2-11.2 14.3-24.2 29-38.7 43.5.5.8-50-28.4-50-28.4 52.2-44.4 81.4-139.9 81.4-139.9l72.5 20.4s-5.9 14-18.4 35.6c290.3-82.3 307.4 50.5 307.4 50.5s19.1 91.8 3.8 235.7z\")),t.ToolOutline=u(\"tool\",i,l(o,\"M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 0 1 144-53.5L537 318.9a32.05 32.05 0 0 0 0 45.3l124.5 124.5a32.05 32.05 0 0 0 45.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z\")),t.ThunderboltOutline=u(\"thunderbolt\",i,l(o,\"M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z\")),t.TrophyOutline=u(\"trophy\",i,l(o,\"M868 160h-92v-40c0-4.4-3.6-8-8-8H256c-4.4 0-8 3.6-8 8v40h-92a44 44 0 0 0-44 44v148c0 81.7 60 149.6 138.2 162C265.7 630.2 359 721.7 476 734.5v105.2H280c-17.7 0-32 14.3-32 32V904c0 4.4 3.6 8 8 8h512c4.4 0 8-3.6 8-8v-32.3c0-17.7-14.3-32-32-32H548V734.5C665 721.7 758.3 630.2 773.8 514 852 501.6 912 433.7 912 352V204a44 44 0 0 0-44-44zM184 352V232h64v207.6a91.99 91.99 0 0 1-64-87.6zm520 128c0 49.1-19.1 95.4-53.9 130.1-34.8 34.8-81 53.9-130.1 53.9h-16c-49.1 0-95.4-19.1-130.1-53.9-34.8-34.8-53.9-81-53.9-130.1V184h384v296zm136-128c0 41-26.9 75.8-64 87.6V232h64v120z\")),t.UnlockOutline=u(\"unlock\",i,l(o,\"M832 464H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v68c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-68c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zm-40 376H232V536h560v304zM484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53a48.01 48.01 0 1 0-56 0z\")),t.UpCircleOutline=u(\"up-circle\",i,l(o,\"M518.5 360.3a7.95 7.95 0 0 0-12.9 0l-178 246c-3.8 5.3 0 12.7 6.5 12.7H381c10.2 0 19.9-4.9 25.9-13.2L512 460.4l105.2 145.4c6 8.3 15.6 13.2 25.9 13.2H690c6.5 0 10.3-7.4 6.5-12.7l-178-246z\",\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\")),t.UpSquareOutline=u(\"up-square\",i,l(o,\"M334 624h46.9c10.2 0 19.9-4.9 25.9-13.2L512 465.4l105.2 145.4c6 8.3 15.6 13.2 25.9 13.2H690c6.5 0 10.3-7.4 6.5-12.7l-178-246a7.95 7.95 0 0 0-12.9 0l-178 246A7.96 7.96 0 0 0 334 624z\",\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.UsbOutline=u(\"usb\",i,l(o,\"M760 432V144c0-17.7-14.3-32-32-32H296c-17.7 0-32 14.3-32 32v288c-66.2 0-120 52.1-120 116v356c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V548c0-24.3 21.6-44 48.1-44h495.8c26.5 0 48.1 19.7 48.1 44v356c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V548c0-63.9-53.8-116-120-116zm-424 0V184h352v248H336zm120-184h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm160 0h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\")),t.VideoCameraOutline=u(\"video-camera\",i,l(o,\"M912 302.3L784 376V224c0-35.3-28.7-64-64-64H128c-35.3 0-64 28.7-64 64v576c0 35.3 28.7 64 64 64h592c35.3 0 64-28.7 64-64V648l128 73.7c21.3 12.3 48-3.1 48-27.6V330c0-24.6-26.7-40-48-27.7zM712 792H136V232h576v560zm176-167l-104-59.8V458.9L888 399v226zM208 360h112c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H208c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\")),t.WalletOutline=u(\"wallet\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 464H528V448h312v128zm0 264H184V184h656v200H496c-17.7 0-32 14.3-32 32v192c0 17.7 14.3 32 32 32h344v200zM580 512a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.WarningOutline=u(\"warning\",i,l(o,\"M464 720a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z\")),t.WechatOutline=u(\"wechat\",i,l(o,\"M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6a21.5 21.5 0 0 1 9.1 17.6c0 2.4-.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-.1 17.8-.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8zm-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1zm-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1zm586.8 415.6c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7a9 9 0 0 0 6.4-2.6 9 9 0 0 0 2.6-6.4c0-2.2-.9-4.4-1.4-6.6-.3-1.2-7.6-28.3-12.2-45.3-.5-1.9-.9-3.8-.9-5.7.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9zm179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9a36.08 36.08 0 0 1-36 35.9z\")),t.WeiboCircleOutline=u(\"weibo-circle\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-44.4 672C353.1 736 236 680.4 236 588.9c0-47.8 30.2-103.1 82.3-155.3 69.5-69.6 150.6-101.4 181.1-70.8 13.5 13.5 14.8 36.8 6.1 64.6-4.5 14 13.1 6.3 13.1 6.3 56.2-23.6 105.2-25 123.1.7 9.6 13.7 8.6 32.8-.2 55.1-4.1 10.2 1.3 11.8 9 14.1 31.7 9.8 66.9 33.6 66.9 75.5.2 69.5-99.7 156.9-249.8 156.9zm207.3-290.8a34.9 34.9 0 0 0-7.2-34.1 34.68 34.68 0 0 0-33.1-10.7 18.24 18.24 0 0 1-7.6-35.7c24.1-5.1 50.1 2.3 67.7 21.9 17.7 19.6 22.4 46.3 14.9 69.8a18.13 18.13 0 0 1-22.9 11.7 18.18 18.18 0 0 1-11.8-22.9zm106 34.3s0 .1 0 0a21.1 21.1 0 0 1-26.6 13.7 21.19 21.19 0 0 1-13.6-26.7c11-34.2 4-73.2-21.7-101.8a104.04 104.04 0 0 0-98.9-32.1 21.14 21.14 0 0 1-25.1-16.3 21.07 21.07 0 0 1 16.2-25.1c49.4-10.5 102.8 4.8 139.1 45.1 36.3 40.2 46.1 95.1 30.6 143.2zm-334.5 6.1c-91.4 9-160.7 65.1-154.7 125.2 5.9 60.1 84.8 101.5 176.2 92.5 91.4-9.1 160.7-65.1 154.7-125.3-5.9-60.1-84.8-101.5-176.2-92.4zm80.2 141.7c-18.7 42.3-72.3 64.8-117.8 50.1-43.9-14.2-62.5-57.7-43.3-96.8 18.9-38.4 68-60.1 111.5-48.8 45 11.7 68 54.2 49.6 95.5zm-93-32.2c-14.2-5.9-32.4.2-41.2 13.9-8.8 13.8-4.7 30.2 9.3 36.6 14.3 6.5 33.2.3 42-13.8 8.8-14.3 4.2-30.6-10.1-36.7zm34.9-14.5c-5.4-2.2-12.2.5-15.4 5.8-3.1 5.4-1.4 11.5 4.1 13.8 5.5 2.3 12.6-.3 15.8-5.8 3-5.6 1-11.8-4.5-13.8z\")),t.WindowsOutline=u(\"windows\",i,l(o,\"M120.1 770.6L443 823.2V543.8H120.1v226.8zm63.4-163.5h196.2v141.6l-196.2-31.9V607.1zm340.3 226.5l382 62.2v-352h-382v289.8zm63.4-226.5h255.3v214.4l-255.3-41.6V607.1zm-63.4-415.7v288.8h382V128.1l-382 63.3zm318.7 225.5H587.3V245l255.3-42.3v214.2zm-722.4 63.3H443V201.9l-322.9 53.5v224.8zM183.5 309l196.2-32.5v140.4H183.5V309z\")),t.YahooOutline=u(\"yahoo\",i,l(o,\"M859.9 681.4h-14.1c-27.1 0-49.2 22.2-49.2 49.3v14.1c0 27.1 22.2 49.3 49.2 49.3h14.1c27.1 0 49.2-22.2 49.2-49.3v-14.1c0-27.1-22.2-49.3-49.2-49.3zM402.6 231C216.2 231 65 357 65 512.5S216.2 794 402.6 794s337.6-126 337.6-281.5S589.1 231 402.6 231zm0 507C245.1 738 121 634.6 121 512.5c0-62.3 32.3-119.7 84.9-161v48.4h37l159.8 159.9v65.3h-84.4v56.3h225.1v-56.3H459v-65.3l103.5-103.6h65.3v-56.3H459v65.3l-28.1 28.1-93.4-93.5h37v-56.3H216.4c49.4-35 114.3-56.6 186.2-56.6 157.6 0 281.6 103.4 281.6 225.5S560.2 738 402.6 738zm534.7-507H824.7c-15.5 0-27.7 12.6-27.1 28.1l13.1 366h84.4l65.4-366.4c2.7-15.2-7.8-27.7-23.2-27.7z\")),t.WeiboSquareOutline=u(\"weibo-square\",i,l(o,\"M433.6 595.1c-14.2-5.9-32.4.2-41.2 13.9-8.8 13.8-4.7 30.2 9.3 36.6 14.3 6.5 33.2.3 42-13.8 8.8-14.3 4.2-30.6-10.1-36.7zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM467.6 736C353.1 736 236 680.4 236 588.9c0-47.8 30.2-103.1 82.3-155.3 69.5-69.6 150.6-101.4 181.1-70.8 13.5 13.5 14.8 36.8 6.1 64.6-4.5 14 13.1 6.3 13.1 6.3 56.2-23.6 105.2-25 123.1.7 9.6 13.7 8.6 32.8-.2 55.1-4.1 10.2 1.3 11.8 9 14.1 31.7 9.8 66.9 33.6 66.9 75.5.2 69.5-99.7 156.9-249.8 156.9zm207.3-290.8a34.9 34.9 0 0 0-7.2-34.1 34.68 34.68 0 0 0-33.1-10.7 18.24 18.24 0 0 1-7.6-35.7c24.1-5.1 50.1 2.3 67.7 21.9 17.7 19.6 22.4 46.3 14.9 69.8a18.13 18.13 0 0 1-22.9 11.7 18.18 18.18 0 0 1-11.8-22.9zm106 34.3s0 .1 0 0a21.1 21.1 0 0 1-26.6 13.7 21.19 21.19 0 0 1-13.6-26.7c11-34.2 4-73.2-21.7-101.8a104.04 104.04 0 0 0-98.9-32.1 21.14 21.14 0 0 1-25.1-16.3 21.07 21.07 0 0 1 16.2-25.1c49.4-10.5 102.8 4.8 139.1 45.1 36.3 40.2 46.1 95.1 30.6 143.2zm-334.5 6.1c-91.4 9-160.7 65.1-154.7 125.2 5.9 60.1 84.8 101.5 176.2 92.5 91.4-9.1 160.7-65.1 154.7-125.3-5.9-60.1-84.8-101.5-176.2-92.4zm80.2 141.7c-18.7 42.3-72.3 64.8-117.8 50.1-43.9-14.2-62.5-57.7-43.3-96.8 18.9-38.4 68-60.1 111.5-48.8 45 11.7 68 54.2 49.6 95.5zm-58.1-46.7c-5.4-2.2-12.2.5-15.4 5.8-3.1 5.4-1.4 11.5 4.1 13.8 5.5 2.3 12.6-.3 15.8-5.8 3-5.6 1-11.8-4.5-13.8z\")),t.YuqueOutline=u(\"yuque\",i,l(o,\"M854.6 370.6c-9.9-39.4 9.9-102.2 73.4-124.4l-67.9-3.6s-25.7-90-143.6-98c-117.8-8.1-194.9-3-195-3 .1 0 87.4 55.6 52.4 154.7-25.6 52.5-65.8 95.6-108.8 144.7-1.3 1.3-2.5 2.6-3.5 3.7C319.4 605 96 860 96 860c245.9 64.4 410.7-6.3 508.2-91.1 20.5-.2 35.9-.3 46.3-.3 135.8 0 250.6-117.6 245.9-248.4-3.2-89.9-31.9-110.2-41.8-149.6zm-204.1 334c-10.6 0-26.2.1-46.8.3l-23.6.2-17.8 15.5c-47.1 41-104.4 71.5-171.4 87.6-52.5 12.6-110 16.2-172.7 9.6 18-20.5 36.5-41.6 55.4-63.1 92-104.6 173.8-197.5 236.9-268.5l1.4-1.4 1.3-1.5c4.1-4.6 20.6-23.3 24.7-28.1 9.7-11.1 17.3-19.9 24.5-28.6 30.7-36.7 52.2-67.8 69-102.2l1.6-3.3 1.2-3.4c13.7-38.8 15.4-76.9 6.2-112.8 22.5.7 46.5 1.9 71.7 3.6 33.3 2.3 55.5 12.9 71.1 29.2 5.8 6 10.2 12.5 13.4 18.7 1 2 1.7 3.6 2.3 5l5 17.7c-15.7 34.5-19.9 73.3-11.4 107.2 3 11.8 6.9 22.4 12.3 34.4 2.1 4.7 9.5 20.1 11 23.3 10.3 22.7 15.4 43 16.7 78.7 3.3 94.6-82.7 181.9-182 181.9z\")),t.YoutubeOutline=u(\"youtube\",i,l(o,\"M960 509.2c0-2.2 0-4.7-.1-7.6-.1-8.1-.3-17.2-.5-26.9-.8-27.9-2.2-55.7-4.4-81.9-3-36.1-7.4-66.2-13.4-88.8a139.52 139.52 0 0 0-98.3-98.5c-28.3-7.6-83.7-12.3-161.7-15.2-37.1-1.4-76.8-2.3-116.5-2.8-13.9-.2-26.8-.3-38.4-.4h-29.4c-11.6.1-24.5.2-38.4.4-39.7.5-79.4 1.4-116.5 2.8-78 3-133.5 7.7-161.7 15.2A139.35 139.35 0 0 0 82.4 304C76.3 326.6 72 356.7 69 392.8c-2.2 26.2-3.6 54-4.4 81.9-.3 9.7-.4 18.8-.5 26.9 0 2.9-.1 5.4-.1 7.6v5.6c0 2.2 0 4.7.1 7.6.1 8.1.3 17.2.5 26.9.8 27.9 2.2 55.7 4.4 81.9 3 36.1 7.4 66.2 13.4 88.8 12.8 47.9 50.4 85.7 98.3 98.5 28.2 7.6 83.7 12.3 161.7 15.2 37.1 1.4 76.8 2.3 116.5 2.8 13.9.2 26.8.3 38.4.4h29.4c11.6-.1 24.5-.2 38.4-.4 39.7-.5 79.4-1.4 116.5-2.8 78-3 133.5-7.7 161.7-15.2 47.9-12.8 85.5-50.5 98.3-98.5 6.1-22.6 10.4-52.7 13.4-88.8 2.2-26.2 3.6-54 4.4-81.9.3-9.7.4-18.8.5-26.9 0-2.9.1-5.4.1-7.6v-5.6zm-72 5.2c0 2.1 0 4.4-.1 7.1-.1 7.8-.3 16.4-.5 25.7-.7 26.6-2.1 53.2-4.2 77.9-2.7 32.2-6.5 58.6-11.2 76.3-6.2 23.1-24.4 41.4-47.4 47.5-21 5.6-73.9 10.1-145.8 12.8-36.4 1.4-75.6 2.3-114.7 2.8-13.7.2-26.4.3-37.8.3h-28.6l-37.8-.3c-39.1-.5-78.2-1.4-114.7-2.8-71.9-2.8-124.9-7.2-145.8-12.8-23-6.2-41.2-24.4-47.4-47.5-4.7-17.7-8.5-44.1-11.2-76.3-2.1-24.7-3.4-51.3-4.2-77.9-.3-9.3-.4-18-.5-25.7 0-2.7-.1-5.1-.1-7.1v-4.8c0-2.1 0-4.4.1-7.1.1-7.8.3-16.4.5-25.7.7-26.6 2.1-53.2 4.2-77.9 2.7-32.2 6.5-58.6 11.2-76.3 6.2-23.1 24.4-41.4 47.4-47.5 21-5.6 73.9-10.1 145.8-12.8 36.4-1.4 75.6-2.3 114.7-2.8 13.7-.2 26.4-.3 37.8-.3h28.6l37.8.3c39.1.5 78.2 1.4 114.7 2.8 71.9 2.8 124.9 7.2 145.8 12.8 23 6.2 41.2 24.4 47.4 47.5 4.7 17.7 8.5 44.1 11.2 76.3 2.1 24.7 3.4 51.3 4.2 77.9.3 9.3.4 18 .5 25.7 0 2.7.1 5.1.1 7.1v4.8zM423 646l232-135-232-133z\")),t.AlibabaOutline=u(\"alibaba\",i,l(o,\"M602.9 669.8c-37.2 2.6-33.6-17.3-11.5-46.2 50.4-67.2 143.7-158.5 147.9-225.2 5.8-86.6-81.3-113.4-171-113.4-62.4 1.6-127 18.9-171 34.6-151.6 53.5-246.6 137.5-306.9 232-62.4 93.4-43 183.2 91.8 185.8 101.8-4.2 170.5-32.5 239.7-68.2.5 0-192.5 55.1-263.9 14.7-7.9-4.2-15.7-10-17.8-26.2 0-33.1 54.6-67.7 86.6-78.7v-56.7c64.5 22.6 140.6 16.3 205.7-32 2.1 5.8 4.2 13.1 3.7 21h11c2.6-22.6-12.6-44.6-37.8-46.2 7.3 5.8 12.6 10.5 15.2 14.7l-1 1-.5.5c-83.9 58.8-165.3 31.5-173.1 29.9l46.7-45.7-13.1-33.1c92.9-32.5 169.5-56.2 296.9-78.7l-28.5-23 14.7-8.9c75.5 21 126.4 36.7 123.8 76.6-1 6.8-3.7 14.7-7.9 23.1C660.1 466.1 594 538 567.2 569c-17.3 20.5-34.6 39.4-46.7 58.3-13.6 19.4-20.5 37.3-21 53.5 2.6 131.8 391.4-61.9 468-112.9-111.7 47.8-232.9 93.5-364.6 101.9zm85-302.9c2.8 5.2 4.1 11.6 4.1 19.1-.1-6.8-1.4-13.3-4.1-19.1z\")),t.AlignCenterOutline=u(\"align-center\",i,l(o,\"M264 230h496c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H264c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm496 424c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H264c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496zm144 140H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-424H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.AlignLeftOutline=u(\"align-left\",i,l(o,\"M120 230h496c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0 424h496c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm784 140H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-424H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.AlignRightOutline=u(\"align-right\",i,l(o,\"M904 158H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 424H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 212H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-424H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.AlipayOutline=u(\"alipay\",i,l(o,\"M789 610.3c-38.7-12.9-90.7-32.7-148.5-53.6 34.8-60.3 62.5-129 80.7-203.6H530.5v-68.6h233.6v-38.3H530.5V132h-95.4c-16.7 0-16.7 16.5-16.7 16.5v97.8H182.2v38.3h236.3v68.6H223.4v38.3h378.4a667.18 667.18 0 0 1-54.5 132.9c-122.8-40.4-253.8-73.2-336.1-53-52.6 13-86.5 36.1-106.5 60.3-91.4 111-25.9 279.6 167.2 279.6C386 811.2 496 747.6 581.2 643 708.3 704 960 808.7 960 808.7V659.4s-31.6-2.5-171-49.1zM253.9 746.6c-150.5 0-195-118.3-120.6-183.1 24.8-21.9 70.2-32.6 94.4-35 89.4-8.8 172.2 25.2 269.9 72.8-68.8 89.5-156.3 145.3-243.7 145.3z\")),t.AliyunOutline=u(\"aliyun\",i,l(o,\"M959.2 383.9c-.3-82.1-66.9-148.6-149.1-148.6H575.9l21.6 85.2 201 43.7a42.58 42.58 0 0 1 32.9 39.7c.1.5.1 216.1 0 216.6a42.58 42.58 0 0 1-32.9 39.7l-201 43.7-21.6 85.3h234.2c82.1 0 148.8-66.5 149.1-148.6V383.9zM225.5 660.4a42.58 42.58 0 0 1-32.9-39.7c-.1-.6-.1-216.1 0-216.6.8-19.4 14.6-35.5 32.9-39.7l201-43.7 21.6-85.2H213.8c-82.1 0-148.8 66.4-149.1 148.6V641c.3 82.1 67 148.6 149.1 148.6H448l-21.6-85.3-200.9-43.9zm200.9-158.8h171v21.3h-171z\")),t.AmazonOutline=u(\"amazon\",i,l(o,\"M825 768.9c-3.3-.9-7.3-.4-11.9 1.3-61.6 28.2-121.5 48.3-179.7 60.2C507.7 856 385.2 842.6 266 790.3c-33.1-14.6-79.1-39.2-138-74a9.36 9.36 0 0 0-5.3-2c-2-.1-3.7.1-5.3.9-1.6.8-2.8 1.8-3.7 3.1-.9 1.3-1.1 3.1-.4 5.4.6 2.2 2.1 4.7 4.6 7.4 10.4 12.2 23.3 25.2 38.6 39s35.6 29.4 60.9 46.8c25.3 17.4 51.8 32.9 79.3 46.4 27.6 13.5 59.6 24.9 96.1 34.1s73 13.8 109.4 13.8c36.2 0 71.4-3.7 105.5-10.9 34.2-7.3 63-15.9 86.5-25.9 23.4-9.9 45-21 64.8-33 19.8-12 34.4-22.2 43.9-30.3 9.5-8.2 16.3-14.6 20.2-19.4 4.6-5.7 6.9-10.6 6.9-14.9.1-4.5-1.7-7.1-5-7.9zM527.4 348.1c-15.2 1.3-33.5 4.1-55 8.3-21.5 4.1-41.4 9.3-59.8 15.4s-37.2 14.6-56.3 25.4c-19.2 10.8-35.5 23.2-49 37s-24.5 31.1-33.1 52c-8.6 20.8-12.9 43.7-12.9 68.7 0 27.1 4.7 51.2 14.3 72.5 9.5 21.3 22.2 38 38.2 50.4 15.9 12.4 34 22.1 54 29.2 20 7.1 41.2 10.3 63.2 9.4 22-.9 43.5-4.3 64.4-10.3 20.8-5.9 40.4-15.4 58.6-28.3 18.2-12.9 33.1-28.2 44.8-45.7 4.3 6.6 8.1 11.5 11.5 14.7l8.7 8.9c5.8 5.9 14.7 14.6 26.7 26.1 11.9 11.5 24.1 22.7 36.3 33.7l104.4-99.9-6-4.9c-4.3-3.3-9.4-8-15.2-14.3-5.8-6.2-11.6-13.1-17.2-20.5-5.7-7.4-10.6-16.1-14.7-25.9-4.1-9.8-6.2-19.3-6.2-28.5V258.7c0-10.1-1.9-21-5.7-32.8-3.9-11.7-10.7-24.5-20.7-38.3-10-13.8-22.4-26.2-37.2-37-14.9-10.8-34.7-20-59.6-27.4-24.8-7.4-52.6-11.1-83.2-11.1-31.3 0-60.4 3.7-87.6 10.9-27.1 7.3-50.3 17-69.7 29.2-19.3 12.2-35.9 26.3-49.7 42.4-13.8 16.1-24.1 32.9-30.8 50.4-6.7 17.5-10.1 35.2-10.1 53.1L408 310c5.5-16.4 12.9-30.6 22-42.8 9.2-12.2 17.9-21 25.8-26.5 8-5.5 16.6-9.9 25.7-13.2 9.2-3.3 15.4-5 18.6-5.4 3.2-.3 5.7-.4 7.6-.4 26.7 0 45.2 7.9 55.6 23.6 6.5 9.5 9.7 23.9 9.7 43.3v56.6c-15.2.6-30.4 1.6-45.6 2.9zM573.1 500c0 16.6-2.2 31.7-6.5 45-9.2 29.1-26.7 47.4-52.4 54.8-22.4 6.6-43.7 3.3-63.9-9.8-21.5-14-32.2-33.8-32.2-59.3 0-19.9 5-36.9 15-51.1 10-14.1 23.3-24.7 40-31.7s33-12 49-14.9c15.9-3 33-4.8 51-5.4V500zm335.2 218.9c-4.3-5.4-15.9-8.9-34.9-10.7-19-1.8-35.5-1.7-49.7.4-15.3 1.8-31.1 6.2-47.3 13.4-16.3 7.1-23.4 13.1-21.6 17.8l.7 1.3.9.7 1.4.2h4.6c.8 0 1.8-.1 3.2-.2 1.4-.1 2.7-.3 3.9-.4 1.2-.1 2.9-.3 5.1-.4 2.1-.1 4.1-.4 6-.7.3 0 3.7-.3 10.3-.9 6.6-.6 11.4-1 14.3-1.3 2.9-.3 7.8-.6 14.5-.9 6.7-.3 12.1-.3 16.1 0 4 .3 8.5.7 13.6 1.1 5.1.4 9.2 1.3 12.4 2.7 3.2 1.3 5.6 3 7.1 5.1 5.2 6.6 4.2 21.2-3 43.9s-14 40.8-20.4 54.2c-2.8 5.7-2.8 9.2 0 10.7s6.7.1 11.9-4c15.6-12.2 28.6-30.6 39.1-55.3 6.1-14.6 10.5-29.8 13.1-45.7 2.4-15.9 2-26.2-1.3-31z\")),t.AntCloudOutline=u(\"ant-cloud\",i,l(o,\"M378.9 738c-3.1 0-6.1-.5-8.8-1.5l4.4 30.7h26.3l-15.5-29.9c-2.1.5-4.2.7-6.4.7zm421-291.2c-12.6 0-24.8 1.5-36.5 4.2-21.4-38.4-62.3-64.3-109.3-64.3-6.9 0-13.6.6-20.2 1.6-35.4-77.4-113.4-131.1-203.9-131.1-112.3 0-205.3 82.6-221.6 190.4C127.3 455.5 64 523.8 64 607c0 88.4 71.6 160.1 160 160.2h50l13.2-27.6c-26.2-8.3-43.3-29-39.1-48.8 4.6-21.6 32.8-33.9 63.1-27.5 22.9 4.9 40.4 19.1 45.5 35.1a26.1 26.1 0 0 1 22.1-12.4h.2c-.8-3.2-1.2-6.5-1.2-9.9 0-20.1 14.8-36.7 34.1-39.6v-25.4c0-4.4 3.6-8 8-8s8 3.6 8 8v26.3c4.6 1.2 8.8 3.2 12.6 5.8l19.5-21.4c3-3.3 8-3.5 11.3-.5 3.3 3 3.5 8 .5 11.3l-20 22-.2.2a40 40 0 0 1-46.9 59.2c-.4 5.6-2.6 10.7-6 14.8l20 38.4H804v-.1c86.5-2.2 156-73 156-160.1 0-88.5-71.7-160.2-160.1-160.2zM338.2 737.2l-4.3 30h24.4l-5.9-41.5c-3.5 4.6-8.3 8.5-14.2 11.5zM797.5 305a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm-65.7 61.3a24 24 0 1 0 48 0 24 24 0 1 0-48 0zM303.4 742.9l-11.6 24.3h26l3.5-24.7c-5.7.8-11.7 1-17.9.4z\")),t.ApartmentOutline=u(\"apartment\",i,l(o,\"M908 640H804V488c0-4.4-3.6-8-8-8H548v-96h108c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16H368c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h108v96H228c-4.4 0-8 3.6-8 8v152H116c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16H292v-88h440v88H620c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16zm-564 76v168H176V716h168zm84-408V140h168v168H428zm420 576H680V716h168v168z\")),t.AntDesignOutline=u(\"ant-design\",i,l(o,\"M716.3 313.8c19-18.9 19-49.7 0-68.6l-69.9-69.9.1.1c-18.5-18.5-50.3-50.3-95.3-95.2-21.2-20.7-55.5-20.5-76.5.5L80.9 474.2a53.84 53.84 0 0 0 0 76.4L474.6 944a54.14 54.14 0 0 0 76.5 0l165.1-165c19-18.9 19-49.7 0-68.6a48.7 48.7 0 0 0-68.7 0l-125 125.2c-5.2 5.2-13.3 5.2-18.5 0L189.5 521.4c-5.2-5.2-5.2-13.3 0-18.5l314.4-314.2c.4-.4.9-.7 1.3-1.1 5.2-4.1 12.4-3.7 17.2 1.1l125.2 125.1c19 19 49.8 19 68.7 0zM408.6 514.4a106.3 106.2 0 1 0 212.6 0 106.3 106.2 0 1 0-212.6 0zm536.2-38.6L821.9 353.5c-19-18.9-49.8-18.9-68.7.1a48.4 48.4 0 0 0 0 68.6l83 82.9c5.2 5.2 5.2 13.3 0 18.5l-81.8 81.7a48.4 48.4 0 0 0 0 68.6 48.7 48.7 0 0 0 68.7 0l121.8-121.7a53.93 53.93 0 0 0-.1-76.4z\")),t.AreaChartOutline=u(\"area-chart\",i,l(o,\"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-616-64h536c4.4 0 8-3.6 8-8V284c0-7.2-8.7-10.7-13.7-5.7L592 488.6l-125.4-124a8.03 8.03 0 0 0-11.3 0l-189 189.6a7.87 7.87 0 0 0-2.3 5.6V720c0 4.4 3.6 8 8 8z\")),t.ArrowLeftOutline=u(\"arrow-left\",i,l(o,\"M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z\")),t.ArrowDownOutline=u(\"arrow-down\",i,l(o,\"M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0 0 48.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z\")),t.ArrowUpOutline=u(\"arrow-up\",i,l(o,\"M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z\")),t.ArrowsAltOutline=u(\"arrows-alt\",i,l(o,\"M855 160.1l-189.2 23.5c-6.6.8-9.3 8.8-4.7 13.5l54.7 54.7-153.5 153.5a8.03 8.03 0 0 0 0 11.3l45.1 45.1c3.1 3.1 8.2 3.1 11.3 0l153.6-153.6 54.7 54.7a7.94 7.94 0 0 0 13.5-4.7L863.9 169a7.9 7.9 0 0 0-8.9-8.9zM416.6 562.3a8.03 8.03 0 0 0-11.3 0L251.8 715.9l-54.7-54.7a7.94 7.94 0 0 0-13.5 4.7L160.1 855c-.6 5.2 3.7 9.5 8.9 8.9l189.2-23.5c6.6-.8 9.3-8.8 4.7-13.5l-54.7-54.7 153.6-153.6c3.1-3.1 3.1-8.2 0-11.3l-45.2-45z\")),t.ArrowRightOutline=u(\"arrow-right\",i,l(o,\"M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91.5c1.9 0 3.8-.7 5.2-2L869 536.2a32.07 32.07 0 0 0 0-48.4z\")),t.AuditOutline=u(\"audit\",i,l(o,\"M296 250c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm184 144H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-48 458H208V148h560v320c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm440-88H728v-36.6c46.3-13.8 80-56.6 80-107.4 0-61.9-50.1-112-112-112s-112 50.1-112 112c0 50.7 33.7 93.6 80 107.4V764H520c-8.8 0-16 7.2-16 16v152c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16V780c0-8.8-7.2-16-16-16zM646 620c0-27.6 22.4-50 50-50s50 22.4 50 50-22.4 50-50 50-50-22.4-50-50zm180 266H566v-60h260v60z\")),t.BarChartOutline=u(\"bar-chart\",i,l(o,\"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-600-80h56c4.4 0 8-3.6 8-8V560c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V384c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v320c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V462c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v242c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v400c0 4.4 3.6 8 8 8z\")),t.BarcodeOutline=u(\"barcode\",i,l(o,\"M120 160H72c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8zm833 0h-48c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8zM200 736h112c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8H200c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8zm321 0h48c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8zm126 0h178c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8H647c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8zm-255 0h48c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8zm-79 64H201c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h112c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm257 0h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm256 0H648c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h178c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-385 0h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\")),t.BarsOutline=u(\"bars\",i,l(r,\"M912 192H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM104 228a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.BgColorsOutline=u(\"bg-colors\",i,l(o,\"M766.4 744.3c43.7 0 79.4-36.2 79.4-80.5 0-53.5-79.4-140.8-79.4-140.8S687 610.3 687 663.8c0 44.3 35.7 80.5 79.4 80.5zm-377.1-44.1c7.1 7.1 18.6 7.1 25.6 0l256.1-256c7.1-7.1 7.1-18.6 0-25.6l-256-256c-.6-.6-1.3-1.2-2-1.7l-78.2-78.2a9.11 9.11 0 0 0-12.8 0l-48 48a9.11 9.11 0 0 0 0 12.8l67.2 67.2-207.8 207.9c-7.1 7.1-7.1 18.6 0 25.6l255.9 256zm12.9-448.6l178.9 178.9H223.4l178.8-178.9zM904 816H120c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8z\")),t.BehanceOutline=u(\"behance\",i,l(o,\"M634 294.3h199.5v48.4H634zM434.1 485.8c44.1-21.1 67.2-53.2 67.2-102.8 0-98.1-73-121.9-157.3-121.9H112v492.4h238.5c89.4 0 173.3-43 173.3-143 0-61.8-29.2-107.5-89.7-124.7zM220.2 345.1h101.5c39.1 0 74.2 10.9 74.2 56.3 0 41.8-27.3 58.6-66 58.6H220.2V345.1zm115.5 324.8H220.1V534.3H338c47.6 0 77.7 19.9 77.7 70.3 0 49.6-35.9 65.3-80 65.3zm575.8-89.5c0-105.5-61.7-193.4-173.3-193.4-108.5 0-182.3 81.7-182.3 188.8 0 111 69.9 187.2 182.3 187.2 85.1 0 140.2-38.3 166.7-120h-86.3c-9.4 30.5-47.6 46.5-77.3 46.5-57.4 0-87.4-33.6-87.4-90.7h256.9c.3-5.9.7-12.1.7-18.4zM653.9 537c3.1-46.9 34.4-76.2 81.2-76.2 49.2 0 73.8 28.9 78.1 76.2H653.9z\")),t.BlockOutline=u(\"block\",i,l(o,\"M856 376H648V168c0-8.8-7.2-16-16-16H168c-8.8 0-16 7.2-16 16v464c0 8.8 7.2 16 16 16h208v208c0 8.8 7.2 16 16 16h464c8.8 0 16-7.2 16-16V392c0-8.8-7.2-16-16-16zm-480 16v188H220V220h360v156H392c-8.8 0-16 7.2-16 16zm204 52v136H444V444h136zm224 360H444V648h188c8.8 0 16-7.2 16-16V444h156v360z\")),t.BoldOutline=u(\"bold\",i,l(o,\"M697.8 481.4c33.6-35 54.2-82.3 54.2-134.3v-10.2C752 229.3 663.9 142 555.3 142H259.4c-15.1 0-27.4 12.3-27.4 27.4v679.1c0 16.3 13.2 29.5 29.5 29.5h318.7c117 0 211.8-94.2 211.8-210.5v-11c0-73-37.4-137.3-94.2-175.1zM328 238h224.7c57.1 0 103.3 44.4 103.3 99.3v9.5c0 54.8-46.3 99.3-103.3 99.3H328V238zm366.6 429.4c0 62.9-51.7 113.9-115.5 113.9H328V542.7h251.1c63.8 0 115.5 51 115.5 113.9v10.8z\")),t.BorderBottomOutline=u(\"border-bottom\",i,l(o,\"M872 808H152c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-720-94h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0-498h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0 332h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0-166h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm166 166h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0-332h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm332 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0 332h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm222-72h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-388 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm388-404h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-388 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm388 426h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-388 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm388-404h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-388 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8z\")),t.BorderLeftOutline=u(\"border-left\",i,l(o,\"M208 144h-56c-4.4 0-8 3.6-8 8v720c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V152c0-4.4-3.6-8-8-8zm166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm498 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM540 310h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM374 808h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderOuterOutline=u(\"border-outer\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656zM484 366h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM302 548h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm364 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-182 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0 182h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8z\")),t.BorderInnerOutline=u(\"border-inner\",i,l(o,\"M872 476H548V144h-72v332H152c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h324v332h72V548h324c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-664h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM650 216h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm56 592h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-56-592h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-166 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm56 592h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-56-426h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm56 260h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderRightOutline=u(\"border-right\",i,l(o,\"M872 144h-56c-4.4 0-8 3.6-8 8v720c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V152c0-4.4-3.6-8-8-8zm-166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-498 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm166 166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM208 808h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm498 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM374 808h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderHorizontalOutline=u(\"border-horizontal\",i,l(o,\"M540 144h-56c-4.4 0-8 3.6-8 8v720c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V152c0-4.4-3.6-8-8-8zm-166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm498 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-664 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm498 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM208 310h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm664 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-664 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm664 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM374 808h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderTopOutline=u(\"border-top\",i,l(o,\"M872 144H152c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM208 310h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm166-166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm166 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332-498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 332h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderVerticleOutline=u(\"border-verticle\",i,l(o,\"M872 476H152c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-166h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-664h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 498h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM650 216h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm56 592h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-56-592h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-166 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm332 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM208 808h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM152 382h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm332 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM208 642h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm332 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.BorderOutline=u(\"border\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\")),t.BranchesOutline=u(\"branches\",i,l(o,\"M740 161c-61.8 0-112 50.2-112 112 0 50.1 33.1 92.6 78.5 106.9v95.9L320 602.4V318.1c44.2-15 76-56.9 76-106.1 0-61.8-50.2-112-112-112s-112 50.2-112 112c0 49.2 31.8 91 76 106.1V706c-44.2 15-76 56.9-76 106.1 0 61.8 50.2 112 112 112s112-50.2 112-112c0-49.2-31.8-91-76-106.1v-27.8l423.5-138.7a50.52 50.52 0 0 0 34.9-48.2V378.2c42.9-15.8 73.6-57 73.6-105.2 0-61.8-50.2-112-112-112zm-504 51a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm96 600a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0zm408-491a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.CheckOutline=u(\"check\",i,l(o,\"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z\")),t.CiOutline=u(\"ci\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm218-572.1h-50.4c-4.4 0-8 3.6-8 8v384.2c0 4.4 3.6 8 8 8H730c4.4 0 8-3.6 8-8V319.9c0-4.4-3.6-8-8-8zm-281.4 49.6c49.5 0 83.1 31.5 87 77.6.4 4.2 3.8 7.4 8 7.4h52.6c2.4 0 4.4-2 4.4-4.4 0-81.2-64-138.1-152.3-138.1C345.4 304 286 373.5 286 488.4v49c0 114 59.4 182.6 162.3 182.6 88 0 152.3-55.1 152.3-132.5 0-2.4-2-4.4-4.4-4.4h-52.7c-4.2 0-7.6 3.2-8 7.3-4.2 43-37.7 72.4-87 72.4-61.1 0-95.6-44.9-95.6-125.2v-49.3c.1-81.4 34.6-126.8 95.7-126.8z\")),t.CloseOutline=u(\"close\",i,l(o,\"M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z\")),t.CloudDownloadOutline=u(\"cloud-download\",i,l(o,\"M624 706.3h-74.1V464c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v242.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.7a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9z\",\"M811.4 366.7C765.6 245.9 648.9 160 512.2 160S258.8 245.8 213 366.6C127.3 389.1 64 467.2 64 560c0 110.5 89.5 200 199.9 200H304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8h-40.1c-33.7 0-65.4-13.4-89-37.7-23.5-24.2-36-56.8-34.9-90.6.9-26.4 9.9-51.2 26.2-72.1 16.7-21.3 40.1-36.8 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4a245.6 245.6 0 0 1 52.4-49.9c41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10C846.1 454.5 884 503.8 884 560c0 33.1-12.9 64.3-36.3 87.7a123.07 123.07 0 0 1-87.6 36.3H720c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h40.1C870.5 760 960 670.5 960 560c0-92.7-63.1-170.7-148.6-193.3z\")),t.CloudServerOutline=u(\"cloud-server\",i,l(o,\"M704 446H320c-4.4 0-8 3.6-8 8v402c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8V454c0-4.4-3.6-8-8-8zm-328 64h272v117H376V510zm272 290H376V683h272v117z\",\"M424 748a32 32 0 1 0 64 0 32 32 0 1 0-64 0zm0-178a32 32 0 1 0 64 0 32 32 0 1 0-64 0z\",\"M811.4 368.9C765.6 248 648.9 162 512.2 162S258.8 247.9 213 368.8C126.9 391.5 63.5 470.2 64 563.6 64.6 668 145.6 752.9 247.6 762c4.7.4 8.7-3.3 8.7-8v-60.4c0-4-3-7.4-7-7.9-27-3.4-52.5-15.2-72.1-34.5-24-23.5-37.2-55.1-37.2-88.6 0-28 9.1-54.4 26.2-76.4 16.7-21.4 40.2-36.9 66.1-43.7l37.9-10 13.9-36.7c8.6-22.8 20.6-44.2 35.7-63.5 14.9-19.2 32.6-36 52.4-50 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.3c19.9 14 37.5 30.8 52.4 50 15.1 19.3 27.1 40.7 35.7 63.5l13.8 36.6 37.8 10c54.2 14.4 92.1 63.7 92.1 120 0 33.6-13.2 65.1-37.2 88.6-19.5 19.2-44.9 31.1-71.9 34.5-4 .5-6.9 3.9-6.9 7.9V754c0 4.7 4.1 8.4 8.8 8 101.7-9.2 182.5-94 183.2-198.2.6-93.4-62.7-172.1-148.6-194.9z\")),t.CloudSyncOutline=u(\"cloud-sync\",i,l(o,\"M811.4 368.9C765.6 248 648.9 162 512.2 162S258.8 247.9 213 368.8C126.9 391.5 63.5 470.2 64 563.6 64.6 668 145.6 752.9 247.6 762c4.7.4 8.7-3.3 8.7-8v-60.4c0-4-3-7.4-7-7.9-27-3.4-52.5-15.2-72.1-34.5-24-23.5-37.2-55.1-37.2-88.6 0-28 9.1-54.4 26.2-76.4 16.7-21.4 40.2-36.9 66.1-43.7l37.9-10 13.9-36.7c8.6-22.8 20.6-44.2 35.7-63.5 14.9-19.2 32.6-36 52.4-50 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.3c19.9 14 37.5 30.8 52.4 50 15.1 19.3 27.1 40.7 35.7 63.5l13.8 36.6 37.8 10c54.2 14.4 92.1 63.7 92.1 120 0 33.6-13.2 65.1-37.2 88.6-19.5 19.2-44.9 31.1-71.9 34.5-4 .5-6.9 3.9-6.9 7.9V754c0 4.7 4.1 8.4 8.8 8 101.7-9.2 182.5-94 183.2-198.2.6-93.4-62.7-172.1-148.6-194.9z\",\"M376.9 656.4c1.8-33.5 15.7-64.7 39.5-88.6 25.4-25.5 60-39.8 96-39.8 36.2 0 70.3 14.1 96 39.8 1.4 1.4 2.7 2.8 4.1 4.3l-25 19.6a8 8 0 0 0 3 14.1l98.2 24c5 1.2 9.9-2.6 9.9-7.7l.5-101.3c0-6.7-7.6-10.5-12.9-6.3L663 532.7c-36.6-42-90.4-68.6-150.5-68.6-107.4 0-195 85.1-199.4 191.7-.2 4.5 3.4 8.3 8 8.3H369c4.2-.1 7.7-3.4 7.9-7.7zM703 664h-47.9c-4.2 0-7.7 3.3-8 7.6-1.8 33.5-15.7 64.7-39.5 88.6-25.4 25.5-60 39.8-96 39.8-36.2 0-70.3-14.1-96-39.8-1.4-1.4-2.7-2.8-4.1-4.3l25-19.6a8 8 0 0 0-3-14.1l-98.2-24c-5-1.2-9.9 2.6-9.9 7.7l-.4 101.4c0 6.7 7.6 10.5 12.9 6.3l23.2-18.2c36.6 42 90.4 68.6 150.5 68.6 107.4 0 195-85.1 199.4-191.7.2-4.5-3.4-8.3-8-8.3z\")),t.CloudUploadOutline=u(\"cloud-upload\",i,l(o,\"M518.3 459a8 8 0 0 0-12.6 0l-112 141.7a7.98 7.98 0 0 0 6.3 12.9h73.9V856c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V613.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 459z\",\"M811.4 366.7C765.6 245.9 648.9 160 512.2 160S258.8 245.8 213 366.6C127.3 389.1 64 467.2 64 560c0 110.5 89.5 200 199.9 200H304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8h-40.1c-33.7 0-65.4-13.4-89-37.7-23.5-24.2-36-56.8-34.9-90.6.9-26.4 9.9-51.2 26.2-72.1 16.7-21.3 40.1-36.8 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4a245.6 245.6 0 0 1 52.4-49.9c41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10C846.1 454.5 884 503.8 884 560c0 33.1-12.9 64.3-36.3 87.7a123.07 123.07 0 0 1-87.6 36.3H720c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h40.1C870.5 760 960 670.5 960 560c0-92.7-63.1-170.7-148.6-193.3z\")),t.ClusterOutline=u(\"cluster\",i,l(o,\"M888 680h-54V540H546v-92h238c8.8 0 16-7.2 16-16V168c0-8.8-7.2-16-16-16H240c-8.8 0-16 7.2-16 16v264c0 8.8 7.2 16 16 16h238v92H190v140h-54c-4.4 0-8 3.6-8 8v176c0 4.4 3.6 8 8 8h176c4.4 0 8-3.6 8-8V688c0-4.4-3.6-8-8-8h-54v-72h220v72h-54c-4.4 0-8 3.6-8 8v176c0 4.4 3.6 8 8 8h176c4.4 0 8-3.6 8-8V688c0-4.4-3.6-8-8-8h-54v-72h220v72h-54c-4.4 0-8 3.6-8 8v176c0 4.4 3.6 8 8 8h176c4.4 0 8-3.6 8-8V688c0-4.4-3.6-8-8-8zM256 805.3c0 1.5-1.2 2.7-2.7 2.7h-58.7c-1.5 0-2.7-1.2-2.7-2.7v-58.7c0-1.5 1.2-2.7 2.7-2.7h58.7c1.5 0 2.7 1.2 2.7 2.7v58.7zm288 0c0 1.5-1.2 2.7-2.7 2.7h-58.7c-1.5 0-2.7-1.2-2.7-2.7v-58.7c0-1.5 1.2-2.7 2.7-2.7h58.7c1.5 0 2.7 1.2 2.7 2.7v58.7zM288 384V216h448v168H288zm544 421.3c0 1.5-1.2 2.7-2.7 2.7h-58.7c-1.5 0-2.7-1.2-2.7-2.7v-58.7c0-1.5 1.2-2.7 2.7-2.7h58.7c1.5 0 2.7 1.2 2.7 2.7v58.7zM360 300a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\")),t.CodepenOutline=u(\"codepen\",i,l(o,\"M911.7 385.3l-.3-1.5c-.2-1-.3-1.9-.6-2.9-.2-.6-.4-1.1-.5-1.7-.3-.8-.5-1.7-.9-2.5-.2-.6-.5-1.1-.8-1.7-.4-.8-.8-1.5-1.2-2.3-.3-.5-.6-1.1-1-1.6-.8-1.2-1.7-2.4-2.6-3.6-.5-.6-1.1-1.3-1.7-1.9-.4-.5-.9-.9-1.4-1.3-.6-.6-1.3-1.1-1.9-1.6-.5-.4-1-.8-1.6-1.2-.2-.1-.4-.3-.6-.4L531.1 117.8a34.3 34.3 0 0 0-38.1 0L127.3 361.3c-.2.1-.4.3-.6.4-.5.4-1 .8-1.6 1.2-.7.5-1.3 1.1-1.9 1.6-.5.4-.9.9-1.4 1.3-.6.6-1.2 1.2-1.7 1.9-1 1.1-1.8 2.3-2.6 3.6-.3.5-.7 1-1 1.6-.4.7-.8 1.5-1.2 2.3-.3.5-.5 1.1-.8 1.7-.3.8-.6 1.7-.9 2.5-.2.6-.4 1.1-.5 1.7-.2.9-.4 1.9-.6 2.9l-.3 1.5c-.2 1.5-.3 3-.3 4.5v243.5c0 1.5.1 3 .3 4.5l.3 1.5.6 2.9c.2.6.3 1.1.5 1.7.3.9.6 1.7.9 2.5.2.6.5 1.1.8 1.7.4.8.7 1.5 1.2 2.3.3.5.6 1.1 1 1.6.5.7.9 1.4 1.5 2.1l1.2 1.5c.5.6 1.1 1.3 1.7 1.9.4.5.9.9 1.4 1.3.6.6 1.3 1.1 1.9 1.6.5.4 1 .8 1.6 1.2.2.1.4.3.6.4L493 905.7c5.6 3.8 12.3 5.8 19.1 5.8 6.6 0 13.3-1.9 19.1-5.8l365.6-243.5c.2-.1.4-.3.6-.4.5-.4 1-.8 1.6-1.2.7-.5 1.3-1.1 1.9-1.6.5-.4.9-.9 1.4-1.3.6-.6 1.2-1.2 1.7-1.9l1.2-1.5 1.5-2.1c.3-.5.7-1 1-1.6.4-.8.8-1.5 1.2-2.3.3-.5.5-1.1.8-1.7.3-.8.6-1.7.9-2.5.2-.5.4-1.1.5-1.7.3-.9.4-1.9.6-2.9l.3-1.5c.2-1.5.3-3 .3-4.5V389.8c-.3-1.5-.4-3-.6-4.5zM546.4 210.5l269.4 179.4-120.3 80.4-149-99.6V210.5zm-68.8 0v160.2l-149 99.6-120.3-80.4 269.3-179.4zM180.7 454.1l86 57.5-86 57.5v-115zm296.9 358.5L208.3 633.2l120.3-80.4 149 99.6v160.2zM512 592.8l-121.6-81.2L512 430.3l121.6 81.2L512 592.8zm34.4 219.8V652.4l149-99.6 120.3 80.4-269.3 179.4zM843.3 569l-86-57.5 86-57.5v115z\")),t.CodeSandboxOutline=u(\"code-sandbox\",i,l(o,\"M709.6 210l.4-.2h.2L512 96 313.9 209.8h-.2l.7.3L151.5 304v416L512 928l360.5-208V304l-162.9-94zM482.7 843.6L339.6 761V621.4L210 547.8V372.9l272.7 157.3v313.4zM238.2 321.5l134.7-77.8 138.9 79.7 139.1-79.9 135.2 78-273.9 158-274-158zM814 548.3l-128.8 73.1v139.1l-143.9 83V530.4L814 373.1v175.2z\")),t.ColumHeightOutline=u(\"colum-height\",i,l(o,\"M840 836H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm0-724H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM610.8 378c6 0 9.4-7 5.7-11.7L515.7 238.7a7.14 7.14 0 0 0-11.3 0L403.6 366.3a7.23 7.23 0 0 0 5.7 11.7H476v268h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H548V378h62.8z\")),t.ColumnWidthOutline=u(\"column-width\",i,l(o,\"M180 176h-60c-4.4 0-8 3.6-8 8v656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V184c0-4.4-3.6-8-8-8zm724 0h-60c-4.4 0-8 3.6-8 8v656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V184c0-4.4-3.6-8-8-8zM785.3 504.3L657.7 403.6a7.23 7.23 0 0 0-11.7 5.7V476H378v-62.8c0-6-7-9.4-11.7-5.7L238.7 508.3a7.14 7.14 0 0 0 0 11.3l127.5 100.8c4.7 3.7 11.7.4 11.7-5.7V548h268v62.8c0 6 7 9.4 11.7 5.7l127.5-100.8c3.8-2.9 3.8-8.5.2-11.4z\")),t.ColumnHeightOutline=u(\"column-height\",i,l(o,\"M840 836H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm0-724H184c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h656c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM610.8 378c6 0 9.4-7 5.7-11.7L515.7 238.7a7.14 7.14 0 0 0-11.3 0L403.6 366.3a7.23 7.23 0 0 0 5.7 11.7H476v268h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H548V378h62.8z\")),t.CoffeeOutline=u(\"coffee\",i,l(r,\"M275 281c19.9 0 36-16.1 36-36V36c0-19.9-16.1-36-36-36s-36 16.1-36 36v209c0 19.9 16.1 36 36 36zm613 144H768c0-39.8-32.2-72-72-72H200c-39.8 0-72 32.2-72 72v248c0 3.4.2 6.7.7 9.9-.5 7-.7 14-.7 21.1 0 176.7 143.3 320 320 320 160.1 0 292.7-117.5 316.3-271H888c39.8 0 72-32.2 72-72V497c0-39.8-32.2-72-72-72zM696 681h-1.1c.7 7.6 1.1 15.2 1.1 23 0 137-111 248-248 248S200 841 200 704c0-7.8.4-15.4 1.1-23H200V425h496v256zm192-8H776V497h112v176zM613 281c19.9 0 36-16.1 36-36V36c0-19.9-16.1-36-36-36s-36 16.1-36 36v209c0 19.9 16.1 36 36 36zm-170 0c19.9 0 36-16.1 36-36V36c0-19.9-16.1-36-36-36s-36 16.1-36 36v209c0 19.9 16.1 36 36 36z\")),t.CopyrightOutline=u(\"copyright\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm5.6-532.7c53 0 89 33.8 93 83.4.3 4.2 3.8 7.4 8 7.4h56.7c2.6 0 4.7-2.1 4.7-4.7 0-86.7-68.4-147.4-162.7-147.4C407.4 290 344 364.2 344 486.8v52.3C344 660.8 407.4 734 517.3 734c94 0 162.7-58.8 162.7-141.4 0-2.6-2.1-4.7-4.7-4.7h-56.8c-4.2 0-7.6 3.2-8 7.3-4.2 46.1-40.1 77.8-93 77.8-65.3 0-102.1-47.9-102.1-133.6v-52.6c.1-87 37-135.5 102.2-135.5z\")),t.DashOutline=u(\"dash\",i,l(o,\"M112 476h160v72H112zm320 0h160v72H432zm320 0h160v72H752z\")),t.DeploymentUnitOutline=u(\"deployment-unit\",i,l(o,\"M888.3 693.2c-42.5-24.6-94.3-18-129.2 12.8l-53-30.7V523.6c0-15.7-8.4-30.3-22-38.1l-136-78.3v-67.1c44.2-15 76-56.8 76-106.1 0-61.9-50.1-112-112-112s-112 50.1-112 112c0 49.3 31.8 91.1 76 106.1v67.1l-136 78.3c-13.6 7.8-22 22.4-22 38.1v151.6l-53 30.7c-34.9-30.8-86.8-37.4-129.2-12.8-53.5 31-71.7 99.4-41 152.9 30.8 53.5 98.9 71.9 152.2 41 42.5-24.6 62.7-73 53.6-118.8l48.7-28.3 140.6 81c6.8 3.9 14.4 5.9 22 5.9s15.2-2 22-5.9L674.5 740l48.7 28.3c-9.1 45.7 11.2 94.2 53.6 118.8 53.3 30.9 121.5 12.6 152.2-41 30.8-53.6 12.6-122-40.7-152.9zm-673 138.4a47.6 47.6 0 0 1-65.2-17.6c-13.2-22.9-5.4-52.3 17.5-65.5a47.6 47.6 0 0 1 65.2 17.6c13.2 22.9 5.4 52.3-17.5 65.5zM522 463.8zM464 234a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm170 446.2l-122 70.3-122-70.3V539.8l122-70.3 122 70.3v140.4zm239.9 133.9c-13.2 22.9-42.4 30.8-65.2 17.6-22.8-13.2-30.7-42.6-17.5-65.5s42.4-30.8 65.2-17.6c22.9 13.2 30.7 42.5 17.5 65.5z\")),t.DesktopOutline=u(\"desktop\",i,l(o,\"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z\")),t.DingdingOutline=u(\"dingding\",i,l(o,\"M573.7 252.5C422.5 197.4 201.3 96.7 201.3 96.7c-15.7-4.1-17.9 11.1-17.9 11.1-5 61.1 33.6 160.5 53.6 182.8 19.9 22.3 319.1 113.7 319.1 113.7S326 357.9 270.5 341.9c-55.6-16-37.9 17.8-37.9 17.8 11.4 61.7 64.9 131.8 107.2 138.4 42.2 6.6 220.1 4 220.1 4s-35.5 4.1-93.2 11.9c-42.7 5.8-97 12.5-111.1 17.8-33.1 12.5 24 62.6 24 62.6 84.7 76.8 129.7 50.5 129.7 50.5 33.3-10.7 61.4-18.5 85.2-24.2L565 743.1h84.6L603 928l205.3-271.9H700.8l22.3-38.7c.3.5.4.8.4.8S799.8 496.1 829 433.8l.6-1h-.1c5-10.8 8.6-19.7 10-25.8 17-71.3-114.5-99.4-265.8-154.5z\")),t.DisconnectOutline=u(\"disconnect\",i,l(o,\"M832.6 191.4c-84.6-84.6-221.5-84.6-306 0l-96.9 96.9 51 51 96.9-96.9c53.8-53.8 144.6-59.5 204 0 59.5 59.5 53.8 150.2 0 204l-96.9 96.9 51.1 51.1 96.9-96.9c84.4-84.6 84.4-221.5-.1-306.1zM446.5 781.6c-53.8 53.8-144.6 59.5-204 0-59.5-59.5-53.8-150.2 0-204l96.9-96.9-51.1-51.1-96.9 96.9c-84.6 84.6-84.6 221.5 0 306s221.5 84.6 306 0l96.9-96.9-51-51-96.8 97zM260.3 209.4a8.03 8.03 0 0 0-11.3 0L209.4 249a8.03 8.03 0 0 0 0 11.3l554.4 554.4c3.1 3.1 8.2 3.1 11.3 0l39.6-39.6c3.1-3.1 3.1-8.2 0-11.3L260.3 209.4z\")),t.DollarOutline=u(\"dollar\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm47.7-395.2l-25.4-5.9V348.6c38 5.2 61.5 29 65.5 58.2.5 4 3.9 6.9 7.9 6.9h44.9c4.7 0 8.4-4.1 8-8.8-6.1-62.3-57.4-102.3-125.9-109.2V263c0-4.4-3.6-8-8-8h-28.1c-4.4 0-8 3.6-8 8v33c-70.8 6.9-126.2 46-126.2 119 0 67.6 49.8 100.2 102.1 112.7l24.7 6.3v142.7c-44.2-5.9-69-29.5-74.1-61.3-.6-3.8-4-6.6-7.9-6.6H363c-4.7 0-8.4 4-8 8.7 4.5 55 46.2 105.6 135.2 112.1V761c0 4.4 3.6 8 8 8h28.4c4.4 0 8-3.6 8-8.1l-.2-31.7c78.3-6.9 134.3-48.8 134.3-124-.1-69.4-44.2-100.4-109-116.4zm-68.6-16.2c-5.6-1.6-10.3-3.1-15-5-33.8-12.2-49.5-31.9-49.5-57.3 0-36.3 27.5-57 64.5-61.7v124zM534.3 677V543.3c3.1.9 5.9 1.6 8.8 2.2 47.3 14.4 63.2 34.4 63.2 65.1 0 39.1-29.4 62.6-72 66.4z\")),t.DoubleRightOutline=u(\"double-right\",i,l(o,\"M533.2 492.3L277.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H188c-6.7 0-10.4 7.7-6.3 12.9L447.1 512 181.7 851.1A7.98 7.98 0 0 0 188 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5zm304 0L581.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H492c-6.7 0-10.4 7.7-6.3 12.9L751.1 512 485.7 851.1A7.98 7.98 0 0 0 492 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z\")),t.DotChartOutline=u(\"dot-chart\",i,l(o,\"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM288 604a64 64 0 1 0 128 0 64 64 0 1 0-128 0zm118-224a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm158 228a96 96 0 1 0 192 0 96 96 0 1 0-192 0zm148-314a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.DoubleLeftOutline=u(\"double-left\",i,l(o,\"M272.9 512l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L186.8 492.3a31.99 31.99 0 0 0 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H532c6.7 0 10.4-7.7 6.3-12.9L272.9 512zm304 0l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L490.8 492.3a31.99 31.99 0 0 0 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H836c6.7 0 10.4-7.7 6.3-12.9L576.9 512z\")),t.DownloadOutline=u(\"download\",i,l(o,\"M505.7 661a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z\")),t.DribbbleOutline=u(\"dribbble\",i,l(o,\"M512 96C282.6 96 96 282.6 96 512s186.6 416 416 416 416-186.6 416-416S741.4 96 512 96zm275.1 191.8c49.5 60.5 79.5 137.5 80.2 221.4-11.7-2.5-129.2-26.3-247.4-11.4-2.5-6.1-5-12.2-7.6-18.3-7.4-17.3-15.3-34.6-23.6-51.5C720 374.3 779.6 298 787.1 287.8zM512 157.2c90.3 0 172.8 33.9 235.5 89.5-6.4 9.1-59.9 81-186.2 128.4-58.2-107-122.7-194.8-132.6-208 27.3-6.6 55.2-9.9 83.3-9.9zM360.9 191c9.4 12.8 72.9 100.9 131.7 205.5C326.4 440.6 180 440 164.1 439.8c23.1-110.3 97.4-201.9 196.8-248.8zM156.7 512.5c0-3.6.1-7.3.2-10.9 15.5.3 187.7 2.5 365.2-50.6 10.2 19.9 19.9 40.1 28.8 60.3-4.7 1.3-9.4 2.7-14 4.2C353.6 574.9 256.1 736.4 248 750.1c-56.7-63-91.3-146.3-91.3-237.6zM512 867.8c-82.2 0-157.9-28-218.1-75 6.4-13.1 78.3-152 278.7-221.9l2.3-.8c49.9 129.6 70.5 238.3 75.8 269.5A350.46 350.46 0 0 1 512 867.8zm198.5-60.7c-3.6-21.6-22.5-125.6-69-253.3C752.9 536 850.7 565.2 862.8 569c-15.8 98.8-72.5 184.2-152.3 238.1z\")),t.DropboxOutline=u(\"dropbox\",i,l(o,\"M64 556.9l264.2 173.5L512.5 577 246.8 412.7zm896-290.3zm0 0L696.8 95 512.5 248.5l265.2 164.2L512.5 577l184.3 153.4L960 558.8 777.7 412.7zM513 609.8L328.2 763.3l-79.4-51.5v57.8L513 928l263.7-158.4v-57.8l-78.9 51.5zM328.2 95L64 265.1l182.8 147.6 265.7-164.2zM64 556.9z\")),t.EllipsisOutline=u(\"ellipsis\",i,l(o,\"M176 511a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm280 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm280 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.EnterOutline=u(\"enter\",i,l(o,\"M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z\")),t.EuroOutline=u(\"euro\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm117.7-588.6c-15.9-3.5-34.4-5.4-55.3-5.4-106.7 0-178.9 55.7-198.6 149.9H344c-4.4 0-8 3.6-8 8v27.2c0 4.4 3.6 8 8 8h26.4c-.3 4.1-.3 8.4-.3 12.8v36.9H344c-4.4 0-8 3.6-8 8V568c0 4.4 3.6 8 8 8h30.2c17.2 99.2 90.4 158 200.2 158 20.9 0 39.4-1.7 55.3-5.1 3.7-.8 6.4-4 6.4-7.8v-42.8c0-5-4.6-8.8-9.5-7.8-14.7 2.8-31.9 4.1-51.8 4.1-68.5 0-114.5-36.6-129.8-98.6h130.6c4.4 0 8-3.6 8-8v-27.2c0-4.4-3.6-8-8-8H439.2v-36c0-4.7 0-9.4.3-13.8h135.9c4.4 0 8-3.6 8-8v-27.2c0-4.4-3.6-8-8-8H447.1c17.2-56.9 62.3-90.4 127.6-90.4 19.9 0 37.1 1.5 51.7 4.4a8 8 0 0 0 9.6-7.8v-42.8c0-3.8-2.6-7-6.3-7.8z\")),t.ExceptionOutline=u(\"exception\",i,l(o,\"M688 312v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zm-392 88c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm376 116c-119.3 0-216 96.7-216 216s96.7 216 216 216 216-96.7 216-216-96.7-216-216-216zm107.5 323.5C750.8 868.2 712.6 884 672 884s-78.8-15.8-107.5-44.5C535.8 810.8 520 772.6 520 732s15.8-78.8 44.5-107.5C593.2 595.8 631.4 580 672 580s78.8 15.8 107.5 44.5C808.2 653.2 824 691.4 824 732s-15.8 78.8-44.5 107.5zM640 812a32 32 0 1 0 64 0 32 32 0 1 0-64 0zm12-64h40c4.4 0 8-3.6 8-8V628c0-4.4-3.6-8-8-8h-40c-4.4 0-8 3.6-8 8v112c0 4.4 3.6 8 8 8zM440 852H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.ExclamationOutline=u(\"exclamation\",i,l(o,\"M448 804a64 64 0 1 0 128 0 64 64 0 1 0-128 0zm32-168h64c4.4 0 8-3.6 8-8V164c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8z\")),t.ExportOutline=u(\"export\",i,l(o,\"M888.3 757.4h-53.8c-4.2 0-7.7 3.5-7.7 7.7v61.8H197.1V197.1h629.8v61.8c0 4.2 3.5 7.7 7.7 7.7h53.8c4.2 0 7.7-3.4 7.7-7.7V158.7c0-17-13.7-30.7-30.7-30.7H158.7c-17 0-30.7 13.7-30.7 30.7v706.6c0 17 13.7 30.7 30.7 30.7h706.6c17 0 30.7-13.7 30.7-30.7V765.1c0-4.3-3.5-7.7-7.7-7.7zm18.6-251.7L765 393.7c-5.3-4.2-13-.4-13 6.3v76H438c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h314v76c0 6.7 7.8 10.5 13 6.3l141.9-112a8 8 0 0 0 0-12.6z\")),t.FallOutline=u(\"fall\",i,l(o,\"M925.9 804l-24-199.2c-.8-6.6-8.9-9.4-13.6-4.7L829 659.5 557.7 388.3c-6.3-6.2-16.4-6.2-22.6 0L433.3 490 156.6 213.3a8.03 8.03 0 0 0-11.3 0l-45 45.2a8.03 8.03 0 0 0 0 11.3L422 591.7c6.2 6.3 16.4 6.3 22.6 0L546.4 490l226.1 226-59.3 59.3a8.01 8.01 0 0 0 4.7 13.6l199.2 24c5.1.7 9.5-3.7 8.8-8.9z\")),t.FileDoneOutline=u(\"file-done\",i,l(o,\"M688 312v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zm-392 88c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm376 116c-119.3 0-216 96.7-216 216s96.7 216 216 216 216-96.7 216-216-96.7-216-216-216zm107.5 323.5C750.8 868.2 712.6 884 672 884s-78.8-15.8-107.5-44.5C535.8 810.8 520 772.6 520 732s15.8-78.8 44.5-107.5C593.2 595.8 631.4 580 672 580s78.8 15.8 107.5 44.5C808.2 653.2 824 691.4 824 732s-15.8 78.8-44.5 107.5zM761 656h-44.3c-2.6 0-5 1.2-6.5 3.3l-63.5 87.8-23.1-31.9a7.92 7.92 0 0 0-6.5-3.3H573c-6.5 0-10.3 7.4-6.5 12.7l73.8 102.1c3.2 4.4 9.7 4.4 12.9 0l114.2-158c3.9-5.3.1-12.7-6.4-12.7zM440 852H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.FileSyncOutline=u(\"file-sync\",i,l(o,\"M296 256c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm192 200v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8zm-48 396H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm104.1-115.6c1.8-34.5 16.2-66.8 40.8-91.4 26.2-26.2 62-41 99.1-41 37.4 0 72.6 14.6 99.1 41 3.2 3.2 6.3 6.6 9.2 10.1L769.2 673a8 8 0 0 0 3 14.1l93.3 22.5c5 1.2 9.8-2.6 9.9-7.7l.6-95.4a8 8 0 0 0-12.9-6.4l-20.3 15.8C805.4 569.6 748.1 540 684 540c-109.9 0-199.6 86.9-204 195.7-.2 4.5 3.5 8.3 8 8.3h48.1c4.3 0 7.8-3.3 8-7.6zM880 744h-48.1c-4.3 0-7.8 3.3-8 7.6-1.8 34.5-16.2 66.8-40.8 91.4-26.2 26.2-62 41-99.1 41-37.4 0-72.6-14.6-99.1-41-3.2-3.2-6.3-6.6-9.2-10.1l23.1-17.9a8 8 0 0 0-3-14.1l-93.3-22.5c-5-1.2-9.8 2.6-9.9 7.7l-.6 95.4a8 8 0 0 0 12.9 6.4l20.3-15.8C562.6 918.4 619.9 948 684 948c109.9 0 199.6-86.9 204-195.7.2-4.5-3.5-8.3-8-8.3z\")),t.FileProtectOutline=u(\"file-protect\",i,l(o,\"M644.7 669.2a7.92 7.92 0 0 0-6.5-3.3H594c-6.5 0-10.3 7.4-6.5 12.7l73.8 102.1c3.2 4.4 9.7 4.4 12.9 0l114.2-158c3.8-5.3 0-12.7-6.5-12.7h-44.3c-2.6 0-5 1.2-6.5 3.3l-63.5 87.8-22.9-31.9zM688 306v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zm-392 88c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm184 458H208V148h560v296c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h312c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm402.6-320.8l-192-66.7c-.9-.3-1.7-.4-2.6-.4s-1.8.1-2.6.4l-192 66.7a7.96 7.96 0 0 0-5.4 7.5v251.1c0 2.5 1.1 4.8 3.1 6.3l192 150.2c1.4 1.1 3.2 1.7 4.9 1.7s3.5-.6 4.9-1.7l192-150.2c1.9-1.5 3.1-3.8 3.1-6.3V538.7c0-3.4-2.2-6.4-5.4-7.5zM826 763.7L688 871.6 550 763.7V577l138-48 138 48v186.7z\")),t.FileSearchOutline=u(\"file-search\",i,l(o,\"M688 312v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zm-392 88c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm144 452H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm445.7 51.5l-93.3-93.3C814.7 780.7 828 743.9 828 704c0-97.2-78.8-176-176-176s-176 78.8-176 176 78.8 176 176 176c35.8 0 69-10.7 96.8-29l94.7 94.7c1.6 1.6 3.6 2.3 5.6 2.3s4.1-.8 5.6-2.3l31-31a7.9 7.9 0 0 0 0-11.2zM652 816c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z\")),t.FileJpgOutline=u(\"file-jpg\",i,l(r,\"M874.6 301.8L596.8 21.3c-4.5-4.5-9.4-8.3-14.7-11.5-1.4-.8-2.8-1.6-4.3-2.3-.9-.5-1.9-.9-2.8-1.3-9-4-18.9-6.2-29-6.2H201c-39.8 0-73 32.2-73 72v880c0 39.8 33.2 72 73 72h623c39.8 0 71-32.2 71-72V352.5c0-19-7-37.2-20.4-50.7zM583 110.4L783.8 312H583V110.4zM823 952H200V72h311v240c0 39.8 33.2 72 73 72h239v568zM350 696.5c0 24.2-7.5 31.4-21.9 31.4-9 0-18.4-5.8-24.8-18.5L272.9 732c13.4 22.9 32.3 34.2 61.3 34.2 41.6 0 60.8-29.9 60.8-66.2V577h-45v119.5zM501.3 577H437v186h44v-62h21.6c39.1 0 73.1-19.6 73.1-63.6 0-45.8-33.5-60.4-74.4-60.4zm-.8 89H481v-53h18.2c21.5 0 33.4 6.2 33.4 24.9 0 18.1-10.5 28.1-32.1 28.1zm182.5-9v36h30v30.1c-4 2.9-11 4.7-17.7 4.7-34.3 0-50.7-21.4-50.7-58.2 0-36.1 19.7-57.4 47.1-57.4 15.3 0 25 6.2 34 14.4l23.7-28.3c-12.7-12.8-32.1-24.2-59.2-24.2-49.6 0-91.1 35.3-91.1 97 0 62.7 40 95.1 91.5 95.1 25.9 0 49.2-10.2 61.5-22.6V657H683z\")),t.FontColorsOutline=u(\"font-colors\",i,l(o,\"M904 816H120c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8zm-650.3-80h85c4.2 0 8-2.7 9.3-6.8l53.7-166h219.2l53.2 166c1.3 4 5 6.8 9.3 6.8h89.1c1.1 0 2.2-.2 3.2-.5a9.7 9.7 0 0 0 6-12.4L573.6 118.6a9.9 9.9 0 0 0-9.2-6.6H462.1c-4.2 0-7.9 2.6-9.2 6.6L244.5 723.1c-.4 1-.5 2.1-.5 3.2-.1 5.3 4.3 9.7 9.7 9.7zm255.9-516.1h4.1l83.8 263.8H424.9l84.7-263.8z\")),t.FontSizeOutline=u(\"font-size\",i,l(o,\"M920 416H616c-4.4 0-8 3.6-8 8v112c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-56h60v320h-46c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h164c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-46V480h60v56c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V424c0-4.4-3.6-8-8-8zM656 296V168c0-4.4-3.6-8-8-8H104c-4.4 0-8 3.6-8 8v128c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-64h168v560h-92c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-92V232h168v64c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8z\")),t.ForkOutline=u(\"fork\",i,l(o,\"M752 100c-61.8 0-112 50.2-112 112 0 47.7 29.9 88.5 72 104.6v27.6L512 601.4 312 344.2v-27.6c42.1-16.1 72-56.9 72-104.6 0-61.8-50.2-112-112-112s-112 50.2-112 112c0 50.6 33.8 93.5 80 107.3v34.4c0 9.7 3.3 19.3 9.3 27L476 672.3v33.6c-44.2 15-76 56.9-76 106.1 0 61.8 50.2 112 112 112s112-50.2 112-112c0-49.2-31.8-91-76-106.1v-33.6l226.7-291.6c6-7.7 9.3-17.3 9.3-27v-34.4c46.2-13.8 80-56.7 80-107.3 0-61.8-50.2-112-112-112zM224 212a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm336 600a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0zm192-552a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\")),t.FormOutline=u(\"form\",i,l(o,\"M904 512h-56c-4.4 0-8 3.6-8 8v320H184V184h320c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V520c0-4.4-3.6-8-8-8z\",\"M355.9 534.9L354 653.8c-.1 8.9 7.1 16.2 16 16.2h.4l118-2.9c2-.1 4-.9 5.4-2.3l415.9-415c3.1-3.1 3.1-8.2 0-11.3L785.4 114.3c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-415.8 415a8.3 8.3 0 0 0-2.3 5.6zm63.5 23.6L779.7 199l45.2 45.1-360.5 359.7-45.7 1.1.7-46.4z\")),t.FullscreenExitOutline=u(\"fullscreen-exit\",i,l(o,\"M391 240.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.7 43.7L200 146.3a8.03 8.03 0 0 0-11.3 0l-42.4 42.3a8.03 8.03 0 0 0 0 11.3L280 333.6l-43.9 43.9a8.01 8.01 0 0 0 4.7 13.6L401 410c5.1.6 9.5-3.7 8.9-8.9L391 240.9zm10.1 373.2L240.8 633c-6.6.8-9.4 8.9-4.7 13.6l43.9 43.9L146.3 824a8.03 8.03 0 0 0 0 11.3l42.4 42.3c3.1 3.1 8.2 3.1 11.3 0L333.7 744l43.7 43.7A8.01 8.01 0 0 0 391 783l18.9-160.1c.6-5.1-3.7-9.4-8.8-8.8zm221.8-204.2L783.2 391c6.6-.8 9.4-8.9 4.7-13.6L744 333.6 877.7 200c3.1-3.1 3.1-8.2 0-11.3l-42.4-42.3a8.03 8.03 0 0 0-11.3 0L690.3 279.9l-43.7-43.7a8.01 8.01 0 0 0-13.6 4.7L614.1 401c-.6 5.2 3.7 9.5 8.8 8.9zM744 690.4l43.9-43.9a8.01 8.01 0 0 0-4.7-13.6L623 614c-5.1-.6-9.5 3.7-8.9 8.9L633 783.1c.8 6.6 8.9 9.4 13.6 4.7l43.7-43.7L824 877.7c3.1 3.1 8.2 3.1 11.3 0l42.4-42.3c3.1-3.1 3.1-8.2 0-11.3L744 690.4z\")),t.FullscreenOutline=u(\"fullscreen\",i,l(o,\"M290 236.4l43.9-43.9a8.01 8.01 0 0 0-4.7-13.6L169 160c-5.1-.6-9.5 3.7-8.9 8.9L179 329.1c.8 6.6 8.9 9.4 13.6 4.7l43.7-43.7L370 423.7c3.1 3.1 8.2 3.1 11.3 0l42.4-42.3c3.1-3.1 3.1-8.2 0-11.3L290 236.4zm352.7 187.3c3.1 3.1 8.2 3.1 11.3 0l133.7-133.6 43.7 43.7a8.01 8.01 0 0 0 13.6-4.7L863.9 169c.6-5.1-3.7-9.5-8.9-8.9L694.8 179c-6.6.8-9.4 8.9-4.7 13.6l43.9 43.9L600.3 370a8.03 8.03 0 0 0 0 11.3l42.4 42.4zM845 694.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.7 43.7L654 600.3a8.03 8.03 0 0 0-11.3 0l-42.4 42.3a8.03 8.03 0 0 0 0 11.3L734 787.6l-43.9 43.9a8.01 8.01 0 0 0 4.7 13.6L855 864c5.1.6 9.5-3.7 8.9-8.9L845 694.9zm-463.7-94.6a8.03 8.03 0 0 0-11.3 0L236.3 733.9l-43.7-43.7a8.01 8.01 0 0 0-13.6 4.7L160.1 855c-.6 5.1 3.7 9.5 8.9 8.9L329.2 845c6.6-.8 9.4-8.9 4.7-13.6L290 787.6 423.7 654c3.1-3.1 3.1-8.2 0-11.3l-42.4-42.4z\")),t.GatewayOutline=u(\"gateway\",i,l(o,\"M928 392c8.8 0 16-7.2 16-16V192c0-8.8-7.2-16-16-16H744c-8.8 0-16 7.2-16 16v56H296v-56c0-8.8-7.2-16-16-16H96c-8.8 0-16 7.2-16 16v184c0 8.8 7.2 16 16 16h56v240H96c-8.8 0-16 7.2-16 16v184c0 8.8 7.2 16 16 16h184c8.8 0 16-7.2 16-16v-56h432v56c0 8.8 7.2 16 16 16h184c8.8 0 16-7.2 16-16V648c0-8.8-7.2-16-16-16h-56V392h56zM792 240h88v88h-88v-88zm-648 88v-88h88v88h-88zm88 456h-88v-88h88v88zm648-88v88h-88v-88h88zm-80-64h-56c-8.8 0-16 7.2-16 16v56H296v-56c0-8.8-7.2-16-16-16h-56V392h56c8.8 0 16-7.2 16-16v-56h432v56c0 8.8 7.2 16 16 16h56v240z\")),t.DownOutline=u(\"down\",i,l(o,\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\")),t.DragOutline=u(\"drag\",i,l(o,\"M909.3 506.3L781.7 405.6a7.23 7.23 0 0 0-11.7 5.7V476H548V254h64.8c6 0 9.4-7 5.7-11.7L517.7 114.7a7.14 7.14 0 0 0-11.3 0L405.6 242.3a7.23 7.23 0 0 0 5.7 11.7H476v222H254v-64.8c0-6-7-9.4-11.7-5.7L114.7 506.3a7.14 7.14 0 0 0 0 11.3l127.5 100.8c4.7 3.7 11.7.4 11.7-5.7V548h222v222h-64.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H548V548h222v64.8c0 6 7 9.4 11.7 5.7l127.5-100.8a7.3 7.3 0 0 0 .1-11.4z\")),t.GlobalOutline=u(\"global\",i,l(o,\"M854.4 800.9c.2-.3.5-.6.7-.9C920.6 722.1 960 621.7 960 512s-39.4-210.1-104.8-288c-.2-.3-.5-.5-.7-.8-1.1-1.3-2.1-2.5-3.2-3.7-.4-.5-.8-.9-1.2-1.4l-4.1-4.7-.1-.1c-1.5-1.7-3.1-3.4-4.6-5.1l-.1-.1c-3.2-3.4-6.4-6.8-9.7-10.1l-.1-.1-4.8-4.8-.3-.3c-1.5-1.5-3-2.9-4.5-4.3-.5-.5-1-1-1.6-1.5-1-1-2-1.9-3-2.8-.3-.3-.7-.6-1-1C736.4 109.2 629.5 64 512 64s-224.4 45.2-304.3 119.2c-.3.3-.7.6-1 1-1 .9-2 1.9-3 2.9-.5.5-1 1-1.6 1.5-1.5 1.4-3 2.9-4.5 4.3l-.3.3-4.8 4.8-.1.1c-3.3 3.3-6.5 6.7-9.7 10.1l-.1.1c-1.6 1.7-3.1 3.4-4.6 5.1l-.1.1c-1.4 1.5-2.8 3.1-4.1 4.7-.4.5-.8.9-1.2 1.4-1.1 1.2-2.1 2.5-3.2 3.7-.2.3-.5.5-.7.8C103.4 301.9 64 402.3 64 512s39.4 210.1 104.8 288c.2.3.5.6.7.9l3.1 3.7c.4.5.8.9 1.2 1.4l4.1 4.7c0 .1.1.1.1.2 1.5 1.7 3 3.4 4.6 5l.1.1c3.2 3.4 6.4 6.8 9.6 10.1l.1.1c1.6 1.6 3.1 3.2 4.7 4.7l.3.3c3.3 3.3 6.7 6.5 10.1 9.6 80.1 74 187 119.2 304.5 119.2s224.4-45.2 304.3-119.2a300 300 0 0 0 10-9.6l.3-.3c1.6-1.6 3.2-3.1 4.7-4.7l.1-.1c3.3-3.3 6.5-6.7 9.6-10.1l.1-.1c1.5-1.7 3.1-3.3 4.6-5 0-.1.1-.1.1-.2 1.4-1.5 2.8-3.1 4.1-4.7.4-.5.8-.9 1.2-1.4a99 99 0 0 0 3.3-3.7zm4.1-142.6c-13.8 32.6-32 62.8-54.2 90.2a444.07 444.07 0 0 0-81.5-55.9c11.6-46.9 18.8-98.4 20.7-152.6H887c-3 40.9-12.6 80.6-28.5 118.3zM887 484H743.5c-1.9-54.2-9.1-105.7-20.7-152.6 29.3-15.6 56.6-34.4 81.5-55.9A373.86 373.86 0 0 1 887 484zM658.3 165.5c39.7 16.8 75.8 40 107.6 69.2a394.72 394.72 0 0 1-59.4 41.8c-15.7-45-35.8-84.1-59.2-115.4 3.7 1.4 7.4 2.9 11 4.4zm-90.6 700.6c-9.2 7.2-18.4 12.7-27.7 16.4V697a389.1 389.1 0 0 1 115.7 26.2c-8.3 24.6-17.9 47.3-29 67.8-17.4 32.4-37.8 58.3-59 75.1zm59-633.1c11 20.6 20.7 43.3 29 67.8A389.1 389.1 0 0 1 540 327V141.6c9.2 3.7 18.5 9.1 27.7 16.4 21.2 16.7 41.6 42.6 59 75zM540 640.9V540h147.5c-1.6 44.2-7.1 87.1-16.3 127.8l-.3 1.2A445.02 445.02 0 0 0 540 640.9zm0-156.9V383.1c45.8-2.8 89.8-12.5 130.9-28.1l.3 1.2c9.2 40.7 14.7 83.5 16.3 127.8H540zm-56 56v100.9c-45.8 2.8-89.8 12.5-130.9 28.1l-.3-1.2c-9.2-40.7-14.7-83.5-16.3-127.8H484zm-147.5-56c1.6-44.2 7.1-87.1 16.3-127.8l.3-1.2c41.1 15.6 85 25.3 130.9 28.1V484H336.5zM484 697v185.4c-9.2-3.7-18.5-9.1-27.7-16.4-21.2-16.7-41.7-42.7-59.1-75.1-11-20.6-20.7-43.3-29-67.8 37.2-14.6 75.9-23.3 115.8-26.1zm0-370a389.1 389.1 0 0 1-115.7-26.2c8.3-24.6 17.9-47.3 29-67.8 17.4-32.4 37.8-58.4 59.1-75.1 9.2-7.2 18.4-12.7 27.7-16.4V327zM365.7 165.5c3.7-1.5 7.3-3 11-4.4-23.4 31.3-43.5 70.4-59.2 115.4-21-12-40.9-26-59.4-41.8 31.8-29.2 67.9-52.4 107.6-69.2zM165.5 365.7c13.8-32.6 32-62.8 54.2-90.2 24.9 21.5 52.2 40.3 81.5 55.9-11.6 46.9-18.8 98.4-20.7 152.6H137c3-40.9 12.6-80.6 28.5-118.3zM137 540h143.5c1.9 54.2 9.1 105.7 20.7 152.6a444.07 444.07 0 0 0-81.5 55.9A373.86 373.86 0 0 1 137 540zm228.7 318.5c-39.7-16.8-75.8-40-107.6-69.2 18.5-15.8 38.4-29.7 59.4-41.8 15.7 45 35.8 84.1 59.2 115.4-3.7-1.4-7.4-2.9-11-4.4zm292.6 0c-3.7 1.5-7.3 3-11 4.4 23.4-31.3 43.5-70.4 59.2-115.4 21 12 40.9 26 59.4 41.8a373.81 373.81 0 0 1-107.6 69.2z\")),t.GooglePlusOutline=u(\"google-plus\",i,l(o,\"M879.5 470.4c-.3-27-.4-54.2-.5-81.3h-80.8c-.3 27-.5 54.1-.7 81.3-27.2.1-54.2.3-81.2.6v80.9c27 .3 54.2.5 81.2.8.3 27 .3 54.1.5 81.1h80.9c.1-27 .3-54.1.5-81.3 27.2-.3 54.2-.4 81.2-.7v-80.9c-26.9-.2-54.1-.2-81.1-.5zm-530 .4c-.1 32.3 0 64.7.1 97 54.2 1.8 108.5 1 162.7 1.8-23.9 120.3-187.4 159.3-273.9 80.7-89-68.9-84.8-220 7.7-284 64.7-51.6 156.6-38.9 221.3 5.8 25.4-23.5 49.2-48.7 72.1-74.7-53.8-42.9-119.8-73.5-190-70.3-146.6-4.9-281.3 123.5-283.7 270.2-9.4 119.9 69.4 237.4 180.6 279.8 110.8 42.7 252.9 13.6 323.7-86 46.7-62.9 56.8-143.9 51.3-220-90.7-.7-181.3-.6-271.9-.3z\")),t.GoogleOutline=u(\"google\",i,l(o,\"M881 442.4H519.7v148.5h206.4c-8.9 48-35.9 88.6-76.6 115.8-34.4 23-78.3 36.6-129.9 36.6-99.9 0-184.4-67.5-214.6-158.2-7.6-23-12-47.6-12-72.9s4.4-49.9 12-72.9c30.3-90.6 114.8-158.1 214.7-158.1 56.3 0 106.8 19.4 146.6 57.4l110-110.1c-66.5-62-153.2-100-256.6-100-149.9 0-279.6 86-342.7 211.4-26 51.8-40.8 110.4-40.8 172.4S151 632.8 177 684.6C240.1 810 369.8 896 519.7 896c103.6 0 190.4-34.4 253.8-93 72.5-66.8 114.4-165.2 114.4-282.1 0-27.2-2.4-53.3-6.9-78.5z\")),t.HeatMapOutline=u(\"heat-map\",i,l(o,\"M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-790.4-23.9L512 231.9 858.7 832H165.3zm319-474.1l-228 394c-12.3 21.3 3.1 48 27.7 48h455.8c24.7 0 40.1-26.7 27.7-48L539.7 358c-6.2-10.7-17-16-27.7-16-10.8 0-21.6 5.3-27.7 16zm214 386H325.7L512 422l186.3 322zm-214-194.1l-57 98.4C415 669.5 430.4 696 455 696h114c24.6 0 39.9-26.5 27.7-47.7l-57-98.4c-6.1-10.6-16.9-15.9-27.7-15.9s-21.5 5.3-27.7 15.9zm57.1 98.4h-58.7l29.4-50.7 29.3 50.7z\")),t.GoldOutline=u(\"gold\",i,l(o,\"M342 472h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H382.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8zm91.2-196h159.5l20.7 128h-201l20.8-128zm2.5 282.7c-.6-3.9-4-6.7-7.9-6.7H166.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248zM196.5 748l20.7-128h159.5l20.7 128H196.5zm709.4 58.7l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H596.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.3-.7 7.3-4.8 6.6-9.2zM626.5 748l20.7-128h159.5l20.7 128H626.5z\")),t.HistoryOutline=u(\"history\",i,l(o,\"M536.1 273H488c-4.4 0-8 3.6-8 8v275.3c0 2.6 1.2 5 3.3 6.5l165.3 120.7c3.6 2.6 8.6 1.9 11.2-1.7l28.6-39c2.7-3.7 1.9-8.7-1.7-11.2L544.1 528.5V281c0-4.4-3.6-8-8-8zm219.8 75.2l156.8 38.3c5 1.2 9.9-2.6 9.9-7.7l.8-161.5c0-6.7-7.7-10.5-12.9-6.3L752.9 334.1a8 8 0 0 0 3 14.1zm167.7 301.1l-56.7-19.5a8 8 0 0 0-10.1 4.8c-1.9 5.1-3.9 10.1-6 15.1-17.8 42.1-43.3 80-75.9 112.5a353 353 0 0 1-112.5 75.9 352.18 352.18 0 0 1-137.7 27.8c-47.8 0-94.1-9.3-137.7-27.8a353 353 0 0 1-112.5-75.9c-32.5-32.5-58-70.4-75.9-112.5A353.44 353.44 0 0 1 171 512c0-47.8 9.3-94.2 27.8-137.8 17.8-42.1 43.3-80 75.9-112.5a353 353 0 0 1 112.5-75.9C430.6 167.3 477 158 524.8 158s94.1 9.3 137.7 27.8A353 353 0 0 1 775 261.7c10.2 10.3 19.8 21 28.6 32.3l59.8-46.8C784.7 146.6 662.2 81.9 524.6 82 285 82.1 92.6 276.7 95 516.4 97.4 751.9 288.9 942 524.8 942c185.5 0 343.5-117.6 403.7-282.3 1.5-4.2-.7-8.9-4.9-10.4z\")),t.IeOutline=u(\"ie\",i,l(o,\"M852.6 367.6c16.3-36.9 32.1-90.7 32.1-131.8 0-109.1-119.5-147.6-314.5-57.9-161.4-10.8-316.8 110.5-355.6 279.7 46.3-52.3 117.4-123.4 183-151.7C316.1 378.3 246.7 470 194 565.6c-31.1 56.9-66 148.8-66 217.5 0 147.9 139.3 129.8 270.4 63 47.1 23.1 99.8 23.4 152.5 23.4 145.7 0 276.4-81.4 325.2-219H694.9c-78.8 132.9-295.2 79.5-295.2-71.2h493.2c9.6-65.4-2.5-143.6-40.3-211.7zM224.8 648.3c26.6 76.7 80.6 143.8 150.4 185-133.1 73.4-259.9 43.6-150.4-185zm174-163.3c3-82.7 75.4-142.3 156-142.3 80.1 0 153 59.6 156 142.3h-312zm276.8-281.4c32.1-15.4 72.8-33 108.8-33 47.1 0 81.4 32.6 81.4 80.6 0 30-11.1 73.5-21.9 101.8-39.3-63.5-98.9-122.4-168.3-149.4z\")),t.InboxOutline=u(\"inbox\",i,l(r,\"M885.2 446.3l-.2-.8-112.2-285.1c-5-16.1-19.9-27.2-36.8-27.2H281.2c-17 0-32.1 11.3-36.9 27.6L139.4 443l-.3.7-.2.8c-1.3 4.9-1.7 9.9-1 14.8-.1 1.6-.2 3.2-.2 4.8V830a60.9 60.9 0 0 0 60.8 60.8h627.2c33.5 0 60.8-27.3 60.9-60.8V464.1c0-1.3 0-2.6-.1-3.7.4-4.9 0-9.6-1.3-14.1zm-295.8-43l-.3 15.7c-.8 44.9-31.8 75.1-77.1 75.1-22.1 0-41.1-7.1-54.8-20.6S436 441.2 435.6 419l-.3-15.7H229.5L309 210h399.2l81.7 193.3H589.4zm-375 76.8h157.3c24.3 57.1 76 90.8 140.4 90.8 33.7 0 65-9.4 90.3-27.2 22.2-15.6 39.5-37.4 50.7-63.6h156.5V814H214.4V480.1z\")),t.ImportOutline=u(\"import\",i,l(o,\"M888.3 757.4h-53.8c-4.2 0-7.7 3.5-7.7 7.7v61.8H197.1V197.1h629.8v61.8c0 4.2 3.5 7.7 7.7 7.7h53.8c4.2 0 7.7-3.4 7.7-7.7V158.7c0-17-13.7-30.7-30.7-30.7H158.7c-17 0-30.7 13.7-30.7 30.7v706.6c0 17 13.7 30.7 30.7 30.7h706.6c17 0 30.7-13.7 30.7-30.7V765.1c0-4.3-3.5-7.7-7.7-7.7zM902 476H588v-76c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-76h314c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.InfoOutline=u(\"info\",i,l(o,\"M448 224a64 64 0 1 0 128 0 64 64 0 1 0-128 0zm96 168h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V400c0-4.4-3.6-8-8-8z\")),t.ItalicOutline=u(\"italic\",i,l(o,\"M798 160H366c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h181.2l-156 544H229c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8H474.4l156-544H798c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z\")),t.IssuesCloseOutline=u(\"issues-close\",i,l(o,\"M464 688a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72-112c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48zm400-188h-59.3c-2.6 0-5 1.2-6.5 3.3L763.7 538.1l-49.9-68.8a7.92 7.92 0 0 0-6.5-3.3H648c-6.5 0-10.3 7.4-6.5 12.7l109.2 150.7a16.1 16.1 0 0 0 26 0l165.8-228.7c3.8-5.3 0-12.7-6.5-12.7zm-44 306h-64.2c-5.5 0-10.6 2.9-13.6 7.5a352.2 352.2 0 0 1-49.8 62.2A355.92 355.92 0 0 1 651.1 840a355 355 0 0 1-138.7 27.9c-48.1 0-94.8-9.4-138.7-27.9a355.92 355.92 0 0 1-113.3-76.3A353.06 353.06 0 0 1 184 650.5c-18.6-43.8-28-90.5-28-138.5s9.4-94.7 28-138.5c17.9-42.4 43.6-80.5 76.4-113.2 32.8-32.7 70.9-58.4 113.3-76.3a355 355 0 0 1 138.7-27.9c48.1 0 94.8 9.4 138.7 27.9 42.4 17.9 80.5 43.6 113.3 76.3 19 19 35.6 39.8 49.8 62.2 2.9 4.7 8.1 7.5 13.6 7.5H892c6 0 9.8-6.3 7.2-11.6C828.8 178.5 684.7 82 517.7 80 278.9 77.2 80.5 272.5 80 511.2 79.5 750.1 273.3 944 512.4 944c169.2 0 315.6-97 386.7-238.4A8 8 0 0 0 892 694z\")),t.KeyOutline=u(\"key\",i,l(o,\"M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5l-41.1 41.1-62.3-62.3a8.15 8.15 0 0 0-11.4 0l-39.8 39.8a8.15 8.15 0 0 0 0 11.4l62.3 62.3-44.9 44.9-62.3-62.3a8.15 8.15 0 0 0-11.4 0l-39.8 39.8a8.15 8.15 0 0 0 0 11.4l62.3 62.3-65.3 65.3a8.03 8.03 0 0 0 0 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6A304.06 304.06 0 0 0 608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644c-60.9 0-118.2-23.7-161.2-66.8-43.1-43-66.8-100.3-66.8-161.2 0-60.9 23.7-118.2 66.8-161.2 43-43.1 100.3-66.8 161.2-66.8 60.9 0 118.2 23.7 161.2 66.8 43.1 43 66.8 100.3 66.8 161.2 0 60.9-23.7 118.2-66.8 161.2z\")),t.LaptopOutline=u(\"laptop\",i,l(o,\"M956.9 845.1L896.4 632V168c0-17.7-14.3-32-32-32h-704c-17.7 0-32 14.3-32 32v464L67.9 845.1C60.4 866 75.8 888 98 888h828.8c22.2 0 37.6-22 30.1-42.9zM200.4 208h624v395h-624V208zm228.3 608l8.1-37h150.3l8.1 37H428.7zm224 0l-19.1-86.7c-.8-3.7-4.1-6.3-7.8-6.3H398.2c-3.8 0-7 2.6-7.8 6.3L371.3 816H151l42.3-149h638.2l42.3 149H652.7z\")),t.LeftOutline=u(\"left\",i,l(o,\"M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z\")),t.LinkOutline=u(\"link\",i,l(o,\"M574 665.4a8.03 8.03 0 0 0-11.3 0L446.5 781.6c-53.8 53.8-144.6 59.5-204 0-59.5-59.5-53.8-150.2 0-204l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3l-39.8-39.8a8.03 8.03 0 0 0-11.3 0L191.4 526.5c-84.6 84.6-84.6 221.5 0 306s221.5 84.6 306 0l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3L574 665.4zm258.6-474c-84.6-84.6-221.5-84.6-306 0L410.3 307.6a8.03 8.03 0 0 0 0 11.3l39.7 39.7c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c53.8-53.8 144.6-59.5 204 0 59.5 59.5 53.8 150.2 0 204L665.3 562.6a8.03 8.03 0 0 0 0 11.3l39.8 39.8c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c84.5-84.6 84.5-221.5 0-306.1zM610.1 372.3a8.03 8.03 0 0 0-11.3 0L372.3 598.7a8.03 8.03 0 0 0 0 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l226.4-226.4c3.1-3.1 3.1-8.2 0-11.3l-39.5-39.6z\")),t.LineChartOutline=u(\"line-chart\",i,l(o,\"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM305.8 637.7c3.1 3.1 8.1 3.1 11.3 0l138.3-137.6L583 628.5c3.1 3.1 8.2 3.1 11.3 0l275.4-275.3c3.1-3.1 3.1-8.2 0-11.3l-39.6-39.6a8.03 8.03 0 0 0-11.3 0l-230 229.9L461.4 404a8.03 8.03 0 0 0-11.3 0L266.3 586.7a8.03 8.03 0 0 0 0 11.3l39.5 39.7z\")),t.LineHeightOutline=u(\"line-height\",i,l(o,\"M648 160H104c-4.4 0-8 3.6-8 8v128c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-64h168v560h-92c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-92V232h168v64c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V168c0-4.4-3.6-8-8-8zm272.8 546H856V318h64.8c6 0 9.4-7 5.7-11.7L825.7 178.7a7.14 7.14 0 0 0-11.3 0L713.6 306.3a7.23 7.23 0 0 0 5.7 11.7H784v388h-64.8c-6 0-9.4 7-5.7 11.7l100.8 127.5c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5a7.2 7.2 0 0 0-5.6-11.7z\")),t.LineOutline=u(\"line\",i,l(o,\"M904 476H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.Loading3QuartersOutline=u(\"loading-3-quarters\",i,l(r,\"M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z\")),t.LoadingOutline=u(\"loading\",i,l(r,\"M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z\")),t.LoginOutline=u(\"login\",i,l(o,\"M521.7 82c-152.5-.4-286.7 78.5-363.4 197.7-3.4 5.3.4 12.3 6.7 12.3h70.3c4.8 0 9.3-2.1 12.3-5.8 7-8.5 14.5-16.7 22.4-24.5 32.6-32.5 70.5-58.1 112.7-75.9 43.6-18.4 90-27.8 137.9-27.8 47.9 0 94.3 9.3 137.9 27.8 42.2 17.8 80.1 43.4 112.7 75.9 32.6 32.5 58.1 70.4 76 112.5C865.7 417.8 875 464.1 875 512c0 47.9-9.4 94.2-27.8 137.8-17.8 42.1-43.4 80-76 112.5s-70.5 58.1-112.7 75.9A352.8 352.8 0 0 1 520.6 866c-47.9 0-94.3-9.4-137.9-27.8A353.84 353.84 0 0 1 270 762.3c-7.9-7.9-15.3-16.1-22.4-24.5-3-3.7-7.6-5.8-12.3-5.8H165c-6.3 0-10.2 7-6.7 12.3C234.9 863.2 368.5 942 520.6 942c236.2 0 428-190.1 430.4-425.6C953.4 277.1 761.3 82.6 521.7 82zM395.02 624v-76h-314c-4.4 0-8-3.6-8-8v-56c0-4.4 3.6-8 8-8h314v-76c0-6.7 7.8-10.5 13-6.3l141.9 112a8 8 0 0 1 0 12.6l-141.9 112c-5.2 4.1-13 .4-13-6.3z\")),t.LogoutOutline=u(\"logout\",i,l(o,\"M868 732h-70.3c-4.8 0-9.3 2.1-12.3 5.8-7 8.5-14.5 16.7-22.4 24.5a353.84 353.84 0 0 1-112.7 75.9A352.8 352.8 0 0 1 512.4 866c-47.9 0-94.3-9.4-137.9-27.8a353.84 353.84 0 0 1-112.7-75.9 353.28 353.28 0 0 1-76-112.5C167.3 606.2 158 559.9 158 512s9.4-94.2 27.8-137.8c17.8-42.1 43.4-80 76-112.5s70.5-58.1 112.7-75.9c43.6-18.4 90-27.8 137.9-27.8 47.9 0 94.3 9.3 137.9 27.8 42.2 17.8 80.1 43.4 112.7 75.9 7.9 7.9 15.3 16.1 22.4 24.5 3 3.7 7.6 5.8 12.3 5.8H868c6.3 0 10.2-7 6.7-12.3C798 160.5 663.8 81.6 511.3 82 271.7 82.6 79.6 277.1 82 516.4 84.4 751.9 276.2 942 512.4 942c152.1 0 285.7-78.8 362.3-197.7 3.4-5.3-.4-12.3-6.7-12.3zm88.9-226.3L815 393.7c-5.3-4.2-13-.4-13 6.3v76H488c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h314v76c0 6.7 7.8 10.5 13 6.3l141.9-112a8 8 0 0 0 0-12.6z\")),t.ManOutline=u(\"man\",i,l(o,\"M874 120H622c-3.3 0-6 2.7-6 6v56c0 3.3 2.7 6 6 6h160.4L583.1 387.3c-50-38.5-111-59.3-175.1-59.3-76.9 0-149.3 30-203.6 84.4S120 539.1 120 616s30 149.3 84.4 203.6C258.7 874 331.1 904 408 904s149.3-30 203.6-84.4C666 765.3 696 692.9 696 616c0-64.1-20.8-124.9-59.2-174.9L836 241.9V402c0 3.3 2.7 6 6 6h56c3.3 0 6-2.7 6-6V150c0-16.5-13.5-30-30-30zM408 828c-116.9 0-212-95.1-212-212s95.1-212 212-212 212 95.1 212 212-95.1 212-212 212z\")),t.MediumOutline=u(\"medium\",i,l(o,\"M834.7 279.8l61.3-58.9V208H683.7L532.4 586.4 360.3 208H137.7v12.9l71.6 86.6c7 6.4 10.6 15.8 9.7 25.2V673c2.2 12.3-1.7 24.8-10.3 33.7L128 805v12.7h228.6v-12.9l-80.6-98a39.99 39.99 0 0 1-11.1-33.7V378.7l200.7 439.2h23.3l172.6-439.2v349.9c0 9.2 0 11.1-6 17.2l-62.1 60.3V819h301.2v-12.9l-59.9-58.9c-5.2-4-7.9-10.7-6.8-17.2V297a18.1 18.1 0 0 1 6.8-17.2z\")),t.MediumWorkmarkOutline=u(\"medium-workmark\",i,l(r,\"M517.2 590.55c0 3.55 0 4.36 2.4 6.55l13.43 13.25v.57h-59.57v-25.47a41.44 41.44 0 0 1-39.5 27.65c-30.61 0-52.84-24.25-52.84-68.87 0-41.8 23.99-69.69 57.65-69.69a35.15 35.15 0 0 1 34.61 21.67v-56.19a6.99 6.99 0 0 0-2.71-6.79l-12.8-12.45v-.56l59.33-7.04v177.37zm-43.74-8.09v-83.83a22.2 22.2 0 0 0-17.74-8.4c-14.48 0-28.47 13.25-28.47 52.62 0 36.86 12.07 49.88 27.1 49.88a23.91 23.91 0 0 0 19.11-10.27zm83.23 28.46V497.74a7.65 7.65 0 0 0-2.4-6.79l-13.19-13.74v-.57h59.56v114.8c0 3.55 0 4.36 2.4 6.54l13.12 12.45v.57l-59.49-.08zm-2.16-175.67c0-13.4 10.74-24.25 23.99-24.25 13.25 0 23.98 10.86 23.98 24.25 0 13.4-10.73 24.25-23.98 24.25s-23.99-10.85-23.99-24.25zm206.83 155.06c0 3.55 0 4.6 2.4 6.79l13.43 13.25v.57h-59.88V581.9a43.4 43.4 0 0 1-41.01 31.2c-26.55 0-40.78-19.56-40.78-56.59 0-17.86 0-37.43.56-59.41a6.91 6.91 0 0 0-2.4-6.55L620.5 477.2v-.57h59.09v73.81c0 24.25 3.51 40.42 18.54 40.42a23.96 23.96 0 0 0 19.35-12.2v-80.85a7.65 7.65 0 0 0-2.4-6.79l-13.27-13.82v-.57h59.56V590.3zm202.76 20.6c0-4.36.8-59.97.8-72.75 0-24.25-3.76-40.98-20.63-40.98a26.7 26.7 0 0 0-21.19 11.64 99.68 99.68 0 0 1 2.4 23.04c0 16.81-.56 38.23-.8 59.66a6.91 6.91 0 0 0 2.4 6.55l13.43 12.45v.56h-60.12c0-4.04.8-59.98.8-72.76 0-24.65-3.76-40.98-20.39-40.98-8.2.3-15.68 4.8-19.83 11.96v82.46c0 3.56 0 4.37 2.4 6.55l13.11 12.45v.56h-59.48V498.15a7.65 7.65 0 0 0-2.4-6.8l-13.19-14.14v-.57H841v28.78c5.53-19 23.13-31.76 42.7-30.96 19.82 0 33.26 11.16 38.93 32.34a46.41 46.41 0 0 1 44.77-32.34c26.55 0 41.58 19.8 41.58 57.23 0 17.87-.56 38.24-.8 59.66a6.5 6.5 0 0 0 2.72 6.55l13.11 12.45v.57h-59.88zM215.87 593.3l17.66 17.05v.57h-89.62v-.57l17.99-17.05a6.91 6.91 0 0 0 2.4-6.55V477.69c0-4.6 0-10.83.8-16.16L104.66 613.1h-.72l-62.6-139.45c-1.37-3.47-1.77-3.72-2.65-6.06v91.43a32.08 32.08 0 0 0 2.96 17.87l25.19 33.46v.57H0v-.57l25.18-33.55a32.16 32.16 0 0 0 2.96-17.78V457.97A19.71 19.71 0 0 0 24 444.15L6.16 420.78v-.56h63.96l53.56 118.1 47.17-118.1h62.6v.56l-17.58 19.8a6.99 6.99 0 0 0-2.72 6.8v139.37a6.5 6.5 0 0 0 2.72 6.55zm70.11-54.65v.56c0 34.6 17.67 48.5 38.38 48.5a43.5 43.5 0 0 0 40.77-24.97h.56c-7.2 34.2-28.14 50.36-59.48 50.36-33.82 0-65.72-20.61-65.72-68.39 0-50.2 31.98-70.25 67.32-70.25 28.46 0 58.76 13.58 58.76 57.24v6.95h-80.59zm0-6.95h39.42v-7.04c0-35.57-7.28-45.03-18.23-45.03-13.27 0-21.35 14.15-21.35 52.07h.16z\")),t.MenuUnfoldOutline=u(\"menu-unfold\",i,l(o,\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z\")),t.MenuFoldOutline=u(\"menu-fold\",i,l(o,\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\")),t.MenuOutline=u(\"menu\",i,l(o,\"M904 160H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0 624H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8zm0-312H120c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z\")),t.MinusOutline=u(\"minus\",i,l(o,\"M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z\")),t.MonitorOutline=u(\"monitor\",i,l(o,\"M692.8 412.7l.2-.2-34.6-44.3a7.97 7.97 0 0 0-11.2-1.4l-50.4 39.3-70.5-90.1a7.97 7.97 0 0 0-11.2-1.4l-37.9 29.7a7.97 7.97 0 0 0-1.4 11.2l70.5 90.2-.2.1 34.6 44.3c2.7 3.5 7.7 4.1 11.2 1.4l50.4-39.3 64.1 82c2.7 3.5 7.7 4.1 11.2 1.4l37.9-29.6c3.5-2.7 4.1-7.7 1.4-11.2l-64.1-82.1zM608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5L114.3 856.1a8.03 8.03 0 0 0 0 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6C473 696.1 537.7 720 608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644s-118.2-23.7-161.2-66.8C403.7 534.2 380 476.9 380 416s23.7-118.2 66.8-161.2c43-43.1 100.3-66.8 161.2-66.8s118.2 23.7 161.2 66.8c43.1 43 66.8 100.3 66.8 161.2s-23.7 118.2-66.8 161.2z\")),t.MoreOutline=u(\"more\",i,l(o,\"M456 231a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 280a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 280a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.OrderedListOutline=u(\"ordered-list\",i,l(o,\"M920 760H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-568H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM216 712H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h72.4v20.5h-35.7c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h35.7V838H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4V716c0-2.2-1.8-4-4-4zM100 188h38v120c0 2.2 1.8 4 4 4h40c2.2 0 4-1.8 4-4V152c0-4.4-3.6-8-8-8h-78c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4zm116 240H100c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4h68.4l-70.3 77.7a8.3 8.3 0 0 0-2.1 5.4V592c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4v-36c0-2.2-1.8-4-4-4h-68.4l70.3-77.7a8.3 8.3 0 0 0 2.1-5.4V432c0-2.2-1.8-4-4-4z\")),t.NumberOutline=u(\"number\",i,l(o,\"M872 394c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H400V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v236H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h228v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h164c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V394h164zM628 630H400V394h228v236z\")),t.PauseOutline=u(\"pause\",i,l(o,\"M304 176h80v672h-80zm408 0h-64c-4.4 0-8 3.6-8 8v656c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V184c0-4.4-3.6-8-8-8z\")),t.PercentageOutline=u(\"percentage\",i,l(o,\"M855.7 210.8l-42.4-42.4a8.03 8.03 0 0 0-11.3 0L168.3 801.9a8.03 8.03 0 0 0 0 11.3l42.4 42.4c3.1 3.1 8.2 3.1 11.3 0L855.6 222c3.2-3 3.2-8.1.1-11.2zM304 448c79.4 0 144-64.6 144-144s-64.6-144-144-144-144 64.6-144 144 64.6 144 144 144zm0-216c39.7 0 72 32.3 72 72s-32.3 72-72 72-72-32.3-72-72 32.3-72 72-72zm416 344c-79.4 0-144 64.6-144 144s64.6 144 144 144 144-64.6 144-144-64.6-144-144-144zm0 216c-39.7 0-72-32.3-72-72s32.3-72 72-72 72 32.3 72 72-32.3 72-72 72z\")),t.PaperClipOutline=u(\"paper-clip\",i,l(o,\"M779.3 196.6c-94.2-94.2-247.6-94.2-341.7 0l-261 260.8c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0 0 12.7 0l261-260.8c32.4-32.4 75.5-50.2 121.3-50.2s88.9 17.8 121.2 50.2c32.4 32.4 50.2 75.5 50.2 121.2 0 45.8-17.8 88.8-50.2 121.2l-266 265.9-43.1 43.1c-40.3 40.3-105.8 40.3-146.1 0-19.5-19.5-30.2-45.4-30.2-73s10.7-53.5 30.2-73l263.9-263.8c6.7-6.6 15.5-10.3 24.9-10.3h.1c9.4 0 18.1 3.7 24.7 10.3 6.7 6.7 10.3 15.5 10.3 24.9 0 9.3-3.7 18.1-10.3 24.7L372.4 653c-1.7 1.7-2.6 4-2.6 6.4s.9 4.7 2.6 6.4l36.9 36.9a9 9 0 0 0 12.7 0l215.6-215.6c19.9-19.9 30.8-46.3 30.8-74.4s-11-54.6-30.8-74.4c-41.1-41.1-107.9-41-149 0L463 364 224.8 602.1A172.22 172.22 0 0 0 174 724.8c0 46.3 18.1 89.8 50.8 122.5 33.9 33.8 78.3 50.7 122.7 50.7 44.4 0 88.8-16.9 122.6-50.7l309.2-309C824.8 492.7 850 432 850 367.5c.1-64.6-25.1-125.3-70.7-170.9z\")),t.PicCenterOutline=u(\"pic-center\",i,l(o,\"M952 792H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-632H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM848 660c8.8 0 16-7.2 16-16V380c0-8.8-7.2-16-16-16H176c-8.8 0-16 7.2-16 16v264c0 8.8 7.2 16 16 16h672zM232 436h560v152H232V436z\")),t.PicLeftOutline=u(\"pic-left\",i,l(o,\"M952 792H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-632H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM608 660c8.8 0 16-7.2 16-16V380c0-8.8-7.2-16-16-16H96c-8.8 0-16 7.2-16 16v264c0 8.8 7.2 16 16 16h512zM152 436h400v152H152V436zm552 210c0 4.4 3.6 8 8 8h224c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H712c-4.4 0-8 3.6-8 8v56zm8-204h224c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H712c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8z\")),t.PlusOutline=u(\"plus\",i,l(o,\"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z\",\"M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z\")),t.PicRightOutline=u(\"pic-right\",i,l(o,\"M952 792H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-632H72c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h880c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-24 500c8.8 0 16-7.2 16-16V380c0-8.8-7.2-16-16-16H416c-8.8 0-16 7.2-16 16v264c0 8.8 7.2 16 16 16h512zM472 436h400v152H472V436zM80 646c0 4.4 3.6 8 8 8h224c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H88c-4.4 0-8 3.6-8 8v56zm8-204h224c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H88c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8z\")),t.PoundOutline=u(\"pound\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm138-209.8H469.8v-4.7c27.4-17.2 43.9-50.4 43.9-91.1 0-14.1-2.2-27.9-5.3-41H607c4.4 0 8-3.6 8-8v-30c0-4.4-3.6-8-8-8H495c-7.2-22.6-13.4-45.7-13.4-70.5 0-43.5 34-70.2 87.3-70.2 21.5 0 42.5 4.1 60.4 10.5 5.2 1.9 10.6-2 10.6-7.6v-39.5c0-3.3-2.1-6.3-5.2-7.5-18.8-7.2-43.8-12.7-70.3-12.7-92.9 0-151.5 44.5-151.5 120.3 0 26.3 6.9 52 14.6 77.1H374c-4.4 0-8 3.6-8 8v30c0 4.4 3.6 8 8 8h67.1c3.4 14.7 5.9 29.4 5.9 44.2 0 45.2-28.8 83.3-72.8 94.2-3.6.9-6.1 4.1-6.1 7.8V722c0 4.4 3.6 8 8 8H650c4.4 0 8-3.6 8-8v-39.8c0-4.4-3.6-8-8-8z\")),t.PoweroffOutline=u(\"poweroff\",i,l(o,\"M705.6 124.9a8 8 0 0 0-11.6 7.2v64.2c0 5.5 2.9 10.6 7.5 13.6a352.2 352.2 0 0 1 62.2 49.8c32.7 32.8 58.4 70.9 76.3 113.3a355 355 0 0 1 27.9 138.7c0 48.1-9.4 94.8-27.9 138.7a355.92 355.92 0 0 1-76.3 113.3 353.06 353.06 0 0 1-113.2 76.4c-43.8 18.6-90.5 28-138.5 28s-94.7-9.4-138.5-28a353.06 353.06 0 0 1-113.2-76.4A355.92 355.92 0 0 1 184 650.4a355 355 0 0 1-27.9-138.7c0-48.1 9.4-94.8 27.9-138.7 17.9-42.4 43.6-80.5 76.3-113.3 19-19 39.8-35.6 62.2-49.8 4.7-2.9 7.5-8.1 7.5-13.6V132c0-6-6.3-9.8-11.6-7.2C178.5 195.2 82 339.3 80 506.3 77.2 745.1 272.5 943.5 511.2 944c239 .5 432.8-193.3 432.8-432.4 0-169.2-97-315.7-238.4-386.7zM480 560h64c4.4 0 8-3.6 8-8V88c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8z\")),t.PullRequestOutline=u(\"pull-request\",i,l(o,\"M788 705.9V192c0-8.8-7.2-16-16-16H602v-68.8c0-6-7-9.4-11.7-5.7L462.7 202.3a7.14 7.14 0 0 0 0 11.3l127.5 100.8c4.7 3.7 11.7.4 11.7-5.7V240h114v465.9c-44.2 15-76 56.9-76 106.1 0 61.8 50.2 112 112 112s112-50.2 112-112c.1-49.2-31.7-91-75.9-106.1zM752 860a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96zM384 212c0-61.8-50.2-112-112-112s-112 50.2-112 112c0 49.2 31.8 91 76 106.1V706c-44.2 15-76 56.9-76 106.1 0 61.8 50.2 112 112 112s112-50.2 112-112c0-49.2-31.8-91-76-106.1V318.1c44.2-15.1 76-56.9 76-106.1zm-160 0a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm96 600a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0z\")),t.QqOutline=u(\"qq\",i,l(o,\"M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112 331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3-34 109.5-23 154.8-14.6 155.8 18 2.2 70.1-82.4 70.1-82.4 0 49 25.2 112.9 79.8 159-26.4 8.1-85.7 29.9-71.6 53.8 11.4 19.3 196.2 12.3 249.5 6.3 53.3 6 238.1 13 249.5-6.3 14.1-23.8-45.3-45.7-71.6-53.8 54.6-46.2 79.8-110.1 79.8-159 0 0 52.1 84.6 70.1 82.4 8.5-1.1 19.5-46.4-14.5-155.8z\")),t.QuestionOutline=u(\"question\",i,l(o,\"M764 280.9c-14-30.6-33.9-58.1-59.3-81.6C653.1 151.4 584.6 125 512 125s-141.1 26.4-192.7 74.2c-25.4 23.6-45.3 51-59.3 81.7-14.6 32-22 65.9-22 100.9v27c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-27c0-99.5 88.6-180.4 197.6-180.4s197.6 80.9 197.6 180.4c0 40.8-14.5 79.2-42 111.2-27.2 31.7-65.6 54.4-108.1 64-24.3 5.5-46.2 19.2-61.7 38.8a110.85 110.85 0 0 0-23.9 68.6v31.4c0 6.2 5 11.2 11.2 11.2h54c6.2 0 11.2-5 11.2-11.2v-31.4c0-15.7 10.9-29.5 26-32.9 58.4-13.2 111.4-44.7 149.3-88.7 19.1-22.3 34-47.1 44.3-74 10.7-27.9 16.1-57.2 16.1-87 0-35-7.4-69-22-100.9zM512 787c-30.9 0-56 25.1-56 56s25.1 56 56 56 56-25.1 56-56-25.1-56-56-56z\")),t.RadarChartOutline=u(\"radar-chart\",i,l(o,\"M926.8 397.1l-396-288a31.81 31.81 0 0 0-37.6 0l-396 288a31.99 31.99 0 0 0-11.6 35.8l151.3 466a32 32 0 0 0 30.4 22.1h489.5c13.9 0 26.1-8.9 30.4-22.1l151.3-466c4.2-13.2-.5-27.6-11.7-35.8zM838.6 417l-98.5 32-200-144.7V199.9L838.6 417zM466 567.2l-89.1 122.3-55.2-169.2L466 567.2zm-116.3-96.8L484 373.3v140.8l-134.3-43.7zM512 599.2l93.9 128.9H418.1L512 599.2zm28.1-225.9l134.2 97.1L540.1 514V373.3zM558 567.2l144.3-46.9-55.2 169.2L558 567.2zm-74-367.3v104.4L283.9 449l-98.5-32L484 199.9zM169.3 470.8l86.5 28.1 80.4 246.4-53.8 73.9-113.1-348.4zM327.1 853l50.3-69h269.3l50.3 69H327.1zm414.5-33.8l-53.8-73.9 80.4-246.4 86.5-28.1-113.1 348.4z\")),t.QrcodeOutline=u(\"qrcode\",i,l(o,\"M468 128H160c-17.7 0-32 14.3-32 32v308c0 4.4 3.6 8 8 8h332c4.4 0 8-3.6 8-8V136c0-4.4-3.6-8-8-8zm-56 284H192V192h220v220zm-138-74h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm194 210H136c-4.4 0-8 3.6-8 8v308c0 17.7 14.3 32 32 32h308c4.4 0 8-3.6 8-8V556c0-4.4-3.6-8-8-8zm-56 284H192V612h220v220zm-138-74h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm590-630H556c-4.4 0-8 3.6-8 8v332c0 4.4 3.6 8 8 8h332c4.4 0 8-3.6 8-8V160c0-17.7-14.3-32-32-32zm-32 284H612V192h220v220zm-138-74h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm194 210h-48c-4.4 0-8 3.6-8 8v134h-78V556c0-4.4-3.6-8-8-8H556c-4.4 0-8 3.6-8 8v332c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V644h78v102c0 4.4 3.6 8 8 8h190c4.4 0 8-3.6 8-8V556c0-4.4-3.6-8-8-8zM746 832h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm142 0h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\")),t.RadiusBottomleftOutline=u(\"radius-bottomleft\",i,l(o,\"M712 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm2-696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM136 374h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0-174h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm752 624h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-230 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm230 624H358c-87.3 0-158-70.7-158-158V484c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v182c0 127 103 230 230 230h182c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.RadiusBottomrightOutline=u(\"radius-bottomright\",i,l(o,\"M368 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-58-624h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm578 102h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm292 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm174 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm230 276h-56c-4.4 0-8 3.6-8 8v182c0 87.3-70.7 158-158 158H484c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h182c127 0 230-103 230-230V484c0-4.4-3.6-8-8-8z\")),t.RadiusUpleftOutline=u(\"radius-upleft\",i,l(o,\"M656 200h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm58 624h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 650h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm696-696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-174 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm174-696H358c-127 0-230 103-230 230v182c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V358c0-87.3 70.7-158 158-158h182c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.RadiusUprightOutline=u(\"radius-upright\",i,l(o,\"M368 128h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-2 696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm522-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 128h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm174 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-48-696H484c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h182c87.3 0 158 70.7 158 158v182c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V358c0-127-103-230-230-230z\")),t.RadiusSettingOutline=u(\"radius-setting\",i,l(o,\"M396 140h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-44 684h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm524-204h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 344h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 160h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 160h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 160h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm320 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm160 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm140-284c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V370c0-127-103-230-230-230H484c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h170c87.3 0 158 70.7 158 158v170zM236 96H92c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8h144c4.4 0 8-3.6 8-8V104c0-4.4-3.6-8-8-8zm-48 101.6c0 1.3-1.1 2.4-2.4 2.4h-43.2c-1.3 0-2.4-1.1-2.4-2.4v-43.2c0-1.3 1.1-2.4 2.4-2.4h43.2c1.3 0 2.4 1.1 2.4 2.4v43.2zM920 780H776c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8h144c4.4 0 8-3.6 8-8V788c0-4.4-3.6-8-8-8zm-48 101.6c0 1.3-1.1 2.4-2.4 2.4h-43.2c-1.3 0-2.4-1.1-2.4-2.4v-43.2c0-1.3 1.1-2.4 2.4-2.4h43.2c1.3 0 2.4 1.1 2.4 2.4v43.2z\")),t.RedditOutline=u(\"reddit\",i,l(o,\"M288 568a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm338.7 119.7c-23.1 18.2-68.9 37.8-114.7 37.8s-91.6-19.6-114.7-37.8c-14.4-11.3-35.3-8.9-46.7 5.5s-8.9 35.3 5.5 46.7C396.3 771.6 457.5 792 512 792s115.7-20.4 155.9-52.1a33.25 33.25 0 1 0-41.2-52.2zM960 456c0-61.9-50.1-112-112-112-42.1 0-78.7 23.2-97.9 57.6-57.6-31.5-127.7-51.8-204.1-56.5L612.9 195l127.9 36.9c11.5 32.6 42.6 56.1 79.2 56.1 46.4 0 84-37.6 84-84s-37.6-84-84-84c-32 0-59.8 17.9-74 44.2L603.5 123a33.2 33.2 0 0 0-39.6 18.4l-90.8 203.9c-74.5 5.2-142.9 25.4-199.2 56.2A111.94 111.94 0 0 0 176 344c-61.9 0-112 50.1-112 112 0 45.8 27.5 85.1 66.8 102.5-7.1 21-10.8 43-10.8 65.5 0 154.6 175.5 280 392 280s392-125.4 392-280c0-22.6-3.8-44.5-10.8-65.5C932.5 541.1 960 501.8 960 456zM820 172.5a31.5 31.5 0 1 1 0 63 31.5 31.5 0 0 1 0-63zM120 456c0-30.9 25.1-56 56-56a56 56 0 0 1 50.6 32.1c-29.3 22.2-53.5 47.8-71.5 75.9a56.23 56.23 0 0 1-35.1-52zm392 381.5c-179.8 0-325.5-95.6-325.5-213.5S332.2 410.5 512 410.5 837.5 506.1 837.5 624 691.8 837.5 512 837.5zM868.8 508c-17.9-28.1-42.2-53.7-71.5-75.9 9-18.9 28.3-32.1 50.6-32.1 30.9 0 56 25.1 56 56 .1 23.5-14.5 43.7-35.1 52zM624 568a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.RedoOutline=u(\"redo\",i,l(o,\"M758.2 839.1C851.8 765.9 912 651.9 912 523.9 912 303 733.5 124.3 512.6 124 291.4 123.7 112 302.8 112 523.9c0 125.2 57.5 236.9 147.6 310.2 3.5 2.8 8.6 2.2 11.4-1.3l39.4-50.5c2.7-3.4 2.1-8.3-1.2-11.1-8.1-6.6-15.9-13.7-23.4-21.2a318.64 318.64 0 0 1-68.6-101.7C200.4 609 192 567.1 192 523.9s8.4-85.1 25.1-124.5c16.1-38.1 39.2-72.3 68.6-101.7 29.4-29.4 63.6-52.5 101.7-68.6C426.9 212.4 468.8 204 512 204s85.1 8.4 124.5 25.1c38.1 16.1 72.3 39.2 101.7 68.6 29.4 29.4 52.5 63.6 68.6 101.7 16.7 39.4 25.1 81.3 25.1 124.5s-8.4 85.1-25.1 124.5a318.64 318.64 0 0 1-68.6 101.7c-9.3 9.3-19.1 18-29.3 26L668.2 724a8 8 0 0 0-14.1 3l-39.6 162.2c-1.2 5 2.6 9.9 7.7 9.9l167 .8c6.7 0 10.5-7.7 6.3-12.9l-37.3-47.9z\")),t.ReloadOutline=u(\"reload\",i,l(o,\"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 0 0-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 0 1 655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 0 1 279 755.2a342.16 342.16 0 0 1-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 0 1 109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 0 0 3 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z\")),t.RetweetOutline=u(\"retweet\",i,l(r,\"M136 552h63.6c4.4 0 8-3.6 8-8V288.7h528.6v72.6c0 1.9.6 3.7 1.8 5.2a8.3 8.3 0 0 0 11.7 1.4L893 255.4c4.3-5 3.6-10.3 0-13.2L749.7 129.8a8.22 8.22 0 0 0-5.2-1.8c-4.6 0-8.4 3.8-8.4 8.4V209H199.7c-39.5 0-71.7 32.2-71.7 71.8V544c0 4.4 3.6 8 8 8zm752-80h-63.6c-4.4 0-8 3.6-8 8v255.3H287.8v-72.6c0-1.9-.6-3.7-1.8-5.2a8.3 8.3 0 0 0-11.7-1.4L131 768.6c-4.3 5-3.6 10.3 0 13.2l143.3 112.4c1.5 1.2 3.3 1.8 5.2 1.8 4.6 0 8.4-3.8 8.4-8.4V815h536.6c39.5 0 71.7-32.2 71.7-71.8V480c-.2-4.4-3.8-8-8.2-8z\")),t.RightOutline=u(\"right\",i,l(o,\"M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z\")),t.RiseOutline=u(\"rise\",i,l(o,\"M917 211.1l-199.2 24c-6.6.8-9.4 8.9-4.7 13.6l59.3 59.3-226 226-101.8-101.7c-6.3-6.3-16.4-6.2-22.6 0L100.3 754.1a8.03 8.03 0 0 0 0 11.3l45 45.2c3.1 3.1 8.2 3.1 11.3 0L433.3 534 535 635.7c6.3 6.2 16.4 6.2 22.6 0L829 364.5l59.3 59.3a8.01 8.01 0 0 0 13.6-4.7l24-199.2c.7-5.1-3.7-9.5-8.9-8.8z\")),t.RollbackOutline=u(\"rollback\",i,l(o,\"M793 242H366v-74c0-6.7-7.7-10.4-12.9-6.3l-142 112a8 8 0 0 0 0 12.6l142 112c5.2 4.1 12.9.4 12.9-6.3v-74h415v470H175c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h618c35.3 0 64-28.7 64-64V306c0-35.3-28.7-64-64-64z\")),t.SafetyOutline=u(\"safety\",i,l(r,\"M512 64L128 192v384c0 212.1 171.9 384 384 384s384-171.9 384-384V192L512 64zm312 512c0 172.3-139.7 312-312 312S200 748.3 200 576V246l312-110 312 110v330z\",\"M378.4 475.1a35.91 35.91 0 0 0-50.9 0 35.91 35.91 0 0 0 0 50.9l129.4 129.4 2.1 2.1a33.98 33.98 0 0 0 48.1 0L730.6 434a33.98 33.98 0 0 0 0-48.1l-2.8-2.8a33.98 33.98 0 0 0-48.1 0L483 579.7 378.4 475.1z\")),t.RobotOutline=u(\"robot\",i,l(o,\"M300 328a60 60 0 1 0 120 0 60 60 0 1 0-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 1 0 120 0 60 60 0 1 0-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z\")),t.SearchOutline=u(\"search\",i,l(o,\"M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z\")),t.ScanOutline=u(\"scan\",i,l(o,\"M136 384h56c4.4 0 8-3.6 8-8V200h176c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H196c-37.6 0-68 30.4-68 68v180c0 4.4 3.6 8 8 8zm512-184h176v176c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V196c0-37.6-30.4-68-68-68H648c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM376 824H200V648c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v180c0 37.6 30.4 68 68 68h180c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm512-184h-56c-4.4 0-8 3.6-8 8v176H648c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h180c37.6 0 68-30.4 68-68V648c0-4.4-3.6-8-8-8zm16-164H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.ScissorOutline=u(\"scissor\",i,l(o,\"M567.1 512l318.5-319.3c5-5 1.5-13.7-5.6-13.7h-90.5c-2.1 0-4.2.8-5.6 2.3l-273.3 274-90.2-90.5c12.5-22.1 19.7-47.6 19.7-74.8 0-83.9-68.1-152-152-152s-152 68.1-152 152 68.1 152 152 152c27.7 0 53.6-7.4 75.9-20.3l90 90.3-90.1 90.3A151.04 151.04 0 0 0 288 582c-83.9 0-152 68.1-152 152s68.1 152 152 152 152-68.1 152-152c0-27.2-7.2-52.7-19.7-74.8l90.2-90.5 273.3 274c1.5 1.5 3.5 2.3 5.6 2.3H880c7.1 0 10.7-8.6 5.6-13.7L567.1 512zM288 370c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80zm0 444c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z\")),t.SelectOutline=u(\"select\",i,l(o,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h360c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H184V184h656v320c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V144c0-17.7-14.3-32-32-32zM653.3 599.4l52.2-52.2a8.01 8.01 0 0 0-4.7-13.6l-179.4-21c-5.1-.6-9.5 3.7-8.9 8.9l21 179.4c.8 6.6 8.9 9.4 13.6 4.7l52.4-52.4 256.2 256.2c3.1 3.1 8.2 3.1 11.3 0l42.4-42.4c3.1-3.1 3.1-8.2 0-11.3L653.3 599.4z\")),t.ShakeOutline=u(\"shake\",i,l(o,\"M324 666a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm616.7-309.6L667.6 83.2C655.2 70.9 638.7 64 621.1 64s-34.1 6.8-46.5 19.2L83.3 574.5a65.85 65.85 0 0 0 0 93.1l273.2 273.2c12.3 12.3 28.9 19.2 46.5 19.2s34.1-6.8 46.5-19.2l491.3-491.3c25.6-25.7 25.6-67.5-.1-93.1zM403 880.1L143.9 621l477.2-477.2 259 259.2L403 880.1zM152.8 373.7a7.9 7.9 0 0 0 11.2 0L373.7 164a7.9 7.9 0 0 0 0-11.2l-38.4-38.4a7.9 7.9 0 0 0-11.2 0L114.3 323.9a7.9 7.9 0 0 0 0 11.2l38.5 38.6zm718.6 276.6a7.9 7.9 0 0 0-11.2 0L650.3 860.1a7.9 7.9 0 0 0 0 11.2l38.4 38.4a7.9 7.9 0 0 0 11.2 0L909.7 700a7.9 7.9 0 0 0 0-11.2l-38.3-38.5z\")),t.ShareAltOutline=u(\"share-alt\",i,l(o,\"M752 664c-28.5 0-54.8 10-75.4 26.7L469.4 540.8a160.68 160.68 0 0 0 0-57.6l207.2-149.9C697.2 350 723.5 360 752 360c66.2 0 120-53.8 120-120s-53.8-120-120-120-120 53.8-120 120c0 11.6 1.6 22.7 4.7 33.3L439.9 415.8C410.7 377.1 364.3 352 312 352c-88.4 0-160 71.6-160 160s71.6 160 160 160c52.3 0 98.7-25.1 127.9-63.8l196.8 142.5c-3.1 10.6-4.7 21.8-4.7 33.3 0 66.2 53.8 120 120 120s120-53.8 120-120-53.8-120-120-120zm0-476c28.7 0 52 23.3 52 52s-23.3 52-52 52-52-23.3-52-52 23.3-52 52-52zM312 600c-48.5 0-88-39.5-88-88s39.5-88 88-88 88 39.5 88 88-39.5 88-88 88zm440 236c-28.7 0-52-23.3-52-52s23.3-52 52-52 52 23.3 52 52-23.3 52-52 52z\")),t.ShoppingCartOutline=u(\"shopping-cart\",i,l(r,\"M922.9 701.9H327.4l29.9-60.9 496.8-.9c16.8 0 31.2-12 34.2-28.6l68.8-385.1c1.8-10.1-.9-20.5-7.5-28.4a34.99 34.99 0 0 0-26.6-12.5l-632-2.1-5.4-25.4c-3.4-16.2-18-28-34.6-28H96.5a35.3 35.3 0 1 0 0 70.6h125.9L246 312.8l58.1 281.3-74.8 122.1a34.96 34.96 0 0 0-3 36.8c6 11.9 18.1 19.4 31.5 19.4h62.8a102.43 102.43 0 0 0-20.6 61.7c0 56.6 46 102.6 102.6 102.6s102.6-46 102.6-102.6c0-22.3-7.4-44-20.6-61.7h161.1a102.43 102.43 0 0 0-20.6 61.7c0 56.6 46 102.6 102.6 102.6s102.6-46 102.6-102.6c0-22.3-7.4-44-20.6-61.7H923c19.4 0 35.3-15.8 35.3-35.3a35.42 35.42 0 0 0-35.4-35.2zM305.7 253l575.8 1.9-56.4 315.8-452.3.8L305.7 253zm96.9 612.7c-17.4 0-31.6-14.2-31.6-31.6 0-17.4 14.2-31.6 31.6-31.6s31.6 14.2 31.6 31.6a31.6 31.6 0 0 1-31.6 31.6zm325.1 0c-17.4 0-31.6-14.2-31.6-31.6 0-17.4 14.2-31.6 31.6-31.6s31.6 14.2 31.6 31.6a31.6 31.6 0 0 1-31.6 31.6z\")),t.ShrinkOutline=u(\"shrink\",i,l(o,\"M881.7 187.4l-45.1-45.1a8.03 8.03 0 0 0-11.3 0L667.8 299.9l-54.7-54.7a7.94 7.94 0 0 0-13.5 4.7L576.1 439c-.6 5.2 3.7 9.5 8.9 8.9l189.2-23.5c6.6-.8 9.3-8.8 4.7-13.5l-54.7-54.7 157.6-157.6c3-3 3-8.1-.1-11.2zM439 576.1l-189.2 23.5c-6.6.8-9.3 8.9-4.7 13.5l54.7 54.7-157.5 157.5a8.03 8.03 0 0 0 0 11.3l45.1 45.1c3.1 3.1 8.2 3.1 11.3 0l157.6-157.6 54.7 54.7a7.94 7.94 0 0 0 13.5-4.7L447.9 585a7.9 7.9 0 0 0-8.9-8.9z\")),t.SlackOutline=u(\"slack\",i,l(o,\"M409.4 128c-42.4 0-76.7 34.4-76.7 76.8 0 20.3 8.1 39.9 22.4 54.3a76.74 76.74 0 0 0 54.3 22.5h76.7v-76.8c0-42.3-34.3-76.7-76.7-76.8zm0 204.8H204.7c-42.4 0-76.7 34.4-76.7 76.8s34.4 76.8 76.7 76.8h204.6c42.4 0 76.7-34.4 76.7-76.8.1-42.4-34.3-76.8-76.6-76.8zM614 486.4c42.4 0 76.8-34.4 76.7-76.8V204.8c0-42.4-34.3-76.8-76.7-76.8-42.4 0-76.7 34.4-76.7 76.8v204.8c0 42.5 34.3 76.8 76.7 76.8zm281.4-76.8c0-42.4-34.4-76.8-76.7-76.8S742 367.2 742 409.6v76.8h76.7c42.3 0 76.7-34.4 76.7-76.8zm-76.8 128H614c-42.4 0-76.7 34.4-76.7 76.8 0 20.3 8.1 39.9 22.4 54.3a76.74 76.74 0 0 0 54.3 22.5h204.6c42.4 0 76.7-34.4 76.7-76.8.1-42.4-34.3-76.7-76.7-76.8zM614 742.4h-76.7v76.8c0 42.4 34.4 76.8 76.7 76.8 42.4 0 76.8-34.4 76.7-76.8.1-42.4-34.3-76.7-76.7-76.8zM409.4 537.6c-42.4 0-76.7 34.4-76.7 76.8v204.8c0 42.4 34.4 76.8 76.7 76.8 42.4 0 76.8-34.4 76.7-76.8V614.4c0-20.3-8.1-39.9-22.4-54.3a76.92 76.92 0 0 0-54.3-22.5zM128 614.4c0 20.3 8.1 39.9 22.4 54.3a76.74 76.74 0 0 0 54.3 22.5c42.4 0 76.8-34.4 76.7-76.8v-76.8h-76.7c-42.3 0-76.7 34.4-76.7 76.8z\")),t.SmallDashOutline=u(\"small-dash\",i,l(o,\"M112 476h72v72h-72zm182 0h72v72h-72zm364 0h72v72h-72zm182 0h72v72h-72zm-364 0h72v72h-72z\")),t.SolutionOutline=u(\"solution\",i,l(o,\"M688 264c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48zm-8 136H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM480 544H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-48 308H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm356.8-74.4c29-26.3 47.2-64.3 47.2-106.6 0-79.5-64.5-144-144-144s-144 64.5-144 144c0 42.3 18.2 80.3 47.2 106.6-57 32.5-96.2 92.7-99.2 162.1-.2 4.5 3.5 8.3 8 8.3h48.1c4.2 0 7.7-3.3 8-7.6C564 871.2 621.7 816 692 816s128 55.2 131.9 124.4c.2 4.2 3.7 7.6 8 7.6H880c4.6 0 8.2-3.8 8-8.3-2.9-69.5-42.2-129.6-99.2-162.1zM692 591c44.2 0 80 35.8 80 80s-35.8 80-80 80-80-35.8-80-80 35.8-80 80-80z\")),t.SketchOutline=u(\"sketch\",i,l(o,\"M925.6 405.1l-203-253.7a6.5 6.5 0 0 0-5-2.4H306.4c-1.9 0-3.8.9-5 2.4l-203 253.7a6.5 6.5 0 0 0 .2 8.3l408.6 459.5c1.2 1.4 3 2.1 4.8 2.1 1.8 0 3.5-.8 4.8-2.1l408.6-459.5a6.5 6.5 0 0 0 .2-8.3zM645.2 206.4l34.4 133.9-132.5-133.9h98.1zm8.2 178.5H370.6L512 242l141.4 142.9zM378.8 206.4h98.1L344.3 340.3l34.5-133.9zm-53.4 7l-44.1 171.5h-93.1l137.2-171.5zM194.6 434.9H289l125.8 247.7-220.2-247.7zM512 763.4L345.1 434.9h333.7L512 763.4zm97.1-80.8L735 434.9h94.4L609.1 682.6zm133.6-297.7l-44.1-171.5 137.2 171.5h-93.1z\")),t.SortDescendingOutline=u(\"sort-descending\",i,l(o,\"M839.6 433.8L749 150.5a9.24 9.24 0 0 0-8.9-6.5h-77.4c-4.1 0-7.6 2.6-8.9 6.5l-91.3 283.3c-.3.9-.5 1.9-.5 2.9 0 5.1 4.2 9.3 9.3 9.3h56.4c4.2 0 7.8-2.8 9-6.8l17.5-61.6h89l17.3 61.5c1.1 4 4.8 6.8 9 6.8h61.2c1 0 1.9-.1 2.8-.4 2.4-.8 4.3-2.4 5.5-4.6 1.1-2.2 1.3-4.7.6-7.1zM663.3 325.5l32.8-116.9h6.3l32.1 116.9h-71.2zm143.5 492.9H677.2v-.4l132.6-188.9c1.1-1.6 1.7-3.4 1.7-5.4v-36.4c0-5.1-4.2-9.3-9.3-9.3h-204c-5.1 0-9.3 4.2-9.3 9.3v43c0 5.1 4.2 9.3 9.3 9.3h122.6v.4L587.7 828.9a9.35 9.35 0 0 0-1.7 5.4v36.4c0 5.1 4.2 9.3 9.3 9.3h211.4c5.1 0 9.3-4.2 9.3-9.3v-43a9.2 9.2 0 0 0-9.2-9.3zM310.3 167.1a8 8 0 0 0-12.6 0L185.7 309c-4.2 5.3-.4 13 6.3 13h76v530c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V322h76c6.7 0 10.5-7.8 6.3-13l-112-141.9z\")),t.SortAscendingOutline=u(\"sort-ascending\",i,l(o,\"M839.6 433.8L749 150.5a9.24 9.24 0 0 0-8.9-6.5h-77.4c-4.1 0-7.6 2.6-8.9 6.5l-91.3 283.3c-.3.9-.5 1.9-.5 2.9 0 5.1 4.2 9.3 9.3 9.3h56.4c4.2 0 7.8-2.8 9-6.8l17.5-61.6h89l17.3 61.5c1.1 4 4.8 6.8 9 6.8h61.2c1 0 1.9-.1 2.8-.4 2.4-.8 4.3-2.4 5.5-4.6 1.1-2.2 1.3-4.7.6-7.1zM663.3 325.5l32.8-116.9h6.3l32.1 116.9h-71.2zm143.5 492.9H677.2v-.4l132.6-188.9c1.1-1.6 1.7-3.4 1.7-5.4v-36.4c0-5.1-4.2-9.3-9.3-9.3h-204c-5.1 0-9.3 4.2-9.3 9.3v43c0 5.1 4.2 9.3 9.3 9.3h122.6v.4L587.7 828.9a9.35 9.35 0 0 0-1.7 5.4v36.4c0 5.1 4.2 9.3 9.3 9.3h211.4c5.1 0 9.3-4.2 9.3-9.3v-43a9.2 9.2 0 0 0-9.2-9.3zM416 702h-76V172c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v530h-76c-6.7 0-10.5 7.8-6.3 13l112 141.9a8 8 0 0 0 12.6 0l112-141.9c4.1-5.2.4-13-6.3-13z\")),t.StockOutline=u(\"stock\",i,l(o,\"M904 747H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM165.7 621.8l39.7 39.5c3.1 3.1 8.2 3.1 11.3 0l234.7-233.9 97.6 97.3a32.11 32.11 0 0 0 45.2 0l264.2-263.2c3.1-3.1 3.1-8.2 0-11.3l-39.7-39.6a8.03 8.03 0 0 0-11.3 0l-235.7 235-97.7-97.3a32.11 32.11 0 0 0-45.2 0L165.7 610.5a7.94 7.94 0 0 0 0 11.3z\")),t.SwapLeftOutline=u(\"swap-left\",i,l(r,\"M872 572H266.8l144.3-183c4.1-5.2.4-13-6.3-13H340c-9.8 0-19.1 4.5-25.1 12.2l-164 208c-16.5 21-1.6 51.8 25.1 51.8h696c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z\")),t.SwapRightOutline=u(\"swap-right\",i,l(r,\"M873.1 596.2l-164-208A32 32 0 0 0 684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z\")),t.StrikethroughOutline=u(\"strikethrough\",i,l(o,\"M952 474H569.9c-10-2-20.5-4-31.6-6-15.9-2.9-22.2-4.1-30.8-5.8-51.3-10-82.2-20-106.8-34.2-35.1-20.5-52.2-48.3-52.2-85.1 0-37 15.2-67.7 44-89 28.4-21 68.8-32.1 116.8-32.1 54.8 0 97.1 14.4 125.8 42.8 14.6 14.4 25.3 32.1 31.8 52.6 1.3 4.1 2.8 10 4.3 17.8.9 4.8 5.2 8.2 9.9 8.2h72.8c5.6 0 10.1-4.6 10.1-10.1v-1c-.7-6.8-1.3-12.1-2-16-7.3-43.5-28-81.7-59.7-110.3-44.4-40.5-109.7-61.8-188.7-61.8-72.3 0-137.4 18.1-183.3 50.9-25.6 18.4-45.4 41.2-58.6 67.7-13.5 27.1-20.3 58.4-20.3 92.9 0 29.5 5.7 54.5 17.3 76.5 8.3 15.7 19.6 29.5 34.1 42H72c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h433.2c2.1.4 3.9.8 5.9 1.2 30.9 6.2 49.5 10.4 66.6 15.2 23 6.5 40.6 13.3 55.2 21.5 35.8 20.2 53.3 49.2 53.3 89 0 35.3-15.5 66.8-43.6 88.8-30.5 23.9-75.6 36.4-130.5 36.4-43.7 0-80.7-8.5-110.2-25-29.1-16.3-49.1-39.8-59.7-69.5-.8-2.2-1.7-5.2-2.7-9-1.2-4.4-5.3-7.5-9.7-7.5h-79.7c-5.6 0-10.1 4.6-10.1 10.1v1c.2 2.3.4 4.2.6 5.7 6.5 48.8 30.3 88.8 70.7 118.8 47.1 34.8 113.4 53.2 191.8 53.2 84.2 0 154.8-19.8 204.2-57.3 25-18.9 44.2-42.2 57.1-69 13-27.1 19.7-57.9 19.7-91.5 0-31.8-5.8-58.4-17.8-81.4-5.8-11.2-13.1-21.5-21.8-30.8H952c4.4 0 8-3.6 8-8v-60a8 8 0 0 0-8-7.9z\")),t.SwapOutline=u(\"swap\",i,l(o,\"M847.9 592H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h605.2L612.9 851c-4.1 5.2-.4 13 6.3 13h72.5c4.9 0 9.5-2.2 12.6-6.1l168.8-214.1c16.5-21 1.6-51.8-25.2-51.8zM872 356H266.8l144.3-183c4.1-5.2.4-13-6.3-13h-72.5c-4.9 0-9.5 2.2-12.6 6.1L150.9 380.2c-16.5 21-1.6 51.8 25.1 51.8h696c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z\")),t.SyncOutline=u(\"sync\",i,l(o,\"M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27A341.5 341.5 0 0 1 755 268.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 0 0 3 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8zm756 7.8h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4A342.45 342.45 0 0 1 512.1 856a342.24 342.24 0 0 1-243.2-100.8c-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47a8 8 0 0 0-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8a8 8 0 0 0-8-8.2z\")),t.TableOutline=u(\"table\",i,l(o,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 208H676V232h212v136zm0 224H676V432h212v160zM412 432h200v160H412V432zm200-64H412V232h200v136zm-476 64h212v160H136V432zm0-200h212v136H136V232zm0 424h212v136H136V656zm276 0h200v136H412V656zm476 136H676V656h212v136z\")),t.TeamOutline=u(\"team\",i,l(o,\"M824.2 699.9a301.55 301.55 0 0 0-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 0 0-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 0 0 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 0 1 612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 0 0 8-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 0 1-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 0 1 612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 0 1-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 0 0 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z\")),t.TaobaoOutline=u(\"taobao\",i,l(o,\"M168.5 273.7a68.7 68.7 0 1 0 137.4 0 68.7 68.7 0 1 0-137.4 0zm730 79.2s-23.7-184.4-426.9-70.1c17.3-30 25.6-49.5 25.6-49.5L396.4 205s-40.6 132.6-113 194.4c0 0 70.1 40.6 69.4 39.4 20.1-20.1 38.2-40.6 53.7-60.4 16.1-7 31.5-13.6 46.7-19.8-18.6 33.5-48.7 83.8-78.8 115.6l42.4 37s28.8-27.7 60.4-61.2h36v61.8H372.9v49.5h140.3v118.5c-1.7 0-3.6 0-5.4-.2-15.4-.7-39.5-3.3-49-18.2-11.5-18.1-3-51.5-2.4-71.9h-97l-3.4 1.8s-35.5 159.1 102.3 155.5c129.1 3.6 203-36 238.6-63.1l14.2 52.6 79.6-33.2-53.9-131.9-64.6 20.1 12.1 45.2c-16.6 12.4-35.6 21.7-56.2 28.4V561.3h137.1v-49.5H628.1V450h137.6v-49.5H521.3c17.6-21.4 31.5-41.1 35-53.6l-42.5-11.6c182.8-65.5 284.5-54.2 283.6 53.2v282.8s10.8 97.1-100.4 90.1l-60.2-12.9-14.2 57.1S882.5 880 903.7 680.2c21.3-200-5.2-327.3-5.2-327.3zm-707.4 18.3l-45.4 69.7 83.6 52.1s56 28.5 29.4 81.9C233.8 625.5 112 736.3 112 736.3l109 68.1c75.4-163.7 70.5-142 89.5-200.7 19.5-60.1 23.7-105.9-9.4-139.1-42.4-42.6-47-46.6-110-93.4z\")),t.ToTopOutline=u(\"to-top\",i,l(o,\"M885 780H165c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zM400 325.7h73.9V664c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V325.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 171a8 8 0 0 0-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13z\")),t.TrademarkOutline=u(\"trademark\",i,l(o,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm87.5-334.7c34.8-12.8 78.4-49 78.4-119.2 0-71.2-45.5-131.1-144.2-131.1H378c-4.4 0-8 3.6-8 8v410c0 4.4 3.6 8 8 8h54.5c4.4 0 8-3.6 8-8V561.2h88.7l74.6 159.2c1.3 2.8 4.1 4.6 7.2 4.6h62a7.9 7.9 0 0 0 7.1-11.5l-80.6-164.2zM522 505h-81.5V357h83.4c48 0 80.9 25.3 80.9 75.5 0 46.9-29.8 72.5-82.8 72.5z\")),t.TransactionOutline=u(\"transaction\",i,l(o,\"M668.6 320c0-4.4-3.6-8-8-8h-54.5c-3 0-5.8 1.7-7.1 4.4l-84.7 168.8H511l-84.7-168.8a8 8 0 0 0-7.1-4.4h-55.7c-1.3 0-2.6.3-3.8 1-3.9 2.1-5.3 7-3.2 10.8l103.9 191.6h-57c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76v39h-76c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76V704c0 4.4 3.6 8 8 8h49.9c4.4 0 8-3.6 8-8v-63.5h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8h-76.3v-39h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8H564l103.7-191.6c.5-1.1.9-2.4.9-3.7zM157.9 504.2a352.7 352.7 0 0 1 103.5-242.4c32.5-32.5 70.3-58.1 112.4-75.9 43.6-18.4 89.9-27.8 137.6-27.8 47.8 0 94.1 9.3 137.6 27.8 42.1 17.8 79.9 43.4 112.4 75.9 10 10 19.3 20.5 27.9 31.4l-50 39.1a8 8 0 0 0 3 14.1l156.8 38.3c5 1.2 9.9-2.6 9.9-7.7l.8-161.5c0-6.7-7.7-10.5-12.9-6.3l-47.8 37.4C770.7 146.3 648.6 82 511.5 82 277 82 86.3 270.1 82 503.8a8 8 0 0 0 8 8.2h60c4.3 0 7.8-3.5 7.9-7.8zM934 512h-60c-4.3 0-7.9 3.5-8 7.8a352.7 352.7 0 0 1-103.5 242.4 352.57 352.57 0 0 1-112.4 75.9c-43.6 18.4-89.9 27.8-137.6 27.8s-94.1-9.3-137.6-27.8a352.57 352.57 0 0 1-112.4-75.9c-10-10-19.3-20.5-27.9-31.4l49.9-39.1a8 8 0 0 0-3-14.1l-156.8-38.3c-5-1.2-9.9 2.6-9.9 7.7l-.8 161.7c0 6.7 7.7 10.5 12.9 6.3l47.8-37.4C253.3 877.7 375.4 942 512.5 942 747 942 937.7 753.9 942 520.2a8 8 0 0 0-8-8.2z\")),t.TwitterOutline=u(\"twitter\",i,l(o,\"M928 254.3c-30.6 13.2-63.9 22.7-98.2 26.4a170.1 170.1 0 0 0 75-94 336.64 336.64 0 0 1-108.2 41.2A170.1 170.1 0 0 0 672 174c-94.5 0-170.5 76.6-170.5 170.6 0 13.2 1.6 26.4 4.2 39.1-141.5-7.4-267.7-75-351.6-178.5a169.32 169.32 0 0 0-23.2 86.1c0 59.2 30.1 111.4 76 142.1a172 172 0 0 1-77.1-21.7v2.1c0 82.9 58.6 151.6 136.7 167.4a180.6 180.6 0 0 1-44.9 5.8c-11.1 0-21.6-1.1-32.2-2.6C211 652 273.9 701.1 348.8 702.7c-58.6 45.9-132 72.9-211.7 72.9-14.3 0-27.5-.5-41.2-2.1C171.5 822 261.2 850 357.8 850 671.4 850 843 590.2 843 364.7c0-7.4 0-14.8-.5-22.2 33.2-24.3 62.3-54.4 85.5-88.2z\")),t.UnderlineOutline=u(\"underline\",i,l(o,\"M824 804H200c-4.4 0-8 3.4-8 7.6v60.8c0 4.2 3.6 7.6 8 7.6h624c4.4 0 8-3.4 8-7.6v-60.8c0-4.2-3.6-7.6-8-7.6zm-312-76c69.4 0 134.6-27.1 183.8-76.2C745 602.7 772 537.4 772 468V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 97-79 176-176 176s-176-79-176-176V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 69.4 27.1 134.6 76.2 183.8C377.3 701 442.6 728 512 728z\")),t.UndoOutline=u(\"undo\",i,l(o,\"M511.4 124C290.5 124.3 112 303 112 523.9c0 128 60.2 242 153.8 315.2l-37.5 48c-4.1 5.3-.3 13 6.3 12.9l167-.8c5.2 0 9-4.9 7.7-9.9L369.8 727a8 8 0 0 0-14.1-3L315 776.1c-10.2-8-20-16.7-29.3-26a318.64 318.64 0 0 1-68.6-101.7C200.4 609 192 567.1 192 523.9s8.4-85.1 25.1-124.5c16.1-38.1 39.2-72.3 68.6-101.7 29.4-29.4 63.6-52.5 101.7-68.6C426.9 212.4 468.8 204 512 204s85.1 8.4 124.5 25.1c38.1 16.1 72.3 39.2 101.7 68.6 29.4 29.4 52.5 63.6 68.6 101.7 16.7 39.4 25.1 81.3 25.1 124.5s-8.4 85.1-25.1 124.5a318.64 318.64 0 0 1-68.6 101.7c-7.5 7.5-15.3 14.5-23.4 21.2a7.93 7.93 0 0 0-1.2 11.1l39.4 50.5c2.8 3.5 7.9 4.1 11.4 1.3C854.5 760.8 912 649.1 912 523.9c0-221.1-179.4-400.2-400.6-399.9z\")),t.UnorderedListOutline=u(\"unordered-list\",i,l(o,\"M912 192H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 284H328c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM104 228a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 1 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 1 0-112 0z\")),t.UpOutline=u(\"up\",i,l(o,\"M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 0 0 140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z\")),t.UploadOutline=u(\"upload\",i,l(o,\"M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 0 0-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z\")),t.UserAddOutline=u(\"user-add\",i,l(o,\"M678.3 642.4c24.2-13 51.9-20.4 81.4-20.4h.1c3 0 4.4-3.6 2.2-5.6a371.67 371.67 0 0 0-103.7-65.8c-.4-.2-.8-.3-1.2-.5C719.2 505 759.6 431.7 759.6 349c0-137-110.8-248-247.5-248S264.7 212 264.7 349c0 82.7 40.4 156 102.6 201.1-.4.2-.8.3-1.2.5-44.7 18.9-84.8 46-119.3 80.6a373.42 373.42 0 0 0-80.4 119.5A373.6 373.6 0 0 0 137 888.8a8 8 0 0 0 8 8.2h59.9c4.3 0 7.9-3.5 8-7.8 2-77.2 32.9-149.5 87.6-204.3C357 628.2 432.2 597 512.2 597c56.7 0 111.1 15.7 158 45.1a8.1 8.1 0 0 0 8.1.3zM512.2 521c-45.8 0-88.9-17.9-121.4-50.4A171.2 171.2 0 0 1 340.5 349c0-45.9 17.9-89.1 50.3-121.6S466.3 177 512.2 177s88.9 17.9 121.4 50.4A171.2 171.2 0 0 1 683.9 349c0 45.9-17.9 89.1-50.3 121.6C601.1 503.1 558 521 512.2 521zM880 759h-84v-84c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v84h-84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h84v84c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-84h84c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.UsergroupAddOutline=u(\"usergroup-add\",i,l(o,\"M892 772h-80v-80c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v80h-80c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h80v80c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-80h80c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM373.5 498.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 0 1-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.8-1.7-203.2 89.2-203.2 200 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 0 0 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.8-1.1 6.4-4.8 5.9-8.8zM824 472c0-109.4-87.9-198.3-196.9-200C516.3 270.3 424 361.2 424 472c0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 0 0-86.4 60.4C357 742.6 326 814.8 324 891.8a8 8 0 0 0 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5C505.8 695.7 563 672 624 672c110.4 0 200-89.5 200-200zm-109.5 90.5C690.3 586.7 658.2 600 624 600s-66.3-13.3-90.5-37.5a127.26 127.26 0 0 1-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4-.1 34.2-13.4 66.3-37.6 90.5z\")),t.UserOutline=u(\"user\",i,l(o,\"M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z\")),t.UserDeleteOutline=u(\"user-delete\",i,l(o,\"M678.3 655.4c24.2-13 51.9-20.4 81.4-20.4h.1c3 0 4.4-3.6 2.2-5.6a371.67 371.67 0 0 0-103.7-65.8c-.4-.2-.8-.3-1.2-.5C719.2 518 759.6 444.7 759.6 362c0-137-110.8-248-247.5-248S264.7 225 264.7 362c0 82.7 40.4 156 102.6 201.1-.4.2-.8.3-1.2.5-44.7 18.9-84.8 46-119.3 80.6a373.42 373.42 0 0 0-80.4 119.5A373.6 373.6 0 0 0 137 901.8a8 8 0 0 0 8 8.2h59.9c4.3 0 7.9-3.5 8-7.8 2-77.2 32.9-149.5 87.6-204.3C357 641.2 432.2 610 512.2 610c56.7 0 111.1 15.7 158 45.1a8.1 8.1 0 0 0 8.1.3zM512.2 534c-45.8 0-88.9-17.9-121.4-50.4A171.2 171.2 0 0 1 340.5 362c0-45.9 17.9-89.1 50.3-121.6S466.3 190 512.2 190s88.9 17.9 121.4 50.4A171.2 171.2 0 0 1 683.9 362c0 45.9-17.9 89.1-50.3 121.6C601.1 516.1 558 534 512.2 534zM880 772H640c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h240c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z\")),t.UsergroupDeleteOutline=u(\"usergroup-delete\",i,l(o,\"M888 784H664c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h224c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM373.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 0 1-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 0 0 8 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7zM824 484c0-109.4-87.9-198.3-196.9-200C516.3 282.3 424 373.2 424 484c0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 0 0-86.4 60.4C357 754.6 326 826.8 324 903.8a8 8 0 0 0 8 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5C505.8 707.7 563 684 624 684c110.4 0 200-89.5 200-200zm-109.5 90.5C690.3 598.7 658.2 612 624 612s-66.3-13.3-90.5-37.5a127.26 127.26 0 0 1-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4-.1 34.2-13.4 66.3-37.6 90.5z\")),t.VerticalAlignBottomOutline=u(\"vertical-align-bottom\",i,l(o,\"M859.9 780H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM505.7 669a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V176c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8z\")),t.VerticalAlignMiddleOutline=u(\"vertical-align-middle\",i,l(o,\"M859.9 474H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zm-353.6-74.7c2.9 3.7 8.5 3.7 11.3 0l100.8-127.5c3.7-4.7.4-11.7-5.7-11.7H550V104c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v156h-62.8c-6 0-9.4 7-5.7 11.7l100.8 127.6zm11.4 225.4a7.14 7.14 0 0 0-11.3 0L405.6 752.3a7.23 7.23 0 0 0 5.7 11.7H474v156c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V764h62.8c6 0 9.4-7 5.7-11.7L517.7 624.7z\")),t.VerticalAlignTopOutline=u(\"vertical-align-top\",i,l(o,\"M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 0 0-12.6 0l-112 141.7a7.98 7.98 0 0 0 6.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z\")),t.VerticalRightOutline=u(\"vertical-right\",i,l(o,\"M326 164h-64c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V172c0-4.4-3.6-8-8-8zm444 72.4V164c0-6.8-7.9-10.5-13.1-6.1L335 512l421.9 354.1c5.2 4.4 13.1.7 13.1-6.1v-72.4c0-9.4-4.2-18.4-11.4-24.5L459.4 512l299.2-251.1c7.2-6.1 11.4-15.1 11.4-24.5z\")),t.VerticalLeftOutline=u(\"vertical-left\",i,l(o,\"M762 164h-64c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V172c0-4.4-3.6-8-8-8zm-508 0v72.4c0 9.5 4.2 18.4 11.4 24.5L564.6 512 265.4 763.1c-7.2 6.1-11.4 15-11.4 24.5V860c0 6.8 7.9 10.5 13.1 6.1L689 512 267.1 157.9A7.95 7.95 0 0 0 254 164z\")),t.WifiOutline=u(\"wifi\",i,l(o,\"M723 620.5C666.8 571.6 593.4 542 513 542s-153.8 29.6-210.1 78.6a8.1 8.1 0 0 0-.8 11.2l36 42.9c2.9 3.4 8 3.8 11.4.9C393.1 637.2 450.3 614 513 614s119.9 23.2 163.5 61.5c3.4 2.9 8.5 2.5 11.4-.9l36-42.9c2.8-3.3 2.4-8.3-.9-11.2zm117.4-140.1C751.7 406.5 637.6 362 513 362s-238.7 44.5-327.5 118.4a8.05 8.05 0 0 0-1 11.3l36 42.9c2.8 3.4 7.9 3.8 11.2 1C308 472.2 406.1 434 513 434s205 38.2 281.2 101.6c3.4 2.8 8.4 2.4 11.2-1l36-42.9c2.8-3.4 2.4-8.5-1-11.3zm116.7-139C835.7 241.8 680.3 182 511 182c-168.2 0-322.6 59-443.7 157.4a8 8 0 0 0-1.1 11.4l36 42.9c2.8 3.3 7.8 3.8 11.1 1.1C222 306.7 360.3 254 511 254c151.8 0 291 53.5 400 142.7 3.4 2.8 8.4 2.3 11.2-1.1l36-42.9c2.9-3.4 2.4-8.5-1.1-11.3zM448 778a64 64 0 1 0 128 0 64 64 0 1 0-128 0z\")),t.ZhihuOutline=u(\"zhihu\",i,l(o,\"M564.7 230.1V803h60l25.2 71.4L756.3 803h131.5V230.1H564.7zm247.7 497h-59.9l-75.1 50.4-17.8-50.4h-18V308.3h170.7v418.8zM526.1 486.9H393.3c2.1-44.9 4.3-104.3 6.6-172.9h130.9l-.1-8.1c0-.6-.2-14.7-2.3-29.1-2.1-15-6.6-34.9-21-34.9H287.8c4.4-20.6 15.7-69.7 29.4-93.8l6.4-11.2-12.9-.7c-.8 0-19.6-.9-41.4 10.6-35.7 19-51.7 56.4-58.7 84.4-18.4 73.1-44.6 123.9-55.7 145.6-3.3 6.4-5.3 10.2-6.2 12.8-1.8 4.9-.8 9.8 2.8 13 10.5 9.5 38.2-2.9 38.5-3 .6-.3 1.3-.6 2.2-1 13.9-6.3 55.1-25 69.8-84.5h56.7c.7 32.2 3.1 138.4 2.9 172.9h-141l-2.1 1.5c-23.1 16.9-30.5 63.2-30.8 65.2l-1.4 9.2h167c-12.3 78.3-26.5 113.4-34 127.4-3.7 7-7.3 14-10.7 20.8-21.3 42.2-43.4 85.8-126.3 153.6-3.6 2.8-7 8-4.8 13.7 2.4 6.3 9.3 9.1 24.6 9.1 5.4 0 11.8-.3 19.4-1 49.9-4.4 100.8-18 135.1-87.6 17-35.1 31.7-71.7 43.9-108.9L497 850l5-12c.8-1.9 19-46.3 5.1-95.9l-.5-1.8-108.1-123-22 16.6c6.4-26.1 10.6-49.9 12.5-71.1h158.7v-8c0-40.1-18.5-63.9-19.2-64.9l-2.4-3z\")),t.WeiboOutline=u(\"weibo\",i,l(o,\"M457.3 543c-68.1-17.7-145 16.2-174.6 76.2-30.1 61.2-1 129.1 67.8 151.3 71.2 23 155.2-12.2 184.4-78.3 28.7-64.6-7.2-131-77.6-149.2zm-52 156.2c-13.8 22.1-43.5 31.7-65.8 21.6-22-10-28.5-35.7-14.6-57.2 13.7-21.4 42.3-31 64.4-21.7 22.4 9.5 29.6 35 16 57.3zm45.5-58.5c-5 8.6-16.1 12.7-24.7 9.1-8.5-3.5-11.2-13.1-6.4-21.5 5-8.4 15.6-12.4 24.1-9.1 8.7 3.2 11.8 12.9 7 21.5zm334.5-197.2c15 4.8 31-3.4 35.9-18.3 11.8-36.6 4.4-78.4-23.2-109a111.39 111.39 0 0 0-106-34.3 28.45 28.45 0 0 0-21.9 33.8 28.39 28.39 0 0 0 33.8 21.8c18.4-3.9 38.3 1.8 51.9 16.7a54.2 54.2 0 0 1 11.3 53.3 28.45 28.45 0 0 0 18.2 36zm99.8-206c-56.7-62.9-140.4-86.9-217.7-70.5a32.98 32.98 0 0 0-25.4 39.3 33.12 33.12 0 0 0 39.3 25.5c55-11.7 114.4 5.4 154.8 50.1 40.3 44.7 51.2 105.7 34 159.1-5.6 17.4 3.9 36 21.3 41.7 17.4 5.6 36-3.9 41.6-21.2v-.1c24.1-75.4 8.9-161.1-47.9-223.9zM729 499c-12.2-3.6-20.5-6.1-14.1-22.1 13.8-34.7 15.2-64.7.3-86-28-40.1-104.8-37.9-192.8-1.1 0 0-27.6 12.1-20.6-9.8 13.5-43.5 11.5-79.9-9.6-101-47.7-47.8-174.6 1.8-283.5 110.6C127.3 471.1 80 557.5 80 632.2 80 775.1 263.2 862 442.5 862c235 0 391.3-136.5 391.3-245 0-65.5-55.2-102.6-104.8-118zM443 810.8c-143 14.1-266.5-50.5-275.8-144.5-9.3-93.9 99.2-181.5 242.2-195.6 143-14.2 266.5 50.5 275.8 144.4C694.4 709 586 796.6 443 810.8z\")),t.WomanOutline=u(\"woman\",i,l(o,\"M712.8 548.8c53.6-53.6 83.2-125 83.2-200.8 0-75.9-29.5-147.2-83.2-200.8C659.2 93.6 587.8 64 512 64s-147.2 29.5-200.8 83.2C257.6 200.9 228 272.1 228 348c0 63.8 20.9 124.4 59.4 173.9 7.3 9.4 15.2 18.3 23.7 26.9 8.5 8.5 17.5 16.4 26.8 23.7 39.6 30.8 86.3 50.4 136.1 57V736H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h114v140c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V812h114c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H550V629.5c61.5-8.2 118.2-36.1 162.8-80.7zM512 556c-55.6 0-107.7-21.6-147.1-60.9C325.6 455.8 304 403.6 304 348s21.6-107.7 60.9-147.1C404.2 161.5 456.4 140 512 140s107.7 21.6 147.1 60.9C698.4 240.2 720 292.4 720 348s-21.6 107.7-60.9 147.1C619.7 534.4 567.6 556 512 556z\")),t.ZoomInOutline=u(\"zoom-in\",i,l(o,\"M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z\")),t.AccountBookTwoTone=u(\"account-book\",a,(function(e,t){return l(o,[t,\"M712 304c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H384v48c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H184v584h656V256H712v48zm-65.6 121.8l-89.3 164.1h49.1c4.4 0 8 3.6 8 8v21.3c0 4.4-3.6 8-8 8h-65.4v33.7h65.4c4.4 0 8 3.6 8 8v21.3c0 4.4-3.6 8-8 8h-65.4V752c0 4.4-3.6 8-8 8h-41.3c-4.4 0-8-3.6-8-8v-53.8h-65.1c-4.4 0-8-3.6-8-8v-21.3c0-4.4 3.6-8 8-8h65.1v-33.7h-65.1c-4.4 0-8-3.6-8-8v-21.3c0-4.4 3.6-8 8-8H467l-89.3-164c-2.1-3.9-.7-8.8 3.2-10.9 1.1-.7 2.5-1 3.8-1h46a8 8 0 0 1 7.1 4.4l73.4 145.4h2.8l73.4-145.4c1.3-2.7 4.1-4.4 7.1-4.4h45c4.5 0 8 3.6 7.9 8 0 1.3-.4 2.6-1 3.8z\"],[e,\"M639.5 414h-45c-3 0-5.8 1.7-7.1 4.4L514 563.8h-2.8l-73.4-145.4a8 8 0 0 0-7.1-4.4h-46c-1.3 0-2.7.3-3.8 1-3.9 2.1-5.3 7-3.2 10.9l89.3 164h-48.6c-4.4 0-8 3.6-8 8v21.3c0 4.4 3.6 8 8 8h65.1v33.7h-65.1c-4.4 0-8 3.6-8 8v21.3c0 4.4 3.6 8 8 8h65.1V752c0 4.4 3.6 8 8 8h41.3c4.4 0 8-3.6 8-8v-53.8h65.4c4.4 0 8-3.6 8-8v-21.3c0-4.4-3.6-8-8-8h-65.4v-33.7h65.4c4.4 0 8-3.6 8-8v-21.3c0-4.4-3.6-8-8-8h-49.1l89.3-164.1c.6-1.2 1-2.5 1-3.8.1-4.4-3.4-8-7.9-8z\"],[e,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v584z\"])})),t.ZoomOutOutline=u(\"zoom-out\",i,l(o,\"M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z\")),t.AlertTwoTone=u(\"alert\",a,(function(e,t){return l(o,[t,\"M340 585c0-5.5 4.5-10 10-10h44c5.5 0 10 4.5 10 10v171h355V563c0-136.4-110.6-247-247-247S265 426.6 265 563v193h75V585z\"],[e,\"M216.9 310.5l39.6-39.6c3.1-3.1 3.1-8.2 0-11.3l-67.9-67.9a8.03 8.03 0 0 0-11.3 0l-39.6 39.6a8.03 8.03 0 0 0 0 11.3l67.9 67.9c3.1 3.1 8.1 3.1 11.3 0zm669.6-79.2l-39.6-39.6a8.03 8.03 0 0 0-11.3 0l-67.9 67.9a8.03 8.03 0 0 0 0 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l67.9-67.9c3.1-3.2 3.1-8.2 0-11.3zM484 180h56c4.4 0 8-3.6 8-8V76c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v96c0 4.4 3.6 8 8 8zm348 712H192c-17.7 0-32 14.3-32 32v24c0 4.4 3.6 8 8 8h688c4.4 0 8-3.6 8-8v-24c0-17.7-14.3-32-32-32zm-639-96c0 17.7 14.3 32 32 32h574c17.7 0 32-14.3 32-32V563c0-176.2-142.8-319-319-319S193 386.8 193 563v233zm72-233c0-136.4 110.6-247 247-247s247 110.6 247 247v193H404V585c0-5.5-4.5-10-10-10h-44c-5.5 0-10 4.5-10 10v171h-75V563z\"])})),t.ApiTwoTone=u(\"api\",a,(function(e,t){return l(o,[t,\"M148.2 674.6zm106.7-92.3c-25 25-38.7 58.1-38.7 93.4s13.8 68.5 38.7 93.4c25 25 58.1 38.7 93.4 38.7 35.3 0 68.5-13.8 93.4-38.7l59.4-59.4-186.8-186.8-59.4 59.4zm420.8-366.1c-35.3 0-68.5 13.8-93.4 38.7l-59.4 59.4 186.8 186.8 59.4-59.4c24.9-25 38.7-58.1 38.7-93.4s-13.8-68.5-38.7-93.4c-25-25-58.1-38.7-93.4-38.7z\"],[e,\"M578.9 546.7a8.03 8.03 0 0 0-11.3 0L501 613.3 410.7 523l66.7-66.7c3.1-3.1 3.1-8.2 0-11.3L441 408.6a8.03 8.03 0 0 0-11.3 0L363 475.3l-43-43a7.85 7.85 0 0 0-5.7-2.3c-2 0-4.1.8-5.7 2.3L206.8 534.2a199.45 199.45 0 0 0-58.6 140.4c-.2 39.5 11.2 79.1 34.3 113.1l-76.1 76.1a8.03 8.03 0 0 0 0 11.3l42.4 42.4c1.6 1.6 3.6 2.3 5.7 2.3s4.1-.8 5.7-2.3l76.1-76.1c33.7 22.9 72.9 34.3 112.1 34.3 51.2 0 102.4-19.5 141.5-58.6l101.9-101.9c3.1-3.1 3.1-8.2 0-11.3l-43-43 66.7-66.7c3.1-3.1 3.1-8.2 0-11.3l-36.6-36.2zM441.7 769.1a131.32 131.32 0 0 1-93.4 38.7c-35.3 0-68.4-13.7-93.4-38.7-24.9-24.9-38.7-58.1-38.7-93.4s13.7-68.4 38.7-93.4l59.4-59.4 186.8 186.8-59.4 59.4zm476-620.3l-42.4-42.4c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-76.1 76.1a199.27 199.27 0 0 0-112.1-34.3c-51.2 0-102.4 19.5-141.5 58.6L432.3 308.7a8.03 8.03 0 0 0 0 11.3L704 591.7c1.6 1.6 3.6 2.3 5.7 2.3 2 0 4.1-.8 5.7-2.3l101.9-101.9c68.9-69 77-175.7 24.3-253.5l76.1-76.1c3.1-3.2 3.1-8.3 0-11.4zM769.1 441.7l-59.4 59.4-186.8-186.8 59.4-59.4c24.9-24.9 58.1-38.7 93.4-38.7s68.4 13.7 93.4 38.7c24.9 24.9 38.7 58.1 38.7 93.4s-13.8 68.4-38.7 93.4z\"])})),t.AppstoreTwoTone=u(\"appstore\",a,(function(e,t){return l(o,[e,\"M864 144H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm52-668H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452 132H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z\"],[t,\"M212 212h200v200H212zm400 0h200v200H612zM212 612h200v200H212zm400 0h200v200H612z\"])})),t.BankTwoTone=u(\"bank\",a,(function(e,t){return l(o,[t,\"M240.9 393.9h542.2L512 196.7z\"],[e,\"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 0 0-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM381 836H264V462h117v374zm189 0H453V462h117v374zm190 0H642V462h118v374zM240.9 393.9L512 196.7l271.1 197.2H240.9z\"])})),t.AudioTwoTone=u(\"audio\",a,(function(e,t){return l(o,[t,\"M512 552c54.3 0 98-43.2 98-96V232c0-52.8-43.7-96-98-96s-98 43.2-98 96v224c0 52.8 43.7 96 98 96z\"],[e,\"M842 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 140.3-113.7 254-254 254S258 594.3 258 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 168.7 126.6 307.9 290 327.6V884H326.7c-13.7 0-24.7 14.3-24.7 32v36c0 4.4 2.8 8 6.2 8h407.6c3.4 0 6.2-3.6 6.2-8v-36c0-17.7-11-32-24.7-32H548V782.1c165.3-18 294-158 294-328.1z\"],[e,\"M512 624c93.9 0 170-75.2 170-168V232c0-92.8-76.1-168-170-168s-170 75.2-170 168v224c0 92.8 76.1 168 170 168zm-98-392c0-52.8 43.7-96 98-96s98 43.2 98 96v224c0 52.8-43.7 96-98 96s-98-43.2-98-96V232z\"])})),t.BellTwoTone=u(\"bell\",a,(function(e,t){return l(o,[t,\"M512 220c-55.6 0-107.8 21.6-147.1 60.9S304 372.4 304 428v340h416V428c0-55.6-21.6-107.8-60.9-147.1S567.6 220 512 220zm280 208c0-141.1-104.3-257.8-240-277.2v.1c135.7 19.4 240 136 240 277.1zM472 150.9v-.1C336.3 170.2 232 286.9 232 428c0-141.1 104.3-257.7 240-277.1z\"],[e,\"M816 768h-24V428c0-141.1-104.3-257.7-240-277.1V112c0-22.1-17.9-40-40-40s-40 17.9-40 40v38.9c-135.7 19.4-240 136-240 277.1v340h-24c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h216c0 61.8 50.2 112 112 112s112-50.2 112-112h216c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM512 888c-26.5 0-48-21.5-48-48h96c0 26.5-21.5 48-48 48zm208-120H304V428c0-55.6 21.6-107.8 60.9-147.1S456.4 220 512 220c55.6 0 107.8 21.6 147.1 60.9S720 372.4 720 428v340z\"])})),t.BookTwoTone=u(\"book\",a,(function(e,t){return l(o,[e,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-260 72h96v209.9L621.5 312 572 347.4V136zM232 888V136h280v296.9c0 3.3 1 6.6 3 9.3a15.9 15.9 0 0 0 22.3 3.7l83.8-59.9 81.4 59.4c2.7 2 6 3.1 9.4 3.1 8.8 0 16-7.2 16-16V136h64v752H232z\"],[t,\"M668 345.9V136h-96v211.4l49.5-35.4z\"],[t,\"M727.9 136v296.5c0 8.8-7.2 16-16 16-3.4 0-6.7-1.1-9.4-3.1L621.1 386l-83.8 59.9a15.9 15.9 0 0 1-22.3-3.7c-2-2.7-3-6-3-9.3V136H232v752h559.9V136h-64z\"])})),t.BoxPlotTwoTone=u(\"box-plot\",a,(function(e,t){return l(o,[t,\"M296 368h88v288h-88zm152 0h280v288H448z\"],[e,\"M952 224h-52c-4.4 0-8 3.6-8 8v248h-92V304c0-4.4-3.6-8-8-8H232c-4.4 0-8 3.6-8 8v176h-92V232c0-4.4-3.6-8-8-8H72c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V548h92v172c0 4.4 3.6 8 8 8h560c4.4 0 8-3.6 8-8V548h92v244c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zM384 656h-88V368h88v288zm344 0H448V368h280v288z\"])})),t.BugTwoTone=u(\"bug\",a,(function(e,t){return l(o,[e,\"M308 412v268c0 36.78 9.68 71.96 27.8 102.9a205.39 205.39 0 0 0 73.3 73.3A202.68 202.68 0 0 0 512 884c36.78 0 71.96-9.68 102.9-27.8a205.39 205.39 0 0 0 73.3-73.3A202.68 202.68 0 0 0 716 680V412H308zm484 172v96c0 6.5-.22 12.95-.66 19.35C859.94 728.64 908 796.7 908 876a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8c0-44.24-23.94-82.89-59.57-103.7a278.63 278.63 0 0 1-22.66 49.02 281.39 281.39 0 0 1-100.45 100.45C611.84 946.07 563.55 960 512 960s-99.84-13.93-141.32-38.23a281.39 281.39 0 0 1-100.45-100.45 278.63 278.63 0 0 1-22.66-49.02A119.95 119.95 0 0 0 188 876a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8c0-79.3 48.07-147.36 116.66-176.65A284.12 284.12 0 0 1 232 680v-96H84a8 8 0 0 1-8-8v-56a8 8 0 0 1 8-8h148V412c-76.77 0-139-62.23-139-139a8 8 0 0 1 8-8h60a8 8 0 0 1 8 8 63 63 0 0 0 63 63h560a63 63 0 0 0 63-63 8 8 0 0 1 8-8h60a8 8 0 0 1 8 8c0 76.77-62.23 139-139 139v100h148a8 8 0 0 1 8 8v56a8 8 0 0 1-8 8H792zM368 272a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8c0-40.04 8.78-76.75 25.9-108.07a184.57 184.57 0 0 1 74.03-74.03C427.25 72.78 463.96 64 504 64h16c40.04 0 76.75 8.78 108.07 25.9a184.57 184.57 0 0 1 74.03 74.03C719.22 195.25 728 231.96 728 272a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8c0-28.33-5.94-53.15-17.08-73.53a112.56 112.56 0 0 0-45.39-45.4C573.15 141.95 548.33 136 520 136h-16c-28.33 0-53.15 5.94-73.53 17.08a112.56 112.56 0 0 0-45.4 45.39C373.95 218.85 368 243.67 368 272z\"],[t,\"M308 412v268c0 36.78 9.68 71.96 27.8 102.9a205.39 205.39 0 0 0 73.3 73.3A202.68 202.68 0 0 0 512 884c36.78 0 71.96-9.68 102.9-27.8a205.39 205.39 0 0 0 73.3-73.3A202.68 202.68 0 0 0 716 680V412H308z\"])})),t.BulbTwoTone=u(\"bulb\",a,(function(e,t){return l(o,[t,\"M512 136c-141.4 0-256 114.6-256 256 0 92.5 49.4 176.3 128.1 221.8l35.9 20.8V752h184V634.6l35.9-20.8C718.6 568.3 768 484.5 768 392c0-141.4-114.6-256-256-256z\"],[e,\"M632 888H392c-4.4 0-8 3.6-8 8v32c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-32c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-328 328 0 121.4 66 227.4 164 284.1V792c0 17.7 14.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98-56.7 164-162.7 164-284.1 0-181.1-146.9-328-328-328zm127.9 549.8L604 634.6V752H420V634.6l-35.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4 114.6-256 256-256s256 114.6 256 256c0 92.5-49.4 176.3-128.1 221.8z\"])})),t.CalculatorTwoTone=u(\"calculator\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm256.2-75h-50.8c-2.2 0-4.5-1.1-5.9-2.9L348 718.6l-35.5 43.5a7.38 7.38 0 0 1-5.9 2.9h-50.8c-6.6 0-10.2-7.9-5.8-13.1l62.7-76.8-61.2-74.9c-4.3-5.2-.7-13.1 5.9-13.1h50.9c2.2 0 4.5 1.1 5.9 2.9l34 41.6 34-41.6c1.5-1.9 3.6-2.9 5.9-2.9h50.8c6.6 0 10.2 7.9 5.9 13.1L383.5 675l62.7 76.8c4.2 5.3.6 13.2-6 13.2zM576 335c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48zm0 265c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48zm0 104c0-2.2 1.4-4 3.2-4h193.5c1.9 0 3.3 1.8 3.3 4v48c0 2.2-1.4 4-3.2 4H579.2c-1.8 0-3.2-1.8-3.2-4v-48zM248 335c0-2.2 1.4-4 3.2-4H320v-68.8c0-1.8 1.8-3.2 4-3.2h48c2.2 0 4 1.4 4 3.2V331h68.7c1.9 0 3.3 1.8 3.3 4v48c0 2.2-1.4 4-3.2 4H376v68.7c0 1.9-1.8 3.3-4 3.3h-48c-2.2 0-4-1.4-4-3.2V387h-68.8c-1.8 0-3.2-1.8-3.2-4v-48z\"],[e,\"M383.5 675l61.3-74.8c4.3-5.2.7-13.1-5.9-13.1h-50.8c-2.3 0-4.4 1-5.9 2.9l-34 41.6-34-41.6a7.69 7.69 0 0 0-5.9-2.9h-50.9c-6.6 0-10.2 7.9-5.9 13.1l61.2 74.9-62.7 76.8c-4.4 5.2-.8 13.1 5.8 13.1h50.8c2.3 0 4.4-1 5.9-2.9l35.5-43.5 35.5 43.5c1.4 1.8 3.7 2.9 5.9 2.9h50.8c6.6 0 10.2-7.9 6-13.2L383.5 675zM251.2 387H320v68.8c0 1.8 1.8 3.2 4 3.2h48c2.2 0 4-1.4 4-3.3V387h68.8c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H376v-68.8c0-1.8-1.8-3.2-4-3.2h-48c-2.2 0-4 1.4-4 3.2V331h-68.8c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm328 369h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm0-104h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4zm0-265h193.6c1.8 0 3.2-1.8 3.2-4v-48c0-2.2-1.4-4-3.3-4H579.2c-1.8 0-3.2 1.8-3.2 4v48c0 2.2 1.4 4 3.2 4z\"])})),t.BuildTwoTone=u(\"build\",a,(function(e,t){return l(o,[t,\"M144 546h200v200H144zm268-268h200v200H412z\"],[e,\"M916 210H376c-17.7 0-32 14.3-32 32v236H108c-17.7 0-32 14.3-32 32v272c0 17.7 14.3 32 32 32h540c17.7 0 32-14.3 32-32V546h236c17.7 0 32-14.3 32-32V242c0-17.7-14.3-32-32-32zM344 746H144V546h200v200zm268 0H412V546h200v200zm0-268H412V278h200v200zm268 0H680V278h200v200z\"])})),t.CalendarTwoTone=u(\"calendar\",a,(function(e,t){return l(o,[t,\"M712 304c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H384v48c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H184v136h656V256H712v48z\"],[e,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zm0-448H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136z\"])})),t.CameraTwoTone=u(\"camera\",a,(function(e,t){return l(o,[t,\"M864 320H677.2l-17.1-47.8-22.9-64.2H386.7l-22.9 64.2-17.1 47.8H160c-4.4 0-8 3.6-8 8v456c0 4.4 3.6 8 8 8h704c4.4 0 8-3.6 8-8V328c0-4.4-3.6-8-8-8zM512 704c-88.4 0-160-71.6-160-160s71.6-160 160-160 160 71.6 160 160-71.6 160-160 160z\"],[e,\"M512 384c-88.4 0-160 71.6-160 160s71.6 160 160 160 160-71.6 160-160-71.6-160-160-160zm0 256c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96z\"],[e,\"M864 248H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 248H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V328c0-44.2-35.8-80-80-80zm8 536c0 4.4-3.6 8-8 8H160c-4.4 0-8-3.6-8-8V328c0-4.4 3.6-8 8-8h186.7l17.1-47.8 22.9-64.2h250.5l22.9 64.2 17.1 47.8H864c4.4 0 8 3.6 8 8v456z\"])})),t.CarTwoTone=u(\"car\",a,(function(e,t){return l(o,[t,\"M199.6 474L184 517v237h656V517l-15.6-43H199.6zM264 621c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm388 75c0 4.4-3.6 8-8 8H380c-4.4 0-8-3.6-8-8v-84c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v36h168v-36c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v84zm108-75c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40z\"],[e,\"M720 581a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"],[e,\"M959 413.4L935.3 372a8 8 0 0 0-10.9-2.9l-50.7 29.6-78.3-216.2a63.9 63.9 0 0 0-60.9-44.4H301.2c-34.7 0-65.5 22.4-76.2 55.5l-74.6 205.2-50.8-29.6a8 8 0 0 0-10.9 2.9L65 413.4c-2.2 3.8-.9 8.6 2.9 10.8l60.4 35.2-14.5 40c-1.2 3.2-1.8 6.6-1.8 10v348.2c0 15.7 11.8 28.4 26.3 28.4h67.6c12.3 0 23-9.3 25.6-22.3l7.7-37.7h545.6l7.7 37.7c2.7 13 13.3 22.3 25.6 22.3h67.6c14.5 0 26.3-12.7 26.3-28.4V509.4c0-3.4-.6-6.8-1.8-10l-14.5-40 60.3-35.2a8 8 0 0 0 3-10.8zM292.7 218.1l.5-1.3.4-1.3c1.1-3.3 4.1-5.5 7.6-5.5h427.6l75.4 208H220l72.7-199.9zM840 754H184V517l15.6-43h624.8l15.6 43v237z\"],[e,\"M224 581a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm420 23h-40c-4.4 0-8 3.6-8 8v36H428v-36c0-4.4-3.6-8-8-8h-40c-4.4 0-8 3.6-8 8v84c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-84c0-4.4-3.6-8-8-8z\"])})),t.CarryOutTwoTone=u(\"carry-out\",a,(function(e,t){return l(o,[e,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v584z\"],[t,\"M712 304c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H384v48c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H184v584h656V256H712v48zm-17.5 128.8L481.9 725.5a16.1 16.1 0 0 1-26 0l-126.4-174c-3.8-5.3 0-12.7 6.5-12.7h55.2c5.2 0 10 2.5 13 6.6l64.7 89 150.9-207.8c3-4.1 7.9-6.6 13-6.6H688c6.5 0 10.3 7.4 6.5 12.8z\"],[e,\"M688 420h-55.2c-5.1 0-10 2.5-13 6.6L468.9 634.4l-64.7-89c-3-4.1-7.8-6.6-13-6.6H336c-6.5 0-10.3 7.4-6.5 12.7l126.4 174a16.1 16.1 0 0 0 26 0l212.6-292.7c3.8-5.4 0-12.8-6.5-12.8z\"])})),t.CheckCircleTwoTone=u(\"check-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm193.4 225.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.3 0 19.9 5 25.9 13.3l71.2 98.8 157.2-218c6-8.4 15.7-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.4 12.7z\"],[e,\"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0 0 51.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z\"])})),t.CheckSquareTwoTone=u(\"check-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm130-367.8h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H688c6.5 0 10.3 7.4 6.5 12.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L307.5 484.9c-3.8-5.3 0-12.7 6.5-12.7z\"],[e,\"M432.2 657.7a31.8 31.8 0 0 0 51.7 0l210.6-292c3.8-5.3 0-12.7-6.5-12.7h-46.9c-10.3 0-19.9 5-25.9 13.3L458 584.3l-71.2-98.8c-6-8.4-15.7-13.3-25.9-13.3H314c-6.5 0-10.3 7.4-6.5 12.7l124.7 172.8z\"])})),t.ClockCircleTwoTone=u(\"clock-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm176.5 509.7l-28.6 39a7.99 7.99 0 0 1-11.2 1.7L483.3 569.8a7.92 7.92 0 0 1-3.3-6.5V288c0-4.4 3.6-8 8-8h48.1c4.4 0 8 3.6 8 8v247.5l142.6 103.1c3.6 2.5 4.4 7.5 1.8 11.1z\"],[e,\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.3c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.9 11.2-1.7l28.6-39c2.6-3.6 1.8-8.6-1.8-11.1z\"])})),t.CloseCircleTwoTone=u(\"close-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm171.8 527.1c1.2 1.5 1.9 3.3 1.9 5.2 0 4.5-3.6 8-8 8l-66-.3-99.3-118.4-99.3 118.5-66.1.3c-4.4 0-8-3.6-8-8 0-1.9.7-3.7 1.9-5.2L471 512.3l-130.1-155a8.32 8.32 0 0 1-1.9-5.2c0-4.5 3.6-8 8-8l66.1.3 99.3 118.4 99.4-118.5 66-.3c4.4 0 8 3.6 8 8 0 1.9-.6 3.8-1.8 5.2l-130.1 155 129.9 154.9z\"],[e,\"M685.8 352c0-4.4-3.6-8-8-8l-66 .3-99.4 118.5-99.3-118.4-66.1-.3c-4.4 0-8 3.5-8 8 0 1.9.7 3.7 1.9 5.2l130.1 155-130.1 154.9a8.32 8.32 0 0 0-1.9 5.2c0 4.4 3.6 8 8 8l66.1-.3 99.3-118.5L611.7 680l66 .3c4.4 0 8-3.5 8-8 0-1.9-.7-3.7-1.9-5.2L553.9 512.2l130.1-155c1.2-1.4 1.8-3.3 1.8-5.2z\"])})),t.CloudTwoTone=u(\"cloud\",a,(function(e,t){return l(o,[t,\"M791.9 492l-37.8-10-13.8-36.5c-8.6-22.7-20.6-44.1-35.7-63.4a245.73 245.73 0 0 0-52.4-49.9c-41.1-28.9-89.5-44.2-140-44.2s-98.9 15.3-140 44.2a245.6 245.6 0 0 0-52.4 49.9 240.47 240.47 0 0 0-35.7 63.4l-13.9 36.6-37.9 9.9a125.7 125.7 0 0 0-66.1 43.7A123.1 123.1 0 0 0 140 612c0 33.1 12.9 64.3 36.3 87.7 23.4 23.4 54.5 36.3 87.6 36.3h496.2c33.1 0 64.2-12.9 87.6-36.3A123.3 123.3 0 0 0 884 612c0-56.2-37.8-105.5-92.1-120z\"],[e,\"M811.4 418.7C765.6 297.9 648.9 212 512.2 212S258.8 297.8 213 418.6C127.3 441.1 64 519.1 64 612c0 110.5 89.5 200 199.9 200h496.2C870.5 812 960 722.5 960 612c0-92.7-63.1-170.7-148.6-193.3zm36.3 281a123.07 123.07 0 0 1-87.6 36.3H263.9c-33.1 0-64.2-12.9-87.6-36.3A123.3 123.3 0 0 1 140 612c0-28 9.1-54.3 26.2-76.3a125.7 125.7 0 0 1 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4a245.6 245.6 0 0 1 52.4-49.9c41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10c54.3 14.5 92.1 63.8 92.1 120 0 33.1-12.9 64.3-36.3 87.7z\"])})),t.CloseSquareTwoTone=u(\"close-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm163.9-473.9A7.95 7.95 0 0 1 354 353h58.9c4.7 0 9.2 2.1 12.3 5.7L512 462.2l86.8-103.5c3-3.6 7.5-5.7 12.3-5.7H670c6.8 0 10.5 7.9 6.1 13.1L553.8 512l122.3 145.9c4.4 5.2.7 13.1-6.1 13.1h-58.9c-4.7 0-9.2-2.1-12.3-5.7L512 561.8l-86.8 103.5c-3 3.6-7.5 5.7-12.3 5.7H354c-6.8 0-10.5-7.9-6.1-13.1L470.2 512 347.9 366.1z\"],[e,\"M354 671h58.9c4.8 0 9.3-2.1 12.3-5.7L512 561.8l86.8 103.5c3.1 3.6 7.6 5.7 12.3 5.7H670c6.8 0 10.5-7.9 6.1-13.1L553.8 512l122.3-145.9c4.4-5.2.7-13.1-6.1-13.1h-58.9c-4.8 0-9.3 2.1-12.3 5.7L512 462.2l-86.8-103.5c-3.1-3.6-7.6-5.7-12.3-5.7H354c-6.8 0-10.5 7.9-6.1 13.1L470.2 512 347.9 657.9A7.95 7.95 0 0 0 354 671z\"])})),t.CodeTwoTone=u(\"code\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm339.5-223h185c4.1 0 7.5 3.6 7.5 8v48c0 4.4-3.4 8-7.5 8h-185c-4.1 0-7.5-3.6-7.5-8v-48c0-4.4 3.4-8 7.5-8zM308 610.3c0-2.3 1.1-4.6 2.9-6.1L420.7 512l-109.8-92.2a7.63 7.63 0 0 1-2.9-6.1V351c0-6.8 7.9-10.5 13.1-6.1l192 160.9c3.9 3.2 3.9 9.1 0 12.3l-192 161c-5.2 4.4-13.1.7-13.1-6.1v-62.7z\"],[e,\"M321.1 679.1l192-161c3.9-3.2 3.9-9.1 0-12.3l-192-160.9A7.95 7.95 0 0 0 308 351v62.7c0 2.4 1 4.6 2.9 6.1L420.7 512l-109.8 92.2a8.1 8.1 0 0 0-2.9 6.1V673c0 6.8 7.9 10.5 13.1 6.1zM516 673c0 4.4 3.4 8 7.5 8h185c4.1 0 7.5-3.6 7.5-8v-48c0-4.4-3.4-8-7.5-8h-185c-4.1 0-7.5 3.6-7.5 8v48z\"])})),t.CompassTwoTone=u(\"compass\",a,(function(e,t){return l(o,[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zM327.6 701.7c-2 .9-4.4 0-5.3-2.1-.4-1-.4-2.2 0-3.2L421 470.9 553.1 603l-225.5 98.7zm375.1-375.1L604 552.1 471.9 420l225.5-98.7c2-.9 4.4 0 5.3 2.1.4 1 .4 2.1 0 3.2z\"],[e,\"M322.3 696.4c-.4 1-.4 2.2 0 3.2.9 2.1 3.3 3 5.3 2.1L553.1 603 421 470.9l-98.7 225.5zm375.1-375.1L471.9 420 604 552.1l98.7-225.5c.4-1.1.4-2.2 0-3.2-.9-2.1-3.3-3-5.3-2.1z\"],[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"])})),t.ContactsTwoTone=u(\"contacts\",a,(function(e,t){return l(o,[t,\"M460.3 526a51.7 52 0 1 0 103.4 0 51.7 52 0 1 0-103.4 0z\"],[t,\"M768 352c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H548v56c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H328v56c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H136v496h752V296H768v56zM661 736h-43.8c-4.2 0-7.6-3.3-7.9-7.5-3.8-50.5-46-90.5-97.2-90.5s-93.4 39.9-97.2 90.5c-.3 4.2-3.7 7.5-7.9 7.5h-43.9a8 8 0 0 1-8-8.4c2.8-53.3 31.9-99.6 74.6-126.1-18.1-20-29.1-46.4-29.1-75.5 0-61.9 49.9-112 111.4-112s111.4 50.1 111.4 112c0 29.1-11 55.6-29.1 75.5 42.7 26.4 71.9 72.8 74.7 126.1a8 8 0 0 1-8 8.4z\"],[e,\"M594.3 601.5a111.8 111.8 0 0 0 29.1-75.5c0-61.9-49.9-112-111.4-112s-111.4 50.1-111.4 112c0 29.1 11 55.5 29.1 75.5a158.09 158.09 0 0 0-74.6 126.1 8 8 0 0 0 8 8.4H407c4.2 0 7.6-3.3 7.9-7.5 3.8-50.6 46-90.5 97.2-90.5s93.4 40 97.2 90.5c.3 4.2 3.7 7.5 7.9 7.5H661a8 8 0 0 0 8-8.4c-2.8-53.3-32-99.7-74.7-126.1zM512 578c-28.5 0-51.7-23.3-51.7-52s23.2-52 51.7-52 51.7 23.3 51.7 52-23.2 52-51.7 52z\"],[e,\"M928 224H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zm-40 568H136V296h120v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h120v496z\"])})),t.ContainerTwoTone=u(\"container\",a,(function(e,t){return l(o,[t,\"M635 771.7c-34.5 28.6-78.2 44.3-123 44.3s-88.5-15.8-123-44.3a194.02 194.02 0 0 1-59.1-84.7H232v201h560V687h-97.9c-11.6 32.8-32 62.3-59.1 84.7z\"],[e,\"M320 501h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"],[e,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V687h97.9c11.6 32.8 32 62.3 59.1 84.7 34.5 28.5 78.2 44.3 123 44.3s88.5-15.7 123-44.3c27.1-22.4 47.5-51.9 59.1-84.7H792v201zm0-264H643.6l-5.2 24.7C626.4 708.5 573.2 752 512 752s-114.4-43.5-126.5-103.3l-5.2-24.7H232V136h560v488z\"],[e,\"M320 341h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"])})),t.ControlTwoTone=u(\"control\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M616 440a36 36 0 1 0 72 0 36 36 0 1 0-72 0zM340.4 601.5l1.5 2.4c0 .1.1.1.1.2l.9 1.2c.1.1.2.2.2.3 1 1.3 2 2.5 3.2 3.6l.2.2c.4.4.8.8 1.2 1.1.8.8 1.7 1.5 2.6 2.1h.1l1.2.9c.1.1.3.2.4.3 1.2.8 2.5 1.6 3.9 2.2.2.1.5.2.7.4.4.2.7.3 1.1.5.3.1.7.3 1 .4.5.2 1 .4 1.5.5.4.1.9.3 1.3.4l.9.3 1.4.3c.2.1.5.1.7.2.7.1 1.4.3 2.1.4.2 0 .4 0 .6.1.6.1 1.1.1 1.7.2.2 0 .4 0 .7.1.8 0 1.5.1 2.3.1s1.5 0 2.3-.1c.2 0 .4 0 .7-.1.6 0 1.2-.1 1.7-.2.2 0 .4 0 .6-.1.7-.1 1.4-.2 2.1-.4.2-.1.5-.1.7-.2l1.4-.3.9-.3c.4-.1.9-.3 1.3-.4.5-.2 1-.4 1.5-.5.3-.1.7-.3 1-.4.4-.2.7-.3 1.1-.5.2-.1.5-.2.7-.4 1.3-.7 2.6-1.4 3.9-2.2.1-.1.3-.2.4-.3l1.2-.9h.1c.9-.7 1.8-1.4 2.6-2.1.4-.4.8-.7 1.2-1.1l.2-.2c1.1-1.1 2.2-2.4 3.2-3.6.1-.1.2-.2.2-.3l.9-1.2c0-.1.1-.1.1-.2l1.5-2.4c.1-.2.2-.3.3-.5 2.7-5.1 4.3-10.9 4.3-17s-1.6-12-4.3-17c-.1-.2-.2-.4-.3-.5l-1.5-2.4c0-.1-.1-.1-.1-.2l-.9-1.2c-.1-.1-.2-.2-.2-.3-1-1.3-2-2.5-3.2-3.6l-.2-.2c-.4-.4-.8-.8-1.2-1.1-.8-.8-1.7-1.5-2.6-2.1h-.1l-1.2-.9c-.1-.1-.3-.2-.4-.3-1.2-.8-2.5-1.6-3.9-2.2-.2-.1-.5-.2-.7-.4-.4-.2-.7-.3-1.1-.5-.3-.1-.7-.3-1-.4-.5-.2-1-.4-1.5-.5-.4-.1-.9-.3-1.3-.4l-.9-.3-1.4-.3c-.2-.1-.5-.1-.7-.2-.7-.1-1.4-.3-2.1-.4-.2 0-.4 0-.6-.1-.6-.1-1.1-.1-1.7-.2-.2 0-.4 0-.7-.1-.8 0-1.5-.1-2.3-.1s-1.5 0-2.3.1c-.2 0-.4 0-.7.1-.6 0-1.2.1-1.7.2-.2 0-.4 0-.6.1-.7.1-1.4.2-2.1.4-.2.1-.5.1-.7.2l-1.4.3-.9.3c-.4.1-.9.3-1.3.4-.5.2-1 .4-1.5.5-.3.1-.7.3-1 .4-.4.2-.7.3-1.1.5-.2.1-.5.2-.7.4-1.3.7-2.6 1.4-3.9 2.2-.1.1-.3.2-.4.3l-1.2.9h-.1c-.9.7-1.8 1.4-2.6 2.1-.4.4-.8.7-1.2 1.1l-.2.2a54.8 54.8 0 0 0-3.2 3.6c-.1.1-.2.2-.2.3l-.9 1.2c0 .1-.1.1-.1.2l-1.5 2.4c-.1.2-.2.3-.3.5-2.7 5.1-4.3 10.9-4.3 17s1.6 12 4.3 17c.1.2.2.3.3.5z\"],[t,\"M184 840h656V184H184v656zm436.4-499.1c-.2 0-.3.1-.4.1v-77c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v77c-.2 0-.3-.1-.4-.1 42 13.4 72.4 52.7 72.4 99.1 0 46.4-30.4 85.7-72.4 99.1.2 0 .3-.1.4-.1v221c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V539c.2 0 .3.1.4.1-42-13.4-72.4-52.7-72.4-99.1 0-46.4 30.4-85.7 72.4-99.1zM340 485V264c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v221c41.7 13.6 72 52.8 72 99s-30.3 85.5-72 99v77c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-77c-41.7-13.6-72-52.8-72-99s30.3-85.5 72-99z\"],[e,\"M340 683v77c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-77c41.7-13.5 72-52.8 72-99s-30.3-85.4-72-99V264c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v221c-41.7 13.5-72 52.8-72 99s30.3 85.4 72 99zm.1-116c.1-.2.2-.3.3-.5l1.5-2.4c0-.1.1-.1.1-.2l.9-1.2c0-.1.1-.2.2-.3 1-1.2 2.1-2.5 3.2-3.6l.2-.2c.4-.4.8-.7 1.2-1.1.8-.7 1.7-1.4 2.6-2.1h.1l1.2-.9c.1-.1.3-.2.4-.3 1.3-.8 2.6-1.5 3.9-2.2.2-.2.5-.3.7-.4.4-.2.7-.3 1.1-.5.3-.1.7-.3 1-.4.5-.1 1-.3 1.5-.5.4-.1.9-.3 1.3-.4l.9-.3 1.4-.3c.2-.1.5-.1.7-.2.7-.2 1.4-.3 2.1-.4.2-.1.4-.1.6-.1.5-.1 1.1-.2 1.7-.2.3-.1.5-.1.7-.1.8-.1 1.5-.1 2.3-.1s1.5.1 2.3.1c.3.1.5.1.7.1.6.1 1.1.1 1.7.2.2.1.4.1.6.1.7.1 1.4.3 2.1.4.2.1.5.1.7.2l1.4.3.9.3c.4.1.9.3 1.3.4.5.1 1 .3 1.5.5.3.1.7.3 1 .4.4.2.7.3 1.1.5.2.2.5.3.7.4 1.4.6 2.7 1.4 3.9 2.2.1.1.3.2.4.3l1.2.9h.1c.9.6 1.8 1.3 2.6 2.1.4.3.8.7 1.2 1.1l.2.2c1.2 1.1 2.2 2.3 3.2 3.6 0 .1.1.2.2.3l.9 1.2c0 .1.1.1.1.2l1.5 2.4A36.03 36.03 0 0 1 408 584c0 6.1-1.6 11.9-4.3 17-.1.2-.2.3-.3.5l-1.5 2.4c0 .1-.1.1-.1.2l-.9 1.2c0 .1-.1.2-.2.3-1 1.2-2.1 2.5-3.2 3.6l-.2.2c-.4.4-.8.7-1.2 1.1-.8.7-1.7 1.4-2.6 2.1h-.1l-1.2.9c-.1.1-.3.2-.4.3-1.3.8-2.6 1.5-3.9 2.2-.2.2-.5.3-.7.4-.4.2-.7.3-1.1.5-.3.1-.7.3-1 .4-.5.1-1 .3-1.5.5-.4.1-.9.3-1.3.4l-.9.3-1.4.3c-.2.1-.5.1-.7.2-.7.2-1.4.3-2.1.4-.2.1-.4.1-.6.1-.5.1-1.1.2-1.7.2-.3.1-.5.1-.7.1-.8.1-1.5.1-2.3.1s-1.5-.1-2.3-.1c-.3-.1-.5-.1-.7-.1-.6-.1-1.1-.1-1.7-.2-.2-.1-.4-.1-.6-.1-.7-.1-1.4-.3-2.1-.4-.2-.1-.5-.1-.7-.2l-1.4-.3-.9-.3c-.4-.1-.9-.3-1.3-.4-.5-.1-1-.3-1.5-.5-.3-.1-.7-.3-1-.4-.4-.2-.7-.3-1.1-.5-.2-.2-.5-.3-.7-.4-1.4-.6-2.7-1.4-3.9-2.2-.1-.1-.3-.2-.4-.3l-1.2-.9h-.1c-.9-.6-1.8-1.3-2.6-2.1-.4-.3-.8-.7-1.2-1.1l-.2-.2c-1.2-1.1-2.2-2.3-3.2-3.6 0-.1-.1-.2-.2-.3l-.9-1.2c0-.1-.1-.1-.1-.2l-1.5-2.4c-.1-.2-.2-.3-.3-.5-2.7-5-4.3-10.9-4.3-17s1.6-11.9 4.3-17zm280.3-27.9c-.1 0-.2-.1-.4-.1v221c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V539c-.1 0-.2.1-.4.1 42-13.4 72.4-52.7 72.4-99.1 0-46.4-30.4-85.7-72.4-99.1.1 0 .2.1.4.1v-77c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v77c.1 0 .2-.1.4-.1-42 13.4-72.4 52.7-72.4 99.1 0 46.4 30.4 85.7 72.4 99.1zM652 404c19.9 0 36 16.1 36 36s-16.1 36-36 36-36-16.1-36-36 16.1-36 36-36z\"])})),t.CopyTwoTone=u(\"copy\",a,(function(e,t){return l(o,[t,\"M232 706h142c22.1 0 40 17.9 40 40v142h250V264H232v442z\"],[e,\"M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32z\"],[e,\"M704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z\"])})),t.CreditCardTwoTone=u(\"credit-card\",a,(function(e,t){return l(o,[t,\"M136 792h752V440H136v352zm507-144c0-4.4 3.6-8 8-8h165c4.4 0 8 3.6 8 8v72c0 4.4-3.6 8-8 8H651c-4.4 0-8-3.6-8-8v-72zM136 232h752v120H136z\"],[e,\"M651 728h165c4.4 0 8-3.6 8-8v-72c0-4.4-3.6-8-8-8H651c-4.4 0-8 3.6-8 8v72c0 4.4 3.6 8 8 8z\"],[e,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136V440h752v352zm0-440H136V232h752v120z\"])})),t.CrownTwoTone=u(\"crown\",a,(function(e,t){return l(o,[t,\"M911.9 283.9v.5L835.5 865c-1 8-7.9 14-15.9 14H204.5c-8.1 0-14.9-6.1-16-14l-76.4-580.6v-.6 1.6L188.5 866c1.1 7.9 7.9 14 16 14h615.1c8 0 14.9-6 15.9-14l76.4-580.6c.1-.5.1-1 0-1.5z\"],[t,\"M773.6 810.6l53.9-409.4-139.8 86.1L512 252.9 336.3 487.3l-139.8-86.1 53.8 409.4h523.3zm-374.2-189c0-62.1 50.5-112.6 112.6-112.6s112.6 50.5 112.6 112.6v1c0 62.1-50.5 112.6-112.6 112.6s-112.6-50.5-112.6-112.6v-1z\"],[e,\"M512 734.2c61.9 0 112.3-50.2 112.6-112.1v-.5c0-62.1-50.5-112.6-112.6-112.6s-112.6 50.5-112.6 112.6v.5c.3 61.9 50.7 112.1 112.6 112.1zm0-160.9c26.6 0 48.2 21.6 48.2 48.3 0 26.6-21.6 48.3-48.2 48.3s-48.2-21.6-48.2-48.3c0-26.6 21.6-48.3 48.2-48.3z\"],[e,\"M188.5 865c1.1 7.9 7.9 14 16 14h615.1c8 0 14.9-6 15.9-14l76.4-580.6v-.5c.3-6.4-6.7-10.8-12.3-7.4L705 396.4 518.4 147.5a8.06 8.06 0 0 0-12.9 0L319 396.4 124.3 276.5c-5.5-3.4-12.6.9-12.2 7.3v.6L188.5 865zm147.8-377.7L512 252.9l175.7 234.4 139.8-86.1-53.9 409.4H250.3l-53.8-409.4 139.8 86.1z\"])})),t.CustomerServiceTwoTone=u(\"customer-service\",a,(function(e,t){return l(o,[t,\"M696 632h128v192H696zm-496 0h128v192H200z\"],[e,\"M512 128c-212.1 0-384 171.9-384 384v360c0 13.3 10.7 24 24 24h184c35.3 0 64-28.7 64-64V624c0-35.3-28.7-64-64-64H200v-48c0-172.3 139.7-312 312-312s312 139.7 312 312v48H688c-35.3 0-64 28.7-64 64v208c0 35.3 28.7 64 64 64h184c13.3 0 24-10.7 24-24V512c0-212.1-171.9-384-384-384zM328 632v192H200V632h128zm496 192H696V632h128v192z\"])})),t.DashboardTwoTone=u(\"dashboard\",a,(function(e,t){return l(o,[t,\"M512 188c-99.3 0-192.7 38.7-263 109-70.3 70.2-109 163.6-109 263 0 105.6 44.5 205.5 122.6 276h498.8A371.12 371.12 0 0 0 884 560c0-99.3-38.7-192.7-109-263-70.2-70.3-163.6-109-263-109zm-30 44c0-4.4 3.6-8 8-8h44c4.4 0 8 3.6 8 8v80c0 4.4-3.6 8-8 8h-44c-4.4 0-8-3.6-8-8v-80zM270 582c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8v-44c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v44zm90.7-204.4l-31.1 31.1a8.03 8.03 0 0 1-11.3 0l-56.6-56.6a8.03 8.03 0 0 1 0-11.3l31.1-31.1c3.1-3.1 8.2-3.1 11.3 0l56.6 56.6c3.1 3.1 3.1 8.2 0 11.3zm291.1 83.5l-84.5 84.5c5 18.7.2 39.4-14.5 54.1a55.95 55.95 0 0 1-79.2 0 55.95 55.95 0 0 1 0-79.2 55.87 55.87 0 0 1 54.1-14.5l84.5-84.5c3.1-3.1 8.2-3.1 11.3 0l28.3 28.3c3.1 3.1 3.1 8.2 0 11.3zm43-52.4l-31.1-31.1a8.03 8.03 0 0 1 0-11.3l56.6-56.6c3.1-3.1 8.2-3.1 11.3 0l31.1 31.1c3.1 3.1 3.1 8.2 0 11.3l-56.6 56.6a8.03 8.03 0 0 1-11.3 0zM846 538v44c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8v-44c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8z\"],[e,\"M623.5 421.5a8.03 8.03 0 0 0-11.3 0L527.7 506c-18.7-5-39.4-.2-54.1 14.5a55.95 55.95 0 0 0 0 79.2 55.95 55.95 0 0 0 79.2 0 55.87 55.87 0 0 0 14.5-54.1l84.5-84.5c3.1-3.1 3.1-8.2 0-11.3l-28.3-28.3zM490 320h44c4.4 0 8-3.6 8-8v-80c0-4.4-3.6-8-8-8h-44c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8z\"],[e,\"M924.8 385.6a446.7 446.7 0 0 0-96-142.4 446.7 446.7 0 0 0-142.4-96C631.1 123.8 572.5 112 512 112s-119.1 11.8-174.4 35.2a446.7 446.7 0 0 0-142.4 96 446.7 446.7 0 0 0-96 142.4C75.8 440.9 64 499.5 64 560c0 132.7 58.3 257.7 159.9 343.1l1.7 1.4c5.8 4.8 13.1 7.5 20.6 7.5h531.7c7.5 0 14.8-2.7 20.6-7.5l1.7-1.4C901.7 817.7 960 692.7 960 560c0-60.5-11.9-119.1-35.2-174.4zM761.4 836H262.6A371.12 371.12 0 0 1 140 560c0-99.4 38.7-192.8 109-263 70.3-70.3 163.7-109 263-109 99.4 0 192.8 38.7 263 109 70.3 70.3 109 163.7 109 263 0 105.6-44.5 205.5-122.6 276z\"],[e,\"M762.7 340.8l-31.1-31.1a8.03 8.03 0 0 0-11.3 0l-56.6 56.6a8.03 8.03 0 0 0 0 11.3l31.1 31.1c3.1 3.1 8.2 3.1 11.3 0l56.6-56.6c3.1-3.1 3.1-8.2 0-11.3zM750 538v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8zM304.1 309.7a8.03 8.03 0 0 0-11.3 0l-31.1 31.1a8.03 8.03 0 0 0 0 11.3l56.6 56.6c3.1 3.1 8.2 3.1 11.3 0l31.1-31.1c3.1-3.1 3.1-8.2 0-11.3l-56.6-56.6zM262 530h-80c-4.4 0-8 3.6-8 8v44c0 4.4 3.6 8 8 8h80c4.4 0 8-3.6 8-8v-44c0-4.4-3.6-8-8-8z\"])})),t.DeleteTwoTone=u(\"delete\",a,(function(e,t){return l(o,[t,\"M292.7 840h438.6l24.2-512h-487z\"],[e,\"M864 256H736v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zm-504-72h304v72H360v-72zm371.3 656H292.7l-24.2-512h487l-24.2 512z\"])})),t.DiffTwoTone=u(\"diff\",a,(function(e,t){return l(o,[t,\"M232 264v624h432V413.8L514.2 264H232zm336 489c0 3.8-3.4 7-7.5 7h-225c-4.1 0-7.5-3.2-7.5-7v-42c0-3.8 3.4-7 7.5-7h225c4.1 0 7.5 3.2 7.5 7v42zm0-262v42c0 3.8-3.4 7-7.5 7H476v84.9c0 3.9-3.1 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V540h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.9 3.4-7 7.5-7H420v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.9 0 7 3.2 7 7.1V484h84.5c4.1 0 7.5 3.1 7.5 7z\"],[e,\"M854.2 306.6L611.3 72.9c-6-5.7-13.9-8.9-22.2-8.9H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h277l219 210.6V824c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V329.6c0-8.7-3.5-17-9.8-23z\"],[e,\"M553.4 201.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V397.3c0-8.5-3.4-16.6-9.4-22.6L553.4 201.4zM664 888H232V264h282.2L664 413.8V888z\"],[e,\"M476 399.1c0-3.9-3.1-7.1-7-7.1h-42c-3.8 0-7 3.2-7 7.1V484h-84.5c-4.1 0-7.5 3.1-7.5 7v42c0 3.8 3.4 7 7.5 7H420v84.9c0 3.9 3.2 7.1 7 7.1h42c3.9 0 7-3.2 7-7.1V540h84.5c4.1 0 7.5-3.2 7.5-7v-42c0-3.9-3.4-7-7.5-7H476v-84.9zM560.5 704h-225c-4.1 0-7.5 3.2-7.5 7v42c0 3.8 3.4 7 7.5 7h225c4.1 0 7.5-3.2 7.5-7v-42c0-3.8-3.4-7-7.5-7z\"])})),t.DatabaseTwoTone=u(\"database\",a,(function(e,t){return l(o,[t,\"M232 616h560V408H232v208zm112-144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zM232 888h560V680H232v208zm112-144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zM232 344h560V136H232v208zm112-144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\"],[e,\"M304 512a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0 272a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0-544a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"],[e,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V680h560v208zm0-272H232V408h560v208zm0-272H232V136h560v208z\"])})),t.DislikeTwoTone=u(\"dislike\",a,(function(e,t){return l(o,[t,\"M273 100.1v428h.3l-.3-428zM820.4 525l-21.9-19 14-25.5a56.2 56.2 0 0 0 6.9-27.3c0-16.5-7.1-32.2-19.6-43l-21.9-19 13.9-25.4a56.2 56.2 0 0 0 6.9-27.3c0-16.5-7.1-32.2-19.6-43l-21.9-19 13.9-25.4a56.2 56.2 0 0 0 6.9-27.3c0-22.4-13.2-42.6-33.6-51.8H345v345.2c18.6 67.2 46.4 168 83.5 302.5a44.28 44.28 0 0 0 42.2 32.3c7.5.1 15-2.2 21.1-6.7 9.9-7.4 15.2-18.6 14.6-30.5l-9.6-198.4h314.4C829 605.5 840 587.1 840 568c0-16.5-7.1-32.2-19.6-43z\"],[e,\"M112 132v364c0 17.7 14.3 32 32 32h65V100h-65c-17.7 0-32 14.3-32 32zm773.9 358.3c3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-28.3-9.3-55.5-26.1-77.7 3.6-12 5.4-24.4 5.4-37 0-51.6-30.7-98.1-78.3-118.4a66.1 66.1 0 0 0-26.5-5.4H273l.3 428 85.8 310.8C372.9 889 418.9 924 470.9 924c29.7 0 57.4-11.8 77.9-33.4 20.5-21.5 31-49.7 29.5-79.4l-6-122.9h239.9c12.1 0 23.9-3.2 34.3-9.3 40.4-23.5 65.5-66.1 65.5-111 0-28.3-9.3-55.5-26.1-77.7zm-74.7 126.1H496.8l9.6 198.4c.6 11.9-4.7 23.1-14.6 30.5-6.1 4.5-13.6 6.8-21.1 6.7a44.28 44.28 0 0 1-42.2-32.3c-37.1-134.4-64.9-235.2-83.5-302.5V172h399.4a56.85 56.85 0 0 1 33.6 51.8c0 9.7-2.3 18.9-6.9 27.3l-13.9 25.4 21.9 19a56.76 56.76 0 0 1 19.6 43c0 9.7-2.3 18.9-6.9 27.3l-13.9 25.4 21.9 19a56.76 56.76 0 0 1 19.6 43c0 9.7-2.3 18.9-6.9 27.3l-14 25.5 21.9 19a56.76 56.76 0 0 1 19.6 43c0 19.1-11 37.5-28.8 48.4z\"])})),t.DownCircleTwoTone=u(\"down-circle\",a,(function(e,t){return l(o,[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm184.4 277.7l-178 246a7.95 7.95 0 0 1-12.9 0l-178-246c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.3 0 19.9 4.9 25.9 13.2L512 563.6l105.2-145.4c6-8.3 15.7-13.2 25.9-13.2H690c6.5 0 10.3 7.4 6.4 12.7z\"],[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[e,\"M690 405h-46.9c-10.2 0-19.9 4.9-25.9 13.2L512 563.6 406.8 418.2c-6-8.3-15.6-13.2-25.9-13.2H334c-6.5 0-10.3 7.4-6.5 12.7l178 246c3.2 4.4 9.7 4.4 12.9 0l178-246c3.9-5.3.1-12.7-6.4-12.7z\"])})),t.DownSquareTwoTone=u(\"down-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm150-440h46.9c10.3 0 19.9 4.9 25.9 13.2L512 558.6l105.2-145.4c6-8.3 15.7-13.2 25.9-13.2H690c6.5 0 10.3 7.4 6.4 12.7l-178 246a7.95 7.95 0 0 1-12.9 0l-178-246c-3.8-5.3 0-12.7 6.5-12.7z\"],[e,\"M505.5 658.7c3.2 4.4 9.7 4.4 12.9 0l178-246c3.9-5.3.1-12.7-6.4-12.7h-46.9c-10.2 0-19.9 4.9-25.9 13.2L512 558.6 406.8 413.2c-6-8.3-15.6-13.2-25.9-13.2H334c-6.5 0-10.3 7.4-6.5 12.7l178 246z\"])})),t.EnvironmentTwoTone=u(\"environment\",a,(function(e,t){return l(o,[t,\"M724.4 224.9C667.7 169.5 592.3 139 512 139s-155.7 30.5-212.4 85.8C243.1 280 212 353.2 212 431.1c0 241.3 234.1 407.2 300 449.1 65.9-41.9 300-207.8 300-449.1 0-77.9-31.1-151.1-87.6-206.2zM512 615c-97.2 0-176-78.8-176-176s78.8-176 176-176 176 78.8 176 176-78.8 176-176 176z\"],[e,\"M512 263c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 0 1 512 551c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 0 1 400 439c0-29.9 11.7-58 32.8-79.2C454 338.6 482.1 327 512 327c29.9 0 58 11.6 79.2 32.8S624 409.1 624 439c0 29.9-11.6 58-32.8 79.2z\"],[e,\"M854.6 289.1a362.49 362.49 0 0 0-79.9-115.7 370.83 370.83 0 0 0-118.2-77.8C610.7 76.6 562.1 67 512 67c-50.1 0-98.7 9.6-144.5 28.5-44.3 18.3-84 44.5-118.2 77.8A363.6 363.6 0 0 0 169.4 289c-19.5 45-29.4 92.8-29.4 142 0 70.6 16.9 140.9 50.1 208.7 26.7 54.5 64 107.6 111 158.1 80.3 86.2 164.5 138.9 188.4 153a43.9 43.9 0 0 0 22.4 6.1c7.8 0 15.5-2 22.4-6.1 23.9-14.1 108.1-66.8 188.4-153 47-50.4 84.3-103.6 111-158.1C867.1 572 884 501.8 884 431.1c0-49.2-9.9-97-29.4-142zM512 880.2c-65.9-41.9-300-207.8-300-449.1 0-77.9 31.1-151.1 87.6-206.3C356.3 169.5 431.7 139 512 139s155.7 30.5 212.4 85.9C780.9 280 812 353.2 812 431.1c0 241.3-234.1 407.2-300 449.1z\"])})),t.EditTwoTone=u(\"edit\",a,(function(e,t){return l(o,[t,\"M761.1 288.3L687.8 215 325.1 577.6l-15.6 89 88.9-15.7z\"],[e,\"M880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32zm-622.3-84c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 0 0 0-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 0 0 9.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89z\"])})),t.ExclamationCircleTwoTone=u(\"exclamation-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm-32 156c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M488 576h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8zm-24 112a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.ExperimentTwoTone=u(\"experiment\",a,(function(e,t){return l(o,[t,\"M551.9 513c19.6 0 35.9-14.2 39.3-32.8A40.02 40.02 0 0 1 552 512a40 40 0 0 1-40-39.4v.5c0 22 17.9 39.9 39.9 39.9zM752 687.8l-.3-.3c-29-17.5-62.3-26.8-97-26.8-44.9 0-87.2 15.7-121 43.8a256.27 256.27 0 0 1-164.9 59.9c-41.2 0-81-9.8-116.7-28L210.5 844h603l-59.9-155.2-1.6-1z\"],[e,\"M879 824.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 0 1-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.6-107.6.1-.2c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1l.6 1.6L813.5 844h-603z\"],[e,\"M552 512c19.3 0 35.4-13.6 39.2-31.8.6-2.7.8-5.4.8-8.2 0-22.1-17.9-40-40-40s-40 17.9-40 40v.6a40 40 0 0 0 40 39.4z\"])})),t.EyeInvisibleTwoTone=u(\"eye-invisible\",a,(function(e,t){return l(o,[t,\"M254.89 758.85l125.57-125.57a176 176 0 0 1 248.82-248.82L757 256.72Q651.69 186.07 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q69.27 145.91 173.09 221.05zM942.2 486.2Q889.46 375.11 816.7 305L672.48 449.27a176.09 176.09 0 0 1-227.22 227.21L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5z\"],[e,\"M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5zM878.63 165.56L836 122.88a8 8 0 0 0-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 0 0 0 11.31L155.17 889a8 8 0 0 0 11.31 0l712.15-712.12a8 8 0 0 0 0-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 0 0-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 0 1 146.2-106.69L401.31 546.2A112 112 0 0 1 396 512z\"],[e,\"M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 0 0 227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 0 1-112 112z\"])})),t.EyeTwoTone=u(\"eye\",a,(function(e,t){return l(o,[t,\"M81.8 537.8a60.3 60.3 0 0 1 0-51.5C176.6 286.5 319.8 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 0 0 0 51.5C176.6 737.5 319.9 838 512 838c-192.1 0-335.4-100.5-430.2-300.2z\"],[t,\"M512 258c-161.3 0-279.4 81.8-362.7 254C232.6 684.2 350.7 766 512 766c161.4 0 279.5-81.8 362.7-254C791.4 339.8 673.3 258 512 258zm-4 430c-97.2 0-176-78.8-176-176s78.8-176 176-176 176 78.8 176 176-78.8 176-176 176z\"],[e,\"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 0 0 0 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258s279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766z\"],[e,\"M508 336c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z\"])})),t.FileAddTwoTone=u(\"file-add\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm126 236v48c0 4.4-3.6 8-8 8H544v108c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V644H372c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h108V472c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v108h108c4.4 0 8 3.6 8 8z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M544 472c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v108H372c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h108v108c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V644h108c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V472z\"])})),t.FileExclamationTwoTone=u(\"file-exclamation\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm-54 96c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v184c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V448zm32 336c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M488 640h48c4.4 0 8-3.6 8-8V448c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8zm-16 104a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"])})),t.FileImageTwoTone=u(\"file-image\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm-134 50c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zm296 294H328.1c-6.7 0-10.4-7.7-6.3-12.9l99.8-127.2a8 8 0 0 1 12.6 0l41.1 52.4 77.8-99.2a8.1 8.1 0 0 1 12.7 0l136.5 174c4.1 5.2.4 12.9-6.3 12.9z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M553.1 509.1l-77.8 99.2-41.1-52.4a8 8 0 0 0-12.6 0l-99.8 127.2a7.98 7.98 0 0 0 6.3 12.9H696c6.7 0 10.4-7.7 6.3-12.9l-136.5-174a8.1 8.1 0 0 0-12.7 0zM360 442a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"])})),t.FileExcelTwoTone=u(\"file-excel\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm51.6 120h35.7a12.04 12.04 0 0 1 10.1 18.5L546.1 623l84 130.4c3.6 5.6 2 13-3.6 16.6-2 1.2-4.2 1.9-6.5 1.9h-37.5c-4.1 0-8-2.1-10.2-5.7L510 664.8l-62.7 101.5c-2.2 3.5-6 5.7-10.2 5.7h-34.5a12.04 12.04 0 0 1-10.2-18.4l83.4-132.8-82.3-130.4c-3.6-5.7-1.9-13.1 3.7-16.6 1.9-1.3 4.1-1.9 6.4-1.9H442c4.2 0 8.1 2.2 10.3 5.8l61.8 102.4 61.2-102.3c2.2-3.6 6.1-5.8 10.3-5.8z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M514.1 580.1l-61.8-102.4c-2.2-3.6-6.1-5.8-10.3-5.8h-38.4c-2.3 0-4.5.6-6.4 1.9-5.6 3.5-7.3 10.9-3.7 16.6l82.3 130.4-83.4 132.8a12.04 12.04 0 0 0 10.2 18.4h34.5c4.2 0 8-2.2 10.2-5.7L510 664.8l62.3 101.4c2.2 3.6 6.1 5.7 10.2 5.7H620c2.3 0 4.5-.7 6.5-1.9 5.6-3.6 7.2-11 3.6-16.6l-84-130.4 85.3-132.5a12.04 12.04 0 0 0-10.1-18.5h-35.7c-4.2 0-8.1 2.2-10.3 5.8l-61.2 102.3z\"])})),t.FileMarkdownTwoTone=u(\"file-markdown\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm72.3 122H641c6.6 0 12 5.4 12 12v272c0 6.6-5.4 12-12 12h-27.2c-6.6 0-12-5.4-12-12V581.7L535 732.3c-2 4.3-6.3 7.1-11 7.1h-24.1a12 12 0 0 1-11-7.1l-66.8-150.2V758c0 6.6-5.4 12-12 12H383c-6.6 0-12-5.4-12-12V486c0-6.6 5.4-12 12-12h35c4.8 0 9.1 2.8 11 7.2l83.2 191 83.1-191c1.9-4.4 6.2-7.2 11-7.2z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M429 481.2c-1.9-4.4-6.2-7.2-11-7.2h-35c-6.6 0-12 5.4-12 12v272c0 6.6 5.4 12 12 12h27.1c6.6 0 12-5.4 12-12V582.1l66.8 150.2a12 12 0 0 0 11 7.1H524c4.7 0 9-2.8 11-7.1l66.8-150.6V758c0 6.6 5.4 12 12 12H641c6.6 0 12-5.4 12-12V486c0-6.6-5.4-12-12-12h-34.7c-4.8 0-9.1 2.8-11 7.2l-83.1 191-83.2-191z\"])})),t.FilePdfTwoTone=u(\"file-pdf\",a,(function(e,t){return l(o,[t,\"M509.2 490.8c-.7-1.3-1.4-1.9-2.2-2-2.9 3.3-2.2 31.5 2.7 51.4 4-13.6 4.7-40.5-.5-49.4zm-1.6 120.5c-7.7 20-18.8 47.3-32.1 71.4 4-1.6 8.1-3.3 12.3-5 17.6-7.2 37.3-15.3 58.9-20.2-14.9-11.8-28.4-27.7-39.1-46.2z\"],[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm55 287.6c16.1-1.9 30.6-2.8 44.3-2.3 12.8.4 23.6 2 32 5.1.2.1.3.1.5.2.4.2.8.3 1.2.5.5.2 1.1.4 1.6.7.1.1.3.1.4.2 4.1 1.8 7.5 4 10.1 6.6 9.1 9.1 11.8 26.1 6.2 39.6-3.2 7.7-11.7 20.5-33.3 20.5-21.8 0-53.9-9.7-82.1-24.8-25.5 4.3-53.7 13.9-80.9 23.1-5.8 2-11.8 4-17.6 5.9-38 65.2-66.5 79.4-84.1 79.4-4.2 0-7.8-.9-10.8-2-6.9-2.6-12.8-8-16.5-15-.9-1.7-1.6-3.4-2.2-5.2-1.6-4.8-2.1-9.6-1.3-13.6l.6-2.7c.1-.2.1-.4.2-.6.2-.7.4-1.4.7-2.1 0-.1.1-.2.1-.3 4.1-11.9 13.6-23.4 27.7-34.6 12.3-9.8 27.1-18.7 45.9-28.4 15.9-28 37.6-75.1 51.2-107.4-10.8-41.8-16.7-74.6-10.1-98.6.9-3.3 2.5-6.4 4.6-9.1.2-.2.3-.4.5-.6.1-.1.1-.2.2-.2 6.3-7.5 16.9-11.9 28.1-11.5 16.6.7 29.7 11.5 33 30.1 1.7 8 2.2 16.5 1.9 25.7v.7c0 .5 0 1-.1 1.5-.7 13.3-3 26.6-7.3 44.7-.4 1.6-.8 3.2-1.2 5.2l-1 4.1-.1.3c.1.2.1.3.2.5l1.8 4.5c.1.3.3.7.4 1 .7 1.6 1.4 3.3 2.1 4.8v.1c8.7 18.8 19.7 33.4 33.9 45.1 4.3 3.5 8.9 6.7 13.9 9.8 1.8-.5 3.5-.7 5.3-.9z\"],[t,\"M391.5 761c5.7-4.4 16.2-14.5 30.1-34.7-10.3 9.4-23.4 22.4-30.1 34.7zm270.9-83l.2-.3h.2c.6-.4.5-.7.4-.9-.1-.1-4.5-9.3-45.1-7.4 35.3 13.9 43.5 9.1 44.3 8.6z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M535.9 585.3c-.8-1.7-1.5-3.3-2.2-4.9-.1-.3-.3-.7-.4-1l-1.8-4.5c-.1-.2-.1-.3-.2-.5l.1-.3.2-1.1c4-16.3 8.6-35.3 9.4-54.4v-.7c.3-8.6-.2-17.2-2-25.6-3.8-21.3-19.5-29.6-32.9-30.2-11.3-.5-21.8 4-28.1 11.4-.1.1-.1.2-.2.2-.2.2-.4.4-.5.6-2.1 2.7-3.7 5.8-4.6 9.1-6.6 24-.7 56.8 10.1 98.6-13.6 32.4-35.3 79.4-51.2 107.4v.1c-27.7 14.3-64.1 35.8-73.6 62.9 0 .1-.1.2-.1.3-.2.7-.5 1.4-.7 2.1-.1.2-.1.4-.2.6-.2.9-.5 1.8-.6 2.7-.9 4-.4 8.8 1.3 13.6.6 1.8 1.3 3.5 2.2 5.2 3.7 7 9.6 12.4 16.5 15 3 1.1 6.6 2 10.8 2 17.6 0 46.1-14.2 84.1-79.4 5.8-1.9 11.8-3.9 17.6-5.9 27.2-9.2 55.4-18.8 80.9-23.1 28.2 15.1 60.3 24.8 82.1 24.8 21.6 0 30.1-12.8 33.3-20.5 5.6-13.5 2.9-30.5-6.2-39.6-2.6-2.6-6-4.8-10.1-6.6-.1-.1-.3-.1-.4-.2-.5-.2-1.1-.4-1.6-.7-.4-.2-.8-.3-1.2-.5-.2-.1-.3-.1-.5-.2-16.2-5.8-41.7-6.7-76.3-2.8l-5.3.6c-5-3-9.6-6.3-13.9-9.8-14.2-11.3-25.1-25.8-33.8-44.7zM391.5 761c6.7-12.3 19.8-25.3 30.1-34.7-13.9 20.2-24.4 30.3-30.1 34.7zM507 488.8c.8.1 1.5.7 2.2 2 5.2 8.9 4.5 35.8.5 49.4-4.9-19.9-5.6-48.1-2.7-51.4zm-19.2 188.9c-4.2 1.7-8.3 3.4-12.3 5 13.3-24.1 24.4-51.4 32.1-71.4 10.7 18.5 24.2 34.4 39.1 46.2-21.6 4.9-41.3 13-58.9 20.2zm175.4-.9c.1.2.2.5-.4.9h-.2l-.2.3c-.8.5-9 5.3-44.3-8.6 40.6-1.9 45 7.3 45.1 7.4z\"])})),t.FilePptTwoTone=u(\"file-ppt\",a,(function(e,t){return l(o,[t,\"M464.5 516.2v108.4h38.9c44.7 0 71.2-10.9 71.2-54.3 0-34.4-20.1-54.1-53.9-54.1h-56.2z\"],[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm90 218.4c0 55.2-36.8 94.1-96.2 94.1h-63.3V760c0 4.4-3.6 8-8 8H424c-4.4 0-8-3.6-8-8V484c0-4.4 3.6-8 8-8v.1h104c59.7 0 96 39.8 96 94.3z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M424 476.1c-4.4-.1-8 3.5-8 7.9v276c0 4.4 3.6 8 8 8h32.5c4.4 0 8-3.6 8-8v-95.5h63.3c59.4 0 96.2-38.9 96.2-94.1 0-54.5-36.3-94.3-96-94.3H424zm150.6 94.2c0 43.4-26.5 54.3-71.2 54.3h-38.9V516.2h56.2c33.8 0 53.9 19.7 53.9 54.1z\"])})),t.FileTextTwoTone=u(\"file-text\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm-22 322c0 4.4-3.6 8-8 8H320c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm200-184v48c0 4.4-3.6 8-8 8H320c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h384c4.4 0 8 3.6 8 8z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8zm192 128H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"])})),t.FileUnknownTwoTone=u(\"file-unknown\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm-22 424c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm110-228.4c.7 44.9-29.7 84.5-74.3 98.9-5.7 1.8-9.7 7.3-9.7 13.3V672c0 5.5-4.5 10-10 10h-32c-5.5 0-10-4.5-10-10v-32c.2-19.8 15.4-37.3 34.7-40.1C549 596.2 570 574.3 570 549c0-28.1-25.8-51.5-58-51.5s-58 23.4-58 51.6c0 5.2-4.4 9.4-9.8 9.4h-32.4c-5.4 0-9.8-4.1-9.8-9.5 0-57.4 50.1-103.7 111.5-103 59.3.8 107.7 46.1 108.5 101.6z\"],[e,\"M854.6 288.7L639.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.6-9.4-22.6zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M480 744a32 32 0 1 0 64 0 32 32 0 1 0-64 0zm-78-195c0 5.4 4.4 9.5 9.8 9.5h32.4c5.4 0 9.8-4.2 9.8-9.4 0-28.2 25.8-51.6 58-51.6s58 23.4 58 51.5c0 25.3-21 47.2-49.3 50.9-19.3 2.8-34.5 20.3-34.7 40.1v32c0 5.5 4.5 10 10 10h32c5.5 0 10-4.5 10-10v-12.2c0-6 4-11.5 9.7-13.3 44.6-14.4 75-54 74.3-98.9-.8-55.5-49.2-100.8-108.5-101.6-61.4-.7-111.5 45.6-111.5 103z\"])})),t.FileZipTwoTone=u(\"file-zip\",a,(function(e,t){return l(o,[t,\"M344 630h32v2h-32z\"],[t,\"M534 352V136H360v64h64v64h-64v64h64v64h-64v64h64v64h-64v62h64v160H296V520h64v-64h-64v-64h64v-64h-64v-64h64v-64h-64v-64h-64v752h560V394H576a42 42 0 0 1-42-42z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h64v64h64v-64h174v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M296 392h64v64h-64zm0-128h64v64h-64zm0 318v160h128V582h-64v-62h-64v62zm48 50v-2h32v64h-32v-62zm16-432h64v64h-64zm0 256h64v64h-64zm0-128h64v64h-64z\"])})),t.FileWordTwoTone=u(\"file-word\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42zm101.3 129.3c1.3-5.4 6.1-9.3 11.7-9.3h35.6a12.04 12.04 0 0 1 11.6 15.1l-74.4 276c-1.4 5.3-6.2 8.9-11.6 8.9h-31.8c-5.4 0-10.2-3.7-11.6-8.9l-52.8-197-52.8 197c-1.4 5.3-6.2 8.9-11.6 8.9h-32c-5.4 0-10.2-3.7-11.6-8.9l-74.2-276a12.02 12.02 0 0 1 11.6-15.1h35.4c5.6 0 10.4 3.9 11.7 9.3L434.6 680l49.7-198.9c1.3-5.4 6.1-9.1 11.6-9.1h32.2c5.5 0 10.3 3.7 11.6 9.1l49.8 199.3 45.8-199.1z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"],[e,\"M528.1 472h-32.2c-5.5 0-10.3 3.7-11.6 9.1L434.6 680l-46.1-198.7c-1.3-5.4-6.1-9.3-11.7-9.3h-35.4a12.02 12.02 0 0 0-11.6 15.1l74.2 276c1.4 5.2 6.2 8.9 11.6 8.9h32c5.4 0 10.2-3.6 11.6-8.9l52.8-197 52.8 197c1.4 5.2 6.2 8.9 11.6 8.9h31.8c5.4 0 10.2-3.6 11.6-8.9l74.4-276a12.04 12.04 0 0 0-11.6-15.1H647c-5.6 0-10.4 3.9-11.7 9.3l-45.8 199.1-49.8-199.3c-1.3-5.4-6.1-9.1-11.6-9.1z\"])})),t.FileTwoTone=u(\"file\",a,(function(e,t){return l(o,[t,\"M534 352V136H232v752h560V394H576a42 42 0 0 1-42-42z\"],[e,\"M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM602 137.8L790.2 326H602V137.8zM792 888H232V136h302v216a42 42 0 0 0 42 42h216v494z\"])})),t.FilterTwoTone=u(\"filter\",a,(function(e,t){return l(o,[t,\"M420.6 798h182.9V642H420.6zM411 561.4l9.5 16.6h183l9.5-16.6L811.3 226H212.7z\"],[e,\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.5 798H420.6V642h182.9v156zm9.5-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z\"])})),t.FireTwoTone=u(\"fire\",a,(function(e,t){return l(o,[t,\"M737 438.6c-9.6 15.5-21.1 30.7-34.4 45.6a73.1 73.1 0 0 1-51 24.4 73.36 73.36 0 0 1-53.4-18.8 74.01 74.01 0 0 1-24.4-59.8c3-47.4-12.4-103.1-45.8-165.7-16.9-31.4-37.1-58.2-61.2-80.4a240 240 0 0 1-12.1 46.5 354.26 354.26 0 0 1-58.2 101 349.6 349.6 0 0 1-58.6 56.8c-34 26.1-62 60-80.8 97.9a275.96 275.96 0 0 0-29.1 124c0 74.9 29.5 145.3 83 198.4 53.7 53.2 125 82.4 201 82.4s147.3-29.2 201-82.4c53.5-53 83-123.5 83-198.4 0-39.2-8.1-77.3-24-113.1-9.3-21-21-40.5-35-58.4z\"],[e,\"M834.1 469.2A347.49 347.49 0 0 0 751.2 354l-29.1-26.7a8.09 8.09 0 0 0-13 3.3l-13 37.3c-8.1 23.4-23 47.3-44.1 70.8-1.4 1.5-3 1.9-4.1 2-1.1.1-2.8-.1-4.3-1.5-1.4-1.2-2.1-3-2-4.8 3.7-60.2-14.3-128.1-53.7-202C555.3 171 510 123.1 453.4 89.7l-41.3-24.3c-5.4-3.2-12.3 1-12 7.3l2.2 48c1.5 32.8-2.3 61.8-11.3 85.9-11 29.5-26.8 56.9-47 81.5a295.64 295.64 0 0 1-47.5 46.1 352.6 352.6 0 0 0-100.3 121.5A347.75 347.75 0 0 0 160 610c0 47.2 9.3 92.9 27.7 136a349.4 349.4 0 0 0 75.5 110.9c32.4 32 70 57.2 111.9 74.7C418.5 949.8 464.5 959 512 959s93.5-9.2 136.9-27.3A348.6 348.6 0 0 0 760.8 857c32.4-32 57.8-69.4 75.5-110.9a344.2 344.2 0 0 0 27.7-136c0-48.8-10-96.2-29.9-140.9zM713 808.5c-53.7 53.2-125 82.4-201 82.4s-147.3-29.2-201-82.4c-53.5-53.1-83-123.5-83-198.4 0-43.5 9.8-85.2 29.1-124 18.8-37.9 46.8-71.8 80.8-97.9a349.6 349.6 0 0 0 58.6-56.8c25-30.5 44.6-64.5 58.2-101a240 240 0 0 0 12.1-46.5c24.1 22.2 44.3 49 61.2 80.4 33.4 62.6 48.8 118.3 45.8 165.7a74.01 74.01 0 0 0 24.4 59.8 73.36 73.36 0 0 0 53.4 18.8c19.7-1 37.8-9.7 51-24.4 13.3-14.9 24.8-30.1 34.4-45.6 14 17.9 25.7 37.4 35 58.4 15.9 35.8 24 73.9 24 113.1 0 74.9-29.5 145.4-83 198.4z\"])})),t.FolderAddTwoTone=u(\"folder-add\",a,(function(e,t){return l(o,[t,\"M372.5 256H184v512h656V370.4H492.1L372.5 256zM540 443.1V528h84.5c4.1 0 7.5 3.1 7.5 7v42c0 3.8-3.4 7-7.5 7H540v84.9c0 3.9-3.1 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V584h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.9 3.4-7 7.5-7H484v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.9 0 7 3.2 7 7.1z\"],[e,\"M880 298.4H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z\"],[e,\"M484 443.1V528h-84.5c-4.1 0-7.5 3.1-7.5 7v42c0 3.8 3.4 7 7.5 7H484v84.9c0 3.9 3.2 7.1 7 7.1h42c3.9 0 7-3.2 7-7.1V584h84.5c4.1 0 7.5-3.2 7.5-7v-42c0-3.9-3.4-7-7.5-7H540v-84.9c0-3.9-3.1-7.1-7-7.1h-42c-3.8 0-7 3.2-7 7.1z\"])})),t.FlagTwoTone=u(\"flag\",a,(function(e,t){return l(o,[t,\"M184 232h368v336H184z\"],[t,\"M624 632c0 4.4-3.6 8-8 8H504v73h336V377H624v255z\"],[e,\"M880 305H624V192c0-17.7-14.3-32-32-32H184v-40c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v784c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V640h248v113c0 17.7 14.3 32 32 32h416c17.7 0 32-14.3 32-32V337c0-17.7-14.3-32-32-32zM184 568V232h368v336H184zm656 145H504v-73h112c4.4 0 8-3.6 8-8V377h216v336z\"])})),t.FolderTwoTone=u(\"folder\",a,(function(e,t){return l(o,[e,\"M880 298.4H521L403.7 186.2a8.15 8.15 0 0 0-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z\"],[t,\"M372.5 256H184v512h656V370.4H492.1z\"])})),t.FolderOpenTwoTone=u(\"folder-open\",a,(function(e,t){return l(o,[t,\"M159 768h612.3l103.4-256H262.3z\"],[e,\"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 0 0-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z\"])})),t.FrownTwoTone=u(\"frown\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm376 272h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 636.1 562.5 597 512 597s-92.1 39.1-95.8 88.6c-.3 4.2-3.9 7.4-8.1 7.4H360a8 8 0 0 1-8-8.4c4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6a8 8 0 0 1-8 8.4zm24-224a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm224 112c-85.5 0-155.6 67.3-160 151.6a8 8 0 0 0 8 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4 3.7-49.5 45.3-88.6 95.8-88.6s92 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 0 0 8-8.4C667.6 600.3 597.5 533 512 533zm128-112a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.FundTwoTone=u(\"fund\",a,(function(e,t){return l(o,[e,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136V232h752v560z\"],[t,\"M136 792h752V232H136v560zm56.4-130.5l214.9-215c3.1-3.1 8.2-3.1 11.3 0L533 561l254.5-254.6c3.1-3.1 8.2-3.1 11.3 0l36.8 36.8c3.1 3.1 3.1 8.2 0 11.3l-297 297.2a8.03 8.03 0 0 1-11.3 0L412.9 537.2 240.4 709.7a8.03 8.03 0 0 1-11.3 0l-36.7-36.9a8.03 8.03 0 0 1 0-11.3z\"],[e,\"M229.1 709.7c3.1 3.1 8.2 3.1 11.3 0l172.5-172.5 114.4 114.5c3.1 3.1 8.2 3.1 11.3 0l297-297.2c3.1-3.1 3.1-8.2 0-11.3l-36.8-36.8a8.03 8.03 0 0 0-11.3 0L533 561 418.6 446.5a8.03 8.03 0 0 0-11.3 0l-214.9 215a8.03 8.03 0 0 0 0 11.3l36.7 36.9z\"])})),t.FunnelPlotTwoTone=u(\"funnel-plot\",a,(function(e,t){return l(o,[t,\"M420.6 798h182.9V650H420.6zM297.7 374h428.6l85-148H212.7zm113.2 197.4l8.4 14.6h185.3l8.4-14.6L689.6 438H334.4z\"],[e,\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 607.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V607.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.5 798H420.6V650h182.9v148zm9.5-226.6l-8.4 14.6H419.3l-8.4-14.6L334.4 438h355.2L613 571.4zM726.3 374H297.7l-85-148h598.6l-85 148z\"])})),t.GiftTwoTone=u(\"gift\",a,(function(e,t){return l(o,[t,\"M546 378h298v104H546zM228 550h250v308H228zm-48-172h298v104H180zm366 172h250v308H546z\"],[e,\"M880 310H732.4c13.6-21.4 21.6-46.8 21.6-74 0-76.1-61.9-138-138-138-41.4 0-78.7 18.4-104 47.4-25.3-29-62.6-47.4-104-47.4-76.1 0-138 61.9-138 138 0 27.2 7.9 52.6 21.6 74H144c-17.7 0-32 14.3-32 32v200c0 4.4 3.6 8 8 8h40v344c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V550h40c4.4 0 8-3.6 8-8V342c0-17.7-14.3-32-32-32zM478 858H228V550h250v308zm0-376H180V378h298v104zm0-176h-70c-38.6 0-70-31.4-70-70s31.4-70 70-70 70 31.4 70 70v70zm68-70c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70h-70v-70zm250 622H546V550h250v308zm48-376H546V378h298v104z\"])})),t.HddTwoTone=u(\"hdd\",a,(function(e,t){return l(o,[t,\"M232 888h560V680H232v208zm448-140c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zM232 616h560V408H232v208zm72-128c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H312c-4.4 0-8-3.6-8-8v-48zm-72-144h560V136H232v208zm72-128c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H312c-4.4 0-8-3.6-8-8v-48z\"],[e,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V680h560v208zm0-272H232V408h560v208zm0-272H232V136h560v208z\"],[e,\"M312 544h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H312c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0-272h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H312c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm328 516a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"])})),t.HeartTwoTone=u(\"heart\",a,(function(e,t){return l(o,[e,\"M923 283.6a260.04 260.04 0 0 0-56.9-82.8 264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9 0 33.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 101.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 20.3-103.3.1-35.3-7-69.6-20.9-101.9zM512 814.8S156 586.7 156 385.5C156 283.6 240.3 201 344.3 201c73.1 0 136.5 40.8 167.7 100.4C543.2 241.8 606.6 201 679.7 201c104 0 188.3 82.6 188.3 184.5 0 201.2-356 429.3-356 429.3z\"],[t,\"M679.7 201c-73.1 0-136.5 40.8-167.7 100.4C480.8 241.8 417.4 201 344.3 201c-104 0-188.3 82.6-188.3 184.5 0 201.2 356 429.3 356 429.3s356-228.1 356-429.3C868 283.6 783.7 201 679.7 201z\"])})),t.HighlightTwoTone=u(\"highlight\",a,(function(e,t){return l(o,[t,\"M229.6 796.3h160.2l54.3-54.1-80.1-78.9zm220.7-397.1l262.8 258.9 147.3-145-262.8-259zm-77.1 166.1l171.4 168.9 68.6-67.6-171.4-168.9z\"],[e,\"M957.6 507.5L603.2 158.3a7.9 7.9 0 0 0-11.2 0L353.3 393.5a8.03 8.03 0 0 0-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 0 0-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8v55.2c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6L539 830a7.9 7.9 0 0 0 11.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0 0 11.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.3H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.3l68.6-67.6 171.4 168.9-68.6 67.6zm168.5-76.1L450.3 399.2l147.3-145.1 262.8 259-147.3 145z\"])})),t.HomeTwoTone=u(\"home\",a,(function(e,t){return l(o,[t,\"M512.1 172.6l-370 369.7h96V868H392V640c0-22.1 17.9-40 40-40h160c22.1 0 40 17.9 40 40v228h153.9V542.3H882L535.2 195.7l-23.1-23.1zm434.5 422.9c-6 6-13.1 10.8-20.8 13.9 7.7-3.2 14.8-7.9 20.8-13.9zm-887-34.7c5 30.3 31.4 53.5 63.1 53.5h.9c-31.9 0-58.9-23-64-53.5zm-.9-10.5v-1.9 1.9zm.1-2.6c.1-3.1.5-6.1 1-9.1-.6 2.9-.9 6-1 9.1z\"],[e,\"M951 510c0-.1-.1-.1-.1-.2l-1.8-2.1c-.1-.1-.2-.3-.4-.4-.7-.8-1.5-1.6-2.2-2.4L560.1 118.8l-25.9-25.9a31.5 31.5 0 0 0-44.4 0L77.5 505a63.6 63.6 0 0 0-16 26.6l-.6 2.1-.3 1.1-.3 1.2c-.2.7-.3 1.4-.4 2.1 0 .1 0 .3-.1.4-.6 3-.9 6-1 9.1v3.3c0 .5 0 1 .1 1.5 0 .5 0 .9.1 1.4 0 .5.1 1 .1 1.5 0 .6.1 1.2.2 1.8 0 .3.1.6.1.9l.3 2.5v.1c5.1 30.5 32.2 53.5 64 53.5h42.5V940h691.7V614.3h43.4c8.6 0 16.9-1.7 24.5-4.9s14.7-7.9 20.8-13.9a63.6 63.6 0 0 0 18.7-45.3c0-14.7-5-28.8-14.3-40.2zM568 868H456V664h112v204zm217.9-325.7V868H632V640c0-22.1-17.9-40-40-40H432c-22.1 0-40 17.9-40 40v228H238.1V542.3h-96l370-369.7 23.1 23.1L882 542.3h-96.1z\"])})),t.HourglassTwoTone=u(\"hourglass\",a,(function(e,t){return l(o,[t,\"M512 548c-42.2 0-81.9 16.4-111.7 46.3A156.63 156.63 0 0 0 354 706v134h316V706c0-42.2-16.4-81.9-46.3-111.7A156.63 156.63 0 0 0 512 548zM354 318c0 42.2 16.4 81.9 46.3 111.7C430.1 459.6 469.8 476 512 476s81.9-16.4 111.7-46.3C653.6 399.9 670 360.2 670 318V184H354v134z\"],[e,\"M742 318V184h86c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H196c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h86v134c0 81.5 42.4 153.2 106.4 194-64 40.8-106.4 112.5-106.4 194v134h-86c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h632c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-86V706c0-81.5-42.4-153.2-106.4-194 64-40.8 106.4-112.5 106.4-194zm-72 388v134H354V706c0-42.2 16.4-81.9 46.3-111.7C430.1 564.4 469.8 548 512 548s81.9 16.4 111.7 46.3C653.6 624.1 670 663.8 670 706zm0-388c0 42.2-16.4 81.9-46.3 111.7C593.9 459.6 554.2 476 512 476s-81.9-16.4-111.7-46.3A156.63 156.63 0 0 1 354 318V184h316v134z\"])})),t.Html5TwoTone=u(\"html5\",a,(function(e,t){return l(o,[e,\"M145 96l66 746.6L511.8 928l299.6-85.4L878.7 96H145zm610.9 700.6l-244.1 69.6-245.2-69.6-56.7-641.2h603.8l-57.8 641.2z\"],[t,\"M209.9 155.4l56.7 641.2 245.2 69.6 244.1-69.6 57.8-641.2H209.9zm530.4 117.9l-4.8 47.2-1.7 19.5H381.7l8.2 94.2H511v-.2h214.7l-3.2 24.3-21.2 242.2-1.7 16.3-187.7 51.7v.4h-1.7l-188.6-52-11.3-144.7h91l6.5 73.2 102.4 27.7h.8v-.2l102.4-27.7 11.4-118.5H511.9v.1H305.4l-22.7-253.5L281 249h461l-1.7 24.3z\"],[e,\"M281 249l1.7 24.3 22.7 253.5h206.5v-.1h112.9l-11.4 118.5L511 672.9v.2h-.8l-102.4-27.7-6.5-73.2h-91l11.3 144.7 188.6 52h1.7v-.4l187.7-51.7 1.7-16.3 21.2-242.2 3.2-24.3H511v.2H389.9l-8.2-94.2h352.1l1.7-19.5 4.8-47.2L742 249H511z\"])})),t.IdcardTwoTone=u(\"idcard\",a,(function(e,t){return l(o,[e,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136V232h752v560z\"],[t,\"M136 792h752V232H136v560zm472-372c0-4.4 1-8 2.3-8h123.4c1.3 0 2.3 3.6 2.3 8v48c0 4.4-1 8-2.3 8H610.3c-1.3 0-2.3-3.6-2.3-8v-48zm0 144c0-4.4 3.2-8 7.1-8h185.7c3.9 0 7.1 3.6 7.1 8v48c0 4.4-3.2 8-7.1 8H615.1c-3.9 0-7.1-3.6-7.1-8v-48zM216.2 664.6c2.8-53.3 31.9-99.6 74.6-126.1-18.1-20-29.1-46.4-29.1-75.5 0-61.9 49.9-112 111.4-112s111.4 50.1 111.4 112c0 29.1-11 55.6-29.1 75.5 42.6 26.4 71.8 72.8 74.6 126.1a8 8 0 0 1-8 8.4h-43.9c-4.2 0-7.6-3.3-7.9-7.5-3.8-50.5-46-90.5-97.2-90.5s-93.4 40-97.2 90.5c-.3 4.2-3.7 7.5-7.9 7.5H224c-4.6 0-8.2-3.8-7.8-8.4z\"],[t,\"M321.3 463a51.7 52 0 1 0 103.4 0 51.7 52 0 1 0-103.4 0z\"],[e,\"M610.3 476h123.4c1.3 0 2.3-3.6 2.3-8v-48c0-4.4-1-8-2.3-8H610.3c-1.3 0-2.3 3.6-2.3 8v48c0 4.4 1 8 2.3 8zm4.8 144h185.7c3.9 0 7.1-3.6 7.1-8v-48c0-4.4-3.2-8-7.1-8H615.1c-3.9 0-7.1 3.6-7.1 8v48c0 4.4 3.2 8 7.1 8zM224 673h43.9c4.2 0 7.6-3.3 7.9-7.5 3.8-50.5 46-90.5 97.2-90.5s93.4 40 97.2 90.5c.3 4.2 3.7 7.5 7.9 7.5H522a8 8 0 0 0 8-8.4c-2.8-53.3-32-99.7-74.6-126.1a111.8 111.8 0 0 0 29.1-75.5c0-61.9-49.9-112-111.4-112s-111.4 50.1-111.4 112c0 29.1 11 55.5 29.1 75.5a158.09 158.09 0 0 0-74.6 126.1c-.4 4.6 3.2 8.4 7.8 8.4zm149-262c28.5 0 51.7 23.3 51.7 52s-23.2 52-51.7 52-51.7-23.3-51.7-52 23.2-52 51.7-52z\"])})),t.InfoCircleTwoTone=u(\"info-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm32 588c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z\"])})),t.InsuranceTwoTone=u(\"insurance\",a,(function(e,t){return l(o,[e,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z\"],[t,\"M521.9 358.8h97.9v41.6h-97.9z\"],[t,\"M214 226.7v427.6l298 232.2 298-232.2V226.7L512 125.1 214 226.7zM413.3 656h-.2c0 4.4-3.6 8-8 8h-37.3c-4.4 0-8-3.6-8-8V471.4c-7.7 9.2-15.4 17.9-23.1 26a6.04 6.04 0 0 1-10.2-2.4l-13.2-43.5c-.6-2-.2-4.1 1.2-5.6 37-43.4 64.7-95.1 82.2-153.6 1.1-3.5 5-5.3 8.4-3.7l38.6 18.3c2.7 1.3 4.1 4.4 3.2 7.2a429.2 429.2 0 0 1-33.6 79V656zm257.9-340v127.2c0 4.4-3.6 8-8 8h-66.7v18.6h98.8c4.4 0 8 3.6 8 8v35.6c0 4.4-3.6 8-8 8h-59c18.1 29.1 41.8 54.3 72.3 76.9 2.6 2.1 3.2 5.9 1.2 8.5l-26.3 35.3a5.92 5.92 0 0 1-8.9.7c-30.6-29.3-56.8-65.2-78.1-106.9V656c0 4.4-3.6 8-8 8h-36.2c-4.4 0-8-3.6-8-8V536c-22 44.7-49 80.8-80.6 107.6a6.38 6.38 0 0 1-4.8 1.4c-1.7-.3-3.2-1.3-4.1-2.8L432 605.7a6 6 0 0 1 1.6-8.1c28.6-20.3 51.9-45.2 71-76h-55.1c-4.4 0-8-3.6-8-8V478c0-4.4 3.6-8 8-8h94.9v-18.6h-65.9c-4.4 0-8-3.6-8-8V316c0-4.4 3.6-8 8-8h184.7c4.4 0 8 3.6 8 8z\"],[e,\"M443.7 306.9l-38.6-18.3c-3.4-1.6-7.3.2-8.4 3.7-17.5 58.5-45.2 110.2-82.2 153.6a5.7 5.7 0 0 0-1.2 5.6l13.2 43.5c1.4 4.5 7 5.8 10.2 2.4 7.7-8.1 15.4-16.8 23.1-26V656c0 4.4 3.6 8 8 8h37.3c4.4 0 8-3.6 8-8h.2V393.1a429.2 429.2 0 0 0 33.6-79c.9-2.8-.5-5.9-3.2-7.2zm26.8 9.1v127.4c0 4.4 3.6 8 8 8h65.9V470h-94.9c-4.4 0-8 3.6-8 8v35.6c0 4.4 3.6 8 8 8h55.1c-19.1 30.8-42.4 55.7-71 76a6 6 0 0 0-1.6 8.1l22.8 36.5c.9 1.5 2.4 2.5 4.1 2.8 1.7.3 3.5-.2 4.8-1.4 31.6-26.8 58.6-62.9 80.6-107.6v120c0 4.4 3.6 8 8 8h36.2c4.4 0 8-3.6 8-8V535.9c21.3 41.7 47.5 77.6 78.1 106.9 2.6 2.5 6.7 2.2 8.9-.7l26.3-35.3c2-2.6 1.4-6.4-1.2-8.5-30.5-22.6-54.2-47.8-72.3-76.9h59c4.4 0 8-3.6 8-8v-35.6c0-4.4-3.6-8-8-8h-98.8v-18.6h66.7c4.4 0 8-3.6 8-8V316c0-4.4-3.6-8-8-8H478.5c-4.4 0-8 3.6-8 8zm51.4 42.8h97.9v41.6h-97.9v-41.6z\"])})),t.InteractionTwoTone=u(\"interaction\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm114-401.9c0-55.3 44.6-100.1 99.7-100.1h205.8v-53.4c0-5.6 6.5-8.8 10.9-5.3L723.5 365c3.5 2.7 3.5 8 0 10.7l-109.1 85.7c-4.4 3.5-10.9.4-10.9-5.3v-53.4H397.8c-19.6 0-35.5 15.9-35.5 35.6v78.9c0 3.8-3.1 6.8-6.8 6.8h-50.7c-3.8 0-6.8-3-6.8-7v-78.9zm2.6 210.3l109.1-85.7c4.4-3.5 10.9-.4 10.9 5.3v53.4h205.6c19.6 0 35.5-15.9 35.5-35.6v-78.9c0-3.8 3.1-6.8 6.8-6.8h50.7c3.8 0 6.8 3.1 6.8 6.8v78.9c0 55.3-44.6 100.1-99.7 100.1H420.6v53.4c0 5.6-6.5 8.8-10.9 5.3l-109.1-85.7c-3.5-2.7-3.5-8 0-10.5z\"],[e,\"M304.8 524h50.7c3.7 0 6.8-3 6.8-6.8v-78.9c0-19.7 15.9-35.6 35.5-35.6h205.7v53.4c0 5.7 6.5 8.8 10.9 5.3l109.1-85.7c3.5-2.7 3.5-8 0-10.7l-109.1-85.7c-4.4-3.5-10.9-.3-10.9 5.3V338H397.7c-55.1 0-99.7 44.8-99.7 100.1V517c0 4 3 7 6.8 7zm-4.2 134.9l109.1 85.7c4.4 3.5 10.9.3 10.9-5.3v-53.4h205.7c55.1 0 99.7-44.8 99.7-100.1v-78.9c0-3.7-3-6.8-6.8-6.8h-50.7c-3.7 0-6.8 3-6.8 6.8v78.9c0 19.7-15.9 35.6-35.5 35.6H420.6V568c0-5.7-6.5-8.8-10.9-5.3l-109.1 85.7c-3.5 2.5-3.5 7.8 0 10.5z\"])})),t.InterationTwoTone=u(\"interation\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm114-401.9c0-55.3 44.6-100.1 99.7-100.1h205.8v-53.4c0-5.6 6.5-8.8 10.9-5.3L723.5 365c3.5 2.7 3.5 8 0 10.7l-109.1 85.7c-4.4 3.5-10.9.4-10.9-5.3v-53.4H397.8c-19.6 0-35.5 15.9-35.5 35.6v78.9c0 3.8-3.1 6.8-6.8 6.8h-50.7c-3.8 0-6.8-3-6.8-7v-78.9zm2.6 210.3l109.1-85.7c4.4-3.5 10.9-.4 10.9 5.3v53.4h205.6c19.6 0 35.5-15.9 35.5-35.6v-78.9c0-3.8 3.1-6.8 6.8-6.8h50.7c3.8 0 6.8 3.1 6.8 6.8v78.9c0 55.3-44.6 100.1-99.7 100.1H420.6v53.4c0 5.6-6.5 8.8-10.9 5.3l-109.1-85.7c-3.5-2.7-3.5-8 0-10.5z\"],[e,\"M304.8 524h50.7c3.7 0 6.8-3 6.8-6.8v-78.9c0-19.7 15.9-35.6 35.5-35.6h205.7v53.4c0 5.7 6.5 8.8 10.9 5.3l109.1-85.7c3.5-2.7 3.5-8 0-10.7l-109.1-85.7c-4.4-3.5-10.9-.3-10.9 5.3V338H397.7c-55.1 0-99.7 44.8-99.7 100.1V517c0 4 3 7 6.8 7zm-4.2 134.9l109.1 85.7c4.4 3.5 10.9.3 10.9-5.3v-53.4h205.7c55.1 0 99.7-44.8 99.7-100.1v-78.9c0-3.7-3-6.8-6.8-6.8h-50.7c-3.7 0-6.8 3-6.8 6.8v78.9c0 19.7-15.9 35.6-35.5 35.6H420.6V568c0-5.7-6.5-8.8-10.9-5.3l-109.1 85.7c-3.5 2.5-3.5 7.8 0 10.5z\"])})),t.LayoutTwoTone=u(\"layout\",a,(function(e,t){return l(o,[t,\"M384 185h456v136H384zm-200 0h136v656H184zm696-73H144c-17.7 0-32 14.3-32 32v1c0-17.7 14.3-32 32-32h736c17.7 0 32 14.3 32 32v-1c0-17.7-14.3-32-32-32zM384 385h456v456H384z\"],[e,\"M880 113H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V145c0-17.7-14.3-32-32-32zM320 841H184V185h136v656zm520 0H384V385h456v456zm0-520H384V185h456v136z\"])})),t.LeftCircleTwoTone=u(\"left-circle\",a,(function(e,t){return l(o,[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm104 240.9c0 10.3-4.9 19.9-13.2 25.9L457.4 512l145.4 105.1c8.3 6 13.2 15.7 13.2 25.9v46.9c0 6.5-7.4 10.3-12.7 6.5l-246-178a7.95 7.95 0 0 1 0-12.9l246-178c5.3-3.8 12.7 0 12.7 6.5v46.9z\"],[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[e,\"M603.3 327.5l-246 178a7.95 7.95 0 0 0 0 12.9l246 178c5.3 3.8 12.7 0 12.7-6.5V643c0-10.2-4.9-19.9-13.2-25.9L457.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5z\"])})),t.LeftSquareTwoTone=u(\"left-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm181.3-334.5l246-178c5.3-3.8 12.7 0 12.7 6.5v46.9c0 10.3-4.9 19.9-13.2 25.9L465.4 512l145.4 105.2c8.3 6 13.2 15.7 13.2 25.9V690c0 6.5-7.4 10.3-12.7 6.4l-246-178a7.95 7.95 0 0 1 0-12.9z\"],[e,\"M365.3 518.4l246 178c5.3 3.9 12.7.1 12.7-6.4v-46.9c0-10.2-4.9-19.9-13.2-25.9L465.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5l-246 178a7.95 7.95 0 0 0 0 12.9z\"])})),t.LikeTwoTone=u(\"like\",a,(function(e,t){return l(o,[t,\"M273 495.9v428l.3-428zm538.2-88.3H496.8l9.6-198.4c.6-11.9-4.7-23.1-14.6-30.5-6.1-4.5-13.6-6.8-21.1-6.7-19.6.1-36.9 13.4-42.2 32.3-37.1 134.4-64.9 235.2-83.5 302.5V852h399.4a56.85 56.85 0 0 0 33.6-51.8c0-9.7-2.3-18.9-6.9-27.3l-13.9-25.4 21.9-19a56.76 56.76 0 0 0 19.6-43c0-9.7-2.3-18.9-6.9-27.3l-13.9-25.4 21.9-19a56.76 56.76 0 0 0 19.6-43c0-9.7-2.3-18.9-6.9-27.3l-14-25.5 21.9-19a56.76 56.76 0 0 0 19.6-43c0-19.1-11-37.5-28.8-48.4z\"],[e,\"M112 528v364c0 17.7 14.3 32 32 32h65V496h-65c-17.7 0-32 14.3-32 32zm773.9 5.7c16.8-22.2 26.1-49.4 26.1-77.7 0-44.9-25.1-87.5-65.5-111a67.67 67.67 0 0 0-34.3-9.3H572.3l6-122.9c1.5-29.7-9-57.9-29.5-79.4a106.4 106.4 0 0 0-77.9-33.4c-52 0-98 35-111.8 85.1l-85.8 310.8-.3 428h472.1c9.3 0 18.2-1.8 26.5-5.4 47.6-20.3 78.3-66.8 78.3-118.4 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7 0-12.6-1.8-25-5.4-37zM820.4 499l-21.9 19 14 25.5a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.1 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.1 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 22.4-13.2 42.6-33.6 51.8H345V506.8c18.6-67.2 46.4-168 83.5-302.5a44.28 44.28 0 0 1 42.2-32.3c7.5-.1 15 2.2 21.1 6.7 9.9 7.4 15.2 18.6 14.6 30.5l-9.6 198.4h314.4C829 418.5 840 436.9 840 456c0 16.5-7.1 32.2-19.6 43z\"])})),t.LockTwoTone=u(\"lock\",a,(function(e,t){return l(o,[e,\"M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240zm460 600H232V536h560v304z\"],[t,\"M232 840h560V536H232v304zm280-226a48.01 48.01 0 0 1 28 87v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53a48.01 48.01 0 0 1 28-87z\"],[e,\"M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53a48.01 48.01 0 1 0-56 0z\"])})),t.MailTwoTone=u(\"mail\",a,(function(e,t){return l(o,[t,\"M477.5 536.3L135.9 270.7l-27.5-21.4 27.6 21.5V792h752V270.8L546.2 536.3a55.99 55.99 0 0 1-68.7 0z\"],[t,\"M876.3 198.8l39.3 50.5-27.6 21.5 27.7-21.5-39.3-50.5z\"],[e,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-94.5 72.1L512 482 190.5 232.1h643zm54.5 38.7V792H136V270.8l-27.6-21.5 27.5 21.4 341.6 265.6a55.99 55.99 0 0 0 68.7 0L888 270.8l27.6-21.5-39.3-50.5h.1l39.3 50.5-27.7 21.5z\"])})),t.MedicineBoxTwoTone=u(\"medicine-box\",a,(function(e,t){return l(o,[t,\"M244.3 328L184 513.4V840h656V513.4L779.7 328H244.3zM660 628c0 4.4-3.6 8-8 8H544v108c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V636H372c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h108V464c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v108h108c4.4 0 8 3.6 8 8v48z\"],[e,\"M652 572H544V464c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v108H372c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h108v108c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V636h108c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"],[e,\"M839.2 278.1a32 32 0 0 0-30.4-22.1H736V144c0-17.7-14.3-32-32-32H320c-17.7 0-32 14.3-32 32v112h-72.8a31.9 31.9 0 0 0-30.4 22.1L112 502v378c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V502l-72.8-223.9zM360 184h304v72H360v-72zm480 656H184V513.4L244.3 328h535.4L840 513.4V840z\"])})),t.MehTwoTone=u(\"meh\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm384 200c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48zm16-152a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm376 144H360c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-24-144a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.MessageTwoTone=u(\"message\",a,(function(e,t){return l(o,[t,\"M775.3 248.9a369.62 369.62 0 0 0-119-80A370.2 370.2 0 0 0 512.1 140h-1.7c-99.7.4-193 39.4-262.8 109.9-69.9 70.5-108 164.1-107.6 263.8.3 60.3 15.3 120.2 43.5 173.1l4.5 8.4V836h140.8l8.4 4.5c52.9 28.2 112.8 43.2 173.1 43.5h1.7c99 0 192-38.2 262.1-107.6 70.4-69.8 109.5-163.1 110.1-262.7.2-50.6-9.5-99.6-28.9-145.8a370.15 370.15 0 0 0-80-119zM312 560a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96zm200 0a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96zm200 0a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M664 512a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm-400 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"],[e,\"M925.2 338.4c-22.6-53.7-55-101.9-96.3-143.3a444.35 444.35 0 0 0-143.3-96.3C630.6 75.7 572.2 64 512 64h-2c-60.6.3-119.3 12.3-174.5 35.9a445.35 445.35 0 0 0-142 96.5c-40.9 41.3-73 89.3-95.2 142.8-23 55.4-34.6 114.3-34.3 174.9A449.4 449.4 0 0 0 112 714v152a46 46 0 0 0 46 46h152.1A449.4 449.4 0 0 0 510 960h2.1c59.9 0 118-11.6 172.7-34.3a444.48 444.48 0 0 0 142.8-95.2c41.3-40.9 73.8-88.7 96.5-142 23.6-55.2 35.6-113.9 35.9-174.5.3-60.9-11.5-120-34.8-175.6zm-151.1 438C704 845.8 611 884 512 884h-1.7c-60.3-.3-120.2-15.3-173.1-43.5l-8.4-4.5H188V695.2l-4.5-8.4C155.3 633.9 140.3 574 140 513.7c-.4-99.7 37.7-193.3 107.6-263.8 69.8-70.5 163.1-109.5 262.8-109.9h1.7c50 0 98.5 9.7 144.2 28.9 44.6 18.7 84.6 45.6 119 80 34.3 34.3 61.3 74.4 80 119 19.4 46.2 29.1 95.2 28.9 145.8-.6 99.6-39.7 192.9-110.1 262.7z\"],[e,\"M464 512a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.MinusCircleTwoTone=u(\"minus-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm192 396c0 4.4-3.6 8-8 8H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h368c4.4 0 8 3.6 8 8v48z\"],[e,\"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"])})),t.MinusSquareTwoTone=u(\"minus-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm136-352c0-4.4 3.6-8 8-8h368c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H328c-4.4 0-8-3.6-8-8v-48z\"],[e,\"M328 544h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"])})),t.MobileTwoTone=u(\"mobile\",a,(function(e,t){return l(o,[e,\"M744 64H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64zm-8 824H288V136h448v752z\"],[t,\"M288 888h448V136H288v752zm224-142c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\"],[e,\"M472 786a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"])})),t.PauseCircleTwoTone=u(\"pause-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm-80 524c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V360c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v304zm224 0c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V360c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v304z\"],[e,\"M424 352h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8zm224 0h-48c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8z\"])})),t.MoneyCollectTwoTone=u(\"money-collect\",a,(function(e,t){return l(o,[t,\"M256 744.4l256 93.1 256-93.1V184H256v560.4zM359.7 313c1.2-.7 2.5-1 3.8-1h55.7a8 8 0 0 1 7.1 4.4L511 485.2h3.3L599 316.4c1.3-2.7 4.1-4.4 7.1-4.4h54.5c4.4 0 8 3.6 8.1 7.9 0 1.3-.4 2.6-1 3.8L564 515.3h57.6c4.4 0 8 3.6 8 8v27.1c0 4.4-3.6 8-8 8h-76.3v39h76.3c4.4 0 8 3.6 8 8v27.1c0 4.4-3.6 8-8 8h-76.3V704c0 4.4-3.6 8-8 8h-49.9c-4.4 0-8-3.6-8-8v-63.4h-76c-4.4 0-8-3.6-8-8v-27.1c0-4.4 3.6-8 8-8h76v-39h-76c-4.4 0-8-3.6-8-8v-27.1c0-4.4 3.6-8 8-8h57L356.5 323.8c-2.1-3.8-.7-8.7 3.2-10.8z\"],[e,\"M911.5 700.7a8 8 0 0 0-10.3-4.8L840 718.2V180c0-37.6-30.4-68-68-68H252c-37.6 0-68 30.4-68 68v538.2l-61.3-22.3c-.9-.3-1.8-.5-2.7-.5-4.4 0-8 3.6-8 8V763c0 3.3 2.1 6.3 5.3 7.5L501 910.1c7.1 2.6 14.8 2.6 21.9 0l383.8-139.5c3.2-1.2 5.3-4.2 5.3-7.5v-59.6c0-1-.2-1.9-.5-2.8zM768 744.4l-256 93.1-256-93.1V184h512v560.4z\"],[e,\"M460.4 515.4h-57c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76v39h-76c-4.4 0-8 3.6-8 8v27.1c0 4.4 3.6 8 8 8h76V704c0 4.4 3.6 8 8 8h49.9c4.4 0 8-3.6 8-8v-63.5h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8h-76.3v-39h76.3c4.4 0 8-3.6 8-8v-27.1c0-4.4-3.6-8-8-8H564l103.7-191.6c.6-1.2 1-2.5 1-3.8-.1-4.3-3.7-7.9-8.1-7.9h-54.5c-3 0-5.8 1.7-7.1 4.4l-84.7 168.8H511l-84.7-168.8a8 8 0 0 0-7.1-4.4h-55.7c-1.3 0-2.6.3-3.8 1-3.9 2.1-5.3 7-3.2 10.8l103.9 191.6z\"])})),t.NotificationTwoTone=u(\"notification\",a,(function(e,t){return l(o,[t,\"M229.6 678.1c-3.7 11.6-5.6 23.9-5.6 36.4 0-12.5 2-24.8 5.7-36.4h-.1zm76.3-260.2H184v188.2h121.9l12.9 5.2L840 820.7V203.3L318.8 412.7z\"],[e,\"M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.7c-3.7 11.6-5.7 23.9-5.7 36.4 0 65.9 53.8 119.5 120 119.5 55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3 16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112zM344 762.3c-26.5 0-48-21.4-48-47.8 0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1zm496 58.4L318.8 611.3l-12.9-5.2H184V417.9h121.9l12.9-5.2L840 203.3v617.4z\"])})),t.PhoneTwoTone=u(\"phone\",a,(function(e,t){return l(o,[t,\"M721.7 184.9L610.9 295.8l120.8 120.7-8 21.6A481.29 481.29 0 0 1 438 723.9l-21.6 8-.9-.9-119.8-120-110.8 110.9 104.5 104.5c10.8 10.7 26 15.7 40.8 13.2 117.9-19.5 235.4-82.9 330.9-178.4s158.9-213.1 178.4-331c2.5-14.8-2.5-30-13.3-40.8L721.7 184.9z\"],[e,\"M877.1 238.7L770.6 132.3c-13-13-30.4-20.3-48.8-20.3s-35.8 7.2-48.8 20.3L558.3 246.8c-13 13-20.3 30.5-20.3 48.9 0 18.5 7.2 35.8 20.3 48.9l89.6 89.7a405.46 405.46 0 0 1-86.4 127.3c-36.7 36.9-79.6 66-127.2 86.6l-89.6-89.7c-13-13-30.4-20.3-48.8-20.3a68.2 68.2 0 0 0-48.8 20.3L132.3 673c-13 13-20.3 30.5-20.3 48.9 0 18.5 7.2 35.8 20.3 48.9l106.4 106.4c22.2 22.2 52.8 34.9 84.2 34.9 6.5 0 12.8-.5 19.2-1.6 132.4-21.8 263.8-92.3 369.9-198.3C818 606 888.4 474.6 910.4 342.1c6.3-37.6-6.3-76.3-33.3-103.4zm-37.6 91.5c-19.5 117.9-82.9 235.5-178.4 331s-213 158.9-330.9 178.4c-14.8 2.5-30-2.5-40.8-13.2L184.9 721.9 295.7 611l119.8 120 .9.9 21.6-8a481.29 481.29 0 0 0 285.7-285.8l8-21.6-120.8-120.7 110.8-110.9 104.5 104.5c10.8 10.8 15.8 26 13.3 40.8z\"])})),t.PictureTwoTone=u(\"picture\",a,(function(e,t){return l(o,[e,\"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 632H136v-39.9l138.5-164.3 150.1 178L658.1 489 888 761.6V792zm0-129.8L664.2 396.8c-3.2-3.8-9-3.8-12.2 0L424.6 666.4l-144-170.7c-3.2-3.8-9-3.8-12.2 0L136 652.7V232h752v430.2z\"],[t,\"M424.6 765.8l-150.1-178L136 752.1V792h752v-30.4L658.1 489z\"],[t,\"M136 652.7l132.4-157c3.2-3.8 9-3.8 12.2 0l144 170.7L652 396.8c3.2-3.8 9-3.8 12.2 0L888 662.2V232H136v420.7zM304 280a88 88 0 1 1 0 176 88 88 0 0 1 0-176z\"],[t,\"M276 368a28 28 0 1 0 56 0 28 28 0 1 0-56 0z\"],[e,\"M304 456a88 88 0 1 0 0-176 88 88 0 0 0 0 176zm0-116c15.5 0 28 12.5 28 28s-12.5 28-28 28-28-12.5-28-28 12.5-28 28-28z\"])})),t.PlayCircleTwoTone=u(\"play-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm164.1 378.2L457.7 677.1a8.02 8.02 0 0 1-12.7-6.5V353a8 8 0 0 1 12.7-6.5l218.4 158.8a7.9 7.9 0 0 1 0 12.9z\"],[e,\"M676.1 505.3L457.7 346.5A8 8 0 0 0 445 353v317.6a8.02 8.02 0 0 0 12.7 6.5l218.4-158.9a7.9 7.9 0 0 0 0-12.9z\"])})),t.PlaySquareTwoTone=u(\"play-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm240-484.7c0-9.4 10.9-14.7 18.3-8.8l199.4 156.7a11.2 11.2 0 0 1 0 17.6L442.3 677.6c-7.4 5.8-18.3.6-18.3-8.8V355.3z\"],[e,\"M442.3 677.6l199.4-156.8a11.2 11.2 0 0 0 0-17.6L442.3 346.5c-7.4-5.9-18.3-.6-18.3 8.8v313.5c0 9.4 10.9 14.6 18.3 8.8z\"])})),t.PieChartTwoTone=u(\"pie-chart\",a,(function(e,t){return l(o,[t,\"M316.2 920.5c-47.6-20.1-90.4-49-127.1-85.7a398.19 398.19 0 0 1-85.7-127.1A397.12 397.12 0 0 1 72 552.2v.2a398.57 398.57 0 0 0 117 282.5c36.7 36.7 79.4 65.5 127 85.6A396.64 396.64 0 0 0 471.6 952c27 0 53.6-2.7 79.7-7.9-25.9 5.2-52.4 7.8-79.3 7.8-54 .1-106.4-10.5-155.8-31.4zM560 472c-4.4 0-8-3.6-8-8V79.9c0-1.3.3-2.5.9-3.6-.9 1.3-1.5 2.9-1.5 4.6v383.7c0 4.4 3.6 8 8 8l383.6-1c1.6 0 3.1-.5 4.4-1.3-1 .5-2.2.7-3.4.7l-384 1z\"],[t,\"M619.8 147.6v256.6l256.4-.7c-13-62.5-44.3-120.5-90-166.1a332.24 332.24 0 0 0-166.4-89.8z\"],[t,\"M438 221.7c-75.9 7.6-146.2 40.9-200.8 95.5C174.5 379.9 140 463.3 140 552s34.5 172.1 97.2 234.8c62.3 62.3 145.1 96.8 233.2 97.2 88.2.4 172.7-34.1 235.3-96.2C761 733 794.6 662.3 802.3 586H438V221.7z\"],[e,\"M864 518H506V160c0-4.4-3.6-8-8-8h-26a398.46 398.46 0 0 0-282.8 117.1 398.19 398.19 0 0 0-85.7 127.1A397.61 397.61 0 0 0 72 552v.2c0 53.9 10.6 106.2 31.4 155.5 20.1 47.6 49 90.4 85.7 127.1 36.7 36.7 79.5 65.6 127.1 85.7A397.61 397.61 0 0 0 472 952c26.9 0 53.4-2.6 79.3-7.8 26.1-5.3 51.7-13.1 76.4-23.6 47.6-20.1 90.4-49 127.1-85.7 36.7-36.7 65.6-79.5 85.7-127.1A397.61 397.61 0 0 0 872 552v-26c0-4.4-3.6-8-8-8zM705.7 787.8A331.59 331.59 0 0 1 470.4 884c-88.1-.4-170.9-34.9-233.2-97.2C174.5 724.1 140 640.7 140 552s34.5-172.1 97.2-234.8c54.6-54.6 124.9-87.9 200.8-95.5V586h364.3c-7.7 76.3-41.3 147-96.6 201.8z\"],[e,\"M952 462.4l-2.6-28.2c-8.5-92.1-49.4-179-115.2-244.6A399.4 399.4 0 0 0 589 74.6L560.7 72c-3.4-.3-6.4 1.5-7.8 4.3a8.7 8.7 0 0 0-.9 3.6V464c0 4.4 3.6 8 8 8l384-1c1.2 0 2.3-.3 3.4-.7a8.1 8.1 0 0 0 4.6-7.9zm-332.2-58.2V147.6a332.24 332.24 0 0 1 166.4 89.8c45.7 45.6 77 103.6 90 166.1l-256.4.7z\"])})),t.PlusCircleTwoTone=u(\"plus-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm192 396c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48z\"],[e,\"M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"])})),t.PlusSquareTwoTone=u(\"plus-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm136-352c0-4.4 3.6-8 8-8h152V328c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v152h152c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H544v152c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V544H328c-4.4 0-8-3.6-8-8v-48z\"],[e,\"M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"])})),t.PoundCircleTwoTone=u(\"pound-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm146 582.1c0 4.4-3.6 8-8 8H376.2c-4.4 0-8-3.6-8-8v-38.5c0-3.7 2.5-6.9 6.1-7.8 44-10.9 72.8-49 72.8-94.2 0-14.7-2.5-29.4-5.9-44.2H374c-4.4 0-8-3.6-8-8v-30c0-4.4 3.6-8 8-8h53.7c-7.8-25.1-14.6-50.7-14.6-77.1 0-75.8 58.6-120.3 151.5-120.3 26.5 0 51.4 5.5 70.3 12.7 3.1 1.2 5.2 4.2 5.2 7.5v39.5a8 8 0 0 1-10.6 7.6c-17.9-6.4-39-10.5-60.4-10.5-53.3 0-87.3 26.6-87.3 70.2 0 24.7 6.2 47.9 13.4 70.5h112c4.4 0 8 3.6 8 8v30c0 4.4-3.6 8-8 8h-98.6c3.1 13.2 5.3 26.9 5.3 41 0 40.7-16.5 73.9-43.9 91.1v4.7h180c4.4 0 8 3.6 8 8v39.8z\"],[e,\"M650 674.3H470v-4.7c27.4-17.2 43.9-50.4 43.9-91.1 0-14.1-2.2-27.8-5.3-41h98.6c4.4 0 8-3.6 8-8v-30c0-4.4-3.6-8-8-8h-112c-7.2-22.6-13.4-45.8-13.4-70.5 0-43.6 34-70.2 87.3-70.2 21.4 0 42.5 4.1 60.4 10.5a8 8 0 0 0 10.6-7.6v-39.5c0-3.3-2.1-6.3-5.2-7.5-18.9-7.2-43.8-12.7-70.3-12.7-92.9 0-151.5 44.5-151.5 120.3 0 26.4 6.8 52 14.6 77.1H374c-4.4 0-8 3.6-8 8v30c0 4.4 3.6 8 8 8h67.2c3.4 14.8 5.9 29.5 5.9 44.2 0 45.2-28.8 83.3-72.8 94.2-3.6.9-6.1 4.1-6.1 7.8v38.5c0 4.4 3.6 8 8 8H650c4.4 0 8-3.6 8-8v-39.8c0-4.4-3.6-8-8-8z\"])})),t.PrinterTwoTone=u(\"printer\",a,(function(e,t){return l(o,[t,\"M360 180h304v152H360zm492 220H172c-6.6 0-12 5.4-12 12v292h132V500h440v204h132V412c0-6.6-5.4-12-12-12zm-24 84c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-40c0-4.4 3.6-8 8-8h40c4.4 0 8 3.6 8 8v40z\"],[e,\"M852 332H732V120c0-4.4-3.6-8-8-8H300c-4.4 0-8 3.6-8 8v212H172c-44.2 0-80 35.8-80 80v328c0 17.7 14.3 32 32 32h168v132c0 4.4 3.6 8 8 8h424c4.4 0 8-3.6 8-8V772h168c17.7 0 32-14.3 32-32V412c0-44.2-35.8-80-80-80zM360 180h304v152H360V180zm304 664H360V568h304v276zm200-140H732V500H292v204H160V412c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4 12 12v292z\"],[e,\"M820 436h-40c-4.4 0-8 3.6-8 8v40c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-40c0-4.4-3.6-8-8-8z\"])})),t.ProfileTwoTone=u(\"profile\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm300-496c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48zm0 144c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48zm0 144c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H492c-4.4 0-8-3.6-8-8v-48zM380 328c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zm0 144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40zm0 144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\"],[e,\"M340 656a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0-144a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm0-144a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm152 320h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0-144h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8zm0-144h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H492c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"])})),t.ProjectTwoTone=u(\"project\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm472-560c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v256c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280zm-192 0c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v184c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280zm-192 0c0-4.4 3.6-8 8-8h80c4.4 0 8 3.6 8 8v464c0 4.4-3.6 8-8 8h-80c-4.4 0-8-3.6-8-8V280z\"],[e,\"M280 752h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8zm192-280h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v184c0 4.4 3.6 8 8 8zm192 72h80c4.4 0 8-3.6 8-8V280c0-4.4-3.6-8-8-8h-80c-4.4 0-8 3.6-8 8v256c0 4.4 3.6 8 8 8z\"])})),t.PushpinTwoTone=u(\"pushpin\",a,(function(e,t){return l(o,[t,\"M474.8 357.7l-24.5 24.5-34.4-3.8c-9.6-1.1-19.3-1.6-28.9-1.6-29 0-57.5 4.7-84.7 14.1-14 4.8-27.4 10.8-40.3 17.9l353.1 353.3a259.92 259.92 0 0 0 30.4-153.9l-3.8-34.4 24.5-24.5L800 415.5 608.5 224 474.8 357.7z\"],[e,\"M878.3 392.1L631.9 145.7c-6.5-6.5-15-9.7-23.5-9.7s-17 3.2-23.5 9.7L423.8 306.9c-12.2-1.4-24.5-2-36.8-2-73.2 0-146.4 24.1-206.5 72.3a33.23 33.23 0 0 0-2.7 49.4l181.7 181.7-215.4 215.2a15.8 15.8 0 0 0-4.6 9.8l-3.4 37.2c-.9 9.4 6.6 17.4 15.9 17.4.5 0 1 0 1.5-.1l37.2-3.4c3.7-.3 7.2-2 9.8-4.6l215.4-215.4 181.7 181.7c6.5 6.5 15 9.7 23.5 9.7 9.7 0 19.3-4.2 25.9-12.4 56.3-70.3 79.7-158.3 70.2-243.4l161.1-161.1c12.9-12.8 12.9-33.8 0-46.8zM666.2 549.3l-24.5 24.5 3.8 34.4a259.92 259.92 0 0 1-30.4 153.9L262 408.8c12.9-7.1 26.3-13.1 40.3-17.9 27.2-9.4 55.7-14.1 84.7-14.1 9.6 0 19.3.5 28.9 1.6l34.4 3.8 24.5-24.5L608.5 224 800 415.5 666.2 549.3z\"])})),t.PropertySafetyTwoTone=u(\"property-safety\",a,(function(e,t){return l(o,[e,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z\"],[t,\"M214 226.7v427.6l298 232.2 298-232.2V226.7L512 125.1 214 226.7zM593.9 318h45c5.5 0 10 4.5 10 10 .1 1.7-.3 3.3-1.1 4.8l-87.7 161.1h45.7c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4v29.7h63.4c5.5 0 10 4.5 10 10v21.3c0 5.5-4.5 10-10 10h-63.4V658c0 5.5-4.5 10-10 10h-41.3c-5.5 0-10-4.5-10-10v-51.8H418c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h63.1v-29.7H418c-5.5 0-10-4.5-10-10v-21.3c0-5.5 4.5-10 10-10h45.2l-88-161.1c-2.6-4.8-.9-10.9 4-13.6 1.5-.8 3.1-1.2 4.8-1.2h46c3.8 0 7.2 2.1 8.9 5.5l72.9 144.3L585 323.5a10 10 0 0 1 8.9-5.5z\"],[e,\"M438.9 323.5a9.88 9.88 0 0 0-8.9-5.5h-46c-1.7 0-3.3.4-4.8 1.2-4.9 2.7-6.6 8.8-4 13.6l88 161.1H418c-5.5 0-10 4.5-10 10v21.3c0 5.5 4.5 10 10 10h63.1v29.7H418c-5.5 0-10 4.5-10 10v21.3c0 5.5 4.5 10 10 10h63.1V658c0 5.5 4.5 10 10 10h41.3c5.5 0 10-4.5 10-10v-51.8h63.4c5.5 0 10-4.5 10-10v-21.3c0-5.5-4.5-10-10-10h-63.4v-29.7h63.4c5.5 0 10-4.5 10-10v-21.3c0-5.5-4.5-10-10-10h-45.7l87.7-161.1c.8-1.5 1.2-3.1 1.1-4.8 0-5.5-4.5-10-10-10h-45a10 10 0 0 0-8.9 5.5l-73.2 144.3-72.9-144.3z\"])})),t.QuestionCircleTwoTone=u(\"question-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm0 632c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm62.9-219.5a48.3 48.3 0 0 0-30.9 44.8V620c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-21.5c0-23.1 6.7-45.9 19.9-64.9 12.9-18.6 30.9-32.8 52.1-40.9 34-13.1 56-41.6 56-72.7 0-44.1-43.1-80-96-80s-96 35.9-96 80v7.6c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V420c0-39.3 17.2-76 48.4-103.3C430.4 290.4 470 276 512 276s81.6 14.5 111.6 40.7C654.8 344 672 380.7 672 420c0 57.8-38.1 109.8-97.1 132.5z\"],[e,\"M472 732a40 40 0 1 0 80 0 40 40 0 1 0-80 0zm151.6-415.3C593.6 290.5 554 276 512 276s-81.6 14.4-111.6 40.7C369.2 344 352 380.7 352 420v7.6c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V420c0-44.1 43.1-80 96-80s96 35.9 96 80c0 31.1-22 59.6-56 72.7-21.2 8.1-39.2 22.3-52.1 40.9-13.2 19-19.9 41.8-19.9 64.9V620c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-22.7a48.3 48.3 0 0 1 30.9-44.8c59-22.7 97.1-74.7 97.1-132.5 0-39.3-17.2-76-48.4-103.3z\"])})),t.ReconciliationTwoTone=u(\"reconciliation\",a,(function(e,t){return l(o,[t,\"M740 344H404V240H304v160h176c17.7 0 32 14.3 32 32v360h328V240H740v104zM584 448c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v56c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-56zm92 301c-50.8 0-92-41.2-92-92s41.2-92 92-92 92 41.2 92 92-41.2 92-92 92zm92-341v96c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-96c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8z\"],[t,\"M642 657a34 34 0 1 0 68 0 34 34 0 1 0-68 0z\"],[e,\"M592 512h48c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm112-104v96c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-96c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8z\"],[e,\"M880 168H668c0-30.9-25.1-56-56-56h-80c-30.9 0-56 25.1-56 56H264c-17.7 0-32 14.3-32 32v200h-88c-17.7 0-32 14.3-32 32v448c0 17.7 14.3 32 32 32h336c17.7 0 32-14.3 32-32v-16h368c17.7 0 32-14.3 32-32V200c0-17.7-14.3-32-32-32zm-412 64h72v-56h64v56h72v48H468v-48zm-20 616H176V616h272v232zm0-296H176v-88h272v88zm392 240H512V432c0-17.7-14.3-32-32-32H304V240h100v104h336V240h100v552z\"],[e,\"M676 565c-50.8 0-92 41.2-92 92s41.2 92 92 92 92-41.2 92-92-41.2-92-92-92zm0 126c-18.8 0-34-15.2-34-34s15.2-34 34-34 34 15.2 34 34-15.2 34-34 34z\"])})),t.RedEnvelopeTwoTone=u(\"red-envelope\",a,(function(e,t){return l(o,[e,\"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-40 824H232V193.1l260.3 204.1c11.6 9.1 27.9 9.1 39.5 0L792 193.1V888zm0-751.3h-31.7L512 331.3 263.7 136.7H232v-.7h560v.7z\"],[t,\"M492.3 397.2L232 193.1V888h560V193.1L531.8 397.2a31.99 31.99 0 0 1-39.5 0zm99.4 60.9h47.8a8.45 8.45 0 0 1 7.4 12.4l-87.2 161h45.9c4.6 0 8.4 3.8 8.4 8.4V665c0 4.6-3.8 8.4-8.4 8.4h-63.3V702h63.3c4.6 0 8.4 3.8 8.4 8.4v25c.2 4.7-3.5 8.5-8.2 8.5h-63.3v49.9c0 4.6-3.8 8.4-8.4 8.4h-43.7c-4.6 0-8.4-3.8-8.4-8.4v-49.9h-63c-4.6 0-8.4-3.8-8.4-8.4v-25.1c0-4.6 3.8-8.4 8.4-8.4h63v-28.6h-63c-4.6 0-8.4-3.8-8.4-8.4v-25.1c0-4.6 3.8-8.4 8.4-8.4h45.4L377 470.4a8.4 8.4 0 0 1 3.4-11.4c1.3-.6 2.6-1 3.9-1h48.8c3.2 0 6.1 1.8 7.5 4.6l71.7 142 71.9-141.9a8.6 8.6 0 0 1 7.5-4.6z\"],[t,\"M232 136.7h31.7L512 331.3l248.3-194.6H792v-.7H232z\"],[e,\"M440.6 462.6a8.38 8.38 0 0 0-7.5-4.6h-48.8c-1.3 0-2.6.4-3.9 1a8.4 8.4 0 0 0-3.4 11.4l87.4 161.1H419c-4.6 0-8.4 3.8-8.4 8.4V665c0 4.6 3.8 8.4 8.4 8.4h63V702h-63c-4.6 0-8.4 3.8-8.4 8.4v25.1c0 4.6 3.8 8.4 8.4 8.4h63v49.9c0 4.6 3.8 8.4 8.4 8.4h43.7c4.6 0 8.4-3.8 8.4-8.4v-49.9h63.3c4.7 0 8.4-3.8 8.2-8.5v-25c0-4.6-3.8-8.4-8.4-8.4h-63.3v-28.6h63.3c4.6 0 8.4-3.8 8.4-8.4v-25.1c0-4.6-3.8-8.4-8.4-8.4h-45.9l87.2-161a8.45 8.45 0 0 0-7.4-12.4h-47.8c-3.1 0-6 1.8-7.5 4.6l-71.9 141.9-71.7-142z\"])})),t.RestTwoTone=u(\"rest\",a,(function(e,t){return l(o,[t,\"M326.4 844h363.2l44.3-520H282l44.4 520zM508 416c79.5 0 144 64.5 144 144s-64.5 144-144 144-144-64.5-144-144 64.5-144 144-144z\"],[e,\"M508 704c79.5 0 144-64.5 144-144s-64.5-144-144-144-144 64.5-144 144 64.5 144 144 144zm0-224c44.2 0 80 35.8 80 80s-35.8 80-80 80-80-35.8-80-80 35.8-80 80-80z\"],[e,\"M832 256h-28.1l-35.7-120.9c-4-13.7-16.5-23.1-30.7-23.1h-451c-14.3 0-26.8 9.4-30.7 23.1L220.1 256H192c-17.7 0-32 14.3-32 32v28c0 4.4 3.6 8 8 8h45.8l47.7 558.7a32 32 0 0 0 31.9 29.3h429.2a32 32 0 0 0 31.9-29.3L802.2 324H856c4.4 0 8-3.6 8-8v-28c0-17.7-14.3-32-32-32zm-518.6-76h397.2l22.4 76H291l22.4-76zm376.2 664H326.4L282 324h451.9l-44.3 520z\"])})),t.RightCircleTwoTone=u(\"right-circle\",a,(function(e,t){return l(o,[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm154.7 378.4l-246 178c-5.3 3.8-12.7 0-12.7-6.5V643c0-10.2 4.9-19.9 13.2-25.9L566.6 512 421.2 406.8c-8.3-6-13.2-15.6-13.2-25.9V334c0-6.5 7.4-10.3 12.7-6.5l246 178c4.4 3.2 4.4 9.7 0 12.9z\"],[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[e,\"M666.7 505.5l-246-178c-5.3-3.8-12.7 0-12.7 6.5v46.9c0 10.3 4.9 19.9 13.2 25.9L566.6 512 421.2 617.1c-8.3 6-13.2 15.7-13.2 25.9v46.9c0 6.5 7.4 10.3 12.7 6.5l246-178c4.4-3.2 4.4-9.7 0-12.9z\"])})),t.RocketTwoTone=u(\"rocket\",a,(function(e,t){return l(o,[t,\"M261.7 621.4c-9.4 14.6-17 30.3-22.5 46.6H324V558.7c-24.8 16.2-46 37.5-62.3 62.7zM700 558.7V668h84.8c-5.5-16.3-13.1-32-22.5-46.6a211.6 211.6 0 0 0-62.3-62.7zm-64-239.9l-124-147-124 147V668h248V318.8zM512 448a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M864 736c0-111.6-65.4-208-160-252.9V317.3c0-15.1-5.3-29.7-15.1-41.2L536.5 95.4C530.1 87.8 521 84 512 84s-18.1 3.8-24.5 11.4L335.1 276.1a63.97 63.97 0 0 0-15.1 41.2v165.8C225.4 528 160 624.4 160 736h156.5c-2.3 7.2-3.5 15-3.5 23.8 0 22.1 7.6 43.7 21.4 60.8a97.2 97.2 0 0 0 43.1 30.6c23.1 54 75.6 88.8 134.5 88.8 29.1 0 57.3-8.6 81.4-24.8 23.6-15.8 41.9-37.9 53-64a97 97 0 0 0 43.1-30.5 97.52 97.52 0 0 0 21.4-60.8c0-8.4-1.1-16.4-3.1-23.8L864 736zm-540-68h-84.8c5.5-16.3 13.1-32 22.5-46.6 16.3-25.2 37.5-46.5 62.3-62.7V668zm64-184.9V318.8l124-147 124 147V668H388V483.1zm240.1 301.1c-5.2 3-11.2 4.2-17.1 3.4l-19.5-2.4-2.8 19.4c-5.4 37.9-38.4 66.5-76.7 66.5s-71.3-28.6-76.7-66.5l-2.8-19.5-19.5 2.5a27.7 27.7 0 0 1-17.1-3.5c-8.7-5-14.1-14.3-14.1-24.4 0-10.6 5.9-19.4 14.6-23.8h231.3c8.8 4.5 14.6 13.3 14.6 23.8-.1 10.2-5.5 19.6-14.2 24.5zM700 668V558.7a211.6 211.6 0 0 1 62.3 62.7c9.4 14.6 17 30.3 22.5 46.6H700z\"],[e,\"M464 400a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.RightSquareTwoTone=u(\"right-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm216-196.9c0-10.2 4.9-19.9 13.2-25.9L558.6 512 413.2 406.8c-8.3-6-13.2-15.6-13.2-25.9V334c0-6.5 7.4-10.3 12.7-6.5l246 178c4.4 3.2 4.4 9.7 0 12.9l-246 178c-5.3 3.9-12.7.1-12.7-6.4v-46.9z\"],[e,\"M412.7 696.4l246-178c4.4-3.2 4.4-9.7 0-12.9l-246-178c-5.3-3.8-12.7 0-12.7 6.5v46.9c0 10.3 4.9 19.9 13.2 25.9L558.6 512 413.2 617.2c-8.3 6-13.2 15.7-13.2 25.9V690c0 6.5 7.4 10.3 12.7 6.4z\"])})),t.SafetyCertificateTwoTone=u(\"safety-certificate\",a,(function(e,t){return l(o,[e,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z\"],[t,\"M214 226.7v427.6l298 232.2 298-232.2V226.7L512 125.1 214 226.7zM632.8 328H688c6.5 0 10.3 7.4 6.5 12.7L481.9 633.4a16.1 16.1 0 0 1-26 0l-126.4-174c-3.8-5.3 0-12.7 6.5-12.7h55.2c5.2 0 10 2.5 13 6.6l64.7 89.1 150.9-207.8c3-4.1 7.9-6.6 13-6.6z\"],[e,\"M404.2 453.3c-3-4.1-7.8-6.6-13-6.6H336c-6.5 0-10.3 7.4-6.5 12.7l126.4 174a16.1 16.1 0 0 0 26 0l212.6-292.7c3.8-5.3 0-12.7-6.5-12.7h-55.2c-5.1 0-10 2.5-13 6.6L468.9 542.4l-64.7-89.1z\"])})),t.SaveTwoTone=u(\"save\",a,(function(e,t){return l(o,[t,\"M704 320c0 17.7-14.3 32-32 32H352c-17.7 0-32-14.3-32-32V184H184v656h656V341.8l-136-136V320zM512 730c-79.5 0-144-64.5-144-144s64.5-144 144-144 144 64.5 144 144-64.5 144-144 144z\"],[e,\"M512 442c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm0 224c-44.2 0-80-35.8-80-80s35.8-80 80-80 80 35.8 80 80-35.8 80-80 80z\"],[e,\"M893.3 293.3L730.7 130.7c-.7-.7-1.4-1.3-2.1-2-.1-.1-.3-.2-.4-.3-.7-.7-1.5-1.3-2.2-1.9a64 64 0 0 0-22-11.7V112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 184h256v104H384V184zm456 656H184V184h136v136c0 17.7 14.3 32 32 32h320c17.7 0 32-14.3 32-32V205.8l136 136V840z\"])})),t.ScheduleTwoTone=u(\"schedule\",a,(function(e,t){return l(o,[t,\"M768 352c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H548v56c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H328v56c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-56H136v496h752V296H768v56zM424 688c0 4.4-3.6 8-8 8H232c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm0-136c0 4.4-3.6 8-8 8H232c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h184c4.4 0 8 3.6 8 8v48zm374.4-91.2l-165 228.7a15.9 15.9 0 0 1-25.8 0L493.5 531.3c-3.8-5.3 0-12.7 6.5-12.7h54.9c5.1 0 9.9 2.4 12.9 6.6l52.8 73.1 103.6-143.7c3-4.1 7.8-6.6 12.8-6.5h54.9c6.5 0 10.3 7.4 6.5 12.7z\"],[e,\"M724.2 454.6L620.6 598.3l-52.8-73.1c-3-4.2-7.8-6.6-12.9-6.6H500c-6.5 0-10.3 7.4-6.5 12.7l114.1 158.2a15.9 15.9 0 0 0 25.8 0l165-228.7c3.8-5.3 0-12.7-6.5-12.7H737c-5-.1-9.8 2.4-12.8 6.5zM416 496H232c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"],[e,\"M928 224H768v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H548v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H328v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56H96c-17.7 0-32 14.3-32 32v576c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32zm-40 568H136V296h120v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h148v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56h120v496z\"],[e,\"M416 632H232c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"])})),t.SecurityScanTwoTone=u(\"security-scan\",a,(function(e,t){return l(o,[e,\"M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z\"],[t,\"M460.7 451.1a80.1 80.1 0 1 0 160.2 0 80.1 80.1 0 1 0-160.2 0z\"],[t,\"M214 226.7v427.6l298 232.2 298-232.2V226.7L512 125.1 214 226.7zm428.7 122.5c56.3 56.3 56.3 147.5 0 203.8-48.5 48.5-123 55.2-178.6 20.1l-77.5 77.5a8.03 8.03 0 0 1-11.3 0l-34-34a8.03 8.03 0 0 1 0-11.3l77.5-77.5c-35.1-55.7-28.4-130.1 20.1-178.6 56.3-56.3 147.5-56.3 203.8 0z\"],[e,\"M418.8 527.8l-77.5 77.5a8.03 8.03 0 0 0 0 11.3l34 34c3.1 3.1 8.2 3.1 11.3 0l77.5-77.5c55.6 35.1 130.1 28.4 178.6-20.1 56.3-56.3 56.3-147.5 0-203.8-56.3-56.3-147.5-56.3-203.8 0-48.5 48.5-55.2 122.9-20.1 178.6zm65.4-133.3a80.1 80.1 0 0 1 113.3 0 80.1 80.1 0 0 1 0 113.3c-31.3 31.3-82 31.3-113.3 0s-31.3-82 0-113.3z\"])})),t.SettingTwoTone=u(\"setting\",a,(function(e,t){return l(o,[t,\"M859.3 569.7l.2.1c3.1-18.9 4.6-38.2 4.6-57.3 0-17.1-1.3-34.3-3.7-51.1 2.4 16.7 3.6 33.6 3.6 50.5 0 19.4-1.6 38.8-4.7 57.8zM99 398.1c-.5-.4-.9-.8-1.4-1.3.7.7 1.4 1.4 2.2 2.1l65.5 55.9v-.1L99 398.1zm536.6-216h.1l-15.5-83.8c-.2-1-.4-1.9-.7-2.8.1.5.3 1.1.4 1.6l15.7 85zm54 546.5l31.4-25.8 92.8 32.9c17-22.9 31.3-47.5 42.6-73.6l-74.7-63.9 6.6-40.1c2.5-15.1 3.8-30.6 3.8-46.1s-1.3-31-3.8-46.1l-6.5-39.9 74.7-63.9c-11.4-26-25.6-50.7-42.6-73.6l-92.8 32.9-31.4-25.8c-23.9-19.6-50.6-35-79.3-45.8l-38.1-14.3-17.9-97a377.5 377.5 0 0 0-85 0l-17.9 97.2-37.9 14.3c-28.5 10.8-55 26.2-78.7 45.7l-31.4 25.9-93.4-33.2c-17 22.9-31.3 47.5-42.6 73.6l75.5 64.5-6.5 40c-2.5 14.9-3.7 30.2-3.7 45.5 0 15.2 1.3 30.6 3.7 45.5l6.5 40-75.5 64.5c11.4 26 25.6 50.7 42.6 73.6l93.4-33.2 31.4 25.9c23.7 19.5 50.2 34.9 78.7 45.7l37.8 14.5 17.9 97.2c28.2 3.2 56.9 3.2 85 0l17.9-97 38.1-14.3c28.8-10.8 55.4-26.2 79.3-45.8zm-177.1-50.3c-30.5 0-59.2-7.8-84.3-21.5C373.3 627 336 568.9 336 502c0-97.2 78.8-176 176-176 66.9 0 125 37.3 154.8 92.2 13.7 25 21.5 53.7 21.5 84.3 0 97.1-78.7 175.8-175.8 175.8zM207.2 812.8c-5.5 1.9-11.2 2.3-16.6 1.2 5.7 1.2 11.7 1 17.5-1l81.4-29c-.1-.1-.3-.2-.4-.3l-81.9 29.1zm717.6-414.7l-65.5 56c0 .2.1.5.1.7l65.4-55.9c7.1-6.1 11.1-14.9 11.2-24-.3 8.8-4.3 17.3-11.2 23.2z\"],[t,\"M935.8 646.6c.5 4.7 0 9.5-1.7 14.1l-.9 2.6a446.02 446.02 0 0 1-79.7 137.9l-1.8 2.1a32 32 0 0 1-35.1 9.5l-81.3-28.9a350 350 0 0 1-99.7 57.6l-15.7 85a32.05 32.05 0 0 1-25.8 25.7l-2.7.5a445.2 445.2 0 0 1-79.2 7.1h.3c26.7 0 53.4-2.4 79.4-7.1l2.7-.5a32.05 32.05 0 0 0 25.8-25.7l15.7-84.9c36.2-13.6 69.6-32.9 99.6-57.5l81.2 28.9a32 32 0 0 0 35.1-9.5l1.8-2.1c34.8-41.1 61.5-87.4 79.6-137.7l.9-2.6c1.6-4.7 2.1-9.7 1.5-14.5z\"],[e,\"M688 502c0-30.3-7.7-58.9-21.2-83.8C637 363.3 578.9 326 512 326c-97.2 0-176 78.8-176 176 0 66.9 37.3 125 92.2 154.8 24.9 13.5 53.4 21.2 83.8 21.2 97.2 0 176-78.8 176-176zm-288 0c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 0 1 624 502c0 29.9-11.7 58-32.8 79.2A111.6 111.6 0 0 1 512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 0 1 400 502z\"],[e,\"M594.1 952.2a32.05 32.05 0 0 0 25.8-25.7l15.7-85a350 350 0 0 0 99.7-57.6l81.3 28.9a32 32 0 0 0 35.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c1.7-4.6 2.2-9.4 1.7-14.1-.9-7.9-4.7-15.4-11-20.9l-65.3-55.9-.2-.1c3.1-19 4.7-38.4 4.7-57.8 0-16.9-1.2-33.9-3.6-50.5-.3-2.2-.7-4.4-1-6.6 0-.2-.1-.5-.1-.7l65.5-56c6.9-5.9 10.9-14.4 11.2-23.2.1-4-.5-8.1-1.9-12l-.9-2.6a443.74 443.74 0 0 0-79.7-137.9l-1.8-2.1a32.12 32.12 0 0 0-35.1-9.5l-81.3 28.9c-30-24.6-63.4-44-99.6-57.6h-.1l-15.7-85c-.1-.5-.2-1.1-.4-1.6a32.08 32.08 0 0 0-25.4-24.1l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 0 0-25.8 25.7l-15.8 85.4a351.86 351.86 0 0 0-99 57.4l-81.9-29.1a32 32 0 0 0-35.1 9.5l-1.8 2.1a446.02 446.02 0 0 0-79.7 137.9l-.9 2.6a32.09 32.09 0 0 0 7.9 33.9c.5.4.9.9 1.4 1.3l66.3 56.6v.1c-3.1 18.8-4.6 37.9-4.6 57 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 0 0-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1c4.9 5.7 11.4 9.4 18.5 10.7 5.4 1 11.1.7 16.6-1.2l81.9-29.1c.1.1.3.2.4.3 29.7 24.3 62.8 43.6 98.6 57.1l15.8 85.4a32.05 32.05 0 0 0 25.8 25.7l2.7.5c26.1 4.7 52.8 7.1 79.5 7.1h.3c26.6 0 53.3-2.4 79.2-7.1l2.7-.5zm-39.8-66.5a377.5 377.5 0 0 1-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 0 1-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97z\"])})),t.ShopTwoTone=u(\"shop\",a,(function(e,t){return l(o,[t,\"M839.5 344h-655c-.3 0-.5.2-.5.5v91.2c0 59.8 49 108.3 109.3 108.3 40.7 0 76.2-22 95.1-54.7 2.9-5.1 8.4-8.3 14.3-8.3s11.3 3.2 14.3 8.3c18.8 32.7 54.3 54.7 95 54.7 40.8 0 76.4-22.1 95.1-54.9 2.9-5 8.2-8.1 13.9-8.1h.6c5.8 0 11 3.1 13.9 8.1 18.8 32.8 54.4 54.9 95.2 54.9C791 544 840 495.5 840 435.7v-91.2c0-.3-.2-.5-.5-.5z\"],[e,\"M882 272.1V144c0-17.7-14.3-32-32-32H174c-17.7 0-32 14.3-32 32v128.1c-16.7 1-30 14.9-30 31.9v131.7a177 177 0 0 0 14.4 70.4c4.3 10.2 9.6 19.8 15.6 28.9v345c0 17.6 14.3 32 32 32h676c17.7 0 32-14.3 32-32V535a175 175 0 0 0 15.6-28.9c9.5-22.3 14.4-46 14.4-70.4V304c0-17-13.3-30.9-30-31.9zM214 184h596v88H214v-88zm362 656.1H448V736h128v104.1zm234.4 0H640V704c0-17.7-14.3-32-32-32H416c-17.7 0-32 14.3-32 32v136.1H214V597.9c2.9 1.4 5.9 2.8 9 4 22.3 9.4 46 14.1 70.4 14.1 24.4 0 48-4.7 70.4-14.1 13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1s48-4.7 70.4-14.1c13.8-5.8 26.8-13.2 38.7-22.1.2-.1.4-.1.6 0a180.4 180.4 0 0 0 38.7 22.1c22.3 9.4 46 14.1 70.4 14.1s48-4.7 70.4-14.1c3-1.3 6-2.6 9-4v242.2zM840 435.7c0 59.8-49 108.3-109.3 108.3-40.8 0-76.4-22.1-95.2-54.9-2.9-5-8.1-8.1-13.9-8.1h-.6c-5.7 0-11 3.1-13.9 8.1A109.24 109.24 0 0 1 512 544c-40.7 0-76.2-22-95-54.7-3-5.1-8.4-8.3-14.3-8.3s-11.4 3.2-14.3 8.3a109.63 109.63 0 0 1-95.1 54.7C233 544 184 495.5 184 435.7v-91.2c0-.3.2-.5.5-.5h655c.3 0 .5.2.5.5v91.2z\"])})),t.ShoppingTwoTone=u(\"shopping\",a,(function(e,t){return l(o,[t,\"M696 472c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-88H400v88c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-88h-96v456h560V384h-96v88z\"],[e,\"M832 312H696v-16c0-101.6-82.4-184-184-184s-184 82.4-184 184v16H192c-17.7 0-32 14.3-32 32v536c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V344c0-17.7-14.3-32-32-32zm-432-16c0-61.9 50.1-112 112-112s112 50.1 112 112v16H400v-16zm392 544H232V384h96v88c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-88h224v88c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-88h96v456z\"])})),t.SkinTwoTone=u(\"skin\",a,(function(e,t){return l(o,[t,\"M512 318c-79.2 0-148.5-48.8-176.7-120H182v196h119v432h422V394h119V198H688.7c-28.2 71.2-97.5 120-176.7 120z\"],[e,\"M870 126H663.8c-17.4 0-32.9 11.9-37 29.3C614.3 208.1 567 246 512 246s-102.3-37.9-114.8-90.7a37.93 37.93 0 0 0-37-29.3H154a44 44 0 0 0-44 44v252a44 44 0 0 0 44 44h75v388a44 44 0 0 0 44 44h478a44 44 0 0 0 44-44V466h75a44 44 0 0 0 44-44V170a44 44 0 0 0-44-44zm-28 268H723v432H301V394H182V198h153.3c28.2 71.2 97.5 120 176.7 120s148.5-48.8 176.7-120H842v196z\"])})),t.SlidersTwoTone=u(\"sliders\",a,(function(e,t){return l(o,[t,\"M180 292h80v440h-80zm369 180h-74a3 3 0 0 0-3 3v74a3 3 0 0 0 3 3h74a3 3 0 0 0 3-3v-74a3 3 0 0 0-3-3zm215-108h80v296h-80z\"],[e,\"M904 296h-66v-96c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v96h-66c-4.4 0-8 3.6-8 8v416c0 4.4 3.6 8 8 8h66v96c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-96h66c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8zm-60 364h-80V364h80v296zM612 404h-66V232c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v172h-66c-4.4 0-8 3.6-8 8v200c0 4.4 3.6 8 8 8h66v172c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8V620h66c4.4 0 8-3.6 8-8V412c0-4.4-3.6-8-8-8zm-60 145a3 3 0 0 1-3 3h-74a3 3 0 0 1-3-3v-74a3 3 0 0 1 3-3h74a3 3 0 0 1 3 3v74zM320 224h-66v-56c0-4.4-3.6-8-8-8h-52c-4.4 0-8 3.6-8 8v56h-66c-4.4 0-8 3.6-8 8v560c0 4.4 3.6 8 8 8h66v56c0 4.4 3.6 8 8 8h52c4.4 0 8-3.6 8-8v-56h66c4.4 0 8-3.6 8-8V232c0-4.4-3.6-8-8-8zm-60 508h-80V292h80v440z\"])})),t.SmileTwoTone=u(\"smile\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zM288 421a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm224 272c-85.5 0-155.6-67.3-160-151.6a8 8 0 0 1 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 589.9 461.5 629 512 629s92.1-39.1 95.8-88.6c.3-4.2 3.9-7.4 8.1-7.4H664a8 8 0 0 1 8 8.4C667.6 625.7 597.5 693 512 693zm176-224a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z\"],[e,\"M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm376 112h-48.1c-4.2 0-7.8 3.2-8.1 7.4-3.7 49.5-45.3 88.6-95.8 88.6s-92-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 0 0-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 0 0-8-8.4zm-24-112a48 48 0 1 0 96 0 48 48 0 1 0-96 0z\"])})),t.SnippetsTwoTone=u(\"snippets\",a,(function(e,t){return l(o,[t,\"M450 510V336H232v552h432V550H490c-22.1 0-40-17.9-40-40z\"],[e,\"M832 112H724V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H500V72c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v40H320c-17.7 0-32 14.3-32 32v120h-96c-17.7 0-32 14.3-32 32v632c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32v-96h96c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zM664 888H232V336h218v174c0 22.1 17.9 40 40 40h174v338zm0-402H514V336h.2L664 485.8v.2zm128 274h-56V456L544 264H360v-80h68v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h152v32c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-32h68v576z\"])})),t.SoundTwoTone=u(\"sound\",a,(function(e,t){return l(o,[t,\"M275.4 424H146v176h129.4l18 11.7L586 803V221L293.3 412.3z\"],[e,\"M892.1 737.8l-110.3-63.7a15.9 15.9 0 0 0-21.7 5.9l-19.9 34.5c-4.4 7.6-1.8 17.4 5.8 21.8L856.3 800a15.9 15.9 0 0 0 21.7-5.9l19.9-34.5c4.4-7.6 1.7-17.4-5.8-21.8zM934 476H806c-8.8 0-16 7.2-16 16v40c0 8.8 7.2 16 16 16h128c8.8 0 16-7.2 16-16v-40c0-8.8-7.2-16-16-16zM760 344a15.9 15.9 0 0 0 21.7 5.9L892 286.2c7.6-4.4 10.2-14.2 5.8-21.8L878 230a15.9 15.9 0 0 0-21.7-5.9L746 287.8a15.99 15.99 0 0 0-5.8 21.8L760 344zM625.9 115c-5.9 0-11.9 1.6-17.4 5.3L254 352H90c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h164l354.5 231.7c5.5 3.6 11.6 5.3 17.4 5.3 16.7 0 32.1-13.3 32.1-32.1V147.1c0-18.8-15.4-32.1-32.1-32.1zM586 803L293.4 611.7l-18-11.7H146V424h129.4l17.9-11.7L586 221v582z\"])})),t.StarTwoTone=u(\"star\",a,(function(e,t){return l(o,[t,\"M512.5 190.4l-94.4 191.3-211.2 30.7 152.8 149-36.1 210.3 188.9-99.3 188.9 99.2-36.1-210.3 152.8-148.9-211.2-30.7z\"],[e,\"M908.6 352.8l-253.9-36.9L541.2 85.8c-3.1-6.3-8.2-11.4-14.5-14.5-15.8-7.8-35-1.3-42.9 14.5L370.3 315.9l-253.9 36.9c-7 1-13.4 4.3-18.3 9.3a32.05 32.05 0 0 0 .6 45.3l183.7 179.1L239 839.4a31.95 31.95 0 0 0 46.4 33.7l227.1-119.4 227.1 119.4c6.2 3.3 13.4 4.4 20.3 3.2 17.4-3 29.1-19.5 26.1-36.9l-43.4-252.9 183.7-179.1c5-4.9 8.3-11.3 9.3-18.3 2.7-17.5-9.5-33.7-27-36.3zM665.3 561.3l36.1 210.3-188.9-99.2-188.9 99.3 36.1-210.3-152.8-149 211.2-30.7 94.4-191.3 94.4 191.3 211.2 30.7-152.8 148.9z\"])})),t.StopTwoTone=u(\"stop\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm288.5 682.8L277.7 224C258 240 240 258 224 277.7l522.8 522.8C682.8 852.7 601 884 512 884c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372c0 89-31.3 170.8-83.5 234.8z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372c89 0 170.8-31.3 234.8-83.5L224 277.7c16-19.7 34-37.7 53.7-53.7l522.8 522.8C852.7 682.8 884 601 884 512c0-205.4-166.6-372-372-372z\"])})),t.SwitcherTwoTone=u(\"switcher\",a,(function(e,t){return l(o,[t,\"M184 840h528V312H184v528zm116-290h296v64H300v-64z\"],[e,\"M880 112H264c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h576v576c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V144c0-17.7-14.3-32-32-32z\"],[e,\"M752 240H144c-17.7 0-32 14.3-32 32v608c0 17.7 14.3 32 32 32h608c17.7 0 32-14.3 32-32V272c0-17.7-14.3-32-32-32zm-40 600H184V312h528v528z\"],[e,\"M300 550h296v64H300z\"])})),t.TabletTwoTone=u(\"tablet\",a,(function(e,t){return l(o,[e,\"M800 64H224c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h576c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64zm-8 824H232V136h560v752z\"],[t,\"M232 888h560V136H232v752zm280-144c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\"],[e,\"M472 784a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"])})),t.TagTwoTone=u(\"tag\",a,(function(e,t){return l(o,[t,\"M589 164.6L189.3 564.3l270.4 270.4L859.4 435 836 188l-247-23.4zM680 432c-48.5 0-88-39.5-88-88s39.5-88 88-88 88 39.5 88 88-39.5 88-88 88z\"],[e,\"M680 256c-48.5 0-88 39.5-88 88s39.5 88 88 88 88-39.5 88-88-39.5-88-88-88zm0 120c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z\"],[e,\"M938 458.8l-29.6-312.6c-1.5-16.2-14.4-29-30.6-30.6L565.2 86h-.4c-3.2 0-5.7 1-7.6 2.9L88.9 557.2a9.96 9.96 0 0 0 0 14.1l363.8 363.8a9.9 9.9 0 0 0 7.1 2.9c2.7 0 5.2-1 7.1-2.9l468.3-468.3c2-2.1 3-5 2.8-8zM459.7 834.7L189.3 564.3 589 164.6 836 188l23.4 247-399.7 399.7z\"])})),t.TagsTwoTone=u(\"tags\",a,(function(e,t){return l(o,[t,\"M477.5 694l311.9-311.8-19-224.6-224.6-19-311.9 311.9L477.5 694zm116-415.5a47.81 47.81 0 0 1 33.9-33.9c16.6-4.4 34.2.3 46.4 12.4a47.93 47.93 0 0 1 12.4 46.4 47.81 47.81 0 0 1-33.9 33.9c-16.6 4.4-34.2-.3-46.4-12.4a48.3 48.3 0 0 1-12.4-46.4z\"],[t,\"M476.6 792.6c-1.7-.2-3.4-1-4.7-2.3L137.7 456.1a8.03 8.03 0 0 1 0-11.3L515.9 66.6c1.2-1.3 2.9-2.1 4.7-2.3h-.4c-2.3-.2-4.7.6-6.3 2.3L135.7 444.8a8.03 8.03 0 0 0 0 11.3l334.2 334.2c1.8 1.9 4.3 2.6 6.7 2.3z\"],[e,\"M889.7 539.8l-39.6-39.5a8.03 8.03 0 0 0-11.3 0l-362 361.3-237.6-237a8.03 8.03 0 0 0-11.3 0l-39.6 39.5a8.03 8.03 0 0 0 0 11.3l243.2 242.8 39.6 39.5c3.1 3.1 8.2 3.1 11.3 0l407.3-406.6c3.1-3.1 3.1-8.2 0-11.3zM652.3 337.3a47.81 47.81 0 0 0 33.9-33.9c4.4-16.6-.3-34.2-12.4-46.4a47.93 47.93 0 0 0-46.4-12.4 47.81 47.81 0 0 0-33.9 33.9c-4.4 16.6.3 34.2 12.4 46.4a48.3 48.3 0 0 0 46.4 12.4z\"],[e,\"M137.7 444.8a8.03 8.03 0 0 0 0 11.3l334.2 334.2c1.3 1.3 2.9 2.1 4.7 2.3 2.4.3 4.8-.5 6.6-2.3L861.4 412c1.7-1.7 2.5-4 2.3-6.3l-25.5-301.4c-.7-7.8-6.8-13.9-14.6-14.6L522.2 64.3h-1.6c-1.8.2-3.4 1-4.7 2.3L137.7 444.8zm408.1-306.2l224.6 19 19 224.6L477.5 694 233.9 450.5l311.9-311.9z\"])})),t.ToolTwoTone=u(\"tool\",a,(function(e,t){return l(o,[t,\"M706.8 488.7a32.05 32.05 0 0 1-45.3 0L537 364.2a32.05 32.05 0 0 1 0-45.3l132.9-132.8a184.2 184.2 0 0 0-144 53.5c-58.1 58.1-69.3 145.3-33.6 214.6L439.5 507c-.1 0-.1-.1-.1-.1L209.3 737l79.2 79.2 274-274.1.1.1 8.8-8.8c69.3 35.7 156.5 24.5 214.6-33.6 39.2-39.1 57.3-92.1 53.6-143.9L706.8 488.7z\"],[e,\"M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 0 1 144-53.5L537 318.9a32.05 32.05 0 0 0 0 45.3l124.5 124.5a32.05 32.05 0 0 0 45.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z\"])})),t.TrademarkCircleTwoTone=u(\"trademark-circle\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm170.7 584.2c-1.1.5-2.3.8-3.5.8h-62c-3.1 0-5.9-1.8-7.2-4.6l-74.6-159.2h-88.7V717c0 4.4-3.6 8-8 8H384c-4.4 0-8-3.6-8-8V307c0-4.4 3.6-8 8-8h155.6c98.8 0 144.2 59.9 144.2 131.1 0 70.2-43.6 106.4-78.4 119.2l80.8 164.2c2.1 3.9.4 8.7-3.5 10.7z\"],[t,\"M529.9 357h-83.4v148H528c53 0 82.8-25.6 82.8-72.4 0-50.3-32.9-75.6-80.9-75.6z\"],[e,\"M605.4 549.3c34.8-12.8 78.4-49 78.4-119.2 0-71.2-45.4-131.1-144.2-131.1H384c-4.4 0-8 3.6-8 8v410c0 4.4 3.6 8 8 8h54.7c4.4 0 8-3.6 8-8V561.2h88.7L610 720.4c1.3 2.8 4.1 4.6 7.2 4.6h62c1.2 0 2.4-.3 3.5-.8 3.9-2 5.6-6.8 3.5-10.7l-80.8-164.2zM528 505h-81.5V357h83.4c48 0 80.9 25.3 80.9 75.6 0 46.8-29.8 72.4-82.8 72.4z\"])})),t.UnlockTwoTone=u(\"unlock\",a,(function(e,t){return l(o,[t,\"M232 840h560V536H232v304zm280-226a48.01 48.01 0 0 1 28 87v53c0 4.4-3.6 8-8 8h-40c-4.4 0-8-3.6-8-8v-53a48.01 48.01 0 0 1 28-87z\"],[e,\"M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53a48.01 48.01 0 1 0-56 0z\"],[e,\"M832 464H332V240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v68c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-68c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zm-40 376H232V536h560v304z\"])})),t.TrophyTwoTone=u(\"trophy\",a,(function(e,t){return l(o,[t,\"M320 480c0 49.1 19.1 95.3 53.9 130.1 34.7 34.8 81 53.9 130.1 53.9h16c49.1 0 95.3-19.1 130.1-53.9 34.8-34.7 53.9-81 53.9-130.1V184H320v296zM184 352c0 41 26.9 75.8 64 87.6-37.1-11.9-64-46.7-64-87.6zm364 382.5C665 721.8 758.4 630.2 773.8 514 758.3 630.2 665 721.7 548 734.5zM250.2 514C265.6 630.2 359 721.8 476 734.5 359 721.7 265.7 630.2 250.2 514z\"],[e,\"M868 160h-92v-40c0-4.4-3.6-8-8-8H256c-4.4 0-8 3.6-8 8v40h-92a44 44 0 0 0-44 44v148c0 81.7 60 149.6 138.2 162C265.7 630.2 359 721.7 476 734.5v105.2H280c-17.7 0-32 14.3-32 32V904c0 4.4 3.6 8 8 8h512c4.4 0 8-3.6 8-8v-32.3c0-17.7-14.3-32-32-32H548V734.5C665 721.7 758.3 630.2 773.8 514 852 501.6 912 433.7 912 352V204a44 44 0 0 0-44-44zM248 439.6a91.99 91.99 0 0 1-64-87.6V232h64v207.6zM704 480c0 49.1-19.1 95.4-53.9 130.1-34.8 34.8-81 53.9-130.1 53.9h-16c-49.1 0-95.4-19.1-130.1-53.9-34.8-34.8-53.9-81-53.9-130.1V184h384v296zm136-128c0 41-26.9 75.8-64 87.6V232h64v120z\"])})),t.UpCircleTwoTone=u(\"up-circle\",a,(function(e,t){return l(o,[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm178 479h-46.9c-10.2 0-19.9-4.9-25.9-13.2L512 460.4 406.8 605.8c-6 8.3-15.6 13.2-25.9 13.2H334c-6.5 0-10.3-7.4-6.5-12.7l178-246c3.2-4.4 9.7-4.4 12.9 0l178 246c3.9 5.3.1 12.7-6.4 12.7z\"],[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[e,\"M518.4 360.3a7.95 7.95 0 0 0-12.9 0l-178 246c-3.8 5.3 0 12.7 6.5 12.7h46.9c10.3 0 19.9-4.9 25.9-13.2L512 460.4l105.2 145.4c6 8.3 15.7 13.2 25.9 13.2H690c6.5 0 10.3-7.4 6.4-12.7l-178-246z\"])})),t.ThunderboltTwoTone=u(\"thunderbolt\",a,(function(e,t){return l(o,[t,\"M695.4 164.1H470.8L281.2 491.5h157.4l-60.3 241 319.8-305.1h-211z\"],[e,\"M848.1 359.3H627.8L825.9 109c4.1-5.3.4-13-6.3-13H436.1c-2.8 0-5.5 1.5-6.9 4L170.1 547.5c-3.1 5.3.7 12 6.9 12h174.4L262 917.1c-1.9 7.8 7.5 13.3 13.3 7.7L853.6 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.3 732.5l60.3-241H281.2l189.6-327.4h224.6L487.1 427.4h211L378.3 732.5z\"])})),t.UpSquareTwoTone=u(\"up-square\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z\"],[t,\"M184 840h656V184H184v656zm143.5-228.7l178-246c3.2-4.4 9.7-4.4 12.9 0l178 246c3.9 5.3.1 12.7-6.4 12.7h-46.9c-10.2 0-19.9-4.9-25.9-13.2L512 465.4 406.8 610.8c-6 8.3-15.6 13.2-25.9 13.2H334c-6.5 0-10.3-7.4-6.5-12.7z\"],[e,\"M334 624h46.9c10.3 0 19.9-4.9 25.9-13.2L512 465.4l105.2 145.4c6 8.3 15.7 13.2 25.9 13.2H690c6.5 0 10.3-7.4 6.4-12.7l-178-246a7.95 7.95 0 0 0-12.9 0l-178 246c-3.8 5.3 0 12.7 6.5 12.7z\"])})),t.UsbTwoTone=u(\"usb\",a,(function(e,t){return l(o,[t,\"M759.9 504H264.1c-26.5 0-48.1 19.7-48.1 44v292h592V548c0-24.3-21.6-44-48.1-44z\"],[e,\"M456 248h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm160 0h-48c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z\"],[e,\"M760 432V144c0-17.7-14.3-32-32-32H296c-17.7 0-32 14.3-32 32v288c-66.2 0-120 52.1-120 116v356c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8V548c0-63.9-53.8-116-120-116zM336 184h352v248H336V184zm472 656H216V548c0-24.3 21.6-44 48.1-44h495.8c26.5 0 48.1 19.7 48.1 44v292z\"])})),t.VideoCameraTwoTone=u(\"video-camera\",a,(function(e,t){return l(o,[t,\"M136 792h576V232H136v560zm64-488c0-4.4 3.6-8 8-8h112c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8H208c-4.4 0-8-3.6-8-8v-48z\"],[e,\"M912 302.3L784 376V224c0-35.3-28.7-64-64-64H128c-35.3 0-64 28.7-64 64v576c0 35.3 28.7 64 64 64h592c35.3 0 64-28.7 64-64V648l128 73.7c21.3 12.3 48-3.1 48-27.6V330c0-24.6-26.7-40-48-27.7zM712 792H136V232h576v560zm176-167l-104-59.8V458.9L888 399v226z\"],[e,\"M208 360h112c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H208c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z\"])})),t.WalletTwoTone=u(\"wallet\",a,(function(e,t){return l(o,[e,\"M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 464H528V448h312v128zm0-192H496c-17.7 0-32 14.3-32 32v192c0 17.7 14.3 32 32 32h344v200H184V184h656v200z\"],[t,\"M528 576h312V448H528v128zm92-104c22.1 0 40 17.9 40 40s-17.9 40-40 40-40-17.9-40-40 17.9-40 40-40z\"],[e,\"M580 512a40 40 0 1 0 80 0 40 40 0 1 0-80 0z\"],[t,\"M184 840h656V640H496c-17.7 0-32-14.3-32-32V416c0-17.7 14.3-32 32-32h344V184H184v656z\"])})),t.WarningTwoTone=u(\"warning\",a,(function(e,t){return l(o,[e,\"M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z\"],[t,\"M172.2 828.1h679.6L512 239.9 172.2 828.1zM560 720a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0zm-16-304v184c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V416c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8z\"],[e,\"M464 720a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8z\"])})),t.CiTwoTone=u(\"ci\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm-63.5 522.8c49.3 0 82.8-29.4 87-72.4.4-4.1 3.8-7.3 8-7.3h52.7c2.4 0 4.4 2 4.4 4.4 0 77.4-64.3 132.5-152.3 132.5C345.4 720 286 651.4 286 537.4v-49C286 373.5 345.4 304 448.3 304c88.3 0 152.3 56.9 152.3 138.1 0 2.4-2 4.4-4.4 4.4h-52.6c-4.2 0-7.6-3.2-8-7.4-3.9-46.1-37.5-77.6-87-77.6-61.1 0-95.6 45.4-95.7 126.8v49.3c0 80.3 34.5 125.2 95.6 125.2zM738 704.1c0 4.4-3.6 8-8 8h-50.4c-4.4 0-8-3.6-8-8V319.9c0-4.4 3.6-8 8-8H730c4.4 0 8 3.6 8 8v384.2z\"],[e,\"M730 311.9h-50.4c-4.4 0-8 3.6-8 8v384.2c0 4.4 3.6 8 8 8H730c4.4 0 8-3.6 8-8V319.9c0-4.4-3.6-8-8-8zm-281.4 49.6c49.5 0 83.1 31.5 87 77.6.4 4.2 3.8 7.4 8 7.4h52.6c2.4 0 4.4-2 4.4-4.4 0-81.2-64-138.1-152.3-138.1C345.4 304 286 373.5 286 488.4v49c0 114 59.4 182.6 162.3 182.6 88 0 152.3-55.1 152.3-132.5 0-2.4-2-4.4-4.4-4.4h-52.7c-4.2 0-7.6 3.2-8 7.3-4.2 43-37.7 72.4-87 72.4-61.1 0-95.6-44.9-95.6-125.2v-49.3c.1-81.4 34.6-126.8 95.7-126.8z\"])})),t.CopyrightTwoTone=u(\"copyright\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm5.5 533c52.9 0 88.8-31.7 93-77.8.4-4.1 3.8-7.3 8-7.3h56.8c2.6 0 4.7 2.1 4.7 4.7 0 82.6-68.7 141.4-162.7 141.4C407.4 734 344 660.8 344 539.1v-52.3C344 364.2 407.4 290 517.3 290c94.3 0 162.7 60.7 162.7 147.4 0 2.6-2.1 4.7-4.7 4.7h-56.7c-4.2 0-7.7-3.2-8-7.4-4-49.6-40-83.4-93-83.4-65.2 0-102.1 48.5-102.2 135.5v52.6c0 85.7 36.8 133.6 102.1 133.6z\"],[e,\"M517.6 351.3c53 0 89 33.8 93 83.4.3 4.2 3.8 7.4 8 7.4h56.7c2.6 0 4.7-2.1 4.7-4.7 0-86.7-68.4-147.4-162.7-147.4C407.4 290 344 364.2 344 486.8v52.3C344 660.8 407.4 734 517.3 734c94 0 162.7-58.8 162.7-141.4 0-2.6-2.1-4.7-4.7-4.7h-56.8c-4.2 0-7.6 3.2-8 7.3-4.2 46.1-40.1 77.8-93 77.8-65.3 0-102.1-47.9-102.1-133.6v-52.6c.1-87 37-135.5 102.2-135.5z\"])})),t.DollarTwoTone=u(\"dollar\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M426.6 410.3c0 25.4 15.7 45.1 49.5 57.3 4.7 1.9 9.4 3.4 15 5v-124c-37 4.7-64.5 25.4-64.5 61.7zm116.5 135.2c-2.9-.6-5.7-1.3-8.8-2.2V677c42.6-3.8 72-27.3 72-66.4 0-30.7-15.9-50.7-63.2-65.1z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm22.4 589.2l.2 31.7c0 4.5-3.6 8.1-8 8.1h-28.4c-4.4 0-8-3.6-8-8v-31.4c-89-6.5-130.7-57.1-135.2-112.1-.4-4.7 3.3-8.7 8-8.7h46.2c3.9 0 7.3 2.8 7.9 6.6 5.1 31.8 29.9 55.4 74.1 61.3V534l-24.7-6.3c-52.3-12.5-102.1-45.1-102.1-112.7 0-73 55.4-112.1 126.2-119v-33c0-4.4 3.6-8 8-8h28.1c4.4 0 8 3.6 8 8v32.7c68.5 6.9 119.8 46.9 125.9 109.2a8.1 8.1 0 0 1-8 8.8h-44.9c-4 0-7.4-2.9-7.9-6.9-4-29.2-27.5-53-65.5-58.2v134.3l25.4 5.9c64.8 16 108.9 47 109 116.4 0 75.2-56 117.1-134.3 124z\"],[e,\"M559.7 488.8l-25.4-5.9V348.6c38 5.2 61.5 29 65.5 58.2.5 4 3.9 6.9 7.9 6.9h44.9c4.7 0 8.4-4.1 8-8.8-6.1-62.3-57.4-102.3-125.9-109.2V263c0-4.4-3.6-8-8-8h-28.1c-4.4 0-8 3.6-8 8v33c-70.8 6.9-126.2 46-126.2 119 0 67.6 49.8 100.2 102.1 112.7l24.7 6.3v142.7c-44.2-5.9-69-29.5-74.1-61.3-.6-3.8-4-6.6-7.9-6.6H363c-4.7 0-8.4 4-8 8.7 4.5 55 46.2 105.6 135.2 112.1V761c0 4.4 3.6 8 8 8h28.4c4.4 0 8-3.6 8-8.1l-.2-31.7c78.3-6.9 134.3-48.8 134.3-124-.1-69.4-44.2-100.4-109-116.4zm-68.6-16.2c-5.6-1.6-10.3-3.1-15-5-33.8-12.2-49.5-31.9-49.5-57.3 0-36.3 27.5-57 64.5-61.7v124zM534.3 677V543.3c3.1.9 5.9 1.6 8.8 2.2 47.3 14.4 63.2 34.4 63.2 65.1 0 39.1-29.4 62.6-72 66.4z\"])})),t.EuroTwoTone=u(\"euro\",a,(function(e,t){return l(o,[e,\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"],[t,\"M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372 372-166.6 372-372-166.6-372-372-372zm117.1 581.1c0 3.8-2.7 7-6.4 7.8-15.9 3.4-34.4 5.1-55.3 5.1-109.8 0-183-58.8-200.2-158H337c-4.4 0-8-3.6-8-8v-27.2c0-4.4 3.6-8 8-8h26.1v-36.9c0-4.4 0-8.7.3-12.8H337c-4.4 0-8-3.6-8-8v-27.2c0-4.4 3.6-8 8-8h31.8C388.5 345.7 460.7 290 567.4 290c20.9 0 39.4 1.9 55.3 5.4 3.7.8 6.3 4 6.3 7.8V346a8 8 0 0 1-9.6 7.8c-14.6-2.9-31.8-4.4-51.7-4.4-65.3 0-110.4 33.5-127.6 90.4h128.3c4.4 0 8 3.6 8 8V475c0 4.4-3.6 8-8 8H432.5c-.3 4.4-.3 9.1-.3 13.8v36h136.4c4.4 0 8 3.6 8 8V568c0 4.4-3.6 8-8 8H438c15.3 62 61.3 98.6 129.8 98.6 19.9 0 37.1-1.3 51.8-4.1 4.9-1 9.5 2.8 9.5 7.8v42.8z\"],[e,\"M619.6 670.5c-14.7 2.8-31.9 4.1-51.8 4.1-68.5 0-114.5-36.6-129.8-98.6h130.6c4.4 0 8-3.6 8-8v-27.2c0-4.4-3.6-8-8-8H432.2v-36c0-4.7 0-9.4.3-13.8h135.9c4.4 0 8-3.6 8-8v-27.2c0-4.4-3.6-8-8-8H440.1c17.2-56.9 62.3-90.4 127.6-90.4 19.9 0 37.1 1.5 51.7 4.4a8 8 0 0 0 9.6-7.8v-42.8c0-3.8-2.6-7-6.3-7.8-15.9-3.5-34.4-5.4-55.3-5.4-106.7 0-178.9 55.7-198.6 149.9H337c-4.4 0-8 3.6-8 8v27.2c0 4.4 3.6 8 8 8h26.4c-.3 4.1-.3 8.4-.3 12.8v36.9H337c-4.4 0-8 3.6-8 8V568c0 4.4 3.6 8 8 8h30.2c17.2 99.2 90.4 158 200.2 158 20.9 0 39.4-1.7 55.3-5.1 3.7-.8 6.4-4 6.4-7.8v-42.8c0-5-4.6-8.8-9.5-7.8z\"])})),t.GoldTwoTone=u(\"gold\",a,(function(e,t){return l(o,[e,\"M435.7 558.7c-.6-3.9-4-6.7-7.9-6.7H166.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248zM196.5 748l20.7-128h159.5l20.7 128H196.5zm709.4 58.7l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H596.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8h342c.4 0 .9 0 1.3-.1 4.3-.7 7.3-4.8 6.6-9.2zM626.5 748l20.7-128h159.5l20.7 128H626.5zM342 472h342c.4 0 .9 0 1.3-.1 4.4-.7 7.3-4.8 6.6-9.2l-40.2-248c-.6-3.9-4-6.7-7.9-6.7H382.2c-3.9 0-7.3 2.8-7.9 6.7l-40.2 248c-.1.4-.1.9-.1 1.3 0 4.4 3.6 8 8 8zm91.2-196h159.5l20.7 128h-201l20.8-128z\"],[t,\"M592.7 276H433.2l-20.8 128h201zM217.2 620l-20.7 128h200.9l-20.7-128zm430 0l-20.7 128h200.9l-20.7-128z\"])})),t.CanlendarTwoTone=u(\"canlendar\",a,(function(e,t){return l(o,[t,\"M712 304c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H384v48c0 4.4-3.6 8-8 8h-56c-4.4 0-8-3.6-8-8v-48H184v136h656V256H712v48z\"],[e,\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zm0-448H184V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136z\"])}))},function(e,t,n){\"use strict\";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var o=r(n(224)),c=r(n(155));t.Column=c.default;var i=r(n(156));t.ColumnGroup=i.default;var a=n(54);t.INTERNAL_COL_DEFINE=a.INTERNAL_COL_DEFINE,t.default=o.default},function(e,t,n){\"use strict\";var r,o=n(0),c=n.n(o),i=n(9),a=n.n(i),l=n(1),u=n.n(l),s=n(12),f=n(87),p=n(88);function h(e){if(\"undefined\"===typeof document)return 0;if(e||void 0===r){var t=document.createElement(\"div\");t.style.width=\"100%\",t.style.height=\"200px\";var n=document.createElement(\"div\"),o=n.style;o.position=\"absolute\",o.top=0,o.left=0,o.pointerEvents=\"none\",o.visibility=\"hidden\",o.width=\"200px\",o.height=\"150px\",o.overflow=\"hidden\",n.appendChild(t),document.body.appendChild(n);var c=t.offsetWidth;n.style.overflow=\"scroll\";var i=t.offsetWidth;c===i&&(i=n.clientWidth),document.body.removeChild(n),r=c-i}return r}var d=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.element,r=void 0===n?document.body:n,o={},c=Object.keys(e);return c.forEach((function(e){o[e]=r.style[e]})),c.forEach((function(t){r.style[t]=e[t]})),o};var v={},m=function(e){if(document.body.scrollHeight>(window.innerHeight||document.documentElement.clientHeight)&&window.innerWidth>document.body.offsetWidth||e){var t=\"ant-scrolling-effect\",n=new RegExp(\"\".concat(t),\"g\"),r=document.body.className;if(e){if(!n.test(r))return;return d(v),v={},void(document.body.className=r.replace(n,\"\").trim())}var o=h();if(o&&(v=d({position:\"relative\",width:\"calc(100% - \".concat(o,\"px)\")}),!n.test(r))){var c=\"\".concat(r,\" \").concat(t);document.body.className=c.trim()}}};function y(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function b(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?y(Object(n),!0).forEach((function(t){g(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):y(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function g(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function w(e){return(w=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function z(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function O(e,t){return(O=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function C(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=_(e);if(t){var o=_(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return M(this,n)}}function M(e,t){return!t||\"object\"!==w(t)&&\"function\"!==typeof t?S(e):t}function S(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function _(e){return(_=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var x=0,k=!(\"undefined\"!==typeof window&&window.document&&window.document.createElement),H=\"createPortal\"in a.a,E={},P=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&O(e,t)}(i,e);var t,n,r,o=C(i);function i(e){var t;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,i),(t=o.call(this,e)).getParent=function(){var e=t.props.getContainer;if(e){if(\"string\"===typeof e)return document.querySelectorAll(e)[0];if(\"function\"===typeof e)return e();if(\"object\"===w(e)&&e instanceof window.HTMLElement)return e}return document.body},t.getContainer=function(){if(k)return null;if(!t.container){t.container=document.createElement(\"div\");var e=t.getParent();e&&e.appendChild(t.container)}return t.setWrapperClassName(),t.container},t.setWrapperClassName=function(){var e=t.props.wrapperClassName;t.container&&e&&e!==t.container.className&&(t.container.className=e)},t.savePortal=function(e){t._component=e},t.removeCurrentContainer=function(e){t.container=null,t._component=null,H||(e?t.renderComponent({afterClose:t.removeContainer,onClose:function(){},visible:!1}):t.removeContainer())},t.switchScrollingEffect=function(){1!==x||Object.keys(E).length?x||(d(E),E={},m(!0)):(m(),E=d({overflow:\"hidden\",overflowX:\"hidden\",overflowY:\"hidden\"}))};var n=e.visible;return x=n?x+1:x,t.state={_self:S(t)},t}return t=i,r=[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=t.prevProps,r=t._self,o=e.visible,c=e.getContainer;if(n){var i=n.visible,a=n.getContainer;o!==i&&(x=o&&!i?x+1:x-1),(\"function\"===typeof c&&\"function\"===typeof a?c.toString()!==a.toString():c!==a)&&r.removeCurrentContainer(!1)}return{prevProps:e}}}],(n=[{key:\"componentDidUpdate\",value:function(){this.setWrapperClassName()}},{key:\"componentWillUnmount\",value:function(){var e=this.props.visible;x=e&&x?x-1:x,this.removeCurrentContainer(e)}},{key:\"render\",value:function(){var e=this,t=this.props,n=t.children,r=t.forceRender,o=t.visible,i=null,a={getOpenCount:function(){return x},getContainer:this.getContainer,switchScrollingEffect:this.switchScrollingEffect};return H?((r||o||this._component)&&(i=c.a.createElement(p.a,{getContainer:this.getContainer,ref:this.savePortal},n(a))),i):c.a.createElement(f.a,{parent:this,visible:o,autoDestroy:!1,getComponent:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n(b(b(b({},t),a),{},{ref:e.savePortal}))},getContainer:this.getContainer,forceRender:r},(function(t){var n=t.renderComponent,r=t.removeContainer;return e.renderComponent=n,e.removeContainer=r,null}))}}])&&z(t.prototype,n),r&&z(t,r),i}(c.a.Component);P.propTypes={wrapperClassName:u.a.string,forceRender:u.a.bool,getContainer:u.a.any,children:u.a.func,visible:u.a.bool};var V=Object(s.polyfill)(P),T=n(8),L=n.n(T),j=n(18);var N={transition:\"transitionend\",WebkitTransition:\"webkitTransitionEnd\",MozTransition:\"transitionend\",OTransition:\"oTransitionEnd otransitionend\"},D=Object.keys(N).filter((function(e){if(\"undefined\"===typeof document)return!1;var t=document.getElementsByTagName(\"html\")[0];return e in(t?t.style:{})}))[0],R=N[D];function A(e,t,n,r){e.addEventListener?e.addEventListener(t,n,r):e.attachEvent&&e.attachEvent(\"on\".concat(t),n)}function I(e,t,n,r){e.removeEventListener?e.removeEventListener(t,n,r):e.attachEvent&&e.detachEvent(\"on\".concat(t),n)}var F=function(e){return!isNaN(parseFloat(e))&&isFinite(e)},W=!(\"undefined\"!==typeof window&&window.document&&window.document.createElement),U=function e(t,n,r,o){if(!n||n===document||n instanceof Document)return!1;if(n===t.parentNode)return!0;var c=Math.max(Math.abs(r),Math.abs(o))===Math.abs(o),i=Math.max(Math.abs(r),Math.abs(o))===Math.abs(r),a=n.scrollHeight-n.clientHeight,l=n.scrollWidth-n.clientWidth,u=document.defaultView.getComputedStyle(n),s=\"auto\"===u.overflowY||\"scroll\"===u.overflowY,f=\"auto\"===u.overflowX||\"scroll\"===u.overflowX,p=a&&s,h=l&&f;return!!(c&&(!p||p&&(n.scrollTop>=a&&o<0||n.scrollTop<=0&&o>0))||i&&(!h||h&&(n.scrollLeft>=l&&l<0||n.scrollLeft<=0&&l>0)))&&e(t,n.parentNode,r,o)};function K(e){return(K=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function B(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Y(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function q(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function G(e){return(G=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function $(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Q(e,t){return(Q=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var X={},Z=function(e){function t(e){var n;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=function(e,t){return!t||\"object\"!==K(t)&&\"function\"!==typeof t?$(e):t}(this,G(t).call(this,e))).domFocus=function(){n.dom&&n.dom.focus()},n.removeStartHandler=function(e){e.touches.length>1||(n.startPos={x:e.touches[0].clientX,y:e.touches[0].clientY})},n.removeMoveHandler=function(e){if(!(e.changedTouches.length>1)){var t=e.currentTarget,r=e.changedTouches[0].clientX-n.startPos.x,o=e.changedTouches[0].clientY-n.startPos.y;(t===n.maskDom||t===n.handlerDom||t===n.contentDom&&U(t,e.target,r,o))&&e.preventDefault()}},n.transitionEnd=function(e){var t=e.target;I(t,R,n.transitionEnd),t.style.transition=\"\"},n.onKeyDown=function(e){if(e.keyCode===j.a.ESC){var t=n.props.onClose;e.stopPropagation(),t&&t(e)}},n.onWrapperTransitionEnd=function(e){var t=n.props,r=t.open,o=t.afterVisibleChange;e.target===n.contentWrapper&&e.propertyName.match(/transform$/)&&(n.dom.style.transition=\"\",!r&&n.getCurrentDrawerSome()&&(document.body.style.overflowX=\"\",n.maskDom&&(n.maskDom.style.left=\"\",n.maskDom.style.width=\"\")),o&&o(!!r))},n.openLevelTransition=function(){var e=n.props,t=e.open,r=e.width,o=e.height,c=n.getHorizontalBoolAndPlacementName(),i=c.isHorizontal,a=c.placementName,l=n.contentDom?n.contentDom.getBoundingClientRect()[i?\"width\":\"height\"]:0,u=(i?r:o)||l;n.setLevelAndScrolling(t,a,u)},n.setLevelTransform=function(e,t,r,o){var c=n.props,i=c.placement,a=c.levelMove,l=c.duration,u=c.ease,s=c.showMask;n.levelDom.forEach((function(c){c.style.transition=\"transform \".concat(l,\" \").concat(u),A(c,R,n.transitionEnd);var f=e?r:0;if(a){var p=function(e,t){var n=\"function\"===typeof e?e(t):e;return Array.isArray(n)?2===n.length?n:[n[0],n[1]]:[n]}(a,{target:c,open:e});f=e?p[0]:p[1]||0}var h=\"number\"===typeof f?\"\".concat(f,\"px\"):f,d=\"left\"===i||\"top\"===i?h:\"-\".concat(h);d=s&&\"right\"===i&&o?\"calc(\".concat(d,\" + \").concat(o,\"px)\"):d,c.style.transform=f?\"\".concat(t,\"(\").concat(d,\")\"):\"\"}))},n.setLevelAndScrolling=function(e,t,r){var o=n.props.onChange;if(!W){var c=document.body.scrollHeight>(window.innerHeight||document.documentElement.clientHeight)&&window.innerWidth>document.body.offsetWidth?h(!0):0;n.setLevelTransform(e,t,r,c),n.toggleScrollingToDrawerAndBody(c)}o&&o(e)},n.toggleScrollingToDrawerAndBody=function(e){var t=n.props,r=t.getOpenCount,o=t.getContainer,c=t.showMask,i=t.open,a=o&&o(),l=r&&r();if(a&&a.parentNode===document.body&&c){var u=[\"touchstart\"],s=[document.body,n.maskDom,n.handlerDom,n.contentDom];i&&\"hidden\"!==document.body.style.overflow?(e&&n.addScrollingEffect(e),1===l&&(document.body.style.overflow=\"hidden\"),document.body.style.touchAction=\"none\",s.forEach((function(e,t){e&&A(e,u[t]||\"touchmove\",t?n.removeMoveHandler:n.removeStartHandler,n.passive)}))):n.getCurrentDrawerSome()&&(l||(document.body.style.overflow=\"\"),document.body.style.touchAction=\"\",e&&n.remScrollingEffect(e),s.forEach((function(e,t){e&&I(e,u[t]||\"touchmove\",t?n.removeMoveHandler:n.removeStartHandler,n.passive)})))}},n.addScrollingEffect=function(e){var t=n.props,r=t.placement,o=t.duration,c=t.ease,i=t.getOpenCount,a=t.switchScrollingEffect;1===(i&&i())&&a();var l=\"width \".concat(o,\" \").concat(c),u=\"transform \".concat(o,\" \").concat(c);switch(n.dom.style.transition=\"none\",r){case\"right\":n.dom.style.transform=\"translateX(-\".concat(e,\"px)\");break;case\"top\":case\"bottom\":n.dom.style.width=\"calc(100% - \".concat(e,\"px)\"),n.dom.style.transform=\"translateZ(0)\"}clearTimeout(n.timeout),n.timeout=setTimeout((function(){n.dom&&(n.dom.style.transition=\"\".concat(u,\",\").concat(l),n.dom.style.width=\"\",n.dom.style.transform=\"\")}))},n.remScrollingEffect=function(e){var t,r=n.props,o=r.placement,c=r.duration,i=r.ease,a=r.getOpenCount,l=r.switchScrollingEffect;a&&a()||l(!0),D&&(document.body.style.overflowX=\"hidden\"),n.dom.style.transition=\"none\";var u=\"width \".concat(c,\" \").concat(i),s=\"transform \".concat(c,\" \").concat(i);switch(o){case\"left\":n.dom.style.width=\"100%\",u=\"width 0s \".concat(i,\" \").concat(c);break;case\"right\":n.dom.style.transform=\"translateX(\".concat(e,\"px)\"),n.dom.style.width=\"100%\",u=\"width 0s \".concat(i,\" \").concat(c),n.maskDom&&(n.maskDom.style.left=\"-\".concat(e,\"px\"),n.maskDom.style.width=\"calc(100% + \".concat(e,\"px)\"));break;case\"top\":case\"bottom\":n.dom.style.width=\"calc(100% + \".concat(e,\"px)\"),n.dom.style.height=\"100%\",n.dom.style.transform=\"translateZ(0)\",t=\"height 0s \".concat(i,\" \").concat(c)}clearTimeout(n.timeout),n.timeout=setTimeout((function(){n.dom&&(n.dom.style.transition=\"\".concat(s,\",\").concat(t?\"\".concat(t,\",\"):\"\").concat(u),n.dom.style.transform=\"\",n.dom.style.width=\"\",n.dom.style.height=\"\")}))},n.getCurrentDrawerSome=function(){return!Object.keys(X).some((function(e){return X[e]}))},n.getLevelDom=function(e){var t=e.level,r=e.getContainer;if(!W){var o,c=r&&r(),i=c?c.parentNode:null;if(n.levelDom=[],\"all\"===t)(i?Array.prototype.slice.call(i.children):[]).forEach((function(e){\"SCRIPT\"!==e.nodeName&&\"STYLE\"!==e.nodeName&&\"LINK\"!==e.nodeName&&e!==c&&n.levelDom.push(e)}));else t&&(o=t,Array.isArray(o)?o:[o]).forEach((function(e){document.querySelectorAll(e).forEach((function(e){n.levelDom.push(e)}))}))}},n.getHorizontalBoolAndPlacementName=function(){var e=n.props.placement,t=\"left\"===e||\"right\"===e;return{isHorizontal:t,placementName:\"translate\".concat(t?\"X\":\"Y\")}},n.state={_self:$(n)},n}var n,r,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Q(e,t)}(t,e),n=t,c=[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=t.prevProps,r=t._self,o={prevProps:e};if(void 0!==n){var c=e.placement,i=e.level;c!==n.placement&&(r.contentDom=null),i!==n.level&&r.getLevelDom(e)}return o}}],(r=[{key:\"componentDidMount\",value:function(){var e=this;if(!W){var t=!1;try{window.addEventListener(\"test\",null,Object.defineProperty({},\"passive\",{get:function(){return t=!0,null}}))}catch(r){}this.passive=!!t&&{passive:!1}}var n=this.props.open;this.drawerId=\"drawer_id_\".concat(Number((Date.now()+Math.random()).toString().replace(\".\",Math.round(9*Math.random()).toString())).toString(16)),this.getLevelDom(this.props),n&&(X[this.drawerId]=n,this.openLevelTransition(),this.forceUpdate((function(){e.domFocus()})))}},{key:\"componentDidUpdate\",value:function(e){var t=this.props.open;t!==e.open&&(t&&this.domFocus(),X[this.drawerId]=!!t,this.openLevelTransition())}},{key:\"componentWillUnmount\",value:function(){var e=this.props,t=e.getOpenCount,n=e.open,r=e.switchScrollingEffect,o=\"function\"===typeof t&&t();delete X[this.drawerId],n&&(this.setLevelTransform(!1),document.body.style.touchAction=\"\"),o||(document.body.style.overflow=\"\",r(!0))}},{key:\"render\",value:function(){var e,t=this,n=this.props,r=n.className,c=n.children,i=n.style,a=n.width,l=n.height,u=(n.defaultOpen,n.open),s=n.prefixCls,f=n.placement,p=(n.level,n.levelMove,n.ease,n.duration,n.getContainer,n.handler),h=(n.onChange,n.afterVisibleChange,n.showMask),d=n.maskClosable,v=n.maskStyle,m=n.onClose,y=n.onHandleClick,b=n.keyboard,g=(n.getOpenCount,n.switchScrollingEffect,Y(n,[\"className\",\"children\",\"style\",\"width\",\"height\",\"defaultOpen\",\"open\",\"prefixCls\",\"placement\",\"level\",\"levelMove\",\"ease\",\"duration\",\"getContainer\",\"handler\",\"onChange\",\"afterVisibleChange\",\"showMask\",\"maskClosable\",\"maskStyle\",\"onClose\",\"onHandleClick\",\"keyboard\",\"getOpenCount\",\"switchScrollingEffect\"])),w=!!this.dom&&u,z=L()(s,(B(e={},\"\".concat(s,\"-\").concat(f),!0),B(e,\"\".concat(s,\"-open\"),w),B(e,r||\"\",!!r),B(e,\"no-mask\",!h),e)),O=this.getHorizontalBoolAndPlacementName().placementName,C=\"left\"===f||\"top\"===f?\"-100%\":\"100%\",M=w?\"\":\"\".concat(O,\"(\").concat(C,\")\"),S=p&&o.cloneElement(p,{onClick:function(e){p.props.onClick&&p.props.onClick(),y&&y(e)},ref:function(e){t.handlerDom=e}});return o.createElement(\"div\",Object.assign({},g,{tabIndex:-1,className:z,style:i,ref:function(e){t.dom=e},onKeyDown:w&&b?this.onKeyDown:void 0,onTransitionEnd:this.onWrapperTransitionEnd}),h&&o.createElement(\"div\",{className:\"\".concat(s,\"-mask\"),onClick:d?m:void 0,style:v,ref:function(e){t.maskDom=e}}),o.createElement(\"div\",{className:\"\".concat(s,\"-content-wrapper\"),style:{transform:M,msTransform:M,width:F(a)?\"\".concat(a,\"px\"):a,height:F(l)?\"\".concat(l,\"px\"):l},ref:function(e){t.contentWrapper=e}},o.createElement(\"div\",{className:\"\".concat(s,\"-content\"),ref:function(e){t.contentDom=e},onTouchStart:w&&h?this.removeStartHandler:void 0,onTouchMove:w&&h?this.removeMoveHandler:void 0},c),S))}}])&&q(n.prototype,r),c&&q(n,c),t}(o.Component);Z.defaultProps={switchScrollingEffect:function(){}};var J=Object(s.polyfill)(Z);function ee(e){return(ee=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function te(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ne(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function re(e,t){return!t||\"object\"!==ee(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function oe(e){return(oe=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function ce(e,t){return(ce=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var ie=function(e){function t(e){var n;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,t),(n=re(this,oe(t).call(this,e))).onHandleClick=function(e){var t=n.props,r=t.onHandleClick,o=t.open;if(r&&r(e),\"undefined\"===typeof o){var c=n.state.open;n.setState({open:!c})}},n.onClose=function(e){var t=n.props,r=t.onClose,o=t.open;r&&r(e),\"undefined\"===typeof o&&n.setState({open:!1})};var r=\"undefined\"!==typeof e.open?e.open:!!e.defaultOpen;return n.state={open:r},\"onMaskClick\"in e&&console.warn(\"`onMaskClick` are removed, please use `onClose` instead.\"),n}var n,r,c;return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&ce(e,t)}(t,e),n=t,c=[{key:\"getDerivedStateFromProps\",value:function(e,t){var n=t.prevProps,r={prevProps:e};return\"undefined\"!==typeof n&&e.open!==n.open&&(r.open=e.open),r}}],(r=[{key:\"render\",value:function(){var e=this,t=this.props,n=(t.defaultOpen,t.getContainer),r=t.wrapperClassName,c=t.forceRender,i=t.handler,a=te(t,[\"defaultOpen\",\"getContainer\",\"wrapperClassName\",\"forceRender\",\"handler\"]),l=this.state.open;if(!n)return o.createElement(\"div\",{className:r,ref:function(t){e.dom=t}},o.createElement(J,Object.assign({},a,{open:l,handler:i,getContainer:function(){return e.dom},onClose:this.onClose,onHandleClick:this.onHandleClick})));var u=!!i||c;return o.createElement(V,{visible:l,forceRender:u,getContainer:n,wrapperClassName:r},(function(t){var n=t.visible,r=t.afterClose,c=te(t,[\"visible\",\"afterClose\"]);return o.createElement(J,Object.assign({},a,c,{open:void 0!==n?n:l,afterVisibleChange:void 0!==r?r:a.afterVisibleChange,handler:i,onClose:e.onClose,onHandleClick:e.onHandleClick}))}))}}])&&ne(n.prototype,r),c&&ne(n,c),t}(o.Component);ie.defaultProps={prefixCls:\"drawer\",placement:\"left\",getContainer:\"body\",defaultOpen:!1,level:\"all\",duration:\".3s\",ease:\"cubic-bezier(0.78, 0.14, 0.15, 0.86)\",onChange:function(){},afterVisibleChange:function(){},handler:o.createElement(\"div\",{className:\"drawer-handle\"},o.createElement(\"i\",{className:\"drawer-handle-icon\"})),showMask:!0,maskClosable:!0,maskStyle:{},wrapperClassName:\"\",className:\"\",keyboard:!0,forceRender:!1};var ae=Object(s.polyfill)(ie),le=n(29),ue=n.n(le),se=n(3),fe=n.n(se),pe=n(20),he=n(16),de=n(13),ve=n(59),me=n(27);function ye(e){return(ye=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function be(){return(be=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function ge(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function we(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function ze(e,t){return(ze=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Oe(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Se(e);if(t){var o=Se(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Ce(this,n)}}function Ce(e,t){return!t||\"object\"!==ye(t)&&\"function\"!==typeof t?Me(e):t}function Me(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Se(e){return(Se=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var _e=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},xe=ue()(null),ke=(Object(me.a)(\"top\",\"right\",\"bottom\",\"left\"),function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&ze(e,t)}(i,e);var t,n,r,c=Oe(i);function i(){var e;return ge(this,i),(e=c.apply(this,arguments)).state={push:!1},e.push=function(){e.setState({push:!0})},e.pull=function(){e.setState({push:!1})},e.onDestroyTransitionEnd=function(){e.getDestroyOnClose()&&(e.props.visible||(e.destroyClose=!0,e.forceUpdate()))},e.getDestroyOnClose=function(){return e.props.destroyOnClose&&!e.props.visible},e.getPushTransform=function(e){return\"left\"===e||\"right\"===e?\"translateX(\".concat(\"left\"===e?180:-180,\"px)\"):\"top\"===e||\"bottom\"===e?\"translateY(\".concat(\"top\"===e?180:-180,\"px)\"):void 0},e.getRcDrawerStyle=function(){var t=e.props,n=t.zIndex,r=t.placement,o=t.style;return be({zIndex:n,transform:e.state.push?e.getPushTransform(r):void 0},o)},e.renderBody=function(){var t=e.props,n=t.bodyStyle,r=t.drawerStyle,c=t.prefixCls,i=t.visible;if(e.destroyClose&&!i)return null;e.destroyClose=!1;var a={};return e.getDestroyOnClose()&&(a.opacity=0,a.transition=\"opacity .3s\"),o.createElement(\"div\",{className:\"\".concat(c,\"-wrapper-body\"),style:be(be({},a),r),onTransitionEnd:e.onDestroyTransitionEnd},e.renderHeader(),o.createElement(\"div\",{className:\"\".concat(c,\"-body\"),style:n},e.props.children))},e.renderProvider=function(t){var n=e.props,r=n.prefixCls,c=n.placement,i=n.className,a=n.wrapClassName,l=n.width,u=n.height,s=n.mask,f=_e(n,[\"prefixCls\",\"placement\",\"className\",\"wrapClassName\",\"width\",\"height\",\"mask\"]);Object(he.a)(void 0===a,\"Drawer\",\"wrapClassName is deprecated, please use className instead.\");var p=s?\"\":\"no-mask\";e.parentDrawer=t;var h={};return\"left\"===c||\"right\"===c?h.width=l:h.height=u,o.createElement(xe.Provider,{value:Me(e)},o.createElement(ae,be({handler:!1},Object(pe.a)(f,[\"zIndex\",\"style\",\"closable\",\"destroyOnClose\",\"drawerStyle\",\"headerStyle\",\"bodyStyle\",\"title\",\"push\",\"visible\",\"getPopupContainer\",\"rootPrefixCls\",\"getPrefixCls\",\"renderEmpty\",\"csp\",\"pageHeader\",\"autoInsertSpaceInButton\"]),h,{prefixCls:r,open:e.props.visible,showMask:s,placement:c,style:e.getRcDrawerStyle(),className:fe()(a,i,p)}),e.renderBody()))},e}return t=i,(n=[{key:\"componentDidMount\",value:function(){this.props.visible&&this.parentDrawer&&this.parentDrawer.push()}},{key:\"componentDidUpdate\",value:function(e){var t=this.props.visible;e.visible!==t&&this.parentDrawer&&(t?this.parentDrawer.push():this.parentDrawer.pull())}},{key:\"componentWillUnmount\",value:function(){this.parentDrawer&&(this.parentDrawer.pull(),this.parentDrawer=null)}},{key:\"renderHeader\",value:function(){var e=this.props,t=e.title,n=e.prefixCls,r=e.closable,c=e.headerStyle;if(!t&&!r)return null;var i=\"\".concat(n,t?\"-header\":\"-header-no-title\");return o.createElement(\"div\",{className:i,style:c},t&&o.createElement(\"div\",{className:\"\".concat(n,\"-title\")},t),r&&this.renderCloseIcon())}},{key:\"renderCloseIcon\",value:function(){var e=this.props,t=e.closable,n=e.prefixCls,r=e.onClose;return t&&o.createElement(\"button\",{onClick:r,\"aria-label\":\"Close\",className:\"\".concat(n,\"-close\")},o.createElement(de.a,{type:\"close\"}))}},{key:\"render\",value:function(){return o.createElement(xe.Consumer,null,this.renderProvider)}}])&&we(t.prototype,n),r&&we(t,r),i}(o.Component));ke.defaultProps={width:256,height:256,closable:!0,placement:\"right\",maskClosable:!0,mask:!0,level:null,keyboard:!0};t.a=Object(ve.c)({prefixCls:\"drawer\"})(ke)},function(e,t,n){var r=n(177);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},function(e,t,n){e.exports=!n(38)&&!n(63)((function(){return 7!=Object.defineProperty(n(125)(\"div\"),\"a\",{get:function(){return 7}}).a}))},function(e,t,n){var r=n(50),o=n(36).document,c=r(o)&&r(o.createElement);e.exports=function(e){return c?o.createElement(e):{}}},function(e,t,n){var r=n(43),o=n(51),c=n(179)(!1),i=n(104)(\"IE_PROTO\");e.exports=function(e,t){var n,a=o(e),l=0,u=[];for(n in a)n!=i&&r(a,n)&&u.push(n);for(;t.length>l;)r(a,n=t[l++])&&(~c(u,n)||u.push(n));return u}},function(e,t,n){var r=n(128);e.exports=Object(\"z\").propertyIsEnumerable(0)?Object:function(e){return\"String\"==r(e)?e.split(\"\"):Object(e)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){\"use strict\";var r=n(75),o=n(48),c=n(130),i=n(49),a=n(109),l=n(186),u=n(111),s=n(189),f=n(52)(\"iterator\"),p=!([].keys&&\"next\"in[].keys()),h=\"keys\",d=\"values\",v=function(){return this};e.exports=function(e,t,n,m,y,b,g){l(n,t,m);var w,z,O,C=function(e){if(!p&&e in x)return x[e];switch(e){case h:case d:return function(){return new n(this,e)}}return function(){return new n(this,e)}},M=t+\" Iterator\",S=y==d,_=!1,x=e.prototype,k=x[f]||x[\"@@iterator\"]||y&&x[y],H=k||C(y),E=y?S?C(\"entries\"):H:void 0,P=\"Array\"==t&&x.entries||k;if(P&&(O=s(P.call(new e)))!==Object.prototype&&O.next&&(u(O,M,!0),r||\"function\"==typeof O[f]||i(O,f,v)),S&&k&&k.name!==d&&(_=!0,H=function(){return k.call(this)}),r&&!g||!p&&!_&&x[f]||i(x,f,H),a[t]=H,a[M]=v,y)if(w={values:S?H:C(d),keys:b?H:C(h),entries:E},g)for(z in w)z in x||c(x,z,w[z]);else o(o.P+o.F*(p||_),t,w);return w}},function(e,t,n){e.exports=n(49)},function(e,t,n){var r=n(126),o=n(106).concat(\"length\",\"prototype\");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},function(e,t,n){var r=n(77),o=n(73),c=n(51),i=n(101),a=n(43),l=n(124),u=Object.getOwnPropertyDescriptor;t.f=n(38)?u:function(e,t){if(e=c(e),t=i(t,!0),l)try{return u(e,t)}catch(n){}if(a(e,t))return o(!r.f.call(e,t),e[t])}},function(e,t,n){e.exports={default:n(215),__esModule:!0}},function(e,t){e.exports=function(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n<e.length;++n)if(e[n]===t)return n;return-1}},function(e,t){var n,r,o=e.exports={};function c(){throw new Error(\"setTimeout has not been defined\")}function i(){throw new Error(\"clearTimeout has not been defined\")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===c||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n=\"function\"===typeof setTimeout?setTimeout:c}catch(e){n=c}try{r=\"function\"===typeof clearTimeout?clearTimeout:i}catch(e){r=i}}();var l,u=[],s=!1,f=-1;function p(){s&&l&&(s=!1,l.length?u=l.concat(u):f=-1,u.length&&h())}function h(){if(!s){var e=a(p);s=!0;for(var t=u.length;t;){for(l=u,u=[];++f<t;)l&&l[f].run();f=-1,t=u.length}l=null,s=!1,function(e){if(r===clearTimeout)return clearTimeout(e);if((r===i||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(e);try{r(e)}catch(t){try{return r.call(null,e)}catch(t){return r.call(this,e)}}}(e)}}function d(e,t){this.fun=e,this.array=t}function v(){}o.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];u.push(new d(e,t)),1!==u.length||s||a(h)},d.prototype.run=function(){this.fun.apply(null,this.array)},o.title=\"browser\",o.browser=!0,o.env={},o.argv=[],o.version=\"\",o.versions={},o.on=v,o.addListener=v,o.once=v,o.off=v,o.removeListener=v,o.removeAllListeners=v,o.emit=v,o.prependListener=v,o.prependOnceListener=v,o.listeners=function(e){return[]},o.binding=function(e){throw new Error(\"process.binding is not supported\")},o.cwd=function(){return\"/\"},o.chdir=function(e){throw new Error(\"process.chdir is not supported\")},o.umask=function(){return 0}},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0}),t.warning=o,t.note=c,t.resetWarned=function(){r={}},t.call=i,t.warningOnce=a,t.noteOnce=function(e,t){i(c,e,t)},t.default=void 0;var r={};function o(e,t){0}function c(e,t){0}function i(e,t,n){t||r[n]||(e(!1,n),r[n]=!0)}function a(e,t){i(o,e,t)}var l=a;t.default=l},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0}),t.storeShape=void 0;var r,o=n(1),c=(r=o)&&r.__esModule?r:{default:r};t.storeShape=c.default.shape({subscribe:c.default.func.isRequired,setState:c.default.func.isRequired,getState:c.default.func.isRequired})},function(e,t,n){var r=n(114)(n(44),\"Map\");e.exports=r},function(e,t,n){(function(t){var n=\"object\"==typeof t&&t&&t.Object===Object&&t;e.exports=n}).call(this,n(78))},function(e,t,n){var r=n(250),o=n(257),c=n(259),i=n(260),a=n(261);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}l.prototype.clear=r,l.prototype.delete=o,l.prototype.get=c,l.prototype.has=i,l.prototype.set=a,e.exports=l},function(e,t,n){var r=n(117),o=n(81);e.exports=function(e,t,n){(void 0!==n&&!o(e[t],n)||void 0===n&&!(t in e))&&r(e,t,n)}},function(e,t,n){var r=n(114),o=function(){try{var e=r(Object,\"defineProperty\");return e({},\"\",{}),e}catch(t){}}();e.exports=o},function(e,t,n){var r=n(272)(Object.getPrototypeOf,Object);e.exports=r},function(e,t){var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===(\"function\"==typeof t&&t.prototype||n)}},function(e,t,n){var r=n(273),o=n(53),c=Object.prototype,i=c.hasOwnProperty,a=c.propertyIsEnumerable,l=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,\"callee\")&&!a.call(e,\"callee\")};e.exports=l},function(e,t){e.exports=function(e){return\"number\"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},function(e,t,n){(function(e){var r=n(44),o=n(275),c=t&&!t.nodeType&&t,i=c&&\"object\"==typeof e&&e&&!e.nodeType&&e,a=i&&i.exports===c?r.Buffer:void 0,l=(a?a.isBuffer:void 0)||o;e.exports=l}).call(this,n(72)(e))},function(e,t,n){var r=n(277),o=n(278),c=n(279),i=c&&c.isTypedArray,a=i?o(i):r;e.exports=a},function(e,t){e.exports=function(e,t){if((\"constructor\"!==t||\"function\"!==typeof e[t])&&\"__proto__\"!=t)return e[t]}},function(e,t,n){var r=n(283),o=n(285),c=n(118);e.exports=function(e){return c(e)?r(e,!0):o(e)}},function(e,t){var n=/^(?:0|[1-9]\\d*)$/;e.exports=function(e,t){var r=typeof e;return!!(t=null==t?9007199254740991:t)&&(\"number\"==r||\"symbol\"!=r&&n.test(e))&&e>-1&&e%1==0&&e<t}},function(e,t){e.exports=function(e){return e}},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function a(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function u(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function s(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function f(e){return(f=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var p=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},h=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var d=p(n(0)),v=p(n(1)),m=n(28),y=h(n(8)),b=h(n(298)),g=h(n(299)),w=h(n(154)),z=h(n(313)),O=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&l(e,t)}(v,e);var t,n,r,p,h=(t=v,function(){var e,n=f(t);if(s()){var r=f(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return u(this,e)});function v(){var e;return i(this,v),(e=h.apply(this,arguments)).handleRowHover=function(t,n){e.props.store.setState({currentHoverKey:t?n:null})},e.renderRows=function(t,n){for(var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=e.context.table,c=o.columnManager,i=o.components,a=o.props,l=a.prefixCls,u=a.childrenColumnName,s=a.rowClassName,f=a.rowRef,p=a.onRowClick,h=a.onRowDoubleClick,v=a.onRowContextMenu,m=a.onRowMouseEnter,y=a.onRowMouseLeave,b=a.onRow,g=e.props,O=g.getRowKey,C=g.fixed,M=g.expander,S=g.isAnyColumnsFixed,_=[],x=function(o){var a=t[o],g=O(a,o),x=\"string\"===typeof s?s:s(a,o,n),k={};c.isAnyColumnsFixed()&&(k.onHover=e.handleRowHover);var H=void 0;H=\"left\"===C?c.leftLeafColumns():\"right\"===C?c.rightLeafColumns():e.getColumns(c.leafColumns());var E=\"\".concat(l,\"-row\"),P=d.createElement(z.default,Object.assign({},M.props,{fixed:C,index:o,prefixCls:E,record:a,key:g,rowKey:g,onRowClick:p,needIndentSpaced:M.needIndentSpaced,onExpandedChange:M.handleExpandChange}),(function(e){return d.createElement(w.default,Object.assign({fixed:C,indent:n,className:x,record:a,index:o,prefixCls:E,childrenColumnName:u,columns:H,onRow:b,onRowDoubleClick:h,onRowContextMenu:v,onRowMouseEnter:m,onRowMouseLeave:y},k,{rowKey:g,ancestorKeys:r,ref:f(a,o,n),components:i,isAnyColumnsFixed:S},e))}));_.push(P),M.renderRows(e.renderRows,_,a,o,n,C,g,r)},k=0;k<t.length;k+=1)x(k);return _},e}return n=v,(r=[{key:\"getColumns\",value:function(e){var t=this.props,n=t.columns,r=void 0===n?[]:n,i=t.fixed,a=this.context.table.props.prefixCls;return(e||r).map((function(e){return function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){c(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},e,{className:e.fixed&&!i?y.default(\"\".concat(a,\"-fixed-columns-in-body\"),e.className):e.className})}))}},{key:\"render\",value:function(){var e=this.context.table,t=e.components,n=e.props,r=n.prefixCls,o=n.scroll,c=n.data,i=n.getBodyWrapper,a=this.props,l=a.expander,u=a.tableClassName,s=a.hasHead,f=a.hasBody,p=a.fixed,h=a.isAnyColumnsFixed,v={};if(!p&&o.x){var m=h?\"max-content\":\"auto\";v.width=!0===o.x?m:o.x}var y,w=f?t.table:\"table\",z=t.body.wrapper;f&&(y=d.createElement(z,{className:\"\".concat(r,\"-tbody\")},this.renderRows(c,0)),i&&(y=i(y)));var O=this.getColumns();return d.createElement(w,{className:u,style:v,key:\"table\"},d.createElement(b.default,{columns:O,fixed:p}),s&&d.createElement(g.default,{expander:l,columns:O,fixed:p}),y)}}])&&a(n.prototype,r),p&&a(n,p),v}(d.Component);O.contextTypes={table:v.any},t.default=m.connect()(O)},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function c(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?c(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):c(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function u(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function s(e,t){return(s=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function f(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function p(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function h(e){return(h=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var d=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},v=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var m=d(n(0)),y=v(n(9)),b=v(n(136)),g=n(28),w=n(12),z=v(n(8)),O=v(n(301)),C=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&s(e,t)}(v,e);var t,n,r,c,d=(t=v,function(){var e,n=h(t);if(p()){var r=h(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return f(this,e)});function v(){var e;return l(this,v),(e=d.apply(this,arguments)).state={},e.onTriggerEvent=function(t,n,r){var o=e.props,c=o.record,i=o.index;return function(){r&&r();for(var e=arguments.length,o=new Array(e),a=0;a<e;a++)o[a]=arguments[a];var l=o[0];n&&n(c,i,l),t&&t.apply(void 0,o)}},e.onMouseEnter=function(){var t=e.props;(0,t.onHover)(!0,t.rowKey)},e.onMouseLeave=function(){var t=e.props;(0,t.onHover)(!1,t.rowKey)},e}return n=v,c=[{key:\"getDerivedStateFromProps\",value:function(e,t){return t.visible||!t.visible&&e.visible?{shouldRender:!0,visible:e.visible}:{visible:e.visible}}}],(r=[{key:\"componentDidMount\",value:function(){this.state.shouldRender&&this.saveRowRef()}},{key:\"shouldComponentUpdate\",value:function(e){return!(!this.props.visible&&!e.visible)}},{key:\"componentDidUpdate\",value:function(){this.state.shouldRender&&!this.rowRef&&this.saveRowRef()}},{key:\"setExpandedRowHeight\",value:function(){var e=this.props,t=e.store,n=e.rowKey,r=t.getState().expandedRowsHeight;r=i({},r,a({},n,this.rowRef.getBoundingClientRect().height)),t.setState({expandedRowsHeight:r})}},{key:\"setRowHeight\",value:function(){var e=this.props,t=e.store,n=e.rowKey,r=t.getState().fixedColumnsBodyRowsHeight,o=this.rowRef.getBoundingClientRect().height;t.setState({fixedColumnsBodyRowsHeight:i({},r,a({},n,o))})}},{key:\"getStyle\",value:function(){var e=this.props,t=e.height,n=e.visible;return t&&t!==this.style.height&&(this.style=i({},this.style,{height:t})),n||this.style.display||(this.style=i({},this.style,{display:\"none\"})),this.style}},{key:\"saveRowRef\",value:function(){this.rowRef=y.default.findDOMNode(this);var e=this.props,t=e.isAnyColumnsFixed,n=e.fixed,r=e.expandedRow,o=e.ancestorKeys;t&&this.rowRef&&(!n&&r&&this.setExpandedRowHeight(),!n&&o.length>=0&&this.setRowHeight())}},{key:\"render\",value:function(){if(!this.state.shouldRender)return null;var e=this.props,t=e.prefixCls,n=e.columns,r=e.record,c=e.rowKey,a=e.index,l=e.onRow,u=e.indent,s=e.indentSize,f=e.hovered,p=e.height,h=e.visible,d=e.components,v=e.hasExpandIcon,y=e.renderExpandIcon,g=e.renderExpandIconCell,w=e.onRowClick,C=e.onRowDoubleClick,M=e.onRowMouseEnter,S=e.onRowMouseLeave,_=e.onRowContextMenu,x=d.body.row,k=d.body.cell,H=this.props.className;f&&(H+=\" \".concat(t,\"-hover\"));var E=[];g(E);for(var P=0;P<n.length;P+=1){var V=n[P];b.default(void 0===V.onCellClick,\"column[onCellClick] is deprecated, please use column[onCell] instead.\"),E.push(m.createElement(O.default,{prefixCls:t,record:r,indentSize:s,indent:u,index:a,column:V,key:V.key||V.dataIndex,expandIcon:v(P)&&y(),component:k}))}var T=l(r,a)||{},L=T.className,j=T.style,N=o(T,[\"className\",\"style\"]),D={height:p};h||(D.display=\"none\"),D=i({},D,{},j);var R=z.default(t,H,\"\".concat(t,\"-level-\").concat(u),L);return m.createElement(x,Object.assign({},N,{onClick:this.onTriggerEvent(N.onClick,w),onDoubleClick:this.onTriggerEvent(N.onDoubleClick,C),onMouseEnter:this.onTriggerEvent(N.onMouseEnter,M,this.onMouseEnter),onMouseLeave:this.onTriggerEvent(N.onMouseLeave,S,this.onMouseLeave),onContextMenu:this.onTriggerEvent(N.onContextMenu,_),className:R,style:D,\"data-row-key\":c}),E)}}])&&u(n.prototype,r),c&&u(n,c),v}(m.Component);function M(e,t){var n=e.expandedRowsHeight,r=e.fixedColumnsBodyRowsHeight,o=t.fixed,c=t.rowKey;return o?n[c]?n[c]:r[c]?r[c]:null:null}C.defaultProps={onRow:function(){},onHover:function(){},hasExpandIcon:function(){},renderExpandIcon:function(){},renderExpandIconCell:function(){}},w.polyfill(C),t.default=g.connect((function(e,t){var n=e.currentHoverKey,r=e.expandedRowKeys,o=void 0===r?[]:r,c=t.rowKey,i=t.ancestorKeys;return{visible:0===i.length||i.every((function(e){return o.includes(e)})),hovered:n===c,height:M(e,t)}}))(C)},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});t.default=function(){return null}},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function c(e,t){return(c=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function i(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function a(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function l(e){return(l=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var u=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t};Object.defineProperty(t,\"__esModule\",{value:!0});var s=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&c(e,t)}(r,e);var t,n=(t=r,function(){var e,n=l(t);if(a()){var r=l(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return i(this,e)});function r(){return o(this,r),n.apply(this,arguments)}return r}(u(n(0)).Component);t.default=s,s.isTableColumnGroup=!0},function(e,t){e.exports={isFunction:function(e){return\"function\"===typeof e},isArray:function(e){return\"[object Array]\"===Object.prototype.toString.apply(e)},each:function(e,t){for(var n=0,r=e.length;n<r&&!1!==t(e[n],n);n++);}}},function(e,t,n){\"use strict\";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var o=r(n(219));t.generate=o.default;var c={red:\"#F5222D\",volcano:\"#FA541C\",orange:\"#FA8C16\",gold:\"#FAAD14\",yellow:\"#FADB14\",lime:\"#A0D911\",green:\"#52C41A\",cyan:\"#13C2C2\",blue:\"#1890FF\",geekblue:\"#2F54EB\",purple:\"#722ED1\",magenta:\"#EB2F96\",grey:\"#666666\"};t.presetPrimaryColors=c;var i={};t.presetPalettes=i,Object.keys(c).forEach((function(e){i[e]=o.default(c[e]),i[e].primary=i[e][5]}));var a=i.red;t.red=a;var l=i.volcano;t.volcano=l;var u=i.gold;t.gold=u;var s=i.orange;t.orange=s;var f=i.yellow;t.yellow=f;var p=i.lime;t.lime=p;var h=i.green;t.green=h;var d=i.cyan;t.cyan=d;var v=i.blue;t.blue=v;var m=i.geekblue;t.geekblue=m;var y=i.purple;t.purple=y;var b=i.magenta;t.magenta=b;var g=i.grey;t.grey=g},function(e,t,n){var r=n(324);e.exports=function(e,t,n){for(n=n||document,e={parentNode:e};(e=e.parentNode)&&e!==n;)if(r(e,t))return e}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return Jt}));var r=n(0),o=n.n(r),c=n(3),i=n.n(c),a=n(20),l=n(59);function u(){return(u=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var s=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},f=function(e){return r.createElement(l.a,null,(function(t){var n,o,c,a=t.getPrefixCls,l=e.prefixCls,f=e.className,p=e.hoverable,h=void 0===p||p,d=s(e,[\"prefixCls\",\"className\",\"hoverable\"]),v=a(\"card\",l),m=i()(\"\".concat(v,\"-grid\"),f,(n={},o=\"\".concat(v,\"-grid-hoverable\"),c=h,o in n?Object.defineProperty(n,o,{value:c,enumerable:!0,configurable:!0,writable:!0}):n[o]=c,n));return r.createElement(\"div\",u({},d,{className:m}))}))};function p(){return(p=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var h=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},d=function(e){return r.createElement(l.a,null,(function(t){var n=t.getPrefixCls,o=e.prefixCls,c=e.className,a=e.avatar,l=e.title,u=e.description,s=h(e,[\"prefixCls\",\"className\",\"avatar\",\"title\",\"description\"]),f=n(\"card\",o),d=i()(\"\".concat(f,\"-meta\"),c),v=a?r.createElement(\"div\",{className:\"\".concat(f,\"-meta-avatar\")},a):null,m=l?r.createElement(\"div\",{className:\"\".concat(f,\"-meta-title\")},l):null,y=u?r.createElement(\"div\",{className:\"\".concat(f,\"-meta-description\")},u):null,b=m||y?r.createElement(\"div\",{className:\"\".concat(f,\"-meta-detail\")},m,y):null;return r.createElement(\"div\",p({},s,{className:d}),v,b)}))},v=n(9),m=n(5),y=n.n(m),b=n(7),g=n.n(b),w=n(26),z=n.n(w),O=n(10),C=n.n(O),M=n(14),S=n.n(M),_=n(6),x=n.n(_),k=n(11),H=n.n(k),E=n(1),P=n.n(E),V=n(8),T=n.n(V),L=n(19),j=n.n(L),N=n(12),D=37,R=38,A=39,I=40;function F(e){var t=[];return o.a.Children.forEach(e,(function(e){e&&t.push(e)})),t}function W(e,t){for(var n=F(e),r=0;r<n.length;r++)if(n[r].key===t)return r;return-1}function U(e,t){e.transform=t,e.webkitTransform=t,e.mozTransform=t}function K(e){return(\"transform\"in e||\"webkitTransform\"in e||\"MozTransform\"in e)&&window.atob}function B(e){return\"left\"===e||\"right\"===e}function Y(e,t){return+window.getComputedStyle(e).getPropertyValue(t).replace(\"px\",\"\")}function q(e){return Object.keys(e).reduce((function(t,n){return\"aria-\"!==n.substr(0,5)&&\"data-\"!==n.substr(0,5)&&\"role\"!==n||(t[n]=e[n]),t}),{})}function G(e,t){return+e.getPropertyValue(t).replace(\"px\",\"\")}function $(e,t,n,r,o){var c=Y(o,\"padding-\"+e);if(!r||!r.parentNode)return c;var i=r.parentNode.childNodes;return Array.prototype.some.call(i,(function(o){var i=window.getComputedStyle(o);return o!==r?(c+=G(i,\"margin-\"+e),c+=o[t],c+=G(i,\"margin-\"+n),\"content-box\"===i.boxSizing&&(c+=G(i,\"border-\"+e+\"-width\")+G(i,\"border-\"+n+\"-width\")),!1):(c+=G(i,\"margin-\"+e),!0)})),c}var Q=n(18),X=n(29),Z=n.n(X),J=Z()({}),ee=J.Provider,te=J.Consumer,ne={width:0,height:0,overflow:\"hidden\",position:\"absolute\"},re=function(e){function t(){var e,n,r,o;C()(this,t);for(var c=arguments.length,i=Array(c),a=0;a<c;a++)i[a]=arguments[a];return n=r=x()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.onKeyDown=function(e){var t=e.target,n=e.which,o=e.shiftKey,c=r.props,i=c.nextElement,a=c.prevElement;n===Q.a.TAB&&document.activeElement===t&&(!o&&i&&i.focus(),o&&a&&a.focus())},o=n,x()(r,o)}return H()(t,e),S()(t,[{key:\"render\",value:function(){var e=this.props.setRef;return o.a.createElement(\"div\",{tabIndex:0,ref:e,style:ne,onKeyDown:this.onKeyDown,role:\"presentation\"})}}]),t}(o.a.Component);re.propTypes={setRef:P.a.func,prevElement:P.a.object,nextElement:P.a.object};var oe=re,ce=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"render\",value:function(){var e,t=this.props,n=t.id,r=t.className,c=t.destroyInactiveTabPane,i=t.active,a=t.forceRender,l=t.rootPrefixCls,u=t.style,s=t.children,f=t.placeholder,p=z()(t,[\"id\",\"className\",\"destroyInactiveTabPane\",\"active\",\"forceRender\",\"rootPrefixCls\",\"style\",\"children\",\"placeholder\"]);this._isActived=this._isActived||i;var h=l+\"-tabpane\",d=T()((e={},g()(e,h,1),g()(e,h+\"-inactive\",!i),g()(e,h+\"-active\",i),g()(e,r,r),e)),v=(c?i:this._isActived)||a;return o.a.createElement(te,null,(function(e){var t=e.sentinelStart,r=e.sentinelEnd,c=e.setPanelSentinelStart,a=e.setPanelSentinelEnd,l=void 0,h=void 0;return i&&v&&(l=o.a.createElement(oe,{setRef:c,prevElement:t}),h=o.a.createElement(oe,{setRef:a,nextElement:r})),o.a.createElement(\"div\",y()({style:u,role:\"tabpanel\",\"aria-hidden\":i?\"false\":\"true\",className:d,id:n},q(p)),l,v?s:f,h)}))}}]),t}(o.a.Component),ie=ce;function ae(e){var t=void 0;return o.a.Children.forEach(e.children,(function(e){!e||t||e.props.disabled||(t=e.key)})),t}ce.propTypes={className:P.a.string,active:P.a.bool,style:P.a.any,destroyInactiveTabPane:P.a.bool,forceRender:P.a.bool,placeholder:P.a.node,rootPrefixCls:P.a.string,children:P.a.node,id:P.a.string},ce.defaultProps={placeholder:null};var le=function(e){function t(e){C()(this,t);var n=x()(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));ue.call(n);var r=void 0;return r=\"activeKey\"in e?e.activeKey:\"defaultActiveKey\"in e?e.defaultActiveKey:ae(e),n.state={activeKey:r},n}return H()(t,e),S()(t,[{key:\"componentWillUnmount\",value:function(){this.destroy=!0,j.a.cancel(this.sentinelId)}},{key:\"updateSentinelContext\",value:function(){var e=this;this.destroy||(j.a.cancel(this.sentinelId),this.sentinelId=j()((function(){e.destroy||e.forceUpdate()})))}},{key:\"render\",value:function(){var e,t=this.props,n=t.prefixCls,r=t.navWrapper,c=t.tabBarPosition,i=t.className,a=t.renderTabContent,l=t.renderTabBar,u=t.destroyInactiveTabPane,s=t.direction,f=z()(t,[\"prefixCls\",\"navWrapper\",\"tabBarPosition\",\"className\",\"renderTabContent\",\"renderTabBar\",\"destroyInactiveTabPane\",\"direction\"]),p=T()((e={},g()(e,n,1),g()(e,n+\"-\"+c,1),g()(e,i,!!i),g()(e,n+\"-rtl\",\"rtl\"===s),e));this.tabBar=l();var h=o.a.cloneElement(this.tabBar,{prefixCls:n,navWrapper:r,key:\"tabBar\",onKeyDown:this.onNavKeyDown,tabBarPosition:c,onTabClick:this.onTabClick,panels:t.children,activeKey:this.state.activeKey,direction:this.props.direction}),d=o.a.cloneElement(a(),{prefixCls:n,tabBarPosition:c,activeKey:this.state.activeKey,destroyInactiveTabPane:u,children:t.children,onChange:this.setActiveKey,key:\"tabContent\",direction:this.props.direction}),v=o.a.createElement(oe,{key:\"sentinelStart\",setRef:this.setSentinelStart,nextElement:this.panelSentinelStart}),m=o.a.createElement(oe,{key:\"sentinelEnd\",setRef:this.setSentinelEnd,prevElement:this.panelSentinelEnd}),b=[];return\"bottom\"===c?b.push(v,d,m,h):b.push(h,v,d,m),o.a.createElement(ee,{value:{sentinelStart:this.sentinelStart,sentinelEnd:this.sentinelEnd,setPanelSentinelStart:this.setPanelSentinelStart,setPanelSentinelEnd:this.setPanelSentinelEnd}},o.a.createElement(\"div\",y()({className:p,style:t.style},q(f),{onScroll:this.onScroll}),b))}}],[{key:\"getDerivedStateFromProps\",value:function(e,t){var n={};return\"activeKey\"in e?n.activeKey=e.activeKey:function(e,t){return o.a.Children.map(e.children,(function(e){return e&&e.key})).indexOf(t)>=0}(e,t.activeKey)||(n.activeKey=ae(e)),Object.keys(n).length>0?n:null}}]),t}(o.a.Component),ue=function(){var e=this;this.onTabClick=function(t,n){e.tabBar.props.onTabClick&&e.tabBar.props.onTabClick(t,n),e.setActiveKey(t)},this.onNavKeyDown=function(t){var n=t.keyCode;if(n===A||n===I){t.preventDefault();var r=e.getNextActiveKey(!0);e.onTabClick(r)}else if(n===D||n===R){t.preventDefault();var o=e.getNextActiveKey(!1);e.onTabClick(o)}},this.onScroll=function(e){var t=e.target;t===e.currentTarget&&t.scrollLeft>0&&(t.scrollLeft=0)},this.setSentinelStart=function(t){e.sentinelStart=t},this.setSentinelEnd=function(t){e.sentinelEnd=t},this.setPanelSentinelStart=function(t){t!==e.panelSentinelStart&&e.updateSentinelContext(),e.panelSentinelStart=t},this.setPanelSentinelEnd=function(t){t!==e.panelSentinelEnd&&e.updateSentinelContext(),e.panelSentinelEnd=t},this.setActiveKey=function(t){e.state.activeKey!==t&&(\"activeKey\"in e.props||e.setState({activeKey:t}),e.props.onChange(t))},this.getNextActiveKey=function(t){var n=e.state.activeKey,r=[];o.a.Children.forEach(e.props.children,(function(e){e&&!e.props.disabled&&(t?r.push(e):r.unshift(e))}));var c=r.length,i=c&&r[0].key;return r.forEach((function(e,t){e.key===n&&(i=t===c-1?r[0].key:r[t+1].key)})),i}};le.propTypes={destroyInactiveTabPane:P.a.bool,renderTabBar:P.a.func.isRequired,renderTabContent:P.a.func.isRequired,navWrapper:P.a.func,onChange:P.a.func,children:P.a.node,prefixCls:P.a.string,className:P.a.string,tabBarPosition:P.a.string,style:P.a.object,activeKey:P.a.string,defaultActiveKey:P.a.string,direction:P.a.string},le.defaultProps={prefixCls:\"rc-tabs\",destroyInactiveTabPane:!1,onChange:function(){},navWrapper:function(e){return e},tabBarPosition:\"top\",children:null,style:{},direction:\"ltr\"},le.TabPane=ie,Object(N.polyfill)(le);var se=le,fe=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"getTabPanes\",value:function(){var e=this.props,t=e.activeKey,n=e.children,r=[];return o.a.Children.forEach(n,(function(n){if(n){var c=n.key,i=t===c;r.push(o.a.cloneElement(n,{active:i,destroyInactiveTabPane:e.destroyInactiveTabPane,rootPrefixCls:e.prefixCls}))}})),r}},{key:\"render\",value:function(){var e,t,n=this.props,r=n.prefixCls,c=n.children,i=n.activeKey,a=n.className,l=n.tabBarPosition,u=n.animated,s=n.animatedWithMargin,f=n.direction,p=n.style,h=T()((e={},g()(e,r+\"-content\",!0),g()(e,u?r+\"-content-animated\":r+\"-content-no-animated\",!0),e),a);if(u){var d=W(c,i);if(-1!==d){var v=s?function(e,t){var n=B(t)?\"marginTop\":\"marginLeft\";return g()({},n,100*-e+\"%\")}(d,l):{transform:t=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:\"ltr\",r=B(t)?\"translateY\":\"translateX\";return B(t)||\"rtl\"!==n?r+\"(\"+100*-e+\"%) translateZ(0)\":r+\"(\"+100*e+\"%) translateZ(0)\"}(d,l,f),WebkitTransform:t,MozTransform:t};p=y()({},p,v)}else p=y()({},p,{display:\"none\"})}return o.a.createElement(\"div\",{className:h,style:p},this.getTabPanes())}}]),t}(o.a.Component),pe=fe;fe.propTypes={animated:P.a.bool,animatedWithMargin:P.a.bool,prefixCls:P.a.string,children:P.a.node,activeKey:P.a.string,style:P.a.any,tabBarPosition:P.a.string,className:P.a.string,destroyInactiveTabPane:P.a.bool,direction:P.a.string},fe.defaultProps={animated:!0};var he=se;function de(e,t){var n=e.props,r=n.styles,o=n.panels,c=n.activeKey,i=n.direction,a=e.props.getRef(\"root\"),l=e.props.getRef(\"nav\")||a,u=e.props.getRef(\"inkBar\"),s=e.props.getRef(\"activeTab\"),f=u.style,p=e.props.tabBarPosition,h=W(o,c);if(t&&(f.display=\"none\"),s){var d=s,v=K(f);if(U(f,\"\"),f.width=\"\",f.height=\"\",f.left=\"\",f.top=\"\",f.bottom=\"\",f.right=\"\",\"top\"===p||\"bottom\"===p){var m=function(e,t){return $(\"left\",\"offsetWidth\",\"right\",e,t)}(d,l),y=d.offsetWidth;y===a.offsetWidth?y=0:r.inkBar&&void 0!==r.inkBar.width&&(y=parseFloat(r.inkBar.width,10))&&(m+=(d.offsetWidth-y)/2),\"rtl\"===i&&(m=Y(d,\"margin-left\")-m),v?U(f,\"translate3d(\"+m+\"px,0,0)\"):f.left=m+\"px\",f.width=y+\"px\"}else{var b=function(e,t){return $(\"top\",\"offsetHeight\",\"bottom\",e,t)}(d,l),g=d.offsetHeight;r.inkBar&&void 0!==r.inkBar.height&&(g=parseFloat(r.inkBar.height,10))&&(b+=(d.offsetHeight-g)/2),v?(U(f,\"translate3d(0,\"+b+\"px,0)\"),f.top=\"0\"):f.top=b+\"px\",f.height=g+\"px\"}}f.display=-1!==h?\"block\":\"none\"}var ve=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"componentDidMount\",value:function(){var e=this;this.timeout=setTimeout((function(){de(e,!0)}),0)}},{key:\"componentDidUpdate\",value:function(){de(this)}},{key:\"componentWillUnmount\",value:function(){clearTimeout(this.timeout)}},{key:\"render\",value:function(){var e,t=this.props,n=t.prefixCls,r=t.styles,c=t.inkBarAnimated,i=n+\"-ink-bar\",a=T()((e={},g()(e,i,!0),g()(e,c?i+\"-animated\":i+\"-no-animated\",!0),e));return o.a.createElement(\"div\",{style:r.inkBar,className:a,key:\"inkBar\",ref:this.props.saveRef(\"inkBar\")})}}]),t}(o.a.Component),me=ve;ve.propTypes={prefixCls:P.a.string,styles:P.a.object,inkBarAnimated:P.a.bool,saveRef:P.a.func,direction:P.a.string},ve.defaultProps={prefixCls:\"\",inkBarAnimated:!0,styles:{},saveRef:function(){}};var ye=n(56),be=n.n(ye),ge=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"render\",value:function(){var e=this,t=this.props,n=t.panels,r=t.activeKey,c=t.prefixCls,i=t.tabBarGutter,a=t.saveRef,l=t.tabBarPosition,u=t.renderTabBarNode,s=t.direction,f=[];return o.a.Children.forEach(n,(function(t,p){if(t){var h=t.key,d=r===h?c+\"-tab-active\":\"\";d+=\" \"+c+\"-tab\";var v={};t.props.disabled?d+=\" \"+c+\"-tab-disabled\":v={onClick:e.props.onTabClick.bind(e,h)};var m={};r===h&&(m.ref=a(\"activeTab\"));var b=i&&p===n.length-1?0:i,w=\"rtl\"===s?\"marginLeft\":\"marginRight\",z=g()({},B(l)?\"marginBottom\":w,b);be()(\"tab\"in t.props,\"There must be `tab` property on children of Tabs.\");var O=o.a.createElement(\"div\",y()({role:\"tab\",\"aria-disabled\":t.props.disabled?\"true\":\"false\",\"aria-selected\":r===h?\"true\":\"false\"},v,{className:d,key:h,style:z},m),t.props.tab);u&&(O=u(O)),f.push(O)}})),o.a.createElement(\"div\",{ref:a(\"navTabsContainer\")},f)}}]),t}(o.a.Component),we=ge;ge.propTypes={activeKey:P.a.string,panels:P.a.node,prefixCls:P.a.string,tabBarGutter:P.a.number,onTabClick:P.a.func,saveRef:P.a.func,renderTabBarNode:P.a.func,tabBarPosition:P.a.string,direction:P.a.string},ge.defaultProps={panels:[],prefixCls:[],tabBarGutter:null,onTabClick:function(){},saveRef:function(){}};var ze=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"render\",value:function(){var e=this.props,t=e.prefixCls,n=e.onKeyDown,c=e.className,i=e.extraContent,a=e.style,l=e.tabBarPosition,u=e.children,s=z()(e,[\"prefixCls\",\"onKeyDown\",\"className\",\"extraContent\",\"style\",\"tabBarPosition\",\"children\"]),f=T()(t+\"-bar\",g()({},c,!!c)),p=\"top\"===l||\"bottom\"===l,h=p?{float:\"right\"}:{},d=i&&i.props?i.props.style:{},v=u;return i&&(v=[Object(r.cloneElement)(i,{key:\"extra\",style:y()({},h,d)}),Object(r.cloneElement)(u,{key:\"content\"})],v=p?v:v.reverse()),o.a.createElement(\"div\",y()({role:\"tablist\",className:f,tabIndex:\"0\",ref:this.props.saveRef(\"root\"),onKeyDown:n,style:a},q(s)),v)}}]),t}(o.a.Component),Oe=ze;ze.propTypes={prefixCls:P.a.string,className:P.a.string,style:P.a.object,tabBarPosition:P.a.oneOf([\"left\",\"right\",\"top\",\"bottom\"]),children:P.a.node,extraContent:P.a.node,onKeyDown:P.a.func,saveRef:P.a.func},ze.defaultProps={prefixCls:\"\",className:\"\",style:{},tabBarPosition:\"top\",extraContent:null,children:null,onKeyDown:function(){},saveRef:function(){}};var Ce=n(93),Me=n.n(Ce),Se=n(95),_e=function(e){function t(e){C()(this,t);var n=x()(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return n.prevTransitionEnd=function(e){if(\"opacity\"===e.propertyName){var t=n.props.getRef(\"container\");n.scrollToActiveTab({target:t,currentTarget:t})}},n.scrollToActiveTab=function(e){var t=n.props.getRef(\"activeTab\"),r=n.props.getRef(\"navWrap\");if((!e||e.target===e.currentTarget)&&t){var o=n.isNextPrevShown()&&n.lastNextPrevShown;if(n.lastNextPrevShown=n.isNextPrevShown(),o){var c=n.getScrollWH(t),i=n.getOffsetWH(r),a=n.offset,l=n.getOffsetLT(r),u=n.getOffsetLT(t);l>u?(a+=l-u,n.setOffset(a)):l+i<u+c&&(a-=u+c-(l+i),n.setOffset(a))}}},n.prev=function(e){n.props.onPrevClick(e);var t=n.props.getRef(\"navWrap\"),r=n.getOffsetWH(t),o=n.offset;n.setOffset(o+r)},n.next=function(e){n.props.onNextClick(e);var t=n.props.getRef(\"navWrap\"),r=n.getOffsetWH(t),o=n.offset;n.setOffset(o-r)},n.offset=0,n.state={next:!1,prev:!1},n}return H()(t,e),S()(t,[{key:\"componentDidMount\",value:function(){var e=this;this.componentDidUpdate(),this.debouncedResize=Me()((function(){e.setNextPrev(),e.scrollToActiveTab()}),200),this.resizeObserver=new Se.a(this.debouncedResize),this.resizeObserver.observe(this.props.getRef(\"container\"))}},{key:\"componentDidUpdate\",value:function(e){var t=this.props;if(e&&e.tabBarPosition!==t.tabBarPosition)this.setOffset(0);else{var n=this.setNextPrev();this.isNextPrevShown(this.state)!==this.isNextPrevShown(n)?this.setState({},this.scrollToActiveTab):e&&t.activeKey===e.activeKey||this.scrollToActiveTab()}}},{key:\"componentWillUnmount\",value:function(){this.resizeObserver&&this.resizeObserver.disconnect(),this.debouncedResize&&this.debouncedResize.cancel&&this.debouncedResize.cancel()}},{key:\"setNextPrev\",value:function(){var e=this.props.getRef(\"nav\"),t=this.props.getRef(\"navTabsContainer\"),n=this.getScrollWH(t||e),r=this.getOffsetWH(this.props.getRef(\"container\"))+1,o=this.getOffsetWH(this.props.getRef(\"navWrap\")),c=this.offset,i=r-n,a=this.state,l=a.next,u=a.prev;if(i>=0)l=!1,this.setOffset(0,!1),c=0;else if(i<c)l=!0;else{l=!1;var s=o-n;this.setOffset(s,!1),c=s}return u=c<0,this.setNext(l),this.setPrev(u),{next:l,prev:u}}},{key:\"getOffsetWH\",value:function(e){var t=this.props.tabBarPosition,n=\"offsetWidth\";return\"left\"!==t&&\"right\"!==t||(n=\"offsetHeight\"),e[n]}},{key:\"getScrollWH\",value:function(e){var t=this.props.tabBarPosition,n=\"scrollWidth\";return\"left\"!==t&&\"right\"!==t||(n=\"scrollHeight\"),e[n]}},{key:\"getOffsetLT\",value:function(e){var t=this.props.tabBarPosition,n=\"left\";return\"left\"!==t&&\"right\"!==t||(n=\"top\"),e.getBoundingClientRect()[n]}},{key:\"setOffset\",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=Math.min(0,e);if(this.offset!==n){this.offset=n;var r={},o=this.props.tabBarPosition,c=this.props.getRef(\"nav\").style,i=K(c);\"left\"===o||\"right\"===o?r=i?{value:\"translate3d(0,\"+n+\"px,0)\"}:{name:\"top\",value:n+\"px\"}:i?(\"rtl\"===this.props.direction&&(n=-n),r={value:\"translate3d(\"+n+\"px,0,0)\"}):r={name:\"left\",value:n+\"px\"},i?U(c,r.value):c[r.name]=r.value,t&&this.setNextPrev()}}},{key:\"setPrev\",value:function(e){this.state.prev!==e&&this.setState({prev:e})}},{key:\"setNext\",value:function(e){this.state.next!==e&&this.setState({next:e})}},{key:\"isNextPrevShown\",value:function(e){return e?e.next||e.prev:this.state.next||this.state.prev}},{key:\"render\",value:function(){var e,t,n,r,c=this.state,i=c.next,a=c.prev,l=this.props,u=l.prefixCls,s=l.scrollAnimated,f=l.navWrapper,p=l.prevIcon,h=l.nextIcon,d=a||i,v=o.a.createElement(\"span\",{onClick:a?this.prev:null,unselectable:\"unselectable\",className:T()((e={},g()(e,u+\"-tab-prev\",1),g()(e,u+\"-tab-btn-disabled\",!a),g()(e,u+\"-tab-arrow-show\",d),e)),onTransitionEnd:this.prevTransitionEnd},p||o.a.createElement(\"span\",{className:u+\"-tab-prev-icon\"})),m=o.a.createElement(\"span\",{onClick:i?this.next:null,unselectable:\"unselectable\",className:T()((t={},g()(t,u+\"-tab-next\",1),g()(t,u+\"-tab-btn-disabled\",!i),g()(t,u+\"-tab-arrow-show\",d),t))},h||o.a.createElement(\"span\",{className:u+\"-tab-next-icon\"})),y=u+\"-nav\",b=T()((n={},g()(n,y,!0),g()(n,s?y+\"-animated\":y+\"-no-animated\",!0),n));return o.a.createElement(\"div\",{className:T()((r={},g()(r,u+\"-nav-container\",1),g()(r,u+\"-nav-container-scrolling\",d),r)),key:\"container\",ref:this.props.saveRef(\"container\")},v,m,o.a.createElement(\"div\",{className:u+\"-nav-wrap\",ref:this.props.saveRef(\"navWrap\")},o.a.createElement(\"div\",{className:u+\"-nav-scroll\"},o.a.createElement(\"div\",{className:b,ref:this.props.saveRef(\"nav\")},f(this.props.children)))))}}]),t}(o.a.Component),xe=_e;_e.propTypes={activeKey:P.a.string,getRef:P.a.func.isRequired,saveRef:P.a.func.isRequired,tabBarPosition:P.a.oneOf([\"left\",\"right\",\"top\",\"bottom\"]),prefixCls:P.a.string,scrollAnimated:P.a.bool,onPrevClick:P.a.func,onNextClick:P.a.func,navWrapper:P.a.func,children:P.a.node,prevIcon:P.a.node,nextIcon:P.a.node,direction:P.a.node},_e.defaultProps={tabBarPosition:\"left\",prefixCls:\"\",scrollAnimated:!0,onPrevClick:function(){},onNextClick:function(){},navWrapper:function(e){return e}};var ke=function(e){function t(){var e,n,r,o;C()(this,t);for(var c=arguments.length,i=Array(c),a=0;a<c;a++)i[a]=arguments[a];return n=r=x()(this,(e=t.__proto__||Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.getRef=function(e){return r[e]},r.saveRef=function(e){return function(t){t&&(r[e]=t)}},o=n,x()(r,o)}return H()(t,e),S()(t,[{key:\"render\",value:function(){return this.props.children(this.saveRef,this.getRef)}}]),t}(o.a.Component),He=ke;ke.propTypes={children:P.a.func},ke.defaultProps={children:function(){return null}};var Ee=function(e){function t(){return C()(this,t),x()(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return H()(t,e),S()(t,[{key:\"render\",value:function(){var e=this.props,t=e.children,n=z()(e,[\"children\"]);return o.a.createElement(He,null,(function(e,r){return o.a.createElement(Oe,y()({saveRef:e},n),o.a.createElement(xe,y()({saveRef:e,getRef:r},n),o.a.createElement(we,y()({saveRef:e,renderTabBarNode:t},n)),o.a.createElement(me,y()({saveRef:e,getRef:r},n))))}))}}]),t}(o.a.Component),Pe=Ee;Ee.propTypes={children:P.a.func};var Ve=n(13);function Te(){return(Te=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Le(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function je(e){return(je=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ne(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function De(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Re(e,t){return(Re=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ae(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Fe(e);if(t){var o=Fe(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Ie(this,n)}}function Ie(e,t){return!t||\"object\"!==je(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Fe(e){return(Fe=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var We=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Re(e,t)}(a,e);var t,n,o,c=Ae(a);function a(){return Ne(this,a),c.apply(this,arguments)}return t=a,(n=[{key:\"render\",value:function(){var e,t,n=this.props,o=n.tabBarStyle,c=n.animated,a=n.renderTabBar,l=n.tabBarExtraContent,u=n.tabPosition,s=n.prefixCls,f=n.className,p=n.size,h=n.type,d=\"object\"===je(c)?c.inkBar:c,v=\"left\"===u||\"right\"===u,m=v?\"up\":\"left\",y=v?\"down\":\"right\",b=r.createElement(\"span\",{className:\"\".concat(s,\"-tab-prev-icon\")},r.createElement(Ve.a,{type:m,className:\"\".concat(s,\"-tab-prev-icon-target\")})),g=r.createElement(\"span\",{className:\"\".concat(s,\"-tab-next-icon\")},r.createElement(Ve.a,{type:y,className:\"\".concat(s,\"-tab-next-icon-target\")})),w=i()(\"\".concat(s,\"-\").concat(u,\"-bar\"),(Le(e={},\"\".concat(s,\"-\").concat(p,\"-bar\"),!!p),Le(e,\"\".concat(s,\"-card-bar\"),h&&h.indexOf(\"card\")>=0),e),f),z=Te(Te({},this.props),{children:null,inkBarAnimated:d,extraContent:l,style:o,prevIcon:b,nextIcon:g,className:w});return t=a?a(z,Pe):r.createElement(Pe,z),r.cloneElement(t)}}])&&De(t.prototype,n),o&&De(t,o),a}(r.Component);We.defaultProps={animated:!0,type:\"line\"};var Ue=n(16),Ke=function(e){if(\"undefined\"!==typeof window&&window.document&&window.document.documentElement){var t=Array.isArray(e)?e:[e],n=window.document.documentElement;return t.some((function(e){return e in n.style}))}return!1},Be=Ke([\"flex\",\"webkitFlex\",\"Flex\",\"msFlex\"]);function Ye(){return(Ye=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function qe(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ge(e){return(Ge=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function $e(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Qe(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Xe(e,t){return(Xe=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Ze(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=et(e);if(t){var o=et(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return Je(this,n)}}function Je(e,t){return!t||\"object\"!==Ge(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function et(e){return(et=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var tt=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},nt=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Xe(e,t)}(u,e);var t,n,o,c=Ze(u);function u(){var e;return $e(this,u),(e=c.apply(this,arguments)).removeTab=function(t,n){if(n.stopPropagation(),t){var r=e.props.onEdit;r&&r(t,\"remove\")}},e.handleChange=function(t){var n=e.props.onChange;n&&n(t)},e.createNewTab=function(t){var n=e.props.onEdit;n&&n(t,\"add\")},e.renderTabs=function(t){var n,o=t.getPrefixCls,c=e.props,l=c.prefixCls,u=c.className,s=void 0===u?\"\":u,f=c.size,p=c.type,h=void 0===p?\"line\":p,d=c.tabPosition,v=c.children,m=c.animated,y=void 0===m||m,b=c.hideAdd,g=e.props.tabBarExtraContent,w=\"object\"===Ge(y)?y.tabPane:y;\"line\"!==h&&(w=\"animated\"in e.props&&w),Object(Ue.a)(!(h.indexOf(\"card\")>=0&&(\"small\"===f||\"large\"===f)),\"Tabs\",\"`type=card|editable-card` doesn't have small or large size, it's by design.\");var z=o(\"tabs\",l),O=i()(s,(qe(n={},\"\".concat(z,\"-vertical\"),\"left\"===d||\"right\"===d),qe(n,\"\".concat(z,\"-\").concat(f),!!f),qe(n,\"\".concat(z,\"-card\"),h.indexOf(\"card\")>=0),qe(n,\"\".concat(z,\"-\").concat(h),!0),qe(n,\"\".concat(z,\"-no-animation\"),!w),n)),C=[];\"editable-card\"===h&&(C=[],r.Children.forEach(v,(function(t,n){if(!r.isValidElement(t))return t;var o=t.props.closable,c=(o=\"undefined\"===typeof o||o)?r.createElement(Ve.a,{type:\"close\",className:\"\".concat(z,\"-close-x\"),onClick:function(n){return e.removeTab(t.key,n)}}):null;C.push(r.cloneElement(t,{tab:r.createElement(\"div\",{className:o?void 0:\"\".concat(z,\"-tab-unclosable\")},t.props.tab,c),key:t.key||n}))})),b||(g=r.createElement(\"span\",null,r.createElement(Ve.a,{type:\"plus\",className:\"\".concat(z,\"-new-tab\"),onClick:e.createNewTab}),g))),g=g?r.createElement(\"div\",{className:\"\".concat(z,\"-extra-content\")},g):null;var M=tt(e.props,[]),S=i()(\"\".concat(z,\"-\").concat(d,\"-content\"),h.indexOf(\"card\")>=0&&\"\".concat(z,\"-card-content\"));return r.createElement(he,Ye({},e.props,{prefixCls:z,className:O,tabBarPosition:d,renderTabBar:function(){return r.createElement(We,Ye({},Object(a.a)(M,[\"className\"]),{tabBarExtraContent:g}))},renderTabContent:function(){return r.createElement(pe,{className:S,animated:w,animatedWithMargin:!0})},onChange:e.handleChange}),C.length>0?C:v)},e}return t=u,(n=[{key:\"componentDidMount\",value:function(){var e=\" no-flex\",t=v.findDOMNode(this);t&&!Be&&-1===t.className.indexOf(e)&&(t.className+=e)}},{key:\"render\",value:function(){return r.createElement(l.a,null,this.renderTabs)}}])&&Qe(t.prototype,n),o&&Qe(t,o),u}(r.Component);nt.TabPane=ie,nt.defaultProps={hideAdd:!1,tabPosition:\"top\"};var rt,ot=Z()({}),ct=n(27);function it(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function at(){return(at=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}if(\"undefined\"!==typeof window){window.matchMedia||(window.matchMedia=function(e){return{media:e,matches:!1,addListener:function(){},removeListener:function(){}}}),rt=n(325)}var lt=[\"xxl\",\"xl\",\"lg\",\"md\",\"sm\",\"xs\"],ut={xs:\"(max-width: 575px)\",sm:\"(min-width: 576px)\",md:\"(min-width: 768px)\",lg:\"(min-width: 992px)\",xl:\"(min-width: 1200px)\",xxl:\"(min-width: 1600px)\"},st=[],ft=-1,pt={},ht={dispatch:function(e){return pt=e,!(st.length<1)&&(st.forEach((function(e){e.func(pt)})),!0)},subscribe:function(e){0===st.length&&this.register();var t=(++ft).toString();return st.push({token:t,func:e}),e(pt),t},unsubscribe:function(e){0===(st=st.filter((function(t){return t.token!==e}))).length&&this.unregister()},unregister:function(){Object.keys(ut).map((function(e){return rt.unregister(ut[e])}))},register:function(){var e=this;Object.keys(ut).map((function(t){return rt.register(ut[t],{match:function(){var n=at(at({},pt),it({},t,!0));e.dispatch(n)},unmatch:function(){var n=at(at({},pt),it({},t,!1));e.dispatch(n)},destroy:function(){}})}))}};function dt(e){return(dt=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function vt(){return(vt=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function mt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function yt(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function bt(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function gt(e,t){return(gt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function wt(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Ot(e);if(t){var o=Ot(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return zt(this,n)}}function zt(e,t){return!t||\"object\"!==dt(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Ot(e){return(Ot=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Ct=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},Mt=Object(ct.a)(\"top\",\"middle\",\"bottom\",\"stretch\"),St=Object(ct.a)(\"start\",\"end\",\"center\",\"space-around\",\"space-between\"),_t=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&gt(e,t)}(a,e);var t,n,o,c=wt(a);function a(){var e;return yt(this,a),(e=c.apply(this,arguments)).state={screens:{}},e.renderRow=function(t){var n,o=t.getPrefixCls,c=e.props,a=c.prefixCls,l=c.type,u=c.justify,s=c.align,f=c.className,p=c.style,h=c.children,d=Ct(c,[\"prefixCls\",\"type\",\"justify\",\"align\",\"className\",\"style\",\"children\"]),v=o(\"row\",a),m=e.getGutter(),y=i()((mt(n={},v,!l),mt(n,\"\".concat(v,\"-\").concat(l),l),mt(n,\"\".concat(v,\"-\").concat(l,\"-\").concat(u),l&&u),mt(n,\"\".concat(v,\"-\").concat(l,\"-\").concat(s),l&&s),n),f),b=vt(vt(vt({},m[0]>0?{marginLeft:m[0]/-2,marginRight:m[0]/-2}:{}),m[1]>0?{marginTop:m[1]/-2,marginBottom:m[1]/-2}:{}),p),g=vt({},d);return delete g.gutter,r.createElement(ot.Provider,{value:{gutter:m}},r.createElement(\"div\",vt({},g,{className:y,style:b}),h))},e}return t=a,(n=[{key:\"componentDidMount\",value:function(){var e=this;this.token=ht.subscribe((function(t){var n=e.props.gutter;(\"object\"===dt(n)||Array.isArray(n)&&(\"object\"===dt(n[0])||\"object\"===dt(n[1])))&&e.setState({screens:t})}))}},{key:\"componentWillUnmount\",value:function(){ht.unsubscribe(this.token)}},{key:\"getGutter\",value:function(){var e=[0,0],t=this.props.gutter,n=this.state.screens;return(Array.isArray(t)?t:[t,0]).forEach((function(t,r){if(\"object\"===dt(t))for(var o=0;o<lt.length;o++){var c=lt[o];if(n[c]&&void 0!==t[c]){e[r]=t[c];break}}else e[r]=t||0})),e}},{key:\"render\",value:function(){return r.createElement(l.a,null,this.renderRow)}}])&&bt(t.prototype,n),o&&bt(t,o),a}(r.Component);_t.defaultProps={gutter:0},_t.propTypes={type:E.oneOf([\"flex\"]),align:E.oneOf(Mt),justify:E.oneOf(St),className:E.string,children:E.node,gutter:E.oneOfType([E.object,E.number,E.array]),prefixCls:E.string};var xt=_t;function kt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ht(){return(Ht=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Et(e){return(Et=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Pt(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Vt(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function Tt(e,t){return(Tt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Lt(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Dt(e);if(t){var o=Dt(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return jt(this,n)}}function jt(e,t){return!t||\"object\"!==Et(t)&&\"function\"!==typeof t?Nt(e):t}function Nt(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function Dt(e){return(Dt=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Rt=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n},At=E.oneOfType([E.object,E.number]),It=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&Tt(e,t)}(a,e);var t,n,o,c=Lt(a);function a(){var e;return Pt(this,a),(e=c.apply(this,arguments)).renderCol=function(t){var n,o=t.getPrefixCls,c=Nt(e).props,a=c.prefixCls,l=c.span,u=c.order,s=c.offset,f=c.push,p=c.pull,h=c.className,d=c.children,v=Rt(c,[\"prefixCls\",\"span\",\"order\",\"offset\",\"push\",\"pull\",\"className\",\"children\"]),m=o(\"col\",a),y={};[\"xs\",\"sm\",\"md\",\"lg\",\"xl\",\"xxl\"].forEach((function(e){var t,n={},r=c[e];\"number\"===typeof r?n.span=r:\"object\"===Et(r)&&(n=r||{}),delete v[e],y=Ht(Ht({},y),(kt(t={},\"\".concat(m,\"-\").concat(e,\"-\").concat(n.span),void 0!==n.span),kt(t,\"\".concat(m,\"-\").concat(e,\"-order-\").concat(n.order),n.order||0===n.order),kt(t,\"\".concat(m,\"-\").concat(e,\"-offset-\").concat(n.offset),n.offset||0===n.offset),kt(t,\"\".concat(m,\"-\").concat(e,\"-push-\").concat(n.push),n.push||0===n.push),kt(t,\"\".concat(m,\"-\").concat(e,\"-pull-\").concat(n.pull),n.pull||0===n.pull),t))}));var b=i()(m,(kt(n={},\"\".concat(m,\"-\").concat(l),void 0!==l),kt(n,\"\".concat(m,\"-order-\").concat(u),u),kt(n,\"\".concat(m,\"-offset-\").concat(s),s),kt(n,\"\".concat(m,\"-push-\").concat(f),f),kt(n,\"\".concat(m,\"-pull-\").concat(p),p),n),h,y);return r.createElement(ot.Consumer,null,(function(e){var t=e.gutter,n=v.style;return t&&(n=Ht(Ht(Ht({},t[0]>0?{paddingLeft:t[0]/2,paddingRight:t[0]/2}:{}),t[1]>0?{paddingTop:t[1]/2,paddingBottom:t[1]/2}:{}),n)),r.createElement(\"div\",Ht({},v,{style:n,className:b}),d)}))},e}return t=a,(n=[{key:\"render\",value:function(){return r.createElement(l.a,null,this.renderCol)}}])&&Vt(t.prototype,n),o&&Vt(t,o),a}(r.Component);It.propTypes={span:E.number,order:E.number,offset:E.number,push:E.number,pull:E.number,className:E.string,children:E.node,xs:At,sm:At,md:At,lg:At,xl:At,xxl:At};var Ft=It;function Wt(e){return(Wt=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function Ut(){return(Ut=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Kt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Bt(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function Yt(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function qt(e,t){return(qt=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function Gt(e){var t=function(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,r=Qt(e);if(t){var o=Qt(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return $t(this,n)}}function $t(e,t){return!t||\"object\"!==Wt(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function Qt(e){return(Qt=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var Xt=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&\"function\"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o<r.length;o++)t.indexOf(r[o])<0&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]])}return n};function Zt(e){return e.map((function(t,n){return r.createElement(\"li\",{style:{width:\"\".concat(100/e.length,\"%\")},key:\"action-\".concat(n)},r.createElement(\"span\",null,t))}))}var Jt=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&qt(e,t)}(u,e);var t,n,o,c=Gt(u);function u(){var e;return Bt(this,u),(e=c.apply(this,arguments)).onTabChange=function(t){e.props.onTabChange&&e.props.onTabChange(t)},e.renderCard=function(t){var n,o,c,l=t.getPrefixCls,u=e.props,s=u.prefixCls,f=u.className,p=u.extra,h=u.headStyle,d=void 0===h?{}:h,v=u.bodyStyle,m=void 0===v?{}:v,y=u.title,b=u.loading,g=u.bordered,w=void 0===g||g,z=u.size,O=void 0===z?\"default\":z,C=u.type,M=u.cover,S=u.actions,_=u.tabList,x=u.children,k=u.activeTabKey,H=u.defaultActiveTabKey,E=u.tabBarExtraContent,P=Xt(u,[\"prefixCls\",\"className\",\"extra\",\"headStyle\",\"bodyStyle\",\"title\",\"loading\",\"bordered\",\"size\",\"type\",\"cover\",\"actions\",\"tabList\",\"children\",\"activeTabKey\",\"defaultActiveTabKey\",\"tabBarExtraContent\"]),V=l(\"card\",s),T=i()(V,f,(Kt(n={},\"\".concat(V,\"-loading\"),b),Kt(n,\"\".concat(V,\"-bordered\"),w),Kt(n,\"\".concat(V,\"-hoverable\"),e.getCompatibleHoverable()),Kt(n,\"\".concat(V,\"-contain-grid\"),e.isContainGrid()),Kt(n,\"\".concat(V,\"-contain-tabs\"),_&&_.length),Kt(n,\"\".concat(V,\"-\").concat(O),\"default\"!==O),Kt(n,\"\".concat(V,\"-type-\").concat(C),!!C),n)),L=0===m.padding||\"0px\"===m.padding?{padding:24}:void 0,j=r.createElement(\"div\",{className:\"\".concat(V,\"-loading-content\"),style:L},r.createElement(xt,{gutter:8},r.createElement(Ft,{span:22},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")}))),r.createElement(xt,{gutter:8},r.createElement(Ft,{span:8},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})),r.createElement(Ft,{span:15},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")}))),r.createElement(xt,{gutter:8},r.createElement(Ft,{span:6},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})),r.createElement(Ft,{span:18},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")}))),r.createElement(xt,{gutter:8},r.createElement(Ft,{span:13},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})),r.createElement(Ft,{span:9},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")}))),r.createElement(xt,{gutter:8},r.createElement(Ft,{span:4},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})),r.createElement(Ft,{span:3},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})),r.createElement(Ft,{span:16},r.createElement(\"div\",{className:\"\".concat(V,\"-loading-block\")})))),N=void 0!==k,D=(Kt(o={},N?\"activeKey\":\"defaultActiveKey\",N?k:H),Kt(o,\"tabBarExtraContent\",E),o),R=_&&_.length?r.createElement(nt,Ut({},D,{className:\"\".concat(V,\"-head-tabs\"),size:\"large\",onChange:e.onTabChange}),_.map((function(e){return r.createElement(nt.TabPane,{tab:e.tab,disabled:e.disabled,key:e.key})}))):null;(y||p||R)&&(c=r.createElement(\"div\",{className:\"\".concat(V,\"-head\"),style:d},r.createElement(\"div\",{className:\"\".concat(V,\"-head-wrapper\")},y&&r.createElement(\"div\",{className:\"\".concat(V,\"-head-title\")},y),p&&r.createElement(\"div\",{className:\"\".concat(V,\"-extra\")},p)),R));var A=M?r.createElement(\"div\",{className:\"\".concat(V,\"-cover\")},M):null,I=r.createElement(\"div\",{className:\"\".concat(V,\"-body\"),style:m},b?j:x),F=S&&S.length?r.createElement(\"ul\",{className:\"\".concat(V,\"-actions\")},Zt(S)):null,W=Object(a.a)(P,[\"onTabChange\",\"noHovering\",\"hoverable\"]);return r.createElement(\"div\",Ut({},W,{className:T}),c,A,I,F)},e}return t=u,(n=[{key:\"componentDidMount\",value:function(){\"noHovering\"in this.props&&(Object(Ue.a)(!this.props.noHovering,\"Card\",\"`noHovering` is deprecated, you can remove it safely or use `hoverable` instead.\"),Object(Ue.a)(!!this.props.noHovering,\"Card\",\"`noHovering={false}` is deprecated, use `hoverable` instead.\"))}},{key:\"getCompatibleHoverable\",value:function(){var e=this.props,t=e.noHovering,n=e.hoverable;return\"noHovering\"in this.props?!t||n:!!n}},{key:\"isContainGrid\",value:function(){var e;return r.Children.forEach(this.props.children,(function(t){t&&t.type&&t.type===f&&(e=!0)})),e}},{key:\"render\",value:function(){return r.createElement(l.a,null,this.renderCard)}}])&&Yt(t.prototype,n),o&&Yt(t,o),u}(r.Component);Jt.Grid=f,Jt.Meta=d},function(e,t,n){\"use strict\";var r=n(94),o={placeholder:\"\\u8bf7\\u9009\\u62e9\\u65f6\\u95f4\"};function c(){return(c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var i={lang:c({placeholder:\"\\u8bf7\\u9009\\u62e9\\u65e5\\u671f\",rangePlaceholder:[\"\\u5f00\\u59cb\\u65e5\\u671f\",\"\\u7ed3\\u675f\\u65e5\\u671f\"]},{today:\"\\u4eca\\u5929\",now:\"\\u6b64\\u523b\",backToToday:\"\\u8fd4\\u56de\\u4eca\\u5929\",ok:\"\\u786e\\u5b9a\",timeSelect:\"\\u9009\\u62e9\\u65f6\\u95f4\",dateSelect:\"\\u9009\\u62e9\\u65e5\\u671f\",weekSelect:\"\\u9009\\u62e9\\u5468\",clear:\"\\u6e05\\u9664\",month:\"\\u6708\",year:\"\\u5e74\",previousMonth:\"\\u4e0a\\u4e2a\\u6708 (\\u7ffb\\u9875\\u4e0a\\u952e)\",nextMonth:\"\\u4e0b\\u4e2a\\u6708 (\\u7ffb\\u9875\\u4e0b\\u952e)\",monthSelect:\"\\u9009\\u62e9\\u6708\\u4efd\",yearSelect:\"\\u9009\\u62e9\\u5e74\\u4efd\",decadeSelect:\"\\u9009\\u62e9\\u5e74\\u4ee3\",yearFormat:\"YYYY\\u5e74\",dayFormat:\"D\\u65e5\",dateFormat:\"YYYY\\u5e74M\\u6708D\\u65e5\",dateTimeFormat:\"YYYY\\u5e74M\\u6708D\\u65e5 HH\\u65f6mm\\u5206ss\\u79d2\",previousYear:\"\\u4e0a\\u4e00\\u5e74 (Control\\u952e\\u52a0\\u5de6\\u65b9\\u5411\\u952e)\",nextYear:\"\\u4e0b\\u4e00\\u5e74 (Control\\u952e\\u52a0\\u53f3\\u65b9\\u5411\\u952e)\",previousDecade:\"\\u4e0a\\u4e00\\u5e74\\u4ee3\",nextDecade:\"\\u4e0b\\u4e00\\u5e74\\u4ee3\",previousCentury:\"\\u4e0a\\u4e00\\u4e16\\u7eaa\",nextCentury:\"\\u4e0b\\u4e00\\u4e16\\u7eaa\"}),timePickerLocale:c({},o)};i.lang.ok=\"\\u786e \\u5b9a\";var a=i,l=a;t.a={locale:\"zh-cn\",Pagination:r.a,DatePicker:a,TimePicker:o,Calendar:l,global:{placeholder:\"\\u8bf7\\u9009\\u62e9\"},Table:{filterTitle:\"\\u7b5b\\u9009\",filterConfirm:\"\\u786e\\u5b9a\",filterReset:\"\\u91cd\\u7f6e\",selectAll:\"\\u5168\\u9009\\u5f53\\u9875\",selectInvert:\"\\u53cd\\u9009\\u5f53\\u9875\",sortTitle:\"\\u6392\\u5e8f\",expand:\"\\u5c55\\u5f00\\u884c\",collapse:\"\\u5173\\u95ed\\u884c\"},Modal:{okText:\"\\u786e\\u5b9a\",cancelText:\"\\u53d6\\u6d88\",justOkText:\"\\u77e5\\u9053\\u4e86\"},Popconfirm:{cancelText:\"\\u53d6\\u6d88\",okText:\"\\u786e\\u5b9a\"},Transfer:{searchPlaceholder:\"\\u8bf7\\u8f93\\u5165\\u641c\\u7d22\\u5185\\u5bb9\",itemUnit:\"\\u9879\",itemsUnit:\"\\u9879\"},Upload:{uploading:\"\\u6587\\u4ef6\\u4e0a\\u4f20\\u4e2d\",removeFile:\"\\u5220\\u9664\\u6587\\u4ef6\",uploadError:\"\\u4e0a\\u4f20\\u9519\\u8bef\",previewFile:\"\\u9884\\u89c8\\u6587\\u4ef6\",downloadFile:\"\\u4e0b\\u8f7d\\u6587\\u4ef6\"},Empty:{description:\"\\u6682\\u65e0\\u6570\\u636e\"},Icon:{icon:\"\\u56fe\\u6807\"},Text:{edit:\"\\u7f16\\u8f91\",copy:\"\\u590d\\u5236\",copied:\"\\u590d\\u5236\\u6210\\u529f\",expand:\"\\u5c55\\u5f00\"},PageHeader:{back:\"\\u8fd4\\u56de\"}}},function(e,t,n){\"use strict\";n.d(t,\"a\",(function(){return c}));var r=n(67);var o=n(86);function c(e){return function(e){if(Array.isArray(e))return Object(r.a)(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||Object(o.a)(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}},function(e,t,n){\"use strict\";function r(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}n.d(t,\"a\",(function(){return r}))},,,,function(e,t,n){\"use strict\";var r=n(0),o=n(71),c=n(169);function i(e){for(var t=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+e,n=1;n<arguments.length;n++)t+=\"&args[]=\"+encodeURIComponent(arguments[n]);return\"Minified React error #\"+e+\"; visit \"+t+\" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\"}if(!r)throw Error(i(227));var a=new Set,l={};function u(e,t){s(e,t),s(e+\"Capture\",t)}function s(e,t){for(l[e]=t,e=0;e<t.length;e++)a.add(t[e])}var f=!(\"undefined\"===typeof window||\"undefined\"===typeof window.document||\"undefined\"===typeof window.document.createElement),p=/^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$/,h=Object.prototype.hasOwnProperty,d={},v={};function m(e,t,n,r,o,c,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=c,this.removeEmptyString=i}var y={};\"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style\".split(\" \").forEach((function(e){y[e]=new m(e,0,!1,e,null,!1,!1)})),[[\"acceptCharset\",\"accept-charset\"],[\"className\",\"class\"],[\"htmlFor\",\"for\"],[\"httpEquiv\",\"http-equiv\"]].forEach((function(e){var t=e[0];y[t]=new m(t,1,!1,e[1],null,!1,!1)})),[\"contentEditable\",\"draggable\",\"spellCheck\",\"value\"].forEach((function(e){y[e]=new m(e,2,!1,e.toLowerCase(),null,!1,!1)})),[\"autoReverse\",\"externalResourcesRequired\",\"focusable\",\"preserveAlpha\"].forEach((function(e){y[e]=new m(e,2,!1,e,null,!1,!1)})),\"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope\".split(\" \").forEach((function(e){y[e]=new m(e,3,!1,e.toLowerCase(),null,!1,!1)})),[\"checked\",\"multiple\",\"muted\",\"selected\"].forEach((function(e){y[e]=new m(e,3,!0,e,null,!1,!1)})),[\"capture\",\"download\"].forEach((function(e){y[e]=new m(e,4,!1,e,null,!1,!1)})),[\"cols\",\"rows\",\"size\",\"span\"].forEach((function(e){y[e]=new m(e,6,!1,e,null,!1,!1)})),[\"rowSpan\",\"start\"].forEach((function(e){y[e]=new m(e,5,!1,e.toLowerCase(),null,!1,!1)}));var b=/[\\-:]([a-z])/g;function g(e){return e[1].toUpperCase()}function w(e,t,n,r){var o=y.hasOwnProperty(t)?y[t]:null;(null!==o?0===o.type:!r&&(2<t.length&&(\"o\"===t[0]||\"O\"===t[0])&&(\"n\"===t[1]||\"N\"===t[1])))||(function(e,t,n,r){if(null===t||\"undefined\"===typeof t||function(e,t,n,r){if(null!==n&&0===n.type)return!1;switch(typeof t){case\"function\":case\"symbol\":return!0;case\"boolean\":return!r&&(null!==n?!n.acceptsBooleans:\"data-\"!==(e=e.toLowerCase().slice(0,5))&&\"aria-\"!==e);default:return!1}}(e,t,n,r))return!0;if(r)return!1;if(null!==n)switch(n.type){case 3:return!t;case 4:return!1===t;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}(t,n,o,r)&&(n=null),r||null===o?function(e){return!!h.call(v,e)||!h.call(d,e)&&(p.test(e)?v[e]=!0:(d[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,\"\"+n)):o.mustUseProperty?e[o.propertyName]=null===n?3!==o.type&&\"\":n:(t=o.attributeName,r=o.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(o=o.type)||4===o&&!0===n?\"\":\"\"+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}\"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height\".split(\" \").forEach((function(e){var t=e.replace(b,g);y[t]=new m(t,1,!1,e,null,!1,!1)})),\"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type\".split(\" \").forEach((function(e){var t=e.replace(b,g);y[t]=new m(t,1,!1,e,\"http://www.w3.org/1999/xlink\",!1,!1)})),[\"xml:base\",\"xml:lang\",\"xml:space\"].forEach((function(e){var t=e.replace(b,g);y[t]=new m(t,1,!1,e,\"http://www.w3.org/XML/1998/namespace\",!1,!1)})),[\"tabIndex\",\"crossOrigin\"].forEach((function(e){y[e]=new m(e,1,!1,e.toLowerCase(),null,!1,!1)})),y.xlinkHref=new m(\"xlinkHref\",1,!1,\"xlink:href\",\"http://www.w3.org/1999/xlink\",!0,!1),[\"src\",\"href\",\"action\",\"formAction\"].forEach((function(e){y[e]=new m(e,1,!1,e.toLowerCase(),null,!0,!0)}));var z=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,O=60103,C=60106,M=60107,S=60108,_=60114,x=60109,k=60110,H=60112,E=60113,P=60120,V=60115,T=60116,L=60121,j=60128,N=60129,D=60130,R=60131;if(\"function\"===typeof Symbol&&Symbol.for){var A=Symbol.for;O=A(\"react.element\"),C=A(\"react.portal\"),M=A(\"react.fragment\"),S=A(\"react.strict_mode\"),_=A(\"react.profiler\"),x=A(\"react.provider\"),k=A(\"react.context\"),H=A(\"react.forward_ref\"),E=A(\"react.suspense\"),P=A(\"react.suspense_list\"),V=A(\"react.memo\"),T=A(\"react.lazy\"),L=A(\"react.block\"),A(\"react.scope\"),j=A(\"react.opaque.id\"),N=A(\"react.debug_trace_mode\"),D=A(\"react.offscreen\"),R=A(\"react.legacy_hidden\")}var I,F=\"function\"===typeof Symbol&&Symbol.iterator;function W(e){return null===e||\"object\"!==typeof e?null:\"function\"===typeof(e=F&&e[F]||e[\"@@iterator\"])?e:null}function U(e){if(void 0===I)try{throw Error()}catch(n){var t=n.stack.trim().match(/\\n( *(at )?)/);I=t&&t[1]||\"\"}return\"\\n\"+I+e}var K=!1;function B(e,t){if(!e||K)return\"\";K=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,\"props\",{set:function(){throw Error()}}),\"object\"===typeof Reflect&&Reflect.construct){try{Reflect.construct(t,[])}catch(l){var r=l}Reflect.construct(e,[],t)}else{try{t.call()}catch(l){r=l}e.call(t.prototype)}else{try{throw Error()}catch(l){r=l}e()}}catch(l){if(l&&r&&\"string\"===typeof l.stack){for(var o=l.stack.split(\"\\n\"),c=r.stack.split(\"\\n\"),i=o.length-1,a=c.length-1;1<=i&&0<=a&&o[i]!==c[a];)a--;for(;1<=i&&0<=a;i--,a--)if(o[i]!==c[a]){if(1!==i||1!==a)do{if(i--,0>--a||o[i]!==c[a])return\"\\n\"+o[i].replace(\" at new \",\" at \")}while(1<=i&&0<=a);break}}}finally{K=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:\"\")?U(e):\"\"}function Y(e){switch(e.tag){case 5:return U(e.type);case 16:return U(\"Lazy\");case 13:return U(\"Suspense\");case 19:return U(\"SuspenseList\");case 0:case 2:case 15:return e=B(e.type,!1);case 11:return e=B(e.type.render,!1);case 22:return e=B(e.type._render,!1);case 1:return e=B(e.type,!0);default:return\"\"}}function q(e){if(null==e)return null;if(\"function\"===typeof e)return e.displayName||e.name||null;if(\"string\"===typeof e)return e;switch(e){case M:return\"Fragment\";case C:return\"Portal\";case _:return\"Profiler\";case S:return\"StrictMode\";case E:return\"Suspense\";case P:return\"SuspenseList\"}if(\"object\"===typeof e)switch(e.$$typeof){case k:return(e.displayName||\"Context\")+\".Consumer\";case x:return(e._context.displayName||\"Context\")+\".Provider\";case H:var t=e.render;return t=t.displayName||t.name||\"\",e.displayName||(\"\"!==t?\"ForwardRef(\"+t+\")\":\"ForwardRef\");case V:return q(e.type);case L:return q(e._render);case T:t=e._payload,e=e._init;try{return q(e(t))}catch(n){}}return null}function G(e){switch(typeof e){case\"boolean\":case\"number\":case\"object\":case\"string\":case\"undefined\":return e;default:return\"\"}}function $(e){var t=e.type;return(e=e.nodeName)&&\"input\"===e.toLowerCase()&&(\"checkbox\"===t||\"radio\"===t)}function Q(e){e._valueTracker||(e._valueTracker=function(e){var t=$(e)?\"checked\":\"value\",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=\"\"+e[t];if(!e.hasOwnProperty(t)&&\"undefined\"!==typeof n&&\"function\"===typeof n.get&&\"function\"===typeof n.set){var o=n.get,c=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(e){r=\"\"+e,c.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=\"\"+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function X(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r=\"\";return e&&(r=$(e)?e.checked?\"true\":\"false\":e.value),(e=r)!==n&&(t.setValue(e),!0)}function Z(e){if(\"undefined\"===typeof(e=e||(\"undefined\"!==typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}function J(e,t){var n=t.checked;return o({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function ee(e,t){var n=null==t.defaultValue?\"\":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=G(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:\"checkbox\"===t.type||\"radio\"===t.type?null!=t.checked:null!=t.value}}function te(e,t){null!=(t=t.checked)&&w(e,\"checked\",t,!1)}function ne(e,t){te(e,t);var n=G(t.value),r=t.type;if(null!=n)\"number\"===r?(0===n&&\"\"===e.value||e.value!=n)&&(e.value=\"\"+n):e.value!==\"\"+n&&(e.value=\"\"+n);else if(\"submit\"===r||\"reset\"===r)return void e.removeAttribute(\"value\");t.hasOwnProperty(\"value\")?oe(e,t.type,n):t.hasOwnProperty(\"defaultValue\")&&oe(e,t.type,G(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function re(e,t,n){if(t.hasOwnProperty(\"value\")||t.hasOwnProperty(\"defaultValue\")){var r=t.type;if(!(\"submit\"!==r&&\"reset\"!==r||void 0!==t.value&&null!==t.value))return;t=\"\"+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}\"\"!==(n=e.name)&&(e.name=\"\"),e.defaultChecked=!!e._wrapperState.initialChecked,\"\"!==n&&(e.name=n)}function oe(e,t,n){\"number\"===t&&Z(e.ownerDocument)===e||(null==n?e.defaultValue=\"\"+e._wrapperState.initialValue:e.defaultValue!==\"\"+n&&(e.defaultValue=\"\"+n))}function ce(e,t){return e=o({children:void 0},t),(t=function(e){var t=\"\";return r.Children.forEach(e,(function(e){null!=e&&(t+=e)})),t}(t.children))&&(e.children=t),e}function ie(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t[\"$\"+n[o]]=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty(\"$\"+e[n].value),e[n].selected!==o&&(e[n].selected=o),o&&r&&(e[n].defaultSelected=!0)}else{for(n=\"\"+G(n),t=null,o=0;o<e.length;o++){if(e[o].value===n)return e[o].selected=!0,void(r&&(e[o].defaultSelected=!0));null!==t||e[o].disabled||(t=e[o])}null!==t&&(t.selected=!0)}}function ae(e,t){if(null!=t.dangerouslySetInnerHTML)throw Error(i(91));return o({},t,{value:void 0,defaultValue:void 0,children:\"\"+e._wrapperState.initialValue})}function le(e,t){var n=t.value;if(null==n){if(n=t.children,t=t.defaultValue,null!=n){if(null!=t)throw Error(i(92));if(Array.isArray(n)){if(!(1>=n.length))throw Error(i(93));n=n[0]}t=n}null==t&&(t=\"\"),n=t}e._wrapperState={initialValue:G(n)}}function ue(e,t){var n=G(t.value),r=G(t.defaultValue);null!=n&&((n=\"\"+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=\"\"+r)}function se(e){var t=e.textContent;t===e._wrapperState.initialValue&&\"\"!==t&&null!==t&&(e.value=t)}var fe=\"http://www.w3.org/1999/xhtml\",pe=\"http://www.w3.org/2000/svg\";function he(e){switch(e){case\"svg\":return\"http://www.w3.org/2000/svg\";case\"math\":return\"http://www.w3.org/1998/Math/MathML\";default:return\"http://www.w3.org/1999/xhtml\"}}function de(e,t){return null==e||\"http://www.w3.org/1999/xhtml\"===e?he(t):\"http://www.w3.org/2000/svg\"===e&&\"foreignObject\"===t?\"http://www.w3.org/1999/xhtml\":e}var ve,me,ye=(me=function(e,t){if(e.namespaceURI!==pe||\"innerHTML\"in e)e.innerHTML=t;else{for((ve=ve||document.createElement(\"div\")).innerHTML=\"<svg>\"+t.valueOf().toString()+\"</svg>\",t=ve.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}},\"undefined\"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(e,t,n,r){MSApp.execUnsafeLocalFunction((function(){return me(e,t)}))}:me);function be(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}var ge={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},we=[\"Webkit\",\"ms\",\"Moz\",\"O\"];function ze(e,t,n){return null==t||\"boolean\"===typeof t||\"\"===t?\"\":n||\"number\"!==typeof t||0===t||ge.hasOwnProperty(e)&&ge[e]?(\"\"+t).trim():t+\"px\"}function Oe(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=0===n.indexOf(\"--\"),o=ze(n,t[n],r);\"float\"===n&&(n=\"cssFloat\"),r?e.setProperty(n,o):e[n]=o}}Object.keys(ge).forEach((function(e){we.forEach((function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),ge[t]=ge[e]}))}));var Ce=o({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Me(e,t){if(t){if(Ce[e]&&(null!=t.children||null!=t.dangerouslySetInnerHTML))throw Error(i(137,e));if(null!=t.dangerouslySetInnerHTML){if(null!=t.children)throw Error(i(60));if(\"object\"!==typeof t.dangerouslySetInnerHTML||!(\"__html\"in t.dangerouslySetInnerHTML))throw Error(i(61))}if(null!=t.style&&\"object\"!==typeof t.style)throw Error(i(62))}}function Se(e,t){if(-1===e.indexOf(\"-\"))return\"string\"===typeof t.is;switch(e){case\"annotation-xml\":case\"color-profile\":case\"font-face\":case\"font-face-src\":case\"font-face-uri\":case\"font-face-format\":case\"font-face-name\":case\"missing-glyph\":return!1;default:return!0}}function _e(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var xe=null,ke=null,He=null;function Ee(e){if(e=eo(e)){if(\"function\"!==typeof xe)throw Error(i(280));var t=e.stateNode;t&&(t=no(t),xe(e.stateNode,e.type,t))}}function Pe(e){ke?He?He.push(e):He=[e]:ke=e}function Ve(){if(ke){var e=ke,t=He;if(He=ke=null,Ee(e),t)for(e=0;e<t.length;e++)Ee(t[e])}}function Te(e,t){return e(t)}function Le(e,t,n,r,o){return e(t,n,r,o)}function je(){}var Ne=Te,De=!1,Re=!1;function Ae(){null===ke&&null===He||(je(),Ve())}function Ie(e,t){var n=e.stateNode;if(null===n)return null;var r=no(n);if(null===r)return null;n=r[t];e:switch(t){case\"onClick\":case\"onClickCapture\":case\"onDoubleClick\":case\"onDoubleClickCapture\":case\"onMouseDown\":case\"onMouseDownCapture\":case\"onMouseMove\":case\"onMouseMoveCapture\":case\"onMouseUp\":case\"onMouseUpCapture\":case\"onMouseEnter\":(r=!r.disabled)||(r=!(\"button\"===(e=e.type)||\"input\"===e||\"select\"===e||\"textarea\"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&\"function\"!==typeof n)throw Error(i(231,t,typeof n));return n}var Fe=!1;if(f)try{var We={};Object.defineProperty(We,\"passive\",{get:function(){Fe=!0}}),window.addEventListener(\"test\",We,We),window.removeEventListener(\"test\",We,We)}catch(me){Fe=!1}function Ue(e,t,n,r,o,c,i,a,l){var u=Array.prototype.slice.call(arguments,3);try{t.apply(n,u)}catch(s){this.onError(s)}}var Ke=!1,Be=null,Ye=!1,qe=null,Ge={onError:function(e){Ke=!0,Be=e}};function $e(e,t,n,r,o,c,i,a,l){Ke=!1,Be=null,Ue.apply(Ge,arguments)}function Qe(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!==(1026&(t=e).flags)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function Xe(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function Ze(e){if(Qe(e)!==e)throw Error(i(188))}function Je(e){if(!(e=function(e){var t=e.alternate;if(!t){if(null===(t=Qe(e)))throw Error(i(188));return t!==e?null:e}for(var n=e,r=t;;){var o=n.return;if(null===o)break;var c=o.alternate;if(null===c){if(null!==(r=o.return)){n=r;continue}break}if(o.child===c.child){for(c=o.child;c;){if(c===n)return Ze(o),e;if(c===r)return Ze(o),t;c=c.sibling}throw Error(i(188))}if(n.return!==r.return)n=o,r=c;else{for(var a=!1,l=o.child;l;){if(l===n){a=!0,n=o,r=c;break}if(l===r){a=!0,r=o,n=c;break}l=l.sibling}if(!a){for(l=c.child;l;){if(l===n){a=!0,n=c,r=o;break}if(l===r){a=!0,r=c,n=o;break}l=l.sibling}if(!a)throw Error(i(189))}}if(n.alternate!==r)throw Error(i(190))}if(3!==n.tag)throw Error(i(188));return n.stateNode.current===n?e:t}(e)))return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}function et(e,t){for(var n=e.alternate;null!==t;){if(t===e||t===n)return!0;t=t.return}return!1}var tt,nt,rt,ot,ct=!1,it=[],at=null,lt=null,ut=null,st=new Map,ft=new Map,pt=[],ht=\"mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit\".split(\" \");function dt(e,t,n,r,o){return{blockedOn:e,domEventName:t,eventSystemFlags:16|n,nativeEvent:o,targetContainers:[r]}}function vt(e,t){switch(e){case\"focusin\":case\"focusout\":at=null;break;case\"dragenter\":case\"dragleave\":lt=null;break;case\"mouseover\":case\"mouseout\":ut=null;break;case\"pointerover\":case\"pointerout\":st.delete(t.pointerId);break;case\"gotpointercapture\":case\"lostpointercapture\":ft.delete(t.pointerId)}}function mt(e,t,n,r,o,c){return null===e||e.nativeEvent!==c?(e=dt(t,n,r,o,c),null!==t&&(null!==(t=eo(t))&&nt(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,null!==o&&-1===t.indexOf(o)&&t.push(o),e)}function yt(e){var t=Jr(e.target);if(null!==t){var n=Qe(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=Xe(n)))return e.blockedOn=t,void ot(e.lanePriority,(function(){c.unstable_runWithPriority(e.priority,(function(){rt(n)}))}))}else if(3===t&&n.stateNode.hydrate)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function bt(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=Jt(e.domEventName,e.eventSystemFlags,t[0],e.nativeEvent);if(null!==n)return null!==(t=eo(n))&&nt(t),e.blockedOn=n,!1;t.shift()}return!0}function gt(e,t,n){bt(e)&&n.delete(t)}function wt(){for(ct=!1;0<it.length;){var e=it[0];if(null!==e.blockedOn){null!==(e=eo(e.blockedOn))&&tt(e);break}for(var t=e.targetContainers;0<t.length;){var n=Jt(e.domEventName,e.eventSystemFlags,t[0],e.nativeEvent);if(null!==n){e.blockedOn=n;break}t.shift()}null===e.blockedOn&&it.shift()}null!==at&&bt(at)&&(at=null),null!==lt&&bt(lt)&&(lt=null),null!==ut&&bt(ut)&&(ut=null),st.forEach(gt),ft.forEach(gt)}function zt(e,t){e.blockedOn===t&&(e.blockedOn=null,ct||(ct=!0,c.unstable_scheduleCallback(c.unstable_NormalPriority,wt)))}function Ot(e){function t(t){return zt(t,e)}if(0<it.length){zt(it[0],e);for(var n=1;n<it.length;n++){var r=it[n];r.blockedOn===e&&(r.blockedOn=null)}}for(null!==at&&zt(at,e),null!==lt&&zt(lt,e),null!==ut&&zt(ut,e),st.forEach(t),ft.forEach(t),n=0;n<pt.length;n++)(r=pt[n]).blockedOn===e&&(r.blockedOn=null);for(;0<pt.length&&null===(n=pt[0]).blockedOn;)yt(n),null===n.blockedOn&&pt.shift()}function Ct(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n[\"Webkit\"+e]=\"webkit\"+t,n[\"Moz\"+e]=\"moz\"+t,n}var Mt={animationend:Ct(\"Animation\",\"AnimationEnd\"),animationiteration:Ct(\"Animation\",\"AnimationIteration\"),animationstart:Ct(\"Animation\",\"AnimationStart\"),transitionend:Ct(\"Transition\",\"TransitionEnd\")},St={},_t={};function xt(e){if(St[e])return St[e];if(!Mt[e])return e;var t,n=Mt[e];for(t in n)if(n.hasOwnProperty(t)&&t in _t)return St[e]=n[t];return e}f&&(_t=document.createElement(\"div\").style,\"AnimationEvent\"in window||(delete Mt.animationend.animation,delete Mt.animationiteration.animation,delete Mt.animationstart.animation),\"TransitionEvent\"in window||delete Mt.transitionend.transition);var kt=xt(\"animationend\"),Ht=xt(\"animationiteration\"),Et=xt(\"animationstart\"),Pt=xt(\"transitionend\"),Vt=new Map,Tt=new Map,Lt=[\"abort\",\"abort\",kt,\"animationEnd\",Ht,\"animationIteration\",Et,\"animationStart\",\"canplay\",\"canPlay\",\"canplaythrough\",\"canPlayThrough\",\"durationchange\",\"durationChange\",\"emptied\",\"emptied\",\"encrypted\",\"encrypted\",\"ended\",\"ended\",\"error\",\"error\",\"gotpointercapture\",\"gotPointerCapture\",\"load\",\"load\",\"loadeddata\",\"loadedData\",\"loadedmetadata\",\"loadedMetadata\",\"loadstart\",\"loadStart\",\"lostpointercapture\",\"lostPointerCapture\",\"playing\",\"playing\",\"progress\",\"progress\",\"seeking\",\"seeking\",\"stalled\",\"stalled\",\"suspend\",\"suspend\",\"timeupdate\",\"timeUpdate\",Pt,\"transitionEnd\",\"waiting\",\"waiting\"];function jt(e,t){for(var n=0;n<e.length;n+=2){var r=e[n],o=e[n+1];o=\"on\"+(o[0].toUpperCase()+o.slice(1)),Tt.set(r,t),Vt.set(r,o),u(o,[r])}}(0,c.unstable_now)();var Nt=8;function Dt(e){if(0!==(1&e))return Nt=15,1;if(0!==(2&e))return Nt=14,2;if(0!==(4&e))return Nt=13,4;var t=24&e;return 0!==t?(Nt=12,t):0!==(32&e)?(Nt=11,32):0!==(t=192&e)?(Nt=10,t):0!==(256&e)?(Nt=9,256):0!==(t=3584&e)?(Nt=8,t):0!==(4096&e)?(Nt=7,4096):0!==(t=4186112&e)?(Nt=6,t):0!==(t=62914560&e)?(Nt=5,t):67108864&e?(Nt=4,67108864):0!==(134217728&e)?(Nt=3,134217728):0!==(t=805306368&e)?(Nt=2,t):0!==(1073741824&e)?(Nt=1,1073741824):(Nt=8,e)}function Rt(e,t){var n=e.pendingLanes;if(0===n)return Nt=0;var r=0,o=0,c=e.expiredLanes,i=e.suspendedLanes,a=e.pingedLanes;if(0!==c)r=c,o=Nt=15;else if(0!==(c=134217727&n)){var l=c&~i;0!==l?(r=Dt(l),o=Nt):0!==(a&=c)&&(r=Dt(a),o=Nt)}else 0!==(c=n&~i)?(r=Dt(c),o=Nt):0!==a&&(r=Dt(a),o=Nt);if(0===r)return 0;if(r=n&((0>(r=31-Kt(r))?0:1<<r)<<1)-1,0!==t&&t!==r&&0===(t&i)){if(Dt(t),o<=Nt)return t;Nt=o}if(0!==(t=e.entangledLanes))for(e=e.entanglements,t&=r;0<t;)o=1<<(n=31-Kt(t)),r|=e[n],t&=~o;return r}function At(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?1073741824:0}function It(e,t){switch(e){case 15:return 1;case 14:return 2;case 12:return 0===(e=Ft(24&~t))?It(10,t):e;case 10:return 0===(e=Ft(192&~t))?It(8,t):e;case 8:return 0===(e=Ft(3584&~t))&&(0===(e=Ft(4186112&~t))&&(e=512)),e;case 2:return 0===(t=Ft(805306368&~t))&&(t=268435456),t}throw Error(i(358,e))}function Ft(e){return e&-e}function Wt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ut(e,t,n){e.pendingLanes|=t;var r=t-1;e.suspendedLanes&=r,e.pingedLanes&=r,(e=e.eventTimes)[t=31-Kt(t)]=n}var Kt=Math.clz32?Math.clz32:function(e){return 0===e?32:31-(Bt(e)/Yt|0)|0},Bt=Math.log,Yt=Math.LN2;var qt=c.unstable_UserBlockingPriority,Gt=c.unstable_runWithPriority,$t=!0;function Qt(e,t,n,r){De||je();var o=Zt,c=De;De=!0;try{Le(o,e,t,n,r)}finally{(De=c)||Ae()}}function Xt(e,t,n,r){Gt(qt,Zt.bind(null,e,t,n,r))}function Zt(e,t,n,r){var o;if($t)if((o=0===(4&t))&&0<it.length&&-1<ht.indexOf(e))e=dt(null,e,t,n,r),it.push(e);else{var c=Jt(e,t,n,r);if(null===c)o&&vt(e,r);else{if(o){if(-1<ht.indexOf(e))return e=dt(c,e,t,n,r),void it.push(e);if(function(e,t,n,r,o){switch(t){case\"focusin\":return at=mt(at,e,t,n,r,o),!0;case\"dragenter\":return lt=mt(lt,e,t,n,r,o),!0;case\"mouseover\":return ut=mt(ut,e,t,n,r,o),!0;case\"pointerover\":var c=o.pointerId;return st.set(c,mt(st.get(c)||null,e,t,n,r,o)),!0;case\"gotpointercapture\":return c=o.pointerId,ft.set(c,mt(ft.get(c)||null,e,t,n,r,o)),!0}return!1}(c,e,t,n,r))return;vt(e,r)}Vr(e,t,r,null,n)}}}function Jt(e,t,n,r){var o=_e(r);if(null!==(o=Jr(o))){var c=Qe(o);if(null===c)o=null;else{var i=c.tag;if(13===i){if(null!==(o=Xe(c)))return o;o=null}else if(3===i){if(c.stateNode.hydrate)return 3===c.tag?c.stateNode.containerInfo:null;o=null}else c!==o&&(o=null)}}return Vr(e,t,r,o,n),null}var en=null,tn=null,nn=null;function rn(){if(nn)return nn;var e,t,n=tn,r=n.length,o=\"value\"in en?en.value:en.textContent,c=o.length;for(e=0;e<r&&n[e]===o[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===o[c-t];t++);return nn=o.slice(e,1<t?1-t:void 0)}function on(e){var t=e.keyCode;return\"charCode\"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function cn(){return!0}function an(){return!1}function ln(e){function t(t,n,r,o,c){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=o,this.target=c,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(o):o[i]);return this.isDefaultPrevented=(null!=o.defaultPrevented?o.defaultPrevented:!1===o.returnValue)?cn:an,this.isPropagationStopped=an,this}return o(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():\"unknown\"!==typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=cn)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():\"unknown\"!==typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=cn)},persist:function(){},isPersistent:cn}),t}var un,sn,fn,pn={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},hn=ln(pn),dn=o({},pn,{view:0,detail:0}),vn=ln(dn),mn=o({},dn,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:xn,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return\"movementX\"in e?e.movementX:(e!==fn&&(fn&&\"mousemove\"===e.type?(un=e.screenX-fn.screenX,sn=e.screenY-fn.screenY):sn=un=0,fn=e),un)},movementY:function(e){return\"movementY\"in e?e.movementY:sn}}),yn=ln(mn),bn=ln(o({},mn,{dataTransfer:0})),gn=ln(o({},dn,{relatedTarget:0})),wn=ln(o({},pn,{animationName:0,elapsedTime:0,pseudoElement:0})),zn=ln(o({},pn,{clipboardData:function(e){return\"clipboardData\"in e?e.clipboardData:window.clipboardData}})),On=ln(o({},pn,{data:0})),Cn={Esc:\"Escape\",Spacebar:\" \",Left:\"ArrowLeft\",Up:\"ArrowUp\",Right:\"ArrowRight\",Down:\"ArrowDown\",Del:\"Delete\",Win:\"OS\",Menu:\"ContextMenu\",Apps:\"ContextMenu\",Scroll:\"ScrollLock\",MozPrintableKey:\"Unidentified\"},Mn={8:\"Backspace\",9:\"Tab\",12:\"Clear\",13:\"Enter\",16:\"Shift\",17:\"Control\",18:\"Alt\",19:\"Pause\",20:\"CapsLock\",27:\"Escape\",32:\" \",33:\"PageUp\",34:\"PageDown\",35:\"End\",36:\"Home\",37:\"ArrowLeft\",38:\"ArrowUp\",39:\"ArrowRight\",40:\"ArrowDown\",45:\"Insert\",46:\"Delete\",112:\"F1\",113:\"F2\",114:\"F3\",115:\"F4\",116:\"F5\",117:\"F6\",118:\"F7\",119:\"F8\",120:\"F9\",121:\"F10\",122:\"F11\",123:\"F12\",144:\"NumLock\",145:\"ScrollLock\",224:\"Meta\"},Sn={Alt:\"altKey\",Control:\"ctrlKey\",Meta:\"metaKey\",Shift:\"shiftKey\"};function _n(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Sn[e])&&!!t[e]}function xn(){return _n}var kn=ln(o({},dn,{key:function(e){if(e.key){var t=Cn[e.key]||e.key;if(\"Unidentified\"!==t)return t}return\"keypress\"===e.type?13===(e=on(e))?\"Enter\":String.fromCharCode(e):\"keydown\"===e.type||\"keyup\"===e.type?Mn[e.keyCode]||\"Unidentified\":\"\"},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:xn,charCode:function(e){return\"keypress\"===e.type?on(e):0},keyCode:function(e){return\"keydown\"===e.type||\"keyup\"===e.type?e.keyCode:0},which:function(e){return\"keypress\"===e.type?on(e):\"keydown\"===e.type||\"keyup\"===e.type?e.keyCode:0}})),Hn=ln(o({},mn,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),En=ln(o({},dn,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:xn})),Pn=ln(o({},pn,{propertyName:0,elapsedTime:0,pseudoElement:0})),Vn=ln(o({},mn,{deltaX:function(e){return\"deltaX\"in e?e.deltaX:\"wheelDeltaX\"in e?-e.wheelDeltaX:0},deltaY:function(e){return\"deltaY\"in e?e.deltaY:\"wheelDeltaY\"in e?-e.wheelDeltaY:\"wheelDelta\"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0})),Tn=[9,13,27,32],Ln=f&&\"CompositionEvent\"in window,jn=null;f&&\"documentMode\"in document&&(jn=document.documentMode);var Nn=f&&\"TextEvent\"in window&&!jn,Dn=f&&(!Ln||jn&&8<jn&&11>=jn),Rn=String.fromCharCode(32),An=!1;function In(e,t){switch(e){case\"keyup\":return-1!==Tn.indexOf(t.keyCode);case\"keydown\":return 229!==t.keyCode;case\"keypress\":case\"mousedown\":case\"focusout\":return!0;default:return!1}}function Fn(e){return\"object\"===typeof(e=e.detail)&&\"data\"in e?e.data:null}var Wn=!1;var Un={color:!0,date:!0,datetime:!0,\"datetime-local\":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Kn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return\"input\"===t?!!Un[e.type]:\"textarea\"===t}function Bn(e,t,n,r){Pe(r),0<(t=Lr(t,\"onChange\")).length&&(n=new hn(\"onChange\",\"change\",null,n,r),e.push({event:n,listeners:t}))}var Yn=null,qn=null;function Gn(e){_r(e,0)}function $n(e){if(X(to(e)))return e}function Qn(e,t){if(\"change\"===e)return t}var Xn=!1;if(f){var Zn;if(f){var Jn=\"oninput\"in document;if(!Jn){var er=document.createElement(\"div\");er.setAttribute(\"oninput\",\"return;\"),Jn=\"function\"===typeof er.oninput}Zn=Jn}else Zn=!1;Xn=Zn&&(!document.documentMode||9<document.documentMode)}function tr(){Yn&&(Yn.detachEvent(\"onpropertychange\",nr),qn=Yn=null)}function nr(e){if(\"value\"===e.propertyName&&$n(qn)){var t=[];if(Bn(t,qn,e,_e(e)),e=Gn,De)e(t);else{De=!0;try{Te(e,t)}finally{De=!1,Ae()}}}}function rr(e,t,n){\"focusin\"===e?(tr(),qn=n,(Yn=t).attachEvent(\"onpropertychange\",nr)):\"focusout\"===e&&tr()}function or(e){if(\"selectionchange\"===e||\"keyup\"===e||\"keydown\"===e)return $n(qn)}function cr(e,t){if(\"click\"===e)return $n(t)}function ir(e,t){if(\"input\"===e||\"change\"===e)return $n(t)}var ar=\"function\"===typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e===1/t)||e!==e&&t!==t},lr=Object.prototype.hasOwnProperty;function ur(e,t){if(ar(e,t))return!0;if(\"object\"!==typeof e||null===e||\"object\"!==typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++)if(!lr.call(t,n[r])||!ar(e[n[r]],t[n[r]]))return!1;return!0}function sr(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function fr(e,t){var n,r=sr(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=sr(r)}}function pr(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?pr(e,t.parentNode):\"contains\"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function hr(){for(var e=window,t=Z();t instanceof e.HTMLIFrameElement;){try{var n=\"string\"===typeof t.contentWindow.location.href}catch(r){n=!1}if(!n)break;t=Z((e=t.contentWindow).document)}return t}function dr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(\"input\"===t&&(\"text\"===e.type||\"search\"===e.type||\"tel\"===e.type||\"url\"===e.type||\"password\"===e.type)||\"textarea\"===t||\"true\"===e.contentEditable)}var vr=f&&\"documentMode\"in document&&11>=document.documentMode,mr=null,yr=null,br=null,gr=!1;function wr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;gr||null==mr||mr!==Z(r)||(\"selectionStart\"in(r=mr)&&dr(r)?r={start:r.selectionStart,end:r.selectionEnd}:r={anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},br&&ur(br,r)||(br=r,0<(r=Lr(yr,\"onSelect\")).length&&(t=new hn(\"onSelect\",\"select\",null,t,n),e.push({event:t,listeners:r}),t.target=mr)))}jt(\"cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focusin focus focusout blur input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange\".split(\" \"),0),jt(\"drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel\".split(\" \"),1),jt(Lt,2);for(var zr=\"change selectionchange textInput compositionstart compositionend compositionupdate\".split(\" \"),Or=0;Or<zr.length;Or++)Tt.set(zr[Or],0);s(\"onMouseEnter\",[\"mouseout\",\"mouseover\"]),s(\"onMouseLeave\",[\"mouseout\",\"mouseover\"]),s(\"onPointerEnter\",[\"pointerout\",\"pointerover\"]),s(\"onPointerLeave\",[\"pointerout\",\"pointerover\"]),u(\"onChange\",\"change click focusin focusout input keydown keyup selectionchange\".split(\" \")),u(\"onSelect\",\"focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange\".split(\" \")),u(\"onBeforeInput\",[\"compositionend\",\"keypress\",\"textInput\",\"paste\"]),u(\"onCompositionEnd\",\"compositionend focusout keydown keypress keyup mousedown\".split(\" \")),u(\"onCompositionStart\",\"compositionstart focusout keydown keypress keyup mousedown\".split(\" \")),u(\"onCompositionUpdate\",\"compositionupdate focusout keydown keypress keyup mousedown\".split(\" \"));var Cr=\"abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting\".split(\" \"),Mr=new Set(\"cancel close invalid load scroll toggle\".split(\" \").concat(Cr));function Sr(e,t,n){var r=e.type||\"unknown-event\";e.currentTarget=n,function(e,t,n,r,o,c,a,l,u){if($e.apply(this,arguments),Ke){if(!Ke)throw Error(i(198));var s=Be;Ke=!1,Be=null,Ye||(Ye=!0,qe=s)}}(r,t,void 0,e),e.currentTarget=null}function _r(e,t){t=0!==(4&t);for(var n=0;n<e.length;n++){var r=e[n],o=r.event;r=r.listeners;e:{var c=void 0;if(t)for(var i=r.length-1;0<=i;i--){var a=r[i],l=a.instance,u=a.currentTarget;if(a=a.listener,l!==c&&o.isPropagationStopped())break e;Sr(o,a,u),c=l}else for(i=0;i<r.length;i++){if(l=(a=r[i]).instance,u=a.currentTarget,a=a.listener,l!==c&&o.isPropagationStopped())break e;Sr(o,a,u),c=l}}}if(Ye)throw e=qe,Ye=!1,qe=null,e}function xr(e,t){var n=ro(t),r=e+\"__bubble\";n.has(r)||(Pr(t,e,2,!1),n.add(r))}var kr=\"_reactListening\"+Math.random().toString(36).slice(2);function Hr(e){e[kr]||(e[kr]=!0,a.forEach((function(t){Mr.has(t)||Er(t,!1,e,null),Er(t,!0,e,null)})))}function Er(e,t,n,r){var o=4<arguments.length&&void 0!==arguments[4]?arguments[4]:0,c=n;if(\"selectionchange\"===e&&9!==n.nodeType&&(c=n.ownerDocument),null!==r&&!t&&Mr.has(e)){if(\"scroll\"!==e)return;o|=2,c=r}var i=ro(c),a=e+\"__\"+(t?\"capture\":\"bubble\");i.has(a)||(t&&(o|=4),Pr(c,e,o,t),i.add(a))}function Pr(e,t,n,r){var o=Tt.get(t);switch(void 0===o?2:o){case 0:o=Qt;break;case 1:o=Xt;break;default:o=Zt}n=o.bind(null,t,n,e),o=void 0,!Fe||\"touchstart\"!==t&&\"touchmove\"!==t&&\"wheel\"!==t||(o=!0),r?void 0!==o?e.addEventListener(t,n,{capture:!0,passive:o}):e.addEventListener(t,n,!0):void 0!==o?e.addEventListener(t,n,{passive:o}):e.addEventListener(t,n,!1)}function Vr(e,t,n,r,o){var c=r;if(0===(1&t)&&0===(2&t)&&null!==r)e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var a=r.stateNode.containerInfo;if(a===o||8===a.nodeType&&a.parentNode===o)break;if(4===i)for(i=r.return;null!==i;){var l=i.tag;if((3===l||4===l)&&((l=i.stateNode.containerInfo)===o||8===l.nodeType&&l.parentNode===o))return;i=i.return}for(;null!==a;){if(null===(i=Jr(a)))return;if(5===(l=i.tag)||6===l){r=c=i;continue e}a=a.parentNode}}r=r.return}!function(e,t,n){if(Re)return e(t,n);Re=!0;try{Ne(e,t,n)}finally{Re=!1,Ae()}}((function(){var r=c,o=_e(n),i=[];e:{var a=Vt.get(e);if(void 0!==a){var l=hn,u=e;switch(e){case\"keypress\":if(0===on(n))break e;case\"keydown\":case\"keyup\":l=kn;break;case\"focusin\":u=\"focus\",l=gn;break;case\"focusout\":u=\"blur\",l=gn;break;case\"beforeblur\":case\"afterblur\":l=gn;break;case\"click\":if(2===n.button)break e;case\"auxclick\":case\"dblclick\":case\"mousedown\":case\"mousemove\":case\"mouseup\":case\"mouseout\":case\"mouseover\":case\"contextmenu\":l=yn;break;case\"drag\":case\"dragend\":case\"dragenter\":case\"dragexit\":case\"dragleave\":case\"dragover\":case\"dragstart\":case\"drop\":l=bn;break;case\"touchcancel\":case\"touchend\":case\"touchmove\":case\"touchstart\":l=En;break;case kt:case Ht:case Et:l=wn;break;case Pt:l=Pn;break;case\"scroll\":l=vn;break;case\"wheel\":l=Vn;break;case\"copy\":case\"cut\":case\"paste\":l=zn;break;case\"gotpointercapture\":case\"lostpointercapture\":case\"pointercancel\":case\"pointerdown\":case\"pointermove\":case\"pointerout\":case\"pointerover\":case\"pointerup\":l=Hn}var s=0!==(4&t),f=!s&&\"scroll\"===e,p=s?null!==a?a+\"Capture\":null:a;s=[];for(var h,d=r;null!==d;){var v=(h=d).stateNode;if(5===h.tag&&null!==v&&(h=v,null!==p&&(null!=(v=Ie(d,p))&&s.push(Tr(d,v,h)))),f)break;d=d.return}0<s.length&&(a=new l(a,u,null,n,o),i.push({event:a,listeners:s}))}}if(0===(7&t)){if(l=\"mouseout\"===e||\"pointerout\"===e,(!(a=\"mouseover\"===e||\"pointerover\"===e)||0!==(16&t)||!(u=n.relatedTarget||n.fromElement)||!Jr(u)&&!u[Xr])&&(l||a)&&(a=o.window===o?o:(a=o.ownerDocument)?a.defaultView||a.parentWindow:window,l?(l=r,null!==(u=(u=n.relatedTarget||n.toElement)?Jr(u):null)&&(u!==(f=Qe(u))||5!==u.tag&&6!==u.tag)&&(u=null)):(l=null,u=r),l!==u)){if(s=yn,v=\"onMouseLeave\",p=\"onMouseEnter\",d=\"mouse\",\"pointerout\"!==e&&\"pointerover\"!==e||(s=Hn,v=\"onPointerLeave\",p=\"onPointerEnter\",d=\"pointer\"),f=null==l?a:to(l),h=null==u?a:to(u),(a=new s(v,d+\"leave\",l,n,o)).target=f,a.relatedTarget=h,v=null,Jr(o)===r&&((s=new s(p,d+\"enter\",u,n,o)).target=h,s.relatedTarget=f,v=s),f=v,l&&u)e:{for(p=u,d=0,h=s=l;h;h=jr(h))d++;for(h=0,v=p;v;v=jr(v))h++;for(;0<d-h;)s=jr(s),d--;for(;0<h-d;)p=jr(p),h--;for(;d--;){if(s===p||null!==p&&s===p.alternate)break e;s=jr(s),p=jr(p)}s=null}else s=null;null!==l&&Nr(i,a,l,s,!1),null!==u&&null!==f&&Nr(i,f,u,s,!0)}if(\"select\"===(l=(a=r?to(r):window).nodeName&&a.nodeName.toLowerCase())||\"input\"===l&&\"file\"===a.type)var m=Qn;else if(Kn(a))if(Xn)m=ir;else{m=or;var y=rr}else(l=a.nodeName)&&\"input\"===l.toLowerCase()&&(\"checkbox\"===a.type||\"radio\"===a.type)&&(m=cr);switch(m&&(m=m(e,r))?Bn(i,m,n,o):(y&&y(e,a,r),\"focusout\"===e&&(y=a._wrapperState)&&y.controlled&&\"number\"===a.type&&oe(a,\"number\",a.value)),y=r?to(r):window,e){case\"focusin\":(Kn(y)||\"true\"===y.contentEditable)&&(mr=y,yr=r,br=null);break;case\"focusout\":br=yr=mr=null;break;case\"mousedown\":gr=!0;break;case\"contextmenu\":case\"mouseup\":case\"dragend\":gr=!1,wr(i,n,o);break;case\"selectionchange\":if(vr)break;case\"keydown\":case\"keyup\":wr(i,n,o)}var b;if(Ln)e:{switch(e){case\"compositionstart\":var g=\"onCompositionStart\";break e;case\"compositionend\":g=\"onCompositionEnd\";break e;case\"compositionupdate\":g=\"onCompositionUpdate\";break e}g=void 0}else Wn?In(e,n)&&(g=\"onCompositionEnd\"):\"keydown\"===e&&229===n.keyCode&&(g=\"onCompositionStart\");g&&(Dn&&\"ko\"!==n.locale&&(Wn||\"onCompositionStart\"!==g?\"onCompositionEnd\"===g&&Wn&&(b=rn()):(tn=\"value\"in(en=o)?en.value:en.textContent,Wn=!0)),0<(y=Lr(r,g)).length&&(g=new On(g,e,null,n,o),i.push({event:g,listeners:y}),b?g.data=b:null!==(b=Fn(n))&&(g.data=b))),(b=Nn?function(e,t){switch(e){case\"compositionend\":return Fn(t);case\"keypress\":return 32!==t.which?null:(An=!0,Rn);case\"textInput\":return(e=t.data)===Rn&&An?null:e;default:return null}}(e,n):function(e,t){if(Wn)return\"compositionend\"===e||!Ln&&In(e,t)?(e=rn(),nn=tn=en=null,Wn=!1,e):null;switch(e){case\"paste\":return null;case\"keypress\":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case\"compositionend\":return Dn&&\"ko\"!==t.locale?null:t.data;default:return null}}(e,n))&&(0<(r=Lr(r,\"onBeforeInput\")).length&&(o=new On(\"onBeforeInput\",\"beforeinput\",null,n,o),i.push({event:o,listeners:r}),o.data=b))}_r(i,t)}))}function Tr(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Lr(e,t){for(var n=t+\"Capture\",r=[];null!==e;){var o=e,c=o.stateNode;5===o.tag&&null!==c&&(o=c,null!=(c=Ie(e,n))&&r.unshift(Tr(e,c,o)),null!=(c=Ie(e,t))&&r.push(Tr(e,c,o))),e=e.return}return r}function jr(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag);return e||null}function Nr(e,t,n,r,o){for(var c=t._reactName,i=[];null!==n&&n!==r;){var a=n,l=a.alternate,u=a.stateNode;if(null!==l&&l===r)break;5===a.tag&&null!==u&&(a=u,o?null!=(l=Ie(n,c))&&i.unshift(Tr(n,l,a)):o||null!=(l=Ie(n,c))&&i.push(Tr(n,l,a))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}function Dr(){}var Rr=null,Ar=null;function Ir(e,t){switch(e){case\"button\":case\"input\":case\"select\":case\"textarea\":return!!t.autoFocus}return!1}function Fr(e,t){return\"textarea\"===e||\"option\"===e||\"noscript\"===e||\"string\"===typeof t.children||\"number\"===typeof t.children||\"object\"===typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var Wr=\"function\"===typeof setTimeout?setTimeout:void 0,Ur=\"function\"===typeof clearTimeout?clearTimeout:void 0;function Kr(e){1===e.nodeType?e.textContent=\"\":9===e.nodeType&&(null!=(e=e.body)&&(e.textContent=\"\"))}function Br(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function Yr(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if(\"$\"===n||\"$!\"===n||\"$?\"===n){if(0===t)return e;t--}else\"/$\"===n&&t++}e=e.previousSibling}return null}var qr=0;var Gr=Math.random().toString(36).slice(2),$r=\"__reactFiber$\"+Gr,Qr=\"__reactProps$\"+Gr,Xr=\"__reactContainer$\"+Gr,Zr=\"__reactEvents$\"+Gr;function Jr(e){var t=e[$r];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Xr]||n[$r]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=Yr(e);null!==e;){if(n=e[$r])return n;e=Yr(e)}return t}n=(e=n).parentNode}return null}function eo(e){return!(e=e[$r]||e[Xr])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function to(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(i(33))}function no(e){return e[Qr]||null}function ro(e){var t=e[Zr];return void 0===t&&(t=e[Zr]=new Set),t}var oo=[],co=-1;function io(e){return{current:e}}function ao(e){0>co||(e.current=oo[co],oo[co]=null,co--)}function lo(e,t){co++,oo[co]=e.current,e.current=t}var uo={},so=io(uo),fo=io(!1),po=uo;function ho(e,t){var n=e.type.contextTypes;if(!n)return uo;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o,c={};for(o in n)c[o]=t[o];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=c),c}function vo(e){return null!==(e=e.childContextTypes)&&void 0!==e}function mo(){ao(fo),ao(so)}function yo(e,t,n){if(so.current!==uo)throw Error(i(168));lo(so,t),lo(fo,n)}function bo(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,\"function\"!==typeof r.getChildContext)return n;for(var c in r=r.getChildContext())if(!(c in e))throw Error(i(108,q(t)||\"Unknown\",c));return o({},n,r)}function go(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||uo,po=so.current,lo(so,e),lo(fo,fo.current),!0}function wo(e,t,n){var r=e.stateNode;if(!r)throw Error(i(169));n?(e=bo(e,t,po),r.__reactInternalMemoizedMergedChildContext=e,ao(fo),ao(so),lo(so,e)):ao(fo),lo(fo,n)}var zo=null,Oo=null,Co=c.unstable_runWithPriority,Mo=c.unstable_scheduleCallback,So=c.unstable_cancelCallback,_o=c.unstable_shouldYield,xo=c.unstable_requestPaint,ko=c.unstable_now,Ho=c.unstable_getCurrentPriorityLevel,Eo=c.unstable_ImmediatePriority,Po=c.unstable_UserBlockingPriority,Vo=c.unstable_NormalPriority,To=c.unstable_LowPriority,Lo=c.unstable_IdlePriority,jo={},No=void 0!==xo?xo:function(){},Do=null,Ro=null,Ao=!1,Io=ko(),Fo=1e4>Io?ko:function(){return ko()-Io};function Wo(){switch(Ho()){case Eo:return 99;case Po:return 98;case Vo:return 97;case To:return 96;case Lo:return 95;default:throw Error(i(332))}}function Uo(e){switch(e){case 99:return Eo;case 98:return Po;case 97:return Vo;case 96:return To;case 95:return Lo;default:throw Error(i(332))}}function Ko(e,t){return e=Uo(e),Co(e,t)}function Bo(e,t,n){return e=Uo(e),Mo(e,t,n)}function Yo(){if(null!==Ro){var e=Ro;Ro=null,So(e)}qo()}function qo(){if(!Ao&&null!==Do){Ao=!0;var e=0;try{var t=Do;Ko(99,(function(){for(;e<t.length;e++){var n=t[e];do{n=n(!0)}while(null!==n)}})),Do=null}catch(n){throw null!==Do&&(Do=Do.slice(e+1)),Mo(Eo,Yo),n}finally{Ao=!1}}}var Go=z.ReactCurrentBatchConfig;function $o(e,t){if(e&&e.defaultProps){for(var n in t=o({},t),e=e.defaultProps)void 0===t[n]&&(t[n]=e[n]);return t}return t}var Qo=io(null),Xo=null,Zo=null,Jo=null;function ec(){Jo=Zo=Xo=null}function tc(e){var t=Qo.current;ao(Qo),e.type._context._currentValue=t}function nc(e,t){for(;null!==e;){var n=e.alternate;if((e.childLanes&t)===t){if(null===n||(n.childLanes&t)===t)break;n.childLanes|=t}else e.childLanes|=t,null!==n&&(n.childLanes|=t);e=e.return}}function rc(e,t){Xo=e,Jo=Zo=null,null!==(e=e.dependencies)&&null!==e.firstContext&&(0!==(e.lanes&t)&&(Li=!0),e.firstContext=null)}function oc(e,t){if(Jo!==e&&!1!==t&&0!==t)if(\"number\"===typeof t&&1073741823!==t||(Jo=e,t=1073741823),t={context:e,observedBits:t,next:null},null===Zo){if(null===Xo)throw Error(i(308));Zo=t,Xo.dependencies={lanes:0,firstContext:t,responders:null}}else Zo=Zo.next=t;return e._currentValue}var cc=!1;function ic(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null},effects:null}}function ac(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function lc(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function uc(e,t){if(null!==(e=e.updateQueue)){var n=(e=e.shared).pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}}function sc(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var o=null,c=null;if(null!==(n=n.firstBaseUpdate)){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};null===c?o=c=i:c=c.next=i,n=n.next}while(null!==n);null===c?o=c=t:c=c.next=t}else o=c=t;return n={baseState:r.baseState,firstBaseUpdate:o,lastBaseUpdate:c,shared:r.shared,effects:r.effects},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function fc(e,t,n,r){var c=e.updateQueue;cc=!1;var i=c.firstBaseUpdate,a=c.lastBaseUpdate,l=c.shared.pending;if(null!==l){c.shared.pending=null;var u=l,s=u.next;u.next=null,null===a?i=s:a.next=s,a=u;var f=e.alternate;if(null!==f){var p=(f=f.updateQueue).lastBaseUpdate;p!==a&&(null===p?f.firstBaseUpdate=s:p.next=s,f.lastBaseUpdate=u)}}if(null!==i){for(p=c.baseState,a=0,f=s=u=null;;){l=i.lane;var h=i.eventTime;if((r&l)===l){null!==f&&(f=f.next={eventTime:h,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var d=e,v=i;switch(l=t,h=n,v.tag){case 1:if(\"function\"===typeof(d=v.payload)){p=d.call(h,p,l);break e}p=d;break e;case 3:d.flags=-4097&d.flags|64;case 0:if(null===(l=\"function\"===typeof(d=v.payload)?d.call(h,p,l):d)||void 0===l)break e;p=o({},p,l);break e;case 2:cc=!0}}null!==i.callback&&(e.flags|=32,null===(l=c.effects)?c.effects=[i]:l.push(i))}else h={eventTime:h,lane:l,tag:i.tag,payload:i.payload,callback:i.callback,next:null},null===f?(s=f=h,u=p):f=f.next=h,a|=l;if(null===(i=i.next)){if(null===(l=c.shared.pending))break;i=l.next,l.next=null,c.lastBaseUpdate=l,c.shared.pending=null}}null===f&&(u=p),c.baseState=u,c.firstBaseUpdate=s,c.lastBaseUpdate=f,Ra|=a,e.lanes=a,e.memoizedState=p}}function pc(e,t,n){if(e=t.effects,t.effects=null,null!==e)for(t=0;t<e.length;t++){var r=e[t],o=r.callback;if(null!==o){if(r.callback=null,r=n,\"function\"!==typeof o)throw Error(i(191,o));o.call(r)}}}var hc=(new r.Component).refs;function dc(e,t,n,r){n=null===(n=n(r,t=e.memoizedState))||void 0===n?t:o({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var vc={isMounted:function(e){return!!(e=e._reactInternals)&&Qe(e)===e},enqueueSetState:function(e,t,n){e=e._reactInternals;var r=ul(),o=sl(e),c=lc(r,o);c.payload=t,void 0!==n&&null!==n&&(c.callback=n),uc(e,c),fl(e,o,r)},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=ul(),o=sl(e),c=lc(r,o);c.tag=1,c.payload=t,void 0!==n&&null!==n&&(c.callback=n),uc(e,c),fl(e,o,r)},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=ul(),r=sl(e),o=lc(n,r);o.tag=2,void 0!==t&&null!==t&&(o.callback=t),uc(e,o),fl(e,r,n)}};function mc(e,t,n,r,o,c,i){return\"function\"===typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,c,i):!t.prototype||!t.prototype.isPureReactComponent||(!ur(n,r)||!ur(o,c))}function yc(e,t,n){var r=!1,o=uo,c=t.contextType;return\"object\"===typeof c&&null!==c?c=oc(c):(o=vo(t)?po:so.current,c=(r=null!==(r=t.contextTypes)&&void 0!==r)?ho(e,o):uo),t=new t(n,c),e.memoizedState=null!==t.state&&void 0!==t.state?t.state:null,t.updater=vc,e.stateNode=t,t._reactInternals=e,r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=o,e.__reactInternalMemoizedMaskedChildContext=c),t}function bc(e,t,n,r){e=t.state,\"function\"===typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),\"function\"===typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&vc.enqueueReplaceState(t,t.state,null)}function gc(e,t,n,r){var o=e.stateNode;o.props=n,o.state=e.memoizedState,o.refs=hc,ic(e);var c=t.contextType;\"object\"===typeof c&&null!==c?o.context=oc(c):(c=vo(t)?po:so.current,o.context=ho(e,c)),fc(e,n,o,r),o.state=e.memoizedState,\"function\"===typeof(c=t.getDerivedStateFromProps)&&(dc(e,t,c,n),o.state=e.memoizedState),\"function\"===typeof t.getDerivedStateFromProps||\"function\"===typeof o.getSnapshotBeforeUpdate||\"function\"!==typeof o.UNSAFE_componentWillMount&&\"function\"!==typeof o.componentWillMount||(t=o.state,\"function\"===typeof o.componentWillMount&&o.componentWillMount(),\"function\"===typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),t!==o.state&&vc.enqueueReplaceState(o,o.state,null),fc(e,n,o,r),o.state=e.memoizedState),\"function\"===typeof o.componentDidMount&&(e.flags|=4)}var wc=Array.isArray;function zc(e,t,n){if(null!==(e=n.ref)&&\"function\"!==typeof e&&\"object\"!==typeof e){if(n._owner){if(n=n._owner){if(1!==n.tag)throw Error(i(309));var r=n.stateNode}if(!r)throw Error(i(147,e));var o=\"\"+e;return null!==t&&null!==t.ref&&\"function\"===typeof t.ref&&t.ref._stringRef===o?t.ref:((t=function(e){var t=r.refs;t===hc&&(t=r.refs={}),null===e?delete t[o]:t[o]=e})._stringRef=o,t)}if(\"string\"!==typeof e)throw Error(i(284));if(!n._owner)throw Error(i(290,e))}return e}function Oc(e,t){if(\"textarea\"!==e.type)throw Error(i(31,\"[object Object]\"===Object.prototype.toString.call(t)?\"object with keys {\"+Object.keys(t).join(\", \")+\"}\":t))}function Cc(e){function t(t,n){if(e){var r=t.lastEffect;null!==r?(r.nextEffect=n,t.lastEffect=n):t.firstEffect=t.lastEffect=n,n.nextEffect=null,n.flags=8}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e,t){for(e=new Map;null!==t;)null!==t.key?e.set(t.key,t):e.set(t.index,t),t=t.sibling;return e}function o(e,t){return(e=Ul(e,t)).index=0,e.sibling=null,e}function c(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.flags=2,n):r:(t.flags=2,n):n}function a(t){return e&&null===t.alternate&&(t.flags=2),t}function l(e,t,n,r){return null===t||6!==t.tag?((t=ql(n,e.mode,r)).return=e,t):((t=o(t,n)).return=e,t)}function u(e,t,n,r){return null!==t&&t.elementType===n.type?((r=o(t,n.props)).ref=zc(e,t,n),r.return=e,r):((r=Kl(n.type,n.key,n.props,null,e.mode,r)).ref=zc(e,t,n),r.return=e,r)}function s(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=Gl(n,e.mode,r)).return=e,t):((t=o(t,n.children||[])).return=e,t)}function f(e,t,n,r,c){return null===t||7!==t.tag?((t=Bl(n,e.mode,r,c)).return=e,t):((t=o(t,n)).return=e,t)}function p(e,t,n){if(\"string\"===typeof t||\"number\"===typeof t)return(t=ql(\"\"+t,e.mode,n)).return=e,t;if(\"object\"===typeof t&&null!==t){switch(t.$$typeof){case O:return(n=Kl(t.type,t.key,t.props,null,e.mode,n)).ref=zc(e,null,t),n.return=e,n;case C:return(t=Gl(t,e.mode,n)).return=e,t}if(wc(t)||W(t))return(t=Bl(t,e.mode,n,null)).return=e,t;Oc(e,t)}return null}function h(e,t,n,r){var o=null!==t?t.key:null;if(\"string\"===typeof n||\"number\"===typeof n)return null!==o?null:l(e,t,\"\"+n,r);if(\"object\"===typeof n&&null!==n){switch(n.$$typeof){case O:return n.key===o?n.type===M?f(e,t,n.props.children,r,o):u(e,t,n,r):null;case C:return n.key===o?s(e,t,n,r):null}if(wc(n)||W(n))return null!==o?null:f(e,t,n,r,null);Oc(e,n)}return null}function d(e,t,n,r,o){if(\"string\"===typeof r||\"number\"===typeof r)return l(t,e=e.get(n)||null,\"\"+r,o);if(\"object\"===typeof r&&null!==r){switch(r.$$typeof){case O:return e=e.get(null===r.key?n:r.key)||null,r.type===M?f(t,e,r.props.children,o,r.key):u(t,e,r,o);case C:return s(t,e=e.get(null===r.key?n:r.key)||null,r,o)}if(wc(r)||W(r))return f(t,e=e.get(n)||null,r,o,null);Oc(t,r)}return null}function v(o,i,a,l){for(var u=null,s=null,f=i,v=i=0,m=null;null!==f&&v<a.length;v++){f.index>v?(m=f,f=null):m=f.sibling;var y=h(o,f,a[v],l);if(null===y){null===f&&(f=m);break}e&&f&&null===y.alternate&&t(o,f),i=c(y,i,v),null===s?u=y:s.sibling=y,s=y,f=m}if(v===a.length)return n(o,f),u;if(null===f){for(;v<a.length;v++)null!==(f=p(o,a[v],l))&&(i=c(f,i,v),null===s?u=f:s.sibling=f,s=f);return u}for(f=r(o,f);v<a.length;v++)null!==(m=d(f,o,v,a[v],l))&&(e&&null!==m.alternate&&f.delete(null===m.key?v:m.key),i=c(m,i,v),null===s?u=m:s.sibling=m,s=m);return e&&f.forEach((function(e){return t(o,e)})),u}function m(o,a,l,u){var s=W(l);if(\"function\"!==typeof s)throw Error(i(150));if(null==(l=s.call(l)))throw Error(i(151));for(var f=s=null,v=a,m=a=0,y=null,b=l.next();null!==v&&!b.done;m++,b=l.next()){v.index>m?(y=v,v=null):y=v.sibling;var g=h(o,v,b.value,u);if(null===g){null===v&&(v=y);break}e&&v&&null===g.alternate&&t(o,v),a=c(g,a,m),null===f?s=g:f.sibling=g,f=g,v=y}if(b.done)return n(o,v),s;if(null===v){for(;!b.done;m++,b=l.next())null!==(b=p(o,b.value,u))&&(a=c(b,a,m),null===f?s=b:f.sibling=b,f=b);return s}for(v=r(o,v);!b.done;m++,b=l.next())null!==(b=d(v,o,m,b.value,u))&&(e&&null!==b.alternate&&v.delete(null===b.key?m:b.key),a=c(b,a,m),null===f?s=b:f.sibling=b,f=b);return e&&v.forEach((function(e){return t(o,e)})),s}return function(e,r,c,l){var u=\"object\"===typeof c&&null!==c&&c.type===M&&null===c.key;u&&(c=c.props.children);var s=\"object\"===typeof c&&null!==c;if(s)switch(c.$$typeof){case O:e:{for(s=c.key,u=r;null!==u;){if(u.key===s){switch(u.tag){case 7:if(c.type===M){n(e,u.sibling),(r=o(u,c.props.children)).return=e,e=r;break e}break;default:if(u.elementType===c.type){n(e,u.sibling),(r=o(u,c.props)).ref=zc(e,u,c),r.return=e,e=r;break e}}n(e,u);break}t(e,u),u=u.sibling}c.type===M?((r=Bl(c.props.children,e.mode,l,c.key)).return=e,e=r):((l=Kl(c.type,c.key,c.props,null,e.mode,l)).ref=zc(e,r,c),l.return=e,e=l)}return a(e);case C:e:{for(u=c.key;null!==r;){if(r.key===u){if(4===r.tag&&r.stateNode.containerInfo===c.containerInfo&&r.stateNode.implementation===c.implementation){n(e,r.sibling),(r=o(r,c.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=Gl(c,e.mode,l)).return=e,e=r}return a(e)}if(\"string\"===typeof c||\"number\"===typeof c)return c=\"\"+c,null!==r&&6===r.tag?(n(e,r.sibling),(r=o(r,c)).return=e,e=r):(n(e,r),(r=ql(c,e.mode,l)).return=e,e=r),a(e);if(wc(c))return v(e,r,c,l);if(W(c))return m(e,r,c,l);if(s&&Oc(e,c),\"undefined\"===typeof c&&!u)switch(e.tag){case 1:case 22:case 0:case 11:case 15:throw Error(i(152,q(e.type)||\"Component\"))}return n(e,r)}}var Mc=Cc(!0),Sc=Cc(!1),_c={},xc=io(_c),kc=io(_c),Hc=io(_c);function Ec(e){if(e===_c)throw Error(i(174));return e}function Pc(e,t){switch(lo(Hc,t),lo(kc,e),lo(xc,_c),e=t.nodeType){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:de(null,\"\");break;default:t=de(t=(e=8===e?t.parentNode:t).namespaceURI||null,e=e.tagName)}ao(xc),lo(xc,t)}function Vc(){ao(xc),ao(kc),ao(Hc)}function Tc(e){Ec(Hc.current);var t=Ec(xc.current),n=de(t,e.type);t!==n&&(lo(kc,e),lo(xc,n))}function Lc(e){kc.current===e&&(ao(xc),ao(kc))}var jc=io(0);function Nc(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||\"$?\"===n.data||\"$!\"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!==(64&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var Dc=null,Rc=null,Ac=!1;function Ic(e,t){var n=Fl(5,null,null,0);n.elementType=\"DELETED\",n.type=\"DELETED\",n.stateNode=t,n.return=e,n.flags=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function Fc(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=\"\"===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);case 13:default:return!1}}function Wc(e){if(Ac){var t=Rc;if(t){var n=t;if(!Fc(e,t)){if(!(t=Br(n.nextSibling))||!Fc(e,t))return e.flags=-1025&e.flags|2,Ac=!1,void(Dc=e);Ic(Dc,n)}Dc=e,Rc=Br(t.firstChild)}else e.flags=-1025&e.flags|2,Ac=!1,Dc=e}}function Uc(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;Dc=e}function Kc(e){if(e!==Dc)return!1;if(!Ac)return Uc(e),Ac=!0,!1;var t=e.type;if(5!==e.tag||\"head\"!==t&&\"body\"!==t&&!Fr(t,e.memoizedProps))for(t=Rc;t;)Ic(e,t),t=Br(t.nextSibling);if(Uc(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(i(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if(\"/$\"===n){if(0===t){Rc=Br(e.nextSibling);break e}t--}else\"$\"!==n&&\"$!\"!==n&&\"$?\"!==n||t++}e=e.nextSibling}Rc=null}}else Rc=Dc?Br(e.stateNode.nextSibling):null;return!0}function Bc(){Rc=Dc=null,Ac=!1}var Yc=[];function qc(){for(var e=0;e<Yc.length;e++)Yc[e]._workInProgressVersionPrimary=null;Yc.length=0}var Gc=z.ReactCurrentDispatcher,$c=z.ReactCurrentBatchConfig,Qc=0,Xc=null,Zc=null,Jc=null,ei=!1,ti=!1;function ni(){throw Error(i(321))}function ri(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!ar(e[n],t[n]))return!1;return!0}function oi(e,t,n,r,o,c){if(Qc=c,Xc=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,Gc.current=null===e||null===e.memoizedState?Ei:Pi,e=n(r,o),ti){c=0;do{if(ti=!1,!(25>c))throw Error(i(301));c+=1,Jc=Zc=null,t.updateQueue=null,Gc.current=Vi,e=n(r,o)}while(ti)}if(Gc.current=Hi,t=null!==Zc&&null!==Zc.next,Qc=0,Jc=Zc=Xc=null,ei=!1,t)throw Error(i(300));return e}function ci(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===Jc?Xc.memoizedState=Jc=e:Jc=Jc.next=e,Jc}function ii(){if(null===Zc){var e=Xc.alternate;e=null!==e?e.memoizedState:null}else e=Zc.next;var t=null===Jc?Xc.memoizedState:Jc.next;if(null!==t)Jc=t,Zc=e;else{if(null===e)throw Error(i(310));e={memoizedState:(Zc=e).memoizedState,baseState:Zc.baseState,baseQueue:Zc.baseQueue,queue:Zc.queue,next:null},null===Jc?Xc.memoizedState=Jc=e:Jc=Jc.next=e}return Jc}function ai(e,t){return\"function\"===typeof t?t(e):t}function li(e){var t=ii(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=Zc,o=r.baseQueue,c=n.pending;if(null!==c){if(null!==o){var a=o.next;o.next=c.next,c.next=a}r.baseQueue=o=c,n.pending=null}if(null!==o){o=o.next,r=r.baseState;var l=a=c=null,u=o;do{var s=u.lane;if((Qc&s)===s)null!==l&&(l=l.next={lane:0,action:u.action,eagerReducer:u.eagerReducer,eagerState:u.eagerState,next:null}),r=u.eagerReducer===e?u.eagerState:e(r,u.action);else{var f={lane:s,action:u.action,eagerReducer:u.eagerReducer,eagerState:u.eagerState,next:null};null===l?(a=l=f,c=r):l=l.next=f,Xc.lanes|=s,Ra|=s}u=u.next}while(null!==u&&u!==o);null===l?c=r:l.next=a,ar(r,t.memoizedState)||(Li=!0),t.memoizedState=r,t.baseState=c,t.baseQueue=l,n.lastRenderedState=r}return[t.memoizedState,n.dispatch]}function ui(e){var t=ii(),n=t.queue;if(null===n)throw Error(i(311));n.lastRenderedReducer=e;var r=n.dispatch,o=n.pending,c=t.memoizedState;if(null!==o){n.pending=null;var a=o=o.next;do{c=e(c,a.action),a=a.next}while(a!==o);ar(c,t.memoizedState)||(Li=!0),t.memoizedState=c,null===t.baseQueue&&(t.baseState=c),n.lastRenderedState=c}return[c,r]}function si(e,t,n){var r=t._getVersion;r=r(t._source);var o=t._workInProgressVersionPrimary;if(null!==o?e=o===r:(e=e.mutableReadLanes,(e=(Qc&e)===e)&&(t._workInProgressVersionPrimary=r,Yc.push(t))),e)return n(t._source);throw Yc.push(t),Error(i(350))}function fi(e,t,n,r){var o=Ea;if(null===o)throw Error(i(349));var c=t._getVersion,a=c(t._source),l=Gc.current,u=l.useState((function(){return si(o,t,n)})),s=u[1],f=u[0];u=Jc;var p=e.memoizedState,h=p.refs,d=h.getSnapshot,v=p.source;p=p.subscribe;var m=Xc;return e.memoizedState={refs:h,source:t,subscribe:r},l.useEffect((function(){h.getSnapshot=n,h.setSnapshot=s;var e=c(t._source);if(!ar(a,e)){e=n(t._source),ar(f,e)||(s(e),e=sl(m),o.mutableReadLanes|=e&o.pendingLanes),e=o.mutableReadLanes,o.entangledLanes|=e;for(var r=o.entanglements,i=e;0<i;){var l=31-Kt(i),u=1<<l;r[l]|=e,i&=~u}}}),[n,t,r]),l.useEffect((function(){return r(t._source,(function(){var e=h.getSnapshot,n=h.setSnapshot;try{n(e(t._source));var r=sl(m);o.mutableReadLanes|=r&o.pendingLanes}catch(c){n((function(){throw c}))}}))}),[t,r]),ar(d,n)&&ar(v,t)&&ar(p,r)||((e={pending:null,dispatch:null,lastRenderedReducer:ai,lastRenderedState:f}).dispatch=s=ki.bind(null,Xc,e),u.queue=e,u.baseQueue=null,f=si(o,t,n),u.memoizedState=u.baseState=f),f}function pi(e,t,n){return fi(ii(),e,t,n)}function hi(e){var t=ci();return\"function\"===typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={pending:null,dispatch:null,lastRenderedReducer:ai,lastRenderedState:e}).dispatch=ki.bind(null,Xc,e),[t.memoizedState,e]}function di(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===(t=Xc.updateQueue)?(t={lastEffect:null},Xc.updateQueue=t,t.lastEffect=e.next=e):null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function vi(e){return e={current:e},ci().memoizedState=e}function mi(){return ii().memoizedState}function yi(e,t,n,r){var o=ci();Xc.flags|=e,o.memoizedState=di(1|t,n,void 0,void 0===r?null:r)}function bi(e,t,n,r){var o=ii();r=void 0===r?null:r;var c=void 0;if(null!==Zc){var i=Zc.memoizedState;if(c=i.destroy,null!==r&&ri(r,i.deps))return void di(t,n,c,r)}Xc.flags|=e,o.memoizedState=di(1|t,n,c,r)}function gi(e,t){return yi(516,4,e,t)}function wi(e,t){return bi(516,4,e,t)}function zi(e,t){return bi(4,2,e,t)}function Oi(e,t){return\"function\"===typeof t?(e=e(),t(e),function(){t(null)}):null!==t&&void 0!==t?(e=e(),t.current=e,function(){t.current=null}):void 0}function Ci(e,t,n){return n=null!==n&&void 0!==n?n.concat([e]):null,bi(4,2,Oi.bind(null,t,e),n)}function Mi(){}function Si(e,t){var n=ii();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&ri(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function _i(e,t){var n=ii();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&ri(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)}function xi(e,t){var n=Wo();Ko(98>n?98:n,(function(){e(!0)})),Ko(97<n?97:n,(function(){var n=$c.transition;$c.transition=1;try{e(!1),t()}finally{$c.transition=n}}))}function ki(e,t,n){var r=ul(),o=sl(e),c={lane:o,action:n,eagerReducer:null,eagerState:null,next:null},i=t.pending;if(null===i?c.next=c:(c.next=i.next,i.next=c),t.pending=c,i=e.alternate,e===Xc||null!==i&&i===Xc)ti=ei=!0;else{if(0===e.lanes&&(null===i||0===i.lanes)&&null!==(i=t.lastRenderedReducer))try{var a=t.lastRenderedState,l=i(a,n);if(c.eagerReducer=i,c.eagerState=l,ar(l,a))return}catch(u){}fl(e,o,r)}}var Hi={readContext:oc,useCallback:ni,useContext:ni,useEffect:ni,useImperativeHandle:ni,useLayoutEffect:ni,useMemo:ni,useReducer:ni,useRef:ni,useState:ni,useDebugValue:ni,useDeferredValue:ni,useTransition:ni,useMutableSource:ni,useOpaqueIdentifier:ni,unstable_isNewReconciler:!1},Ei={readContext:oc,useCallback:function(e,t){return ci().memoizedState=[e,void 0===t?null:t],e},useContext:oc,useEffect:gi,useImperativeHandle:function(e,t,n){return n=null!==n&&void 0!==n?n.concat([e]):null,yi(4,2,Oi.bind(null,t,e),n)},useLayoutEffect:function(e,t){return yi(4,2,e,t)},useMemo:function(e,t){var n=ci();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=ci();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={pending:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=ki.bind(null,Xc,e),[r.memoizedState,e]},useRef:vi,useState:hi,useDebugValue:Mi,useDeferredValue:function(e){var t=hi(e),n=t[0],r=t[1];return gi((function(){var t=$c.transition;$c.transition=1;try{r(e)}finally{$c.transition=t}}),[e]),n},useTransition:function(){var e=hi(!1),t=e[0];return vi(e=xi.bind(null,e[1])),[e,t]},useMutableSource:function(e,t,n){var r=ci();return r.memoizedState={refs:{getSnapshot:t,setSnapshot:null},source:e,subscribe:n},fi(r,e,t,n)},useOpaqueIdentifier:function(){if(Ac){var e=!1,t=function(e){return{$$typeof:j,toString:e,valueOf:e}}((function(){throw e||(e=!0,n(\"r:\"+(qr++).toString(36))),Error(i(355))})),n=hi(t)[1];return 0===(2&Xc.mode)&&(Xc.flags|=516,di(5,(function(){n(\"r:\"+(qr++).toString(36))}),void 0,null)),t}return hi(t=\"r:\"+(qr++).toString(36)),t},unstable_isNewReconciler:!1},Pi={readContext:oc,useCallback:Si,useContext:oc,useEffect:wi,useImperativeHandle:Ci,useLayoutEffect:zi,useMemo:_i,useReducer:li,useRef:mi,useState:function(){return li(ai)},useDebugValue:Mi,useDeferredValue:function(e){var t=li(ai),n=t[0],r=t[1];return wi((function(){var t=$c.transition;$c.transition=1;try{r(e)}finally{$c.transition=t}}),[e]),n},useTransition:function(){var e=li(ai)[0];return[mi().current,e]},useMutableSource:pi,useOpaqueIdentifier:function(){return li(ai)[0]},unstable_isNewReconciler:!1},Vi={readContext:oc,useCallback:Si,useContext:oc,useEffect:wi,useImperativeHandle:Ci,useLayoutEffect:zi,useMemo:_i,useReducer:ui,useRef:mi,useState:function(){return ui(ai)},useDebugValue:Mi,useDeferredValue:function(e){var t=ui(ai),n=t[0],r=t[1];return wi((function(){var t=$c.transition;$c.transition=1;try{r(e)}finally{$c.transition=t}}),[e]),n},useTransition:function(){var e=ui(ai)[0];return[mi().current,e]},useMutableSource:pi,useOpaqueIdentifier:function(){return ui(ai)[0]},unstable_isNewReconciler:!1},Ti=z.ReactCurrentOwner,Li=!1;function ji(e,t,n,r){t.child=null===e?Sc(t,null,n,r):Mc(t,e.child,n,r)}function Ni(e,t,n,r,o){n=n.render;var c=t.ref;return rc(t,o),r=oi(e,t,n,r,c,o),null===e||Li?(t.flags|=1,ji(e,t,r,o),t.child):(t.updateQueue=e.updateQueue,t.flags&=-517,e.lanes&=~o,na(e,t,o))}function Di(e,t,n,r,o,c){if(null===e){var i=n.type;return\"function\"!==typeof i||Wl(i)||void 0!==i.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Kl(n.type,null,r,t,t.mode,c)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=i,Ri(e,t,i,r,o,c))}return i=e.child,0===(o&c)&&(o=i.memoizedProps,(n=null!==(n=n.compare)?n:ur)(o,r)&&e.ref===t.ref)?na(e,t,c):(t.flags|=1,(e=Ul(i,r)).ref=t.ref,e.return=t,t.child=e)}function Ri(e,t,n,r,o,c){if(null!==e&&ur(e.memoizedProps,r)&&e.ref===t.ref){if(Li=!1,0===(c&o))return t.lanes=e.lanes,na(e,t,c);0!==(16384&e.flags)&&(Li=!0)}return Fi(e,t,n,r,c)}function Ai(e,t,n){var r=t.pendingProps,o=r.children,c=null!==e?e.memoizedState:null;if(\"hidden\"===r.mode||\"unstable-defer-without-hiding\"===r.mode)if(0===(4&t.mode))t.memoizedState={baseLanes:0},gl(t,n);else{if(0===(1073741824&n))return e=null!==c?c.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e},gl(t,e),null;t.memoizedState={baseLanes:0},gl(t,null!==c?c.baseLanes:n)}else null!==c?(r=c.baseLanes|n,t.memoizedState=null):r=n,gl(t,r);return ji(e,t,o,n),t.child}function Ii(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&(t.flags|=128)}function Fi(e,t,n,r,o){var c=vo(n)?po:so.current;return c=ho(t,c),rc(t,o),n=oi(e,t,n,r,c,o),null===e||Li?(t.flags|=1,ji(e,t,n,o),t.child):(t.updateQueue=e.updateQueue,t.flags&=-517,e.lanes&=~o,na(e,t,o))}function Wi(e,t,n,r,o){if(vo(n)){var c=!0;go(t)}else c=!1;if(rc(t,o),null===t.stateNode)null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),yc(t,n,r),gc(t,n,r,o),r=!0;else if(null===e){var i=t.stateNode,a=t.memoizedProps;i.props=a;var l=i.context,u=n.contextType;\"object\"===typeof u&&null!==u?u=oc(u):u=ho(t,u=vo(n)?po:so.current);var s=n.getDerivedStateFromProps,f=\"function\"===typeof s||\"function\"===typeof i.getSnapshotBeforeUpdate;f||\"function\"!==typeof i.UNSAFE_componentWillReceiveProps&&\"function\"!==typeof i.componentWillReceiveProps||(a!==r||l!==u)&&bc(t,i,r,u),cc=!1;var p=t.memoizedState;i.state=p,fc(t,r,i,o),l=t.memoizedState,a!==r||p!==l||fo.current||cc?(\"function\"===typeof s&&(dc(t,n,s,r),l=t.memoizedState),(a=cc||mc(t,n,a,r,p,l,u))?(f||\"function\"!==typeof i.UNSAFE_componentWillMount&&\"function\"!==typeof i.componentWillMount||(\"function\"===typeof i.componentWillMount&&i.componentWillMount(),\"function\"===typeof i.UNSAFE_componentWillMount&&i.UNSAFE_componentWillMount()),\"function\"===typeof i.componentDidMount&&(t.flags|=4)):(\"function\"===typeof i.componentDidMount&&(t.flags|=4),t.memoizedProps=r,t.memoizedState=l),i.props=r,i.state=l,i.context=u,r=a):(\"function\"===typeof i.componentDidMount&&(t.flags|=4),r=!1)}else{i=t.stateNode,ac(e,t),a=t.memoizedProps,u=t.type===t.elementType?a:$o(t.type,a),i.props=u,f=t.pendingProps,p=i.context,\"object\"===typeof(l=n.contextType)&&null!==l?l=oc(l):l=ho(t,l=vo(n)?po:so.current);var h=n.getDerivedStateFromProps;(s=\"function\"===typeof h||\"function\"===typeof i.getSnapshotBeforeUpdate)||\"function\"!==typeof i.UNSAFE_componentWillReceiveProps&&\"function\"!==typeof i.componentWillReceiveProps||(a!==f||p!==l)&&bc(t,i,r,l),cc=!1,p=t.memoizedState,i.state=p,fc(t,r,i,o);var d=t.memoizedState;a!==f||p!==d||fo.current||cc?(\"function\"===typeof h&&(dc(t,n,h,r),d=t.memoizedState),(u=cc||mc(t,n,u,r,p,d,l))?(s||\"function\"!==typeof i.UNSAFE_componentWillUpdate&&\"function\"!==typeof i.componentWillUpdate||(\"function\"===typeof i.componentWillUpdate&&i.componentWillUpdate(r,d,l),\"function\"===typeof i.UNSAFE_componentWillUpdate&&i.UNSAFE_componentWillUpdate(r,d,l)),\"function\"===typeof i.componentDidUpdate&&(t.flags|=4),\"function\"===typeof i.getSnapshotBeforeUpdate&&(t.flags|=256)):(\"function\"!==typeof i.componentDidUpdate||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),\"function\"!==typeof i.getSnapshotBeforeUpdate||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=256),t.memoizedProps=r,t.memoizedState=d),i.props=r,i.state=d,i.context=l,r=u):(\"function\"!==typeof i.componentDidUpdate||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),\"function\"!==typeof i.getSnapshotBeforeUpdate||a===e.memoizedProps&&p===e.memoizedState||(t.flags|=256),r=!1)}return Ui(e,t,n,r,c,o)}function Ui(e,t,n,r,o,c){Ii(e,t);var i=0!==(64&t.flags);if(!r&&!i)return o&&wo(t,n,!1),na(e,t,c);r=t.stateNode,Ti.current=t;var a=i&&\"function\"!==typeof n.getDerivedStateFromError?null:r.render();return t.flags|=1,null!==e&&i?(t.child=Mc(t,e.child,null,c),t.child=Mc(t,null,a,c)):ji(e,t,a,c),t.memoizedState=r.state,o&&wo(t,n,!0),t.child}function Ki(e){var t=e.stateNode;t.pendingContext?yo(0,t.pendingContext,t.pendingContext!==t.context):t.context&&yo(0,t.context,!1),Pc(e,t.containerInfo)}var Bi,Yi,qi,Gi={dehydrated:null,retryLane:0};function $i(e,t,n){var r,o=t.pendingProps,c=jc.current,i=!1;return(r=0!==(64&t.flags))||(r=(null===e||null!==e.memoizedState)&&0!==(2&c)),r?(i=!0,t.flags&=-65):null!==e&&null===e.memoizedState||void 0===o.fallback||!0===o.unstable_avoidThisFallback||(c|=1),lo(jc,1&c),null===e?(void 0!==o.fallback&&Wc(t),e=o.children,c=o.fallback,i?(e=Qi(t,e,c,n),t.child.memoizedState={baseLanes:n},t.memoizedState=Gi,e):\"number\"===typeof o.unstable_expectedLoadTime?(e=Qi(t,e,c,n),t.child.memoizedState={baseLanes:n},t.memoizedState=Gi,t.lanes=33554432,e):((n=Yl({mode:\"visible\",children:e},t.mode,n,null)).return=t,t.child=n)):(e.memoizedState,i?(o=Zi(e,t,o.children,o.fallback,n),i=t.child,c=e.child.memoizedState,i.memoizedState=null===c?{baseLanes:n}:{baseLanes:c.baseLanes|n},i.childLanes=e.childLanes&~n,t.memoizedState=Gi,o):(n=Xi(e,t,o.children,n),t.memoizedState=null,n))}function Qi(e,t,n,r){var o=e.mode,c=e.child;return t={mode:\"hidden\",children:t},0===(2&o)&&null!==c?(c.childLanes=0,c.pendingProps=t):c=Yl(t,o,0,null),n=Bl(n,o,r,null),c.return=e,n.return=e,c.sibling=n,e.child=c,n}function Xi(e,t,n,r){var o=e.child;return e=o.sibling,n=Ul(o,{mode:\"visible\",children:n}),0===(2&t.mode)&&(n.lanes=r),n.return=t,n.sibling=null,null!==e&&(e.nextEffect=null,e.flags=8,t.firstEffect=t.lastEffect=e),t.child=n}function Zi(e,t,n,r,o){var c=t.mode,i=e.child;e=i.sibling;var a={mode:\"hidden\",children:n};return 0===(2&c)&&t.child!==i?((n=t.child).childLanes=0,n.pendingProps=a,null!==(i=n.lastEffect)?(t.firstEffect=n.firstEffect,t.lastEffect=i,i.nextEffect=null):t.firstEffect=t.lastEffect=null):n=Ul(i,a),null!==e?r=Ul(e,r):(r=Bl(r,c,o,null)).flags|=2,r.return=t,n.return=t,n.sibling=r,t.child=n,r}function Ji(e,t){e.lanes|=t;var n=e.alternate;null!==n&&(n.lanes|=t),nc(e.return,t)}function ea(e,t,n,r,o,c){var i=e.memoizedState;null===i?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:o,lastEffect:c}:(i.isBackwards=t,i.rendering=null,i.renderingStartTime=0,i.last=r,i.tail=n,i.tailMode=o,i.lastEffect=c)}function ta(e,t,n){var r=t.pendingProps,o=r.revealOrder,c=r.tail;if(ji(e,t,r.children,n),0!==(2&(r=jc.current)))r=1&r|2,t.flags|=64;else{if(null!==e&&0!==(64&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&Ji(e,n);else if(19===e.tag)Ji(e,n);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(lo(jc,r),0===(2&t.mode))t.memoizedState=null;else switch(o){case\"forwards\":for(n=t.child,o=null;null!==n;)null!==(e=n.alternate)&&null===Nc(e)&&(o=n),n=n.sibling;null===(n=o)?(o=t.child,t.child=null):(o=n.sibling,n.sibling=null),ea(t,!1,o,n,c,t.lastEffect);break;case\"backwards\":for(n=null,o=t.child,t.child=null;null!==o;){if(null!==(e=o.alternate)&&null===Nc(e)){t.child=o;break}e=o.sibling,o.sibling=n,n=o,o=e}ea(t,!0,n,null,c,t.lastEffect);break;case\"together\":ea(t,!1,null,null,void 0,t.lastEffect);break;default:t.memoizedState=null}return t.child}function na(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),Ra|=t.lanes,0!==(n&t.childLanes)){if(null!==e&&t.child!==e.child)throw Error(i(153));if(null!==t.child){for(n=Ul(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Ul(e,e.pendingProps)).return=t;n.sibling=null}return t.child}return null}function ra(e,t){if(!Ac)switch(e.tailMode){case\"hidden\":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case\"collapsed\":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function oa(e,t,n){var r=t.pendingProps;switch(t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return vo(t.type)&&mo(),null;case 3:return Vc(),ao(fo),ao(so),qc(),(r=t.stateNode).pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),null!==e&&null!==e.child||(Kc(t)?t.flags|=4:r.hydrate||(t.flags|=256)),null;case 5:Lc(t);var c=Ec(Hc.current);if(n=t.type,null!==e&&null!=t.stateNode)Yi(e,t,n,r),e.ref!==t.ref&&(t.flags|=128);else{if(!r){if(null===t.stateNode)throw Error(i(166));return null}if(e=Ec(xc.current),Kc(t)){r=t.stateNode,n=t.type;var a=t.memoizedProps;switch(r[$r]=t,r[Qr]=a,n){case\"dialog\":xr(\"cancel\",r),xr(\"close\",r);break;case\"iframe\":case\"object\":case\"embed\":xr(\"load\",r);break;case\"video\":case\"audio\":for(e=0;e<Cr.length;e++)xr(Cr[e],r);break;case\"source\":xr(\"error\",r);break;case\"img\":case\"image\":case\"link\":xr(\"error\",r),xr(\"load\",r);break;case\"details\":xr(\"toggle\",r);break;case\"input\":ee(r,a),xr(\"invalid\",r);break;case\"select\":r._wrapperState={wasMultiple:!!a.multiple},xr(\"invalid\",r);break;case\"textarea\":le(r,a),xr(\"invalid\",r)}for(var u in Me(n,a),e=null,a)a.hasOwnProperty(u)&&(c=a[u],\"children\"===u?\"string\"===typeof c?r.textContent!==c&&(e=[\"children\",c]):\"number\"===typeof c&&r.textContent!==\"\"+c&&(e=[\"children\",\"\"+c]):l.hasOwnProperty(u)&&null!=c&&\"onScroll\"===u&&xr(\"scroll\",r));switch(n){case\"input\":Q(r),re(r,a,!0);break;case\"textarea\":Q(r),se(r);break;case\"select\":case\"option\":break;default:\"function\"===typeof a.onClick&&(r.onclick=Dr)}r=e,t.updateQueue=r,null!==r&&(t.flags|=4)}else{switch(u=9===c.nodeType?c:c.ownerDocument,e===fe&&(e=he(n)),e===fe?\"script\"===n?((e=u.createElement(\"div\")).innerHTML=\"<script><\\/script>\",e=e.removeChild(e.firstChild)):\"string\"===typeof r.is?e=u.createElement(n,{is:r.is}):(e=u.createElement(n),\"select\"===n&&(u=e,r.multiple?u.multiple=!0:r.size&&(u.size=r.size))):e=u.createElementNS(e,n),e[$r]=t,e[Qr]=r,Bi(e,t),t.stateNode=e,u=Se(n,r),n){case\"dialog\":xr(\"cancel\",e),xr(\"close\",e),c=r;break;case\"iframe\":case\"object\":case\"embed\":xr(\"load\",e),c=r;break;case\"video\":case\"audio\":for(c=0;c<Cr.length;c++)xr(Cr[c],e);c=r;break;case\"source\":xr(\"error\",e),c=r;break;case\"img\":case\"image\":case\"link\":xr(\"error\",e),xr(\"load\",e),c=r;break;case\"details\":xr(\"toggle\",e),c=r;break;case\"input\":ee(e,r),c=J(e,r),xr(\"invalid\",e);break;case\"option\":c=ce(e,r);break;case\"select\":e._wrapperState={wasMultiple:!!r.multiple},c=o({},r,{value:void 0}),xr(\"invalid\",e);break;case\"textarea\":le(e,r),c=ae(e,r),xr(\"invalid\",e);break;default:c=r}Me(n,c);var s=c;for(a in s)if(s.hasOwnProperty(a)){var f=s[a];\"style\"===a?Oe(e,f):\"dangerouslySetInnerHTML\"===a?null!=(f=f?f.__html:void 0)&&ye(e,f):\"children\"===a?\"string\"===typeof f?(\"textarea\"!==n||\"\"!==f)&&be(e,f):\"number\"===typeof f&&be(e,\"\"+f):\"suppressContentEditableWarning\"!==a&&\"suppressHydrationWarning\"!==a&&\"autoFocus\"!==a&&(l.hasOwnProperty(a)?null!=f&&\"onScroll\"===a&&xr(\"scroll\",e):null!=f&&w(e,a,f,u))}switch(n){case\"input\":Q(e),re(e,r,!1);break;case\"textarea\":Q(e),se(e);break;case\"option\":null!=r.value&&e.setAttribute(\"value\",\"\"+G(r.value));break;case\"select\":e.multiple=!!r.multiple,null!=(a=r.value)?ie(e,!!r.multiple,a,!1):null!=r.defaultValue&&ie(e,!!r.multiple,r.defaultValue,!0);break;default:\"function\"===typeof c.onClick&&(e.onclick=Dr)}Ir(n,r)&&(t.flags|=4)}null!==t.ref&&(t.flags|=128)}return null;case 6:if(e&&null!=t.stateNode)qi(0,t,e.memoizedProps,r);else{if(\"string\"!==typeof r&&null===t.stateNode)throw Error(i(166));n=Ec(Hc.current),Ec(xc.current),Kc(t)?(r=t.stateNode,n=t.memoizedProps,r[$r]=t,r.nodeValue!==n&&(t.flags|=4)):((r=(9===n.nodeType?n:n.ownerDocument).createTextNode(r))[$r]=t,t.stateNode=r)}return null;case 13:return ao(jc),r=t.memoizedState,0!==(64&t.flags)?(t.lanes=n,t):(r=null!==r,n=!1,null===e?void 0!==t.memoizedProps.fallback&&Kc(t):n=null!==e.memoizedState,r&&!n&&0!==(2&t.mode)&&(null===e&&!0!==t.memoizedProps.unstable_avoidThisFallback||0!==(1&jc.current)?0===ja&&(ja=3):(0!==ja&&3!==ja||(ja=4),null===Ea||0===(134217727&Ra)&&0===(134217727&Aa)||vl(Ea,Va))),(r||n)&&(t.flags|=4),null);case 4:return Vc(),null===e&&Hr(t.stateNode.containerInfo),null;case 10:return tc(t),null;case 17:return vo(t.type)&&mo(),null;case 19:if(ao(jc),null===(r=t.memoizedState))return null;if(a=0!==(64&t.flags),null===(u=r.rendering))if(a)ra(r,!1);else{if(0!==ja||null!==e&&0!==(64&e.flags))for(e=t.child;null!==e;){if(null!==(u=Nc(e))){for(t.flags|=64,ra(r,!1),null!==(a=u.updateQueue)&&(t.updateQueue=a,t.flags|=4),null===r.lastEffect&&(t.firstEffect=null),t.lastEffect=r.lastEffect,r=n,n=t.child;null!==n;)e=r,(a=n).flags&=2,a.nextEffect=null,a.firstEffect=null,a.lastEffect=null,null===(u=a.alternate)?(a.childLanes=0,a.lanes=e,a.child=null,a.memoizedProps=null,a.memoizedState=null,a.updateQueue=null,a.dependencies=null,a.stateNode=null):(a.childLanes=u.childLanes,a.lanes=u.lanes,a.child=u.child,a.memoizedProps=u.memoizedProps,a.memoizedState=u.memoizedState,a.updateQueue=u.updateQueue,a.type=u.type,e=u.dependencies,a.dependencies=null===e?null:{lanes:e.lanes,firstContext:e.firstContext}),n=n.sibling;return lo(jc,1&jc.current|2),t.child}e=e.sibling}null!==r.tail&&Fo()>Ua&&(t.flags|=64,a=!0,ra(r,!1),t.lanes=33554432)}else{if(!a)if(null!==(e=Nc(u))){if(t.flags|=64,a=!0,null!==(n=e.updateQueue)&&(t.updateQueue=n,t.flags|=4),ra(r,!0),null===r.tail&&\"hidden\"===r.tailMode&&!u.alternate&&!Ac)return null!==(t=t.lastEffect=r.lastEffect)&&(t.nextEffect=null),null}else 2*Fo()-r.renderingStartTime>Ua&&1073741824!==n&&(t.flags|=64,a=!0,ra(r,!1),t.lanes=33554432);r.isBackwards?(u.sibling=t.child,t.child=u):(null!==(n=r.last)?n.sibling=u:t.child=u,r.last=u)}return null!==r.tail?(n=r.tail,r.rendering=n,r.tail=n.sibling,r.lastEffect=t.lastEffect,r.renderingStartTime=Fo(),n.sibling=null,t=jc.current,lo(jc,a?1&t|2:1&t),n):null;case 23:case 24:return wl(),null!==e&&null!==e.memoizedState!==(null!==t.memoizedState)&&\"unstable-defer-without-hiding\"!==r.mode&&(t.flags|=4),null}throw Error(i(156,t.tag))}function ca(e){switch(e.tag){case 1:vo(e.type)&&mo();var t=e.flags;return 4096&t?(e.flags=-4097&t|64,e):null;case 3:if(Vc(),ao(fo),ao(so),qc(),0!==(64&(t=e.flags)))throw Error(i(285));return e.flags=-4097&t|64,e;case 5:return Lc(e),null;case 13:return ao(jc),4096&(t=e.flags)?(e.flags=-4097&t|64,e):null;case 19:return ao(jc),null;case 4:return Vc(),null;case 10:return tc(e),null;case 23:case 24:return wl(),null;default:return null}}function ia(e,t){try{var n=\"\",r=t;do{n+=Y(r),r=r.return}while(r);var o=n}catch(c){o=\"\\nError generating stack: \"+c.message+\"\\n\"+c.stack}return{value:e,source:t,stack:o}}function aa(e,t){try{console.error(t.value)}catch(n){setTimeout((function(){throw n}))}}Bi=function(e,t){for(var n=t.child;null!==n;){if(5===n.tag||6===n.tag)e.appendChild(n.stateNode);else if(4!==n.tag&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===t)break;for(;null===n.sibling;){if(null===n.return||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}},Yi=function(e,t,n,r){var c=e.memoizedProps;if(c!==r){e=t.stateNode,Ec(xc.current);var i,a=null;switch(n){case\"input\":c=J(e,c),r=J(e,r),a=[];break;case\"option\":c=ce(e,c),r=ce(e,r),a=[];break;case\"select\":c=o({},c,{value:void 0}),r=o({},r,{value:void 0}),a=[];break;case\"textarea\":c=ae(e,c),r=ae(e,r),a=[];break;default:\"function\"!==typeof c.onClick&&\"function\"===typeof r.onClick&&(e.onclick=Dr)}for(f in Me(n,r),n=null,c)if(!r.hasOwnProperty(f)&&c.hasOwnProperty(f)&&null!=c[f])if(\"style\"===f){var u=c[f];for(i in u)u.hasOwnProperty(i)&&(n||(n={}),n[i]=\"\")}else\"dangerouslySetInnerHTML\"!==f&&\"children\"!==f&&\"suppressContentEditableWarning\"!==f&&\"suppressHydrationWarning\"!==f&&\"autoFocus\"!==f&&(l.hasOwnProperty(f)?a||(a=[]):(a=a||[]).push(f,null));for(f in r){var s=r[f];if(u=null!=c?c[f]:void 0,r.hasOwnProperty(f)&&s!==u&&(null!=s||null!=u))if(\"style\"===f)if(u){for(i in u)!u.hasOwnProperty(i)||s&&s.hasOwnProperty(i)||(n||(n={}),n[i]=\"\");for(i in s)s.hasOwnProperty(i)&&u[i]!==s[i]&&(n||(n={}),n[i]=s[i])}else n||(a||(a=[]),a.push(f,n)),n=s;else\"dangerouslySetInnerHTML\"===f?(s=s?s.__html:void 0,u=u?u.__html:void 0,null!=s&&u!==s&&(a=a||[]).push(f,s)):\"children\"===f?\"string\"!==typeof s&&\"number\"!==typeof s||(a=a||[]).push(f,\"\"+s):\"suppressContentEditableWarning\"!==f&&\"suppressHydrationWarning\"!==f&&(l.hasOwnProperty(f)?(null!=s&&\"onScroll\"===f&&xr(\"scroll\",e),a||u===s||(a=[])):\"object\"===typeof s&&null!==s&&s.$$typeof===j?s.toString():(a=a||[]).push(f,s))}n&&(a=a||[]).push(\"style\",n);var f=a;(t.updateQueue=f)&&(t.flags|=4)}},qi=function(e,t,n,r){n!==r&&(t.flags|=4)};var la=\"function\"===typeof WeakMap?WeakMap:Map;function ua(e,t,n){(n=lc(-1,n)).tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){qa||(qa=!0,Ga=r),aa(0,t)},n}function sa(e,t,n){(n=lc(-1,n)).tag=3;var r=e.type.getDerivedStateFromError;if(\"function\"===typeof r){var o=t.value;n.payload=function(){return aa(0,t),r(o)}}var c=e.stateNode;return null!==c&&\"function\"===typeof c.componentDidCatch&&(n.callback=function(){\"function\"!==typeof r&&(null===$a?$a=new Set([this]):$a.add(this),aa(0,t));var e=t.stack;this.componentDidCatch(t.value,{componentStack:null!==e?e:\"\"})}),n}var fa=\"function\"===typeof WeakSet?WeakSet:Set;function pa(e){var t=e.ref;if(null!==t)if(\"function\"===typeof t)try{t(null)}catch(n){Dl(e,n)}else t.current=null}function ha(e,t){switch(t.tag){case 0:case 11:case 15:case 22:return;case 1:if(256&t.flags&&null!==e){var n=e.memoizedProps,r=e.memoizedState;t=(e=t.stateNode).getSnapshotBeforeUpdate(t.elementType===t.type?n:$o(t.type,n),r),e.__reactInternalSnapshotBeforeUpdate=t}return;case 3:return void(256&t.flags&&Kr(t.stateNode.containerInfo));case 5:case 6:case 4:case 17:return}throw Error(i(163))}function da(e,t,n){switch(n.tag){case 0:case 11:case 15:case 22:if(null!==(t=null!==(t=n.updateQueue)?t.lastEffect:null)){e=t=t.next;do{if(3===(3&e.tag)){var r=e.create;e.destroy=r()}e=e.next}while(e!==t)}if(null!==(t=null!==(t=n.updateQueue)?t.lastEffect:null)){e=t=t.next;do{var o=e;r=o.next,0!==(4&(o=o.tag))&&0!==(1&o)&&(Ll(n,e),Tl(n,e)),e=r}while(e!==t)}return;case 1:return e=n.stateNode,4&n.flags&&(null===t?e.componentDidMount():(r=n.elementType===n.type?t.memoizedProps:$o(n.type,t.memoizedProps),e.componentDidUpdate(r,t.memoizedState,e.__reactInternalSnapshotBeforeUpdate))),void(null!==(t=n.updateQueue)&&pc(n,t,e));case 3:if(null!==(t=n.updateQueue)){if(e=null,null!==n.child)switch(n.child.tag){case 5:e=n.child.stateNode;break;case 1:e=n.child.stateNode}pc(n,t,e)}return;case 5:return e=n.stateNode,void(null===t&&4&n.flags&&Ir(n.type,n.memoizedProps)&&e.focus());case 6:case 4:case 12:return;case 13:return void(null===n.memoizedState&&(n=n.alternate,null!==n&&(n=n.memoizedState,null!==n&&(n=n.dehydrated,null!==n&&Ot(n)))));case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(i(163))}function va(e,t){for(var n=e;;){if(5===n.tag){var r=n.stateNode;if(t)\"function\"===typeof(r=r.style).setProperty?r.setProperty(\"display\",\"none\",\"important\"):r.display=\"none\";else{r=n.stateNode;var o=n.memoizedProps.style;o=void 0!==o&&null!==o&&o.hasOwnProperty(\"display\")?o.display:null,r.style.display=ze(\"display\",o)}}else if(6===n.tag)n.stateNode.nodeValue=t?\"\":n.memoizedProps;else if((23!==n.tag&&24!==n.tag||null===n.memoizedState||n===e)&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===e)break;for(;null===n.sibling;){if(null===n.return||n.return===e)return;n=n.return}n.sibling.return=n.return,n=n.sibling}}function ma(e,t){if(Oo&&\"function\"===typeof Oo.onCommitFiberUnmount)try{Oo.onCommitFiberUnmount(zo,t)}catch(c){}switch(t.tag){case 0:case 11:case 14:case 15:case 22:if(null!==(e=t.updateQueue)&&null!==(e=e.lastEffect)){var n=e=e.next;do{var r=n,o=r.destroy;if(r=r.tag,void 0!==o)if(0!==(4&r))Ll(t,n);else{r=t;try{o()}catch(c){Dl(r,c)}}n=n.next}while(n!==e)}break;case 1:if(pa(t),\"function\"===typeof(e=t.stateNode).componentWillUnmount)try{e.props=t.memoizedProps,e.state=t.memoizedState,e.componentWillUnmount()}catch(c){Dl(t,c)}break;case 5:pa(t);break;case 4:Oa(e,t)}}function ya(e){e.alternate=null,e.child=null,e.dependencies=null,e.firstEffect=null,e.lastEffect=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.return=null,e.updateQueue=null}function ba(e){return 5===e.tag||3===e.tag||4===e.tag}function ga(e){e:{for(var t=e.return;null!==t;){if(ba(t))break e;t=t.return}throw Error(i(160))}var n=t;switch(t=n.stateNode,n.tag){case 5:var r=!1;break;case 3:case 4:t=t.containerInfo,r=!0;break;default:throw Error(i(161))}16&n.flags&&(be(t,\"\"),n.flags&=-17);e:t:for(n=e;;){for(;null===n.sibling;){if(null===n.return||ba(n.return)){n=null;break e}n=n.return}for(n.sibling.return=n.return,n=n.sibling;5!==n.tag&&6!==n.tag&&18!==n.tag;){if(2&n.flags)continue t;if(null===n.child||4===n.tag)continue t;n.child.return=n,n=n.child}if(!(2&n.flags)){n=n.stateNode;break e}}r?wa(e,n,t):za(e,n,t)}function wa(e,t,n){var r=e.tag,o=5===r||6===r;if(o)e=o?e.stateNode:e.stateNode.instance,t?8===n.nodeType?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(8===n.nodeType?(t=n.parentNode).insertBefore(e,n):(t=n).appendChild(e),null!==(n=n._reactRootContainer)&&void 0!==n||null!==t.onclick||(t.onclick=Dr));else if(4!==r&&null!==(e=e.child))for(wa(e,t,n),e=e.sibling;null!==e;)wa(e,t,n),e=e.sibling}function za(e,t,n){var r=e.tag,o=5===r||6===r;if(o)e=o?e.stateNode:e.stateNode.instance,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&null!==(e=e.child))for(za(e,t,n),e=e.sibling;null!==e;)za(e,t,n),e=e.sibling}function Oa(e,t){for(var n,r,o=t,c=!1;;){if(!c){c=o.return;e:for(;;){if(null===c)throw Error(i(160));switch(n=c.stateNode,c.tag){case 5:r=!1;break e;case 3:case 4:n=n.containerInfo,r=!0;break e}c=c.return}c=!0}if(5===o.tag||6===o.tag){e:for(var a=e,l=o,u=l;;)if(ma(a,u),null!==u.child&&4!==u.tag)u.child.return=u,u=u.child;else{if(u===l)break e;for(;null===u.sibling;){if(null===u.return||u.return===l)break e;u=u.return}u.sibling.return=u.return,u=u.sibling}r?(a=n,l=o.stateNode,8===a.nodeType?a.parentNode.removeChild(l):a.removeChild(l)):n.removeChild(o.stateNode)}else if(4===o.tag){if(null!==o.child){n=o.stateNode.containerInfo,r=!0,o.child.return=o,o=o.child;continue}}else if(ma(e,o),null!==o.child){o.child.return=o,o=o.child;continue}if(o===t)break;for(;null===o.sibling;){if(null===o.return||o.return===t)return;4===(o=o.return).tag&&(c=!1)}o.sibling.return=o.return,o=o.sibling}}function Ca(e,t){switch(t.tag){case 0:case 11:case 14:case 15:case 22:var n=t.updateQueue;if(null!==(n=null!==n?n.lastEffect:null)){var r=n=n.next;do{3===(3&r.tag)&&(e=r.destroy,r.destroy=void 0,void 0!==e&&e()),r=r.next}while(r!==n)}return;case 1:return;case 5:if(null!=(n=t.stateNode)){r=t.memoizedProps;var o=null!==e?e.memoizedProps:r;e=t.type;var c=t.updateQueue;if(t.updateQueue=null,null!==c){for(n[Qr]=r,\"input\"===e&&\"radio\"===r.type&&null!=r.name&&te(n,r),Se(e,o),t=Se(e,r),o=0;o<c.length;o+=2){var a=c[o],l=c[o+1];\"style\"===a?Oe(n,l):\"dangerouslySetInnerHTML\"===a?ye(n,l):\"children\"===a?be(n,l):w(n,a,l,t)}switch(e){case\"input\":ne(n,r);break;case\"textarea\":ue(n,r);break;case\"select\":e=n._wrapperState.wasMultiple,n._wrapperState.wasMultiple=!!r.multiple,null!=(c=r.value)?ie(n,!!r.multiple,c,!1):e!==!!r.multiple&&(null!=r.defaultValue?ie(n,!!r.multiple,r.defaultValue,!0):ie(n,!!r.multiple,r.multiple?[]:\"\",!1))}}}return;case 6:if(null===t.stateNode)throw Error(i(162));return void(t.stateNode.nodeValue=t.memoizedProps);case 3:return void((n=t.stateNode).hydrate&&(n.hydrate=!1,Ot(n.containerInfo)));case 12:return;case 13:return null!==t.memoizedState&&(Wa=Fo(),va(t.child,!0)),void Ma(t);case 19:return void Ma(t);case 17:return;case 23:case 24:return void va(t,null!==t.memoizedState)}throw Error(i(163))}function Ma(e){var t=e.updateQueue;if(null!==t){e.updateQueue=null;var n=e.stateNode;null===n&&(n=e.stateNode=new fa),t.forEach((function(t){var r=Al.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}}function Sa(e,t){return null!==e&&(null===(e=e.memoizedState)||null!==e.dehydrated)&&(null!==(t=t.memoizedState)&&null===t.dehydrated)}var _a=Math.ceil,xa=z.ReactCurrentDispatcher,ka=z.ReactCurrentOwner,Ha=0,Ea=null,Pa=null,Va=0,Ta=0,La=io(0),ja=0,Na=null,Da=0,Ra=0,Aa=0,Ia=0,Fa=null,Wa=0,Ua=1/0;function Ka(){Ua=Fo()+500}var Ba,Ya=null,qa=!1,Ga=null,$a=null,Qa=!1,Xa=null,Za=90,Ja=[],el=[],tl=null,nl=0,rl=null,ol=-1,cl=0,il=0,al=null,ll=!1;function ul(){return 0!==(48&Ha)?Fo():-1!==ol?ol:ol=Fo()}function sl(e){if(0===(2&(e=e.mode)))return 1;if(0===(4&e))return 99===Wo()?1:2;if(0===cl&&(cl=Da),0!==Go.transition){0!==il&&(il=null!==Fa?Fa.pendingLanes:0),e=cl;var t=4186112&~il;return 0===(t&=-t)&&(0===(t=(e=4186112&~e)&-e)&&(t=8192)),t}return e=Wo(),0!==(4&Ha)&&98===e?e=It(12,cl):e=It(e=function(e){switch(e){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}(e),cl),e}function fl(e,t,n){if(50<nl)throw nl=0,rl=null,Error(i(185));if(null===(e=pl(e,t)))return null;Ut(e,t,n),e===Ea&&(Aa|=t,4===ja&&vl(e,Va));var r=Wo();1===t?0!==(8&Ha)&&0===(48&Ha)?ml(e):(hl(e,n),0===Ha&&(Ka(),Yo())):(0===(4&Ha)||98!==r&&99!==r||(null===tl?tl=new Set([e]):tl.add(e)),hl(e,n)),Fa=e}function pl(e,t){e.lanes|=t;var n=e.alternate;for(null!==n&&(n.lanes|=t),n=e,e=e.return;null!==e;)e.childLanes|=t,null!==(n=e.alternate)&&(n.childLanes|=t),n=e,e=e.return;return 3===n.tag?n.stateNode:null}function hl(e,t){for(var n=e.callbackNode,r=e.suspendedLanes,o=e.pingedLanes,c=e.expirationTimes,a=e.pendingLanes;0<a;){var l=31-Kt(a),u=1<<l,s=c[l];if(-1===s){if(0===(u&r)||0!==(u&o)){s=t,Dt(u);var f=Nt;c[l]=10<=f?s+250:6<=f?s+5e3:-1}}else s<=t&&(e.expiredLanes|=u);a&=~u}if(r=Rt(e,e===Ea?Va:0),t=Nt,0===r)null!==n&&(n!==jo&&So(n),e.callbackNode=null,e.callbackPriority=0);else{if(null!==n){if(e.callbackPriority===t)return;n!==jo&&So(n)}15===t?(n=ml.bind(null,e),null===Do?(Do=[n],Ro=Mo(Eo,qo)):Do.push(n),n=jo):14===t?n=Bo(99,ml.bind(null,e)):n=Bo(n=function(e){switch(e){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(i(358,e))}}(t),dl.bind(null,e)),e.callbackPriority=t,e.callbackNode=n}}function dl(e){if(ol=-1,il=cl=0,0!==(48&Ha))throw Error(i(327));var t=e.callbackNode;if(Vl()&&e.callbackNode!==t)return null;var n=Rt(e,e===Ea?Va:0);if(0===n)return null;var r=n,o=Ha;Ha|=16;var c=Cl();for(Ea===e&&Va===r||(Ka(),zl(e,r));;)try{_l();break}catch(l){Ol(e,l)}if(ec(),xa.current=c,Ha=o,null!==Pa?r=0:(Ea=null,Va=0,r=ja),0!==(Da&Aa))zl(e,0);else if(0!==r){if(2===r&&(Ha|=64,e.hydrate&&(e.hydrate=!1,Kr(e.containerInfo)),0!==(n=At(e))&&(r=Ml(e,n))),1===r)throw t=Na,zl(e,0),vl(e,n),hl(e,Fo()),t;switch(e.finishedWork=e.current.alternate,e.finishedLanes=n,r){case 0:case 1:throw Error(i(345));case 2:Hl(e);break;case 3:if(vl(e,n),(62914560&n)===n&&10<(r=Wa+500-Fo())){if(0!==Rt(e,0))break;if(((o=e.suspendedLanes)&n)!==n){ul(),e.pingedLanes|=e.suspendedLanes&o;break}e.timeoutHandle=Wr(Hl.bind(null,e),r);break}Hl(e);break;case 4:if(vl(e,n),(4186112&n)===n)break;for(r=e.eventTimes,o=-1;0<n;){var a=31-Kt(n);c=1<<a,(a=r[a])>o&&(o=a),n&=~c}if(n=o,10<(n=(120>(n=Fo()-n)?120:480>n?480:1080>n?1080:1920>n?1920:3e3>n?3e3:4320>n?4320:1960*_a(n/1960))-n)){e.timeoutHandle=Wr(Hl.bind(null,e),n);break}Hl(e);break;case 5:Hl(e);break;default:throw Error(i(329))}}return hl(e,Fo()),e.callbackNode===t?dl.bind(null,e):null}function vl(e,t){for(t&=~Ia,t&=~Aa,e.suspendedLanes|=t,e.pingedLanes&=~t,e=e.expirationTimes;0<t;){var n=31-Kt(t),r=1<<n;e[n]=-1,t&=~r}}function ml(e){if(0!==(48&Ha))throw Error(i(327));if(Vl(),e===Ea&&0!==(e.expiredLanes&Va)){var t=Va,n=Ml(e,t);0!==(Da&Aa)&&(n=Ml(e,t=Rt(e,t)))}else n=Ml(e,t=Rt(e,0));if(0!==e.tag&&2===n&&(Ha|=64,e.hydrate&&(e.hydrate=!1,Kr(e.containerInfo)),0!==(t=At(e))&&(n=Ml(e,t))),1===n)throw n=Na,zl(e,0),vl(e,t),hl(e,Fo()),n;return e.finishedWork=e.current.alternate,e.finishedLanes=t,Hl(e),hl(e,Fo()),null}function yl(e,t){var n=Ha;Ha|=1;try{return e(t)}finally{0===(Ha=n)&&(Ka(),Yo())}}function bl(e,t){var n=Ha;Ha&=-2,Ha|=8;try{return e(t)}finally{0===(Ha=n)&&(Ka(),Yo())}}function gl(e,t){lo(La,Ta),Ta|=t,Da|=t}function wl(){Ta=La.current,ao(La)}function zl(e,t){e.finishedWork=null,e.finishedLanes=0;var n=e.timeoutHandle;if(-1!==n&&(e.timeoutHandle=-1,Ur(n)),null!==Pa)for(n=Pa.return;null!==n;){var r=n;switch(r.tag){case 1:null!==(r=r.type.childContextTypes)&&void 0!==r&&mo();break;case 3:Vc(),ao(fo),ao(so),qc();break;case 5:Lc(r);break;case 4:Vc();break;case 13:case 19:ao(jc);break;case 10:tc(r);break;case 23:case 24:wl()}n=n.return}Ea=e,Pa=Ul(e.current,null),Va=Ta=Da=t,ja=0,Na=null,Ia=Aa=Ra=0}function Ol(e,t){for(;;){var n=Pa;try{if(ec(),Gc.current=Hi,ei){for(var r=Xc.memoizedState;null!==r;){var o=r.queue;null!==o&&(o.pending=null),r=r.next}ei=!1}if(Qc=0,Jc=Zc=Xc=null,ti=!1,ka.current=null,null===n||null===n.return){ja=1,Na=t,Pa=null;break}e:{var c=e,i=n.return,a=n,l=t;if(t=Va,a.flags|=2048,a.firstEffect=a.lastEffect=null,null!==l&&\"object\"===typeof l&&\"function\"===typeof l.then){var u=l;if(0===(2&a.mode)){var s=a.alternate;s?(a.updateQueue=s.updateQueue,a.memoizedState=s.memoizedState,a.lanes=s.lanes):(a.updateQueue=null,a.memoizedState=null)}var f=0!==(1&jc.current),p=i;do{var h;if(h=13===p.tag){var d=p.memoizedState;if(null!==d)h=null!==d.dehydrated;else{var v=p.memoizedProps;h=void 0!==v.fallback&&(!0!==v.unstable_avoidThisFallback||!f)}}if(h){var m=p.updateQueue;if(null===m){var y=new Set;y.add(u),p.updateQueue=y}else m.add(u);if(0===(2&p.mode)){if(p.flags|=64,a.flags|=16384,a.flags&=-2981,1===a.tag)if(null===a.alternate)a.tag=17;else{var b=lc(-1,1);b.tag=2,uc(a,b)}a.lanes|=1;break e}l=void 0,a=t;var g=c.pingCache;if(null===g?(g=c.pingCache=new la,l=new Set,g.set(u,l)):void 0===(l=g.get(u))&&(l=new Set,g.set(u,l)),!l.has(a)){l.add(a);var w=Rl.bind(null,c,u,a);u.then(w,w)}p.flags|=4096,p.lanes=t;break e}p=p.return}while(null!==p);l=Error((q(a.type)||\"A React component\")+\" suspended while rendering, but no fallback UI was specified.\\n\\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.\")}5!==ja&&(ja=2),l=ia(l,a),p=i;do{switch(p.tag){case 3:c=l,p.flags|=4096,t&=-t,p.lanes|=t,sc(p,ua(0,c,t));break e;case 1:c=l;var z=p.type,O=p.stateNode;if(0===(64&p.flags)&&(\"function\"===typeof z.getDerivedStateFromError||null!==O&&\"function\"===typeof O.componentDidCatch&&(null===$a||!$a.has(O)))){p.flags|=4096,t&=-t,p.lanes|=t,sc(p,sa(p,c,t));break e}}p=p.return}while(null!==p)}kl(n)}catch(C){t=C,Pa===n&&null!==n&&(Pa=n=n.return);continue}break}}function Cl(){var e=xa.current;return xa.current=Hi,null===e?Hi:e}function Ml(e,t){var n=Ha;Ha|=16;var r=Cl();for(Ea===e&&Va===t||zl(e,t);;)try{Sl();break}catch(o){Ol(e,o)}if(ec(),Ha=n,xa.current=r,null!==Pa)throw Error(i(261));return Ea=null,Va=0,ja}function Sl(){for(;null!==Pa;)xl(Pa)}function _l(){for(;null!==Pa&&!_o();)xl(Pa)}function xl(e){var t=Ba(e.alternate,e,Ta);e.memoizedProps=e.pendingProps,null===t?kl(e):Pa=t,ka.current=null}function kl(e){var t=e;do{var n=t.alternate;if(e=t.return,0===(2048&t.flags)){if(null!==(n=oa(n,t,Ta)))return void(Pa=n);if(24!==(n=t).tag&&23!==n.tag||null===n.memoizedState||0!==(1073741824&Ta)||0===(4&n.mode)){for(var r=0,o=n.child;null!==o;)r|=o.lanes|o.childLanes,o=o.sibling;n.childLanes=r}null!==e&&0===(2048&e.flags)&&(null===e.firstEffect&&(e.firstEffect=t.firstEffect),null!==t.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=t.firstEffect),e.lastEffect=t.lastEffect),1<t.flags&&(null!==e.lastEffect?e.lastEffect.nextEffect=t:e.firstEffect=t,e.lastEffect=t))}else{if(null!==(n=ca(t)))return n.flags&=2047,void(Pa=n);null!==e&&(e.firstEffect=e.lastEffect=null,e.flags|=2048)}if(null!==(t=t.sibling))return void(Pa=t);Pa=t=e}while(null!==t);0===ja&&(ja=5)}function Hl(e){var t=Wo();return Ko(99,El.bind(null,e,t)),null}function El(e,t){do{Vl()}while(null!==Xa);if(0!==(48&Ha))throw Error(i(327));var n=e.finishedWork;if(null===n)return null;if(e.finishedWork=null,e.finishedLanes=0,n===e.current)throw Error(i(177));e.callbackNode=null;var r=n.lanes|n.childLanes,o=r,c=e.pendingLanes&~o;e.pendingLanes=o,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=o,e.mutableReadLanes&=o,e.entangledLanes&=o,o=e.entanglements;for(var a=e.eventTimes,l=e.expirationTimes;0<c;){var u=31-Kt(c),s=1<<u;o[u]=0,a[u]=-1,l[u]=-1,c&=~s}if(null!==tl&&0===(24&r)&&tl.has(e)&&tl.delete(e),e===Ea&&(Pa=Ea=null,Va=0),1<n.flags?null!==n.lastEffect?(n.lastEffect.nextEffect=n,r=n.firstEffect):r=n:r=n.firstEffect,null!==r){if(o=Ha,Ha|=32,ka.current=null,Rr=$t,dr(a=hr())){if(\"selectionStart\"in a)l={start:a.selectionStart,end:a.selectionEnd};else e:if(l=(l=a.ownerDocument)&&l.defaultView||window,(s=l.getSelection&&l.getSelection())&&0!==s.rangeCount){l=s.anchorNode,c=s.anchorOffset,u=s.focusNode,s=s.focusOffset;try{l.nodeType,u.nodeType}catch(_){l=null;break e}var f=0,p=-1,h=-1,d=0,v=0,m=a,y=null;t:for(;;){for(var b;m!==l||0!==c&&3!==m.nodeType||(p=f+c),m!==u||0!==s&&3!==m.nodeType||(h=f+s),3===m.nodeType&&(f+=m.nodeValue.length),null!==(b=m.firstChild);)y=m,m=b;for(;;){if(m===a)break t;if(y===l&&++d===c&&(p=f),y===u&&++v===s&&(h=f),null!==(b=m.nextSibling))break;y=(m=y).parentNode}m=b}l=-1===p||-1===h?null:{start:p,end:h}}else l=null;l=l||{start:0,end:0}}else l=null;Ar={focusedElem:a,selectionRange:l},$t=!1,al=null,ll=!1,Ya=r;do{try{Pl()}catch(_){if(null===Ya)throw Error(i(330));Dl(Ya,_),Ya=Ya.nextEffect}}while(null!==Ya);al=null,Ya=r;do{try{for(a=e;null!==Ya;){var g=Ya.flags;if(16&g&&be(Ya.stateNode,\"\"),128&g){var w=Ya.alternate;if(null!==w){var z=w.ref;null!==z&&(\"function\"===typeof z?z(null):z.current=null)}}switch(1038&g){case 2:ga(Ya),Ya.flags&=-3;break;case 6:ga(Ya),Ya.flags&=-3,Ca(Ya.alternate,Ya);break;case 1024:Ya.flags&=-1025;break;case 1028:Ya.flags&=-1025,Ca(Ya.alternate,Ya);break;case 4:Ca(Ya.alternate,Ya);break;case 8:Oa(a,l=Ya);var O=l.alternate;ya(l),null!==O&&ya(O)}Ya=Ya.nextEffect}}catch(_){if(null===Ya)throw Error(i(330));Dl(Ya,_),Ya=Ya.nextEffect}}while(null!==Ya);if(z=Ar,w=hr(),g=z.focusedElem,a=z.selectionRange,w!==g&&g&&g.ownerDocument&&pr(g.ownerDocument.documentElement,g)){null!==a&&dr(g)&&(w=a.start,void 0===(z=a.end)&&(z=w),\"selectionStart\"in g?(g.selectionStart=w,g.selectionEnd=Math.min(z,g.value.length)):(z=(w=g.ownerDocument||document)&&w.defaultView||window).getSelection&&(z=z.getSelection(),l=g.textContent.length,O=Math.min(a.start,l),a=void 0===a.end?O:Math.min(a.end,l),!z.extend&&O>a&&(l=a,a=O,O=l),l=fr(g,O),c=fr(g,a),l&&c&&(1!==z.rangeCount||z.anchorNode!==l.node||z.anchorOffset!==l.offset||z.focusNode!==c.node||z.focusOffset!==c.offset)&&((w=w.createRange()).setStart(l.node,l.offset),z.removeAllRanges(),O>a?(z.addRange(w),z.extend(c.node,c.offset)):(w.setEnd(c.node,c.offset),z.addRange(w))))),w=[];for(z=g;z=z.parentNode;)1===z.nodeType&&w.push({element:z,left:z.scrollLeft,top:z.scrollTop});for(\"function\"===typeof g.focus&&g.focus(),g=0;g<w.length;g++)(z=w[g]).element.scrollLeft=z.left,z.element.scrollTop=z.top}$t=!!Rr,Ar=Rr=null,e.current=n,Ya=r;do{try{for(g=e;null!==Ya;){var C=Ya.flags;if(36&C&&da(g,Ya.alternate,Ya),128&C){w=void 0;var M=Ya.ref;if(null!==M){var S=Ya.stateNode;switch(Ya.tag){case 5:w=S;break;default:w=S}\"function\"===typeof M?M(w):M.current=w}}Ya=Ya.nextEffect}}catch(_){if(null===Ya)throw Error(i(330));Dl(Ya,_),Ya=Ya.nextEffect}}while(null!==Ya);Ya=null,No(),Ha=o}else e.current=n;if(Qa)Qa=!1,Xa=e,Za=t;else for(Ya=r;null!==Ya;)t=Ya.nextEffect,Ya.nextEffect=null,8&Ya.flags&&((C=Ya).sibling=null,C.stateNode=null),Ya=t;if(0===(r=e.pendingLanes)&&($a=null),1===r?e===rl?nl++:(nl=0,rl=e):nl=0,n=n.stateNode,Oo&&\"function\"===typeof Oo.onCommitFiberRoot)try{Oo.onCommitFiberRoot(zo,n,void 0,64===(64&n.current.flags))}catch(_){}if(hl(e,Fo()),qa)throw qa=!1,e=Ga,Ga=null,e;return 0!==(8&Ha)||Yo(),null}function Pl(){for(;null!==Ya;){var e=Ya.alternate;ll||null===al||(0!==(8&Ya.flags)?et(Ya,al)&&(ll=!0):13===Ya.tag&&Sa(e,Ya)&&et(Ya,al)&&(ll=!0));var t=Ya.flags;0!==(256&t)&&ha(e,Ya),0===(512&t)||Qa||(Qa=!0,Bo(97,(function(){return Vl(),null}))),Ya=Ya.nextEffect}}function Vl(){if(90!==Za){var e=97<Za?97:Za;return Za=90,Ko(e,jl)}return!1}function Tl(e,t){Ja.push(t,e),Qa||(Qa=!0,Bo(97,(function(){return Vl(),null})))}function Ll(e,t){el.push(t,e),Qa||(Qa=!0,Bo(97,(function(){return Vl(),null})))}function jl(){if(null===Xa)return!1;var e=Xa;if(Xa=null,0!==(48&Ha))throw Error(i(331));var t=Ha;Ha|=32;var n=el;el=[];for(var r=0;r<n.length;r+=2){var o=n[r],c=n[r+1],a=o.destroy;if(o.destroy=void 0,\"function\"===typeof a)try{a()}catch(u){if(null===c)throw Error(i(330));Dl(c,u)}}for(n=Ja,Ja=[],r=0;r<n.length;r+=2){o=n[r],c=n[r+1];try{var l=o.create;o.destroy=l()}catch(u){if(null===c)throw Error(i(330));Dl(c,u)}}for(l=e.current.firstEffect;null!==l;)e=l.nextEffect,l.nextEffect=null,8&l.flags&&(l.sibling=null,l.stateNode=null),l=e;return Ha=t,Yo(),!0}function Nl(e,t,n){uc(e,t=ua(0,t=ia(n,t),1)),t=ul(),null!==(e=pl(e,1))&&(Ut(e,1,t),hl(e,t))}function Dl(e,t){if(3===e.tag)Nl(e,e,t);else for(var n=e.return;null!==n;){if(3===n.tag){Nl(n,e,t);break}if(1===n.tag){var r=n.stateNode;if(\"function\"===typeof n.type.getDerivedStateFromError||\"function\"===typeof r.componentDidCatch&&(null===$a||!$a.has(r))){var o=sa(n,e=ia(t,e),1);if(uc(n,o),o=ul(),null!==(n=pl(n,1)))Ut(n,1,o),hl(n,o);else if(\"function\"===typeof r.componentDidCatch&&(null===$a||!$a.has(r)))try{r.componentDidCatch(t,e)}catch(c){}break}}n=n.return}}function Rl(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),t=ul(),e.pingedLanes|=e.suspendedLanes&n,Ea===e&&(Va&n)===n&&(4===ja||3===ja&&(62914560&Va)===Va&&500>Fo()-Wa?zl(e,0):Ia|=n),hl(e,t)}function Al(e,t){var n=e.stateNode;null!==n&&n.delete(t),0===(t=0)&&(0===(2&(t=e.mode))?t=1:0===(4&t)?t=99===Wo()?1:2:(0===cl&&(cl=Da),0===(t=Ft(62914560&~cl))&&(t=4194304))),n=ul(),null!==(e=pl(e,t))&&(Ut(e,t,n),hl(e,n))}function Il(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.flags=0,this.lastEffect=this.firstEffect=this.nextEffect=null,this.childLanes=this.lanes=0,this.alternate=null}function Fl(e,t,n,r){return new Il(e,t,n,r)}function Wl(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Ul(e,t){var n=e.alternate;return null===n?((n=Fl(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.nextEffect=null,n.firstEffect=null,n.lastEffect=null),n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Kl(e,t,n,r,o,c){var a=2;if(r=e,\"function\"===typeof e)Wl(e)&&(a=1);else if(\"string\"===typeof e)a=5;else e:switch(e){case M:return Bl(n.children,o,c,t);case N:a=8,o|=16;break;case S:a=8,o|=1;break;case _:return(e=Fl(12,n,t,8|o)).elementType=_,e.type=_,e.lanes=c,e;case E:return(e=Fl(13,n,t,o)).type=E,e.elementType=E,e.lanes=c,e;case P:return(e=Fl(19,n,t,o)).elementType=P,e.lanes=c,e;case D:return Yl(n,o,c,t);case R:return(e=Fl(24,n,t,o)).elementType=R,e.lanes=c,e;default:if(\"object\"===typeof e&&null!==e)switch(e.$$typeof){case x:a=10;break e;case k:a=9;break e;case H:a=11;break e;case V:a=14;break e;case T:a=16,r=null;break e;case L:a=22;break e}throw Error(i(130,null==e?e:typeof e,\"\"))}return(t=Fl(a,n,t,o)).elementType=e,t.type=r,t.lanes=c,t}function Bl(e,t,n,r){return(e=Fl(7,e,r,t)).lanes=n,e}function Yl(e,t,n,r){return(e=Fl(23,e,r,t)).elementType=D,e.lanes=n,e}function ql(e,t,n){return(e=Fl(6,e,null,t)).lanes=n,e}function Gl(e,t,n){return(t=Fl(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function $l(e,t,n){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.pendingContext=this.context=null,this.hydrate=n,this.callbackNode=null,this.callbackPriority=0,this.eventTimes=Wt(0),this.expirationTimes=Wt(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Wt(0),this.mutableSourceEagerHydrationData=null}function Ql(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:C,key:null==r?null:\"\"+r,children:e,containerInfo:t,implementation:n}}function Xl(e,t,n,r){var o=t.current,c=ul(),a=sl(o);e:if(n){t:{if(Qe(n=n._reactInternals)!==n||1!==n.tag)throw Error(i(170));var l=n;do{switch(l.tag){case 3:l=l.stateNode.context;break t;case 1:if(vo(l.type)){l=l.stateNode.__reactInternalMemoizedMergedChildContext;break t}}l=l.return}while(null!==l);throw Error(i(171))}if(1===n.tag){var u=n.type;if(vo(u)){n=bo(n,u,l);break e}}n=l}else n=uo;return null===t.context?t.context=n:t.pendingContext=n,(t=lc(c,a)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),uc(o,t),fl(o,a,c),a}function Zl(e){if(!(e=e.current).child)return null;switch(e.child.tag){case 5:default:return e.child.stateNode}}function Jl(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function eu(e,t){Jl(e,t),(e=e.alternate)&&Jl(e,t)}function tu(e,t,n){var r=null!=n&&null!=n.hydrationOptions&&n.hydrationOptions.mutableSources||null;if(n=new $l(e,t,null!=n&&!0===n.hydrate),t=Fl(3,null,null,2===t?7:1===t?3:0),n.current=t,t.stateNode=n,ic(t),e[Xr]=n.current,Hr(8===e.nodeType?e.parentNode:e),r)for(e=0;e<r.length;e++){var o=(t=r[e])._getVersion;o=o(t._source),null==n.mutableSourceEagerHydrationData?n.mutableSourceEagerHydrationData=[t,o]:n.mutableSourceEagerHydrationData.push(t,o)}this._internalRoot=n}function nu(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType&&(8!==e.nodeType||\" react-mount-point-unstable \"!==e.nodeValue))}function ru(e,t,n,r,o){var c=n._reactRootContainer;if(c){var i=c._internalRoot;if(\"function\"===typeof o){var a=o;o=function(){var e=Zl(i);a.call(e)}}Xl(t,i,e,o)}else{if(c=n._reactRootContainer=function(e,t){if(t||(t=!(!(t=e?9===e.nodeType?e.documentElement:e.firstChild:null)||1!==t.nodeType||!t.hasAttribute(\"data-reactroot\"))),!t)for(var n;n=e.lastChild;)e.removeChild(n);return new tu(e,0,t?{hydrate:!0}:void 0)}(n,r),i=c._internalRoot,\"function\"===typeof o){var l=o;o=function(){var e=Zl(i);l.call(e)}}bl((function(){Xl(t,i,e,o)}))}return Zl(i)}function ou(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!nu(t))throw Error(i(200));return Ql(e,t,null,n)}Ba=function(e,t,n){var r=t.lanes;if(null!==e)if(e.memoizedProps!==t.pendingProps||fo.current)Li=!0;else{if(0===(n&r)){switch(Li=!1,t.tag){case 3:Ki(t),Bc();break;case 5:Tc(t);break;case 1:vo(t.type)&&go(t);break;case 4:Pc(t,t.stateNode.containerInfo);break;case 10:r=t.memoizedProps.value;var o=t.type._context;lo(Qo,o._currentValue),o._currentValue=r;break;case 13:if(null!==t.memoizedState)return 0!==(n&t.child.childLanes)?$i(e,t,n):(lo(jc,1&jc.current),null!==(t=na(e,t,n))?t.sibling:null);lo(jc,1&jc.current);break;case 19:if(r=0!==(n&t.childLanes),0!==(64&e.flags)){if(r)return ta(e,t,n);t.flags|=64}if(null!==(o=t.memoizedState)&&(o.rendering=null,o.tail=null,o.lastEffect=null),lo(jc,jc.current),r)break;return null;case 23:case 24:return t.lanes=0,Ai(e,t,n)}return na(e,t,n)}Li=0!==(16384&e.flags)}else Li=!1;switch(t.lanes=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),e=t.pendingProps,o=ho(t,so.current),rc(t,n),o=oi(null,t,r,e,o,n),t.flags|=1,\"object\"===typeof o&&null!==o&&\"function\"===typeof o.render&&void 0===o.$$typeof){if(t.tag=1,t.memoizedState=null,t.updateQueue=null,vo(r)){var c=!0;go(t)}else c=!1;t.memoizedState=null!==o.state&&void 0!==o.state?o.state:null,ic(t);var a=r.getDerivedStateFromProps;\"function\"===typeof a&&dc(t,r,a,e),o.updater=vc,t.stateNode=o,o._reactInternals=t,gc(t,r,e,n),t=Ui(null,t,r,!0,c,n)}else t.tag=0,ji(null,t,o,n),t=t.child;return t;case 16:o=t.elementType;e:{switch(null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),e=t.pendingProps,o=(c=o._init)(o._payload),t.type=o,c=t.tag=function(e){if(\"function\"===typeof e)return Wl(e)?1:0;if(void 0!==e&&null!==e){if((e=e.$$typeof)===H)return 11;if(e===V)return 14}return 2}(o),e=$o(o,e),c){case 0:t=Fi(null,t,o,e,n);break e;case 1:t=Wi(null,t,o,e,n);break e;case 11:t=Ni(null,t,o,e,n);break e;case 14:t=Di(null,t,o,$o(o.type,e),r,n);break e}throw Error(i(306,o,\"\"))}return t;case 0:return r=t.type,o=t.pendingProps,Fi(e,t,r,o=t.elementType===r?o:$o(r,o),n);case 1:return r=t.type,o=t.pendingProps,Wi(e,t,r,o=t.elementType===r?o:$o(r,o),n);case 3:if(Ki(t),r=t.updateQueue,null===e||null===r)throw Error(i(282));if(r=t.pendingProps,o=null!==(o=t.memoizedState)?o.element:null,ac(e,t),fc(t,r,null,n),(r=t.memoizedState.element)===o)Bc(),t=na(e,t,n);else{if((c=(o=t.stateNode).hydrate)&&(Rc=Br(t.stateNode.containerInfo.firstChild),Dc=t,c=Ac=!0),c){if(null!=(e=o.mutableSourceEagerHydrationData))for(o=0;o<e.length;o+=2)(c=e[o])._workInProgressVersionPrimary=e[o+1],Yc.push(c);for(n=Sc(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|1024,n=n.sibling}else ji(e,t,r,n),Bc();t=t.child}return t;case 5:return Tc(t),null===e&&Wc(t),r=t.type,o=t.pendingProps,c=null!==e?e.memoizedProps:null,a=o.children,Fr(r,o)?a=null:null!==c&&Fr(r,c)&&(t.flags|=16),Ii(e,t),ji(e,t,a,n),t.child;case 6:return null===e&&Wc(t),null;case 13:return $i(e,t,n);case 4:return Pc(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=Mc(t,null,r,n):ji(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,Ni(e,t,r,o=t.elementType===r?o:$o(r,o),n);case 7:return ji(e,t,t.pendingProps,n),t.child;case 8:case 12:return ji(e,t,t.pendingProps.children,n),t.child;case 10:e:{r=t.type._context,o=t.pendingProps,a=t.memoizedProps,c=o.value;var l=t.type._context;if(lo(Qo,l._currentValue),l._currentValue=c,null!==a)if(l=a.value,0===(c=ar(l,c)?0:0|(\"function\"===typeof r._calculateChangedBits?r._calculateChangedBits(l,c):1073741823))){if(a.children===o.children&&!fo.current){t=na(e,t,n);break e}}else for(null!==(l=t.child)&&(l.return=t);null!==l;){var u=l.dependencies;if(null!==u){a=l.child;for(var s=u.firstContext;null!==s;){if(s.context===r&&0!==(s.observedBits&c)){1===l.tag&&((s=lc(-1,n&-n)).tag=2,uc(l,s)),l.lanes|=n,null!==(s=l.alternate)&&(s.lanes|=n),nc(l.return,n),u.lanes|=n;break}s=s.next}}else a=10===l.tag&&l.type===t.type?null:l.child;if(null!==a)a.return=l;else for(a=l;null!==a;){if(a===t){a=null;break}if(null!==(l=a.sibling)){l.return=a.return,a=l;break}a=a.return}l=a}ji(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=(c=t.pendingProps).children,rc(t,n),r=r(o=oc(o,c.unstable_observedBits)),t.flags|=1,ji(e,t,r,n),t.child;case 14:return c=$o(o=t.type,t.pendingProps),Di(e,t,o,c=$o(o.type,c),r,n);case 15:return Ri(e,t,t.type,t.pendingProps,r,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:$o(r,o),null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2),t.tag=1,vo(r)?(e=!0,go(t)):e=!1,rc(t,n),yc(t,r,o),gc(t,r,o,n),Ui(null,t,r,!0,e,n);case 19:return ta(e,t,n);case 23:case 24:return Ai(e,t,n)}throw Error(i(156,t.tag))},tu.prototype.render=function(e){Xl(e,this._internalRoot,null,null)},tu.prototype.unmount=function(){var e=this._internalRoot,t=e.containerInfo;Xl(null,e,null,(function(){t[Xr]=null}))},tt=function(e){13===e.tag&&(fl(e,4,ul()),eu(e,4))},nt=function(e){13===e.tag&&(fl(e,67108864,ul()),eu(e,67108864))},rt=function(e){if(13===e.tag){var t=ul(),n=sl(e);fl(e,n,t),eu(e,n)}},ot=function(e,t){return t()},xe=function(e,t,n){switch(t){case\"input\":if(ne(e,n),t=n.name,\"radio\"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll(\"input[name=\"+JSON.stringify(\"\"+t)+'][type=\"radio\"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var o=no(r);if(!o)throw Error(i(90));X(r),ne(r,o)}}}break;case\"textarea\":ue(e,n);break;case\"select\":null!=(t=n.value)&&ie(e,!!n.multiple,t,!1)}},Te=yl,Le=function(e,t,n,r,o){var c=Ha;Ha|=4;try{return Ko(98,e.bind(null,t,n,r,o))}finally{0===(Ha=c)&&(Ka(),Yo())}},je=function(){0===(49&Ha)&&(function(){if(null!==tl){var e=tl;tl=null,e.forEach((function(e){e.expiredLanes|=24&e.pendingLanes,hl(e,Fo())}))}Yo()}(),Vl())},Ne=function(e,t){var n=Ha;Ha|=2;try{return e(t)}finally{0===(Ha=n)&&(Ka(),Yo())}};var cu={Events:[eo,to,no,Pe,Ve,Vl,{current:!1}]},iu={findFiberByHostInstance:Jr,bundleType:0,version:\"17.0.2\",rendererPackageName:\"react-dom\"},au={bundleType:iu.bundleType,version:iu.version,rendererPackageName:iu.rendererPackageName,rendererConfig:iu.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:z.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return null===(e=Je(e))?null:e.stateNode},findFiberByHostInstance:iu.findFiberByHostInstance||function(){return null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null};if(\"undefined\"!==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var lu=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!lu.isDisabled&&lu.supportsFiber)try{zo=lu.inject(au),Oo=lu}catch(me){}}t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=cu,t.createPortal=ou,t.findDOMNode=function(e){if(null==e)return null;if(1===e.nodeType)return e;var t=e._reactInternals;if(void 0===t){if(\"function\"===typeof e.render)throw Error(i(188));throw Error(i(268,Object.keys(e)))}return e=null===(e=Je(t))?null:e.stateNode},t.flushSync=function(e,t){var n=Ha;if(0!==(48&n))return e(t);Ha|=1;try{if(e)return Ko(99,e.bind(null,t))}finally{Ha=n,Yo()}},t.hydrate=function(e,t,n){if(!nu(t))throw Error(i(200));return ru(null,e,t,!0,n)},t.render=function(e,t,n){if(!nu(t))throw Error(i(200));return ru(null,e,t,!1,n)},t.unmountComponentAtNode=function(e){if(!nu(e))throw Error(i(40));return!!e._reactRootContainer&&(bl((function(){ru(null,null,e,!1,(function(){e._reactRootContainer=null,e[Xr]=null}))})),!0)},t.unstable_batchedUpdates=yl,t.unstable_createPortal=function(e,t){return ou(e,t,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)},t.unstable_renderSubtreeIntoContainer=function(e,t,n,r){if(!nu(n))throw Error(i(200));if(null==e||void 0===e._reactInternals)throw Error(i(38));return ru(e,t,n,!1,r)},t.version=\"17.0.2\"},function(e,t,n){\"use strict\";var r=n(71),o=60103,c=60106;t.Fragment=60107,t.StrictMode=60108,t.Profiler=60114;var i=60109,a=60110,l=60112;t.Suspense=60113;var u=60115,s=60116;if(\"function\"===typeof Symbol&&Symbol.for){var f=Symbol.for;o=f(\"react.element\"),c=f(\"react.portal\"),t.Fragment=f(\"react.fragment\"),t.StrictMode=f(\"react.strict_mode\"),t.Profiler=f(\"react.profiler\"),i=f(\"react.provider\"),a=f(\"react.context\"),l=f(\"react.forward_ref\"),t.Suspense=f(\"react.suspense\"),u=f(\"react.memo\"),s=f(\"react.lazy\")}var p=\"function\"===typeof Symbol&&Symbol.iterator;function h(e){for(var t=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+e,n=1;n<arguments.length;n++)t+=\"&args[]=\"+encodeURIComponent(arguments[n]);return\"Minified React error #\"+e+\"; visit \"+t+\" for the full message or use the non-minified dev environment for full errors and additional helpful warnings.\"}var d={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},v={};function m(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||d}function y(){}function b(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||d}m.prototype.isReactComponent={},m.prototype.setState=function(e,t){if(\"object\"!==typeof e&&\"function\"!==typeof e&&null!=e)throw Error(h(85));this.updater.enqueueSetState(this,e,t,\"setState\")},m.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,\"forceUpdate\")},y.prototype=m.prototype;var g=b.prototype=new y;g.constructor=b,r(g,m.prototype),g.isPureReactComponent=!0;var w={current:null},z=Object.prototype.hasOwnProperty,O={key:!0,ref:!0,__self:!0,__source:!0};function C(e,t,n){var r,c={},i=null,a=null;if(null!=t)for(r in void 0!==t.ref&&(a=t.ref),void 0!==t.key&&(i=\"\"+t.key),t)z.call(t,r)&&!O.hasOwnProperty(r)&&(c[r]=t[r]);var l=arguments.length-2;if(1===l)c.children=n;else if(1<l){for(var u=Array(l),s=0;s<l;s++)u[s]=arguments[s+2];c.children=u}if(e&&e.defaultProps)for(r in l=e.defaultProps)void 0===c[r]&&(c[r]=l[r]);return{$$typeof:o,type:e,key:i,ref:a,props:c,_owner:w.current}}function M(e){return\"object\"===typeof e&&null!==e&&e.$$typeof===o}var S=/\\/+/g;function _(e,t){return\"object\"===typeof e&&null!==e&&null!=e.key?function(e){var t={\"=\":\"=0\",\":\":\"=2\"};return\"$\"+e.replace(/[=:]/g,(function(e){return t[e]}))}(\"\"+e.key):t.toString(36)}function x(e,t,n,r,i){var a=typeof e;\"undefined\"!==a&&\"boolean\"!==a||(e=null);var l=!1;if(null===e)l=!0;else switch(a){case\"string\":case\"number\":l=!0;break;case\"object\":switch(e.$$typeof){case o:case c:l=!0}}if(l)return i=i(l=e),e=\"\"===r?\".\"+_(l,0):r,Array.isArray(i)?(n=\"\",null!=e&&(n=e.replace(S,\"$&/\")+\"/\"),x(i,t,n,\"\",(function(e){return e}))):null!=i&&(M(i)&&(i=function(e,t){return{$$typeof:o,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(i,n+(!i.key||l&&l.key===i.key?\"\":(\"\"+i.key).replace(S,\"$&/\")+\"/\")+e)),t.push(i)),1;if(l=0,r=\"\"===r?\".\":r+\":\",Array.isArray(e))for(var u=0;u<e.length;u++){var s=r+_(a=e[u],u);l+=x(a,t,n,s,i)}else if(\"function\"===typeof(s=function(e){return null===e||\"object\"!==typeof e?null:\"function\"===typeof(e=p&&e[p]||e[\"@@iterator\"])?e:null}(e)))for(e=s.call(e),u=0;!(a=e.next()).done;)l+=x(a=a.value,t,n,s=r+_(a,u++),i);else if(\"object\"===a)throw t=\"\"+e,Error(h(31,\"[object Object]\"===t?\"object with keys {\"+Object.keys(e).join(\", \")+\"}\":t));return l}function k(e,t,n){if(null==e)return e;var r=[],o=0;return x(e,r,\"\",\"\",(function(e){return t.call(n,e,o++)})),r}function H(e){if(-1===e._status){var t=e._result;t=t(),e._status=0,e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}if(1===e._status)return e._result;throw e._result}var E={current:null};function P(){var e=E.current;if(null===e)throw Error(h(321));return e}var V={ReactCurrentDispatcher:E,ReactCurrentBatchConfig:{transition:0},ReactCurrentOwner:w,IsSomeRendererActing:{current:!1},assign:r};t.Children={map:k,forEach:function(e,t,n){k(e,(function(){t.apply(this,arguments)}),n)},count:function(e){var t=0;return k(e,(function(){t++})),t},toArray:function(e){return k(e,(function(e){return e}))||[]},only:function(e){if(!M(e))throw Error(h(143));return e}},t.Component=m,t.PureComponent=b,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=V,t.cloneElement=function(e,t,n){if(null===e||void 0===e)throw Error(h(267,e));var c=r({},e.props),i=e.key,a=e.ref,l=e._owner;if(null!=t){if(void 0!==t.ref&&(a=t.ref,l=w.current),void 0!==t.key&&(i=\"\"+t.key),e.type&&e.type.defaultProps)var u=e.type.defaultProps;for(s in t)z.call(t,s)&&!O.hasOwnProperty(s)&&(c[s]=void 0===t[s]&&void 0!==u?u[s]:t[s])}var s=arguments.length-2;if(1===s)c.children=n;else if(1<s){u=Array(s);for(var f=0;f<s;f++)u[f]=arguments[f+2];c.children=u}return{$$typeof:o,type:e.type,key:i,ref:a,props:c,_owner:l}},t.createContext=function(e,t){return void 0===t&&(t=null),(e={$$typeof:a,_calculateChangedBits:t,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider={$$typeof:i,_context:e},e.Consumer=e},t.createElement=C,t.createFactory=function(e){var t=C.bind(null,e);return t.type=e,t},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:l,render:e}},t.isValidElement=M,t.lazy=function(e){return{$$typeof:s,_payload:{_status:-1,_result:e},_init:H}},t.memo=function(e,t){return{$$typeof:u,type:e,compare:void 0===t?null:t}},t.useCallback=function(e,t){return P().useCallback(e,t)},t.useContext=function(e,t){return P().useContext(e,t)},t.useDebugValue=function(){},t.useEffect=function(e,t){return P().useEffect(e,t)},t.useImperativeHandle=function(e,t,n){return P().useImperativeHandle(e,t,n)},t.useLayoutEffect=function(e,t){return P().useLayoutEffect(e,t)},t.useMemo=function(e,t){return P().useMemo(e,t)},t.useReducer=function(e,t,n){return P().useReducer(e,t,n)},t.useRef=function(e){return P().useRef(e)},t.useState=function(e){return P().useState(e)},t.version=\"17.0.2\"},function(e,t,n){\"use strict\";e.exports=n(170)},function(e,t,n){\"use strict\";var r,o,c,i;if(\"object\"===typeof performance&&\"function\"===typeof performance.now){var a=performance;t.unstable_now=function(){return a.now()}}else{var l=Date,u=l.now();t.unstable_now=function(){return l.now()-u}}if(\"undefined\"===typeof window||\"function\"!==typeof MessageChannel){var s=null,f=null,p=function e(){if(null!==s)try{var n=t.unstable_now();s(!0,n),s=null}catch(r){throw setTimeout(e,0),r}};r=function(e){null!==s?setTimeout(r,0,e):(s=e,setTimeout(p,0))},o=function(e,t){f=setTimeout(e,t)},c=function(){clearTimeout(f)},t.unstable_shouldYield=function(){return!1},i=t.unstable_forceFrameRate=function(){}}else{var h=window.setTimeout,d=window.clearTimeout;if(\"undefined\"!==typeof console){var v=window.cancelAnimationFrame;\"function\"!==typeof window.requestAnimationFrame&&console.error(\"This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills\"),\"function\"!==typeof v&&console.error(\"This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills\")}var m=!1,y=null,b=-1,g=5,w=0;t.unstable_shouldYield=function(){return t.unstable_now()>=w},i=function(){},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error(\"forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported\"):g=0<e?Math.floor(1e3/e):5};var z=new MessageChannel,O=z.port2;z.port1.onmessage=function(){if(null!==y){var e=t.unstable_now();w=e+g;try{y(!0,e)?O.postMessage(null):(m=!1,y=null)}catch(n){throw O.postMessage(null),n}}else m=!1},r=function(e){y=e,m||(m=!0,O.postMessage(null))},o=function(e,n){b=h((function(){e(t.unstable_now())}),n)},c=function(){d(b),b=-1}}function C(e,t){var n=e.length;e.push(t);e:for(;;){var r=n-1>>>1,o=e[r];if(!(void 0!==o&&0<_(o,t)))break e;e[r]=t,e[n]=o,n=r}}function M(e){return void 0===(e=e[0])?null:e}function S(e){var t=e[0];if(void 0!==t){var n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,o=e.length;r<o;){var c=2*(r+1)-1,i=e[c],a=c+1,l=e[a];if(void 0!==i&&0>_(i,n))void 0!==l&&0>_(l,i)?(e[r]=l,e[a]=n,r=a):(e[r]=i,e[c]=n,r=c);else{if(!(void 0!==l&&0>_(l,n)))break e;e[r]=l,e[a]=n,r=a}}}return t}return null}function _(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var x=[],k=[],H=1,E=null,P=3,V=!1,T=!1,L=!1;function j(e){for(var t=M(k);null!==t;){if(null===t.callback)S(k);else{if(!(t.startTime<=e))break;S(k),t.sortIndex=t.expirationTime,C(x,t)}t=M(k)}}function N(e){if(L=!1,j(e),!T)if(null!==M(x))T=!0,r(D);else{var t=M(k);null!==t&&o(N,t.startTime-e)}}function D(e,n){T=!1,L&&(L=!1,c()),V=!0;var r=P;try{for(j(n),E=M(x);null!==E&&(!(E.expirationTime>n)||e&&!t.unstable_shouldYield());){var i=E.callback;if(\"function\"===typeof i){E.callback=null,P=E.priorityLevel;var a=i(E.expirationTime<=n);n=t.unstable_now(),\"function\"===typeof a?E.callback=a:E===M(x)&&S(x),j(n)}else S(x);E=M(x)}if(null!==E)var l=!0;else{var u=M(k);null!==u&&o(N,u.startTime-n),l=!1}return l}finally{E=null,P=r,V=!1}}var R=i;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){T||V||(T=!0,r(D))},t.unstable_getCurrentPriorityLevel=function(){return P},t.unstable_getFirstCallbackNode=function(){return M(x)},t.unstable_next=function(e){switch(P){case 1:case 2:case 3:var t=3;break;default:t=P}var n=P;P=t;try{return e()}finally{P=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=R,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=P;P=e;try{return t()}finally{P=n}},t.unstable_scheduleCallback=function(e,n,i){var a=t.unstable_now();switch(\"object\"===typeof i&&null!==i?i=\"number\"===typeof(i=i.delay)&&0<i?a+i:a:i=a,e){case 1:var l=-1;break;case 2:l=250;break;case 5:l=1073741823;break;case 4:l=1e4;break;default:l=5e3}return e={id:H++,callback:n,priorityLevel:e,startTime:i,expirationTime:l=i+l,sortIndex:-1},i>a?(e.sortIndex=i,C(k,e),null===M(x)&&e===M(k)&&(L?c():L=!0,o(N,i-a))):(e.sortIndex=l,C(x,e),T||V||(T=!0,r(D))),e},t.unstable_wrapCallback=function(e){var t=P;return function(){var n=P;P=t;try{return e.apply(this,arguments)}finally{P=n}}}},function(e,t,n){!function(e){\"use strict\";e.defineLocale(\"zh-cn\",{months:\"\\u4e00\\u6708_\\u4e8c\\u6708_\\u4e09\\u6708_\\u56db\\u6708_\\u4e94\\u6708_\\u516d\\u6708_\\u4e03\\u6708_\\u516b\\u6708_\\u4e5d\\u6708_\\u5341\\u6708_\\u5341\\u4e00\\u6708_\\u5341\\u4e8c\\u6708\".split(\"_\"),monthsShort:\"1\\u6708_2\\u6708_3\\u6708_4\\u6708_5\\u6708_6\\u6708_7\\u6708_8\\u6708_9\\u6708_10\\u6708_11\\u6708_12\\u6708\".split(\"_\"),weekdays:\"\\u661f\\u671f\\u65e5_\\u661f\\u671f\\u4e00_\\u661f\\u671f\\u4e8c_\\u661f\\u671f\\u4e09_\\u661f\\u671f\\u56db_\\u661f\\u671f\\u4e94_\\u661f\\u671f\\u516d\".split(\"_\"),weekdaysShort:\"\\u5468\\u65e5_\\u5468\\u4e00_\\u5468\\u4e8c_\\u5468\\u4e09_\\u5468\\u56db_\\u5468\\u4e94_\\u5468\\u516d\".split(\"_\"),weekdaysMin:\"\\u65e5_\\u4e00_\\u4e8c_\\u4e09_\\u56db_\\u4e94_\\u516d\".split(\"_\"),longDateFormat:{LT:\"HH:mm\",LTS:\"HH:mm:ss\",L:\"YYYY/MM/DD\",LL:\"YYYY\\u5e74M\\u6708D\\u65e5\",LLL:\"YYYY\\u5e74M\\u6708D\\u65e5Ah\\u70b9mm\\u5206\",LLLL:\"YYYY\\u5e74M\\u6708D\\u65e5ddddAh\\u70b9mm\\u5206\",l:\"YYYY/M/D\",ll:\"YYYY\\u5e74M\\u6708D\\u65e5\",lll:\"YYYY\\u5e74M\\u6708D\\u65e5 HH:mm\",llll:\"YYYY\\u5e74M\\u6708D\\u65e5dddd HH:mm\"},meridiemParse:/\\u51cc\\u6668|\\u65e9\\u4e0a|\\u4e0a\\u5348|\\u4e2d\\u5348|\\u4e0b\\u5348|\\u665a\\u4e0a/,meridiemHour:function(e,t){return 12===e&&(e=0),\"\\u51cc\\u6668\"===t||\"\\u65e9\\u4e0a\"===t||\"\\u4e0a\\u5348\"===t?e:\"\\u4e0b\\u5348\"===t||\"\\u665a\\u4e0a\"===t?e+12:e>=11?e:e+12},meridiem:function(e,t,n){var r=100*e+t;return r<600?\"\\u51cc\\u6668\":r<900?\"\\u65e9\\u4e0a\":r<1130?\"\\u4e0a\\u5348\":r<1230?\"\\u4e2d\\u5348\":r<1800?\"\\u4e0b\\u5348\":\"\\u665a\\u4e0a\"},calendar:{sameDay:\"[\\u4eca\\u5929]LT\",nextDay:\"[\\u660e\\u5929]LT\",nextWeek:function(e){return e.week()!==this.week()?\"[\\u4e0b]dddLT\":\"[\\u672c]dddLT\"},lastDay:\"[\\u6628\\u5929]LT\",lastWeek:function(e){return this.week()!==e.week()?\"[\\u4e0a]dddLT\":\"[\\u672c]dddLT\"},sameElse:\"L\"},dayOfMonthOrdinalParse:/\\d{1,2}(\\u65e5|\\u6708|\\u5468)/,ordinal:function(e,t){switch(t){case\"d\":case\"D\":case\"DDD\":return e+\"\\u65e5\";case\"M\":return e+\"\\u6708\";case\"w\":case\"W\":return e+\"\\u5468\";default:return e}},relativeTime:{future:\"%s\\u540e\",past:\"%s\\u524d\",s:\"\\u51e0\\u79d2\",ss:\"%d \\u79d2\",m:\"1 \\u5206\\u949f\",mm:\"%d \\u5206\\u949f\",h:\"1 \\u5c0f\\u65f6\",hh:\"%d \\u5c0f\\u65f6\",d:\"1 \\u5929\",dd:\"%d \\u5929\",w:\"1 \\u5468\",ww:\"%d \\u5468\",M:\"1 \\u4e2a\\u6708\",MM:\"%d \\u4e2a\\u6708\",y:\"1 \\u5e74\",yy:\"%d \\u5e74\"},week:{dow:1,doy:4}})}(n(24))},,function(e,t,n){\"use strict\";n(71);var r=n(0),o=60103;if(t.Fragment=60107,\"function\"===typeof Symbol&&Symbol.for){var c=Symbol.for;o=c(\"react.element\"),t.Fragment=c(\"react.fragment\")}var i=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,a=Object.prototype.hasOwnProperty,l={key:!0,ref:!0,__self:!0,__source:!0};function u(e,t,n){var r,c={},u=null,s=null;for(r in void 0!==n&&(u=\"\"+n),void 0!==t.key&&(u=\"\"+t.key),void 0!==t.ref&&(s=t.ref),t)a.call(t,r)&&!l.hasOwnProperty(r)&&(c[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===c[r]&&(c[r]=t[r]);return{$$typeof:o,type:e,key:u,ref:s,props:c,_owner:i.current}}t.jsx=u,t.jsxs=u},function(e,t,n){e.exports={default:n(175),__esModule:!0}},function(e,t,n){n(176),e.exports=n(37).Object.assign},function(e,t,n){var r=n(48);r(r.S+r.F,\"Object\",{assign:n(178)})},function(e,t){e.exports=function(e){if(\"function\"!=typeof e)throw TypeError(e+\" is not a function!\");return e}},function(e,t,n){\"use strict\";var r=n(38),o=n(74),c=n(107),i=n(77),a=n(108),l=n(127),u=Object.assign;e.exports=!u||n(63)((function(){var e={},t={},n=Symbol(),r=\"abcdefghijklmnopqrst\";return e[n]=7,r.split(\"\").forEach((function(e){t[e]=e})),7!=u({},e)[n]||Object.keys(u({},t)).join(\"\")!=r}))?function(e,t){for(var n=a(e),u=arguments.length,s=1,f=c.f,p=i.f;u>s;)for(var h,d=l(arguments[s++]),v=f?o(d).concat(f(d)):o(d),m=v.length,y=0;m>y;)h=v[y++],r&&!p.call(d,h)||(n[h]=d[h]);return n}:u},function(e,t,n){var r=n(51),o=n(180),c=n(181);e.exports=function(e){return function(t,n,i){var a,l=r(t),u=o(l.length),s=c(i,u);if(e&&n!=n){for(;u>s;)if((a=l[s++])!=a)return!0}else for(;u>s;s++)if((e||s in l)&&l[s]===n)return e||s||0;return!e&&-1}}},function(e,t,n){var r=n(103),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},function(e,t,n){var r=n(103),o=Math.max,c=Math.min;e.exports=function(e,t){return(e=r(e))<0?o(e+t,0):c(e,t)}},function(e,t,n){e.exports={default:n(183),__esModule:!0}},function(e,t,n){n(184),n(190),e.exports=n(112).f(\"iterator\")},function(e,t,n){\"use strict\";var r=n(185)(!0);n(129)(String,\"String\",(function(e){this._t=String(e),this._i=0}),(function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})}))},function(e,t,n){var r=n(103),o=n(102);e.exports=function(e){return function(t,n){var c,i,a=String(o(t)),l=r(n),u=a.length;return l<0||l>=u?e?\"\":void 0:(c=a.charCodeAt(l))<55296||c>56319||l+1===u||(i=a.charCodeAt(l+1))<56320||i>57343?e?a.charAt(l):c:e?a.slice(l,l+2):i-56320+(c-55296<<10)+65536}}},function(e,t,n){\"use strict\";var r=n(110),o=n(73),c=n(111),i={};n(49)(i,n(52)(\"iterator\"),(function(){return this})),e.exports=function(e,t,n){e.prototype=r(i,{next:o(1,n)}),c(e,t+\" Iterator\")}},function(e,t,n){var r=n(42),o=n(62),c=n(74);e.exports=n(38)?Object.defineProperties:function(e,t){o(e);for(var n,i=c(t),a=i.length,l=0;a>l;)r.f(e,n=i[l++],t[n]);return e}},function(e,t,n){var r=n(36).document;e.exports=r&&r.documentElement},function(e,t,n){var r=n(43),o=n(108),c=n(104)(\"IE_PROTO\"),i=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=o(e),r(e,c)?e[c]:\"function\"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?i:null}},function(e,t,n){n(191);for(var r=n(36),o=n(49),c=n(109),i=n(52)(\"toStringTag\"),a=\"CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList\".split(\",\"),l=0;l<a.length;l++){var u=a[l],s=r[u],f=s&&s.prototype;f&&!f[i]&&o(f,i,u),c[u]=c.Array}},function(e,t,n){\"use strict\";var r=n(192),o=n(193),c=n(109),i=n(51);e.exports=n(129)(Array,\"Array\",(function(e,t){this._t=i(e),this._i=0,this._k=t}),(function(){var e=this._t,t=this._k,n=this._i++;return!e||n>=e.length?(this._t=void 0,o(1)):o(0,\"keys\"==t?n:\"values\"==t?e[n]:[n,e[n]])}),\"values\"),c.Arguments=c.Array,r(\"keys\"),r(\"values\"),r(\"entries\")},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){e.exports={default:n(195),__esModule:!0}},function(e,t,n){n(196),n(201),n(202),n(203),e.exports=n(37).Symbol},function(e,t,n){\"use strict\";var r=n(36),o=n(43),c=n(38),i=n(48),a=n(130),l=n(197).KEY,u=n(63),s=n(105),f=n(111),p=n(76),h=n(52),d=n(112),v=n(113),m=n(198),y=n(199),b=n(62),g=n(50),w=n(108),z=n(51),O=n(101),C=n(73),M=n(110),S=n(200),_=n(132),x=n(107),k=n(42),H=n(74),E=_.f,P=k.f,V=S.f,T=r.Symbol,L=r.JSON,j=L&&L.stringify,N=h(\"_hidden\"),D=h(\"toPrimitive\"),R={}.propertyIsEnumerable,A=s(\"symbol-registry\"),I=s(\"symbols\"),F=s(\"op-symbols\"),W=Object.prototype,U=\"function\"==typeof T&&!!x.f,K=r.QObject,B=!K||!K.prototype||!K.prototype.findChild,Y=c&&u((function(){return 7!=M(P({},\"a\",{get:function(){return P(this,\"a\",{value:7}).a}})).a}))?function(e,t,n){var r=E(W,t);r&&delete W[t],P(e,t,n),r&&e!==W&&P(W,t,r)}:P,q=function(e){var t=I[e]=M(T.prototype);return t._k=e,t},G=U&&\"symbol\"==typeof T.iterator?function(e){return\"symbol\"==typeof e}:function(e){return e instanceof T},$=function(e,t,n){return e===W&&$(F,t,n),b(e),t=O(t,!0),b(n),o(I,t)?(n.enumerable?(o(e,N)&&e[N][t]&&(e[N][t]=!1),n=M(n,{enumerable:C(0,!1)})):(o(e,N)||P(e,N,C(1,{})),e[N][t]=!0),Y(e,t,n)):P(e,t,n)},Q=function(e,t){b(e);for(var n,r=m(t=z(t)),o=0,c=r.length;c>o;)$(e,n=r[o++],t[n]);return e},X=function(e){var t=R.call(this,e=O(e,!0));return!(this===W&&o(I,e)&&!o(F,e))&&(!(t||!o(this,e)||!o(I,e)||o(this,N)&&this[N][e])||t)},Z=function(e,t){if(e=z(e),t=O(t,!0),e!==W||!o(I,t)||o(F,t)){var n=E(e,t);return!n||!o(I,t)||o(e,N)&&e[N][t]||(n.enumerable=!0),n}},J=function(e){for(var t,n=V(z(e)),r=[],c=0;n.length>c;)o(I,t=n[c++])||t==N||t==l||r.push(t);return r},ee=function(e){for(var t,n=e===W,r=V(n?F:z(e)),c=[],i=0;r.length>i;)!o(I,t=r[i++])||n&&!o(W,t)||c.push(I[t]);return c};U||(a((T=function(){if(this instanceof T)throw TypeError(\"Symbol is not a constructor!\");var e=p(arguments.length>0?arguments[0]:void 0),t=function t(n){this===W&&t.call(F,n),o(this,N)&&o(this[N],e)&&(this[N][e]=!1),Y(this,e,C(1,n))};return c&&B&&Y(W,e,{configurable:!0,set:t}),q(e)}).prototype,\"toString\",(function(){return this._k})),_.f=Z,k.f=$,n(131).f=S.f=J,n(77).f=X,x.f=ee,c&&!n(75)&&a(W,\"propertyIsEnumerable\",X,!0),d.f=function(e){return q(h(e))}),i(i.G+i.W+i.F*!U,{Symbol:T});for(var te=\"hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables\".split(\",\"),ne=0;te.length>ne;)h(te[ne++]);for(var re=H(h.store),oe=0;re.length>oe;)v(re[oe++]);i(i.S+i.F*!U,\"Symbol\",{for:function(e){return o(A,e+=\"\")?A[e]:A[e]=T(e)},keyFor:function(e){if(!G(e))throw TypeError(e+\" is not a symbol!\");for(var t in A)if(A[t]===e)return t},useSetter:function(){B=!0},useSimple:function(){B=!1}}),i(i.S+i.F*!U,\"Object\",{create:function(e,t){return void 0===t?M(e):Q(M(e),t)},defineProperty:$,defineProperties:Q,getOwnPropertyDescriptor:Z,getOwnPropertyNames:J,getOwnPropertySymbols:ee});var ce=u((function(){x.f(1)}));i(i.S+i.F*ce,\"Object\",{getOwnPropertySymbols:function(e){return x.f(w(e))}}),L&&i(i.S+i.F*(!U||u((function(){var e=T();return\"[null]\"!=j([e])||\"{}\"!=j({a:e})||\"{}\"!=j(Object(e))}))),\"JSON\",{stringify:function(e){for(var t,n,r=[e],o=1;arguments.length>o;)r.push(arguments[o++]);if(n=t=r[1],(g(t)||void 0!==e)&&!G(e))return y(t)||(t=function(e,t){if(\"function\"==typeof n&&(t=n.call(this,e,t)),!G(t))return t}),r[1]=t,j.apply(L,r)}}),T.prototype[D]||n(49)(T.prototype,D,T.prototype.valueOf),f(T,\"Symbol\"),f(Math,\"Math\",!0),f(r.JSON,\"JSON\",!0)},function(e,t,n){var r=n(76)(\"meta\"),o=n(50),c=n(43),i=n(42).f,a=0,l=Object.isExtensible||function(){return!0},u=!n(63)((function(){return l(Object.preventExtensions({}))})),s=function(e){i(e,r,{value:{i:\"O\"+ ++a,w:{}}})},f=e.exports={KEY:r,NEED:!1,fastKey:function(e,t){if(!o(e))return\"symbol\"==typeof e?e:(\"string\"==typeof e?\"S\":\"P\")+e;if(!c(e,r)){if(!l(e))return\"F\";if(!t)return\"E\";s(e)}return e[r].i},getWeak:function(e,t){if(!c(e,r)){if(!l(e))return!0;if(!t)return!1;s(e)}return e[r].w},onFreeze:function(e){return u&&f.NEED&&l(e)&&!c(e,r)&&s(e),e}}},function(e,t,n){var r=n(74),o=n(107),c=n(77);e.exports=function(e){var t=r(e),n=o.f;if(n)for(var i,a=n(e),l=c.f,u=0;a.length>u;)l.call(e,i=a[u++])&&t.push(i);return t}},function(e,t,n){var r=n(128);e.exports=Array.isArray||function(e){return\"Array\"==r(e)}},function(e,t,n){var r=n(51),o=n(131).f,c={}.toString,i=\"object\"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return i&&\"[object Window]\"==c.call(e)?function(e){try{return o(e)}catch(t){return i.slice()}}(e):o(r(e))}},function(e,t){},function(e,t,n){n(113)(\"asyncIterator\")},function(e,t,n){n(113)(\"observable\")},function(e,t,n){e.exports={default:n(205),__esModule:!0}},function(e,t,n){n(206),e.exports=n(37).Object.setPrototypeOf},function(e,t,n){var r=n(48);r(r.S,\"Object\",{setPrototypeOf:n(207).set})},function(e,t,n){var r=n(50),o=n(62),c=function(e,t){if(o(e),!r(t)&&null!==t)throw TypeError(t+\": can't set as prototype!\")};e.exports={set:Object.setPrototypeOf||(\"__proto__\"in{}?function(e,t,r){try{(r=n(123)(Function.call,n(132).f(Object.prototype,\"__proto__\").set,2))(e,[]),t=!(e instanceof Array)}catch(o){t=!0}return function(e,n){return c(e,n),t?e.__proto__=n:r(e,n),e}}({},!1):void 0),check:c}},function(e,t,n){e.exports={default:n(209),__esModule:!0}},function(e,t,n){n(210);var r=n(37).Object;e.exports=function(e,t){return r.create(e,t)}},function(e,t,n){var r=n(48);r(r.S,\"Object\",{create:n(110)})},function(e,t,n){\"use strict\";var r=n(212);function o(){}function c(){}c.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,c,i){if(i!==r){var a=new Error(\"Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types\");throw a.name=\"Invariant Violation\",a}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:c,resetWarningCache:o};return n.PropTypes=n,n}},function(e,t,n){\"use strict\";e.exports=\"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED\"},function(e,t,n){\"use strict\";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,\"__esModule\",{value:!0});var o=r(n(214)),c=r(n(71)),i=!1,a=[\"altKey\",\"bubbles\",\"cancelable\",\"ctrlKey\",\"currentTarget\",\"eventPhase\",\"metaKey\",\"shiftKey\",\"target\",\"timeStamp\",\"view\",\"type\"];function l(e){return null===e||void 0===e}var u=[{reg:/^key/,props:[\"char\",\"charCode\",\"key\",\"keyCode\",\"which\"],fix:function(e,t){l(e.which)&&(e.which=l(t.charCode)?t.keyCode:t.charCode),void 0===e.metaKey&&(e.metaKey=e.ctrlKey)}},{reg:/^touch/,props:[\"touches\",\"changedTouches\",\"targetTouches\"]},{reg:/^hashchange$/,props:[\"newURL\",\"oldURL\"]},{reg:/^gesturechange$/i,props:[\"rotation\",\"scale\"]},{reg:/^(mousewheel|DOMMouseScroll)$/,props:[],fix:function(e,t){var n=void 0,r=void 0,o=void 0,c=t.wheelDelta,i=t.axis,a=t.wheelDeltaY,l=t.wheelDeltaX,u=t.detail;c&&(o=c/120),u&&(o=0-(u%3===0?u/3:u)),void 0!==i&&(i===e.HORIZONTAL_AXIS?(r=0,n=0-o):i===e.VERTICAL_AXIS&&(n=0,r=o)),void 0!==a&&(r=a/120),void 0!==l&&(n=-1*l/120),n||r||(r=o),void 0!==n&&(e.deltaX=n),void 0!==r&&(e.deltaY=r),void 0!==o&&(e.delta=o)}},{reg:/^mouse|contextmenu|click|mspointer|(^DOMMouseScroll$)/i,props:[\"buttons\",\"clientX\",\"clientY\",\"button\",\"offsetX\",\"relatedTarget\",\"which\",\"fromElement\",\"toElement\",\"offsetY\",\"pageX\",\"pageY\",\"screenX\",\"screenY\"],fix:function(e,t){var n=void 0,r=void 0,o=void 0,c=e.target,i=t.button;return c&&l(e.pageX)&&!l(t.clientX)&&(r=(n=c.ownerDocument||document).documentElement,o=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||o&&o.scrollLeft||0)-(r&&r.clientLeft||o&&o.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||o&&o.scrollTop||0)-(r&&r.clientTop||o&&o.clientTop||0)),e.which||void 0===i||(e.which=1&i?1:2&i?3:4&i?2:0),!e.relatedTarget&&e.fromElement&&(e.relatedTarget=e.fromElement===c?e.toElement:e.fromElement),e}}];function s(){return true}function f(){return i}function p(e){var t=e.type,n=\"function\"===typeof e.stopPropagation||\"boolean\"===typeof e.cancelBubble;o.default.call(this),this.nativeEvent=e;var r=f;\"defaultPrevented\"in e?r=e.defaultPrevented?s:f:\"getPreventDefault\"in e?r=e.getPreventDefault()?s:f:\"returnValue\"in e&&(r=e.returnValue===i?s:f),this.isDefaultPrevented=r;var c=[],l=void 0,p=void 0,h=a.concat();for(u.forEach((function(e){t.match(e.reg)&&(h=h.concat(e.props),e.fix&&c.push(e.fix))})),l=h.length;l;)this[p=h[--l]]=e[p];for(!this.target&&n&&(this.target=e.srcElement||document),this.target&&3===this.target.nodeType&&(this.target=this.target.parentNode),l=c.length;l;)(0,c[--l])(this,e);this.timeStamp=e.timeStamp||Date.now()}var h=o.default.prototype;(0,c.default)(p.prototype,h,{constructor:p,preventDefault:function(){var e=this.nativeEvent;e.preventDefault?e.preventDefault():e.returnValue=i,h.preventDefault.call(this)},stopPropagation:function(){var e=this.nativeEvent;e.stopPropagation?e.stopPropagation():e.cancelBubble=true,h.stopPropagation.call(this)}}),t.default=p,e.exports=t.default},function(e,t,n){\"use strict\";function r(){return!1}function o(){return!0}function c(){this.timeStamp=Date.now(),this.target=void 0,this.currentTarget=void 0}Object.defineProperty(t,\"__esModule\",{value:!0}),c.prototype={isEventObject:1,constructor:c,isDefaultPrevented:r,isPropagationStopped:r,isImmediatePropagationStopped:r,preventDefault:function(){this.isDefaultPrevented=o},stopPropagation:function(){this.isPropagationStopped=o},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=o,this.stopPropagation()},halt:function(e){e?this.stopImmediatePropagation():this.stopPropagation(),this.preventDefault()}},t.default=c,e.exports=t.default},function(e,t,n){n(216);var r=n(37).Object;e.exports=function(e,t,n){return r.defineProperty(e,t,n)}},function(e,t,n){var r=n(48);r(r.S+r.F*!n(38),\"Object\",{defineProperty:n(42).f})},function(e,t,n){\"use strict\";t.__esModule=!0;var r=n(0),o=(i(r),i(n(1))),c=i(n(218));i(n(56));function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function l(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}function u(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var s=1073741823;function f(e){var t=[];return{on:function(e){t.push(e)},off:function(e){t=t.filter((function(t){return t!==e}))},get:function(){return e},set:function(n,r){e=n,t.forEach((function(t){return t(e,r)}))}}}t.default=function(e,t){var n,i,p=\"__create-react-context-\"+(0,c.default)()+\"__\",h=function(e){function n(){var t,r;a(this,n);for(var o=arguments.length,c=Array(o),i=0;i<o;i++)c[i]=arguments[i];return t=r=l(this,e.call.apply(e,[this].concat(c))),r.emitter=f(r.props.value),l(r,t)}return u(n,e),n.prototype.getChildContext=function(){var e;return(e={})[p]=this.emitter,e},n.prototype.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n=this.props.value,r=e.value,o=void 0;((c=n)===(i=r)?0!==c||1/c===1/i:c!==c&&i!==i)?o=0:(o=\"function\"===typeof t?t(n,r):s,0!==(o|=0)&&this.emitter.set(e.value,o))}var c,i},n.prototype.render=function(){return this.props.children},n}(r.Component);h.childContextTypes=((n={})[p]=o.default.object.isRequired,n);var d=function(t){function n(){var e,r;a(this,n);for(var o=arguments.length,c=Array(o),i=0;i<o;i++)c[i]=arguments[i];return e=r=l(this,t.call.apply(t,[this].concat(c))),r.state={value:r.getValue()},r.onUpdate=function(e,t){0!==((0|r.observedBits)&t)&&r.setState({value:r.getValue()})},l(r,e)}return u(n,t),n.prototype.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=void 0===t||null===t?s:t},n.prototype.componentDidMount=function(){this.context[p]&&this.context[p].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=void 0===e||null===e?s:e},n.prototype.componentWillUnmount=function(){this.context[p]&&this.context[p].off(this.onUpdate)},n.prototype.getValue=function(){return this.context[p]?this.context[p].get():e},n.prototype.render=function(){return(e=this.props.children,Array.isArray(e)?e[0]:e)(this.state.value);var e},n}(r.Component);return d.contextTypes=((i={})[p]=o.default.object,i),{Provider:h,Consumer:d}},e.exports=t.default},function(e,t,n){\"use strict\";(function(t){var n=\"__global_unique_id__\";e.exports=function(){return t[n]=(t[n]||0)+1}}).call(this,n(78))},function(e,t,n){\"use strict\";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var o=r(n(220));function c(e,t,n){var r;return(r=Math.round(e.h)>=60&&Math.round(e.h)<=240?n?Math.round(e.h)-2*t:Math.round(e.h)+2*t:n?Math.round(e.h)+2*t:Math.round(e.h)-2*t)<0?r+=360:r>=360&&(r-=360),r}function i(e,t,n){return 0===e.h&&0===e.s?e.s:((r=n?Math.round(100*e.s)-16*t:4===t?Math.round(100*e.s)+16:Math.round(100*e.s)+5*t)>100&&(r=100),n&&5===t&&r>10&&(r=10),r<6&&(r=6),r);var r}function a(e,t,n){return n?Math.round(100*e.v)+5*t:Math.round(100*e.v)-15*t}t.default=function(e){for(var t=[],n=o.default(e),r=5;r>0;r-=1){var l=n.toHsv(),u=o.default({h:c(l,r,!0),s:i(l,r,!0),v:a(l,r,!0)}).toHexString();t.push(u)}for(t.push(n.toHexString()),r=1;r<=4;r+=1){l=n.toHsv(),u=o.default({h:c(l,r),s:i(l,r),v:a(l,r)}).toHexString();t.push(u)}return t}},function(e,t,n){var r;!function(o){var c=/^\\s+/,i=/\\s+$/,a=0,l=o.round,u=o.min,s=o.max,f=o.random;function p(e,t){if(t=t||{},(e=e||\"\")instanceof p)return e;if(!(this instanceof p))return new p(e,t);var n=function(e){var t={r:0,g:0,b:0},n=1,r=null,a=null,l=null,f=!1,p=!1;\"string\"==typeof e&&(e=function(e){e=e.replace(c,\"\").replace(i,\"\").toLowerCase();var t,n=!1;if(E[e])e=E[e],n=!0;else if(\"transparent\"==e)return{r:0,g:0,b:0,a:0,format:\"name\"};if(t=I.rgb.exec(e))return{r:t[1],g:t[2],b:t[3]};if(t=I.rgba.exec(e))return{r:t[1],g:t[2],b:t[3],a:t[4]};if(t=I.hsl.exec(e))return{h:t[1],s:t[2],l:t[3]};if(t=I.hsla.exec(e))return{h:t[1],s:t[2],l:t[3],a:t[4]};if(t=I.hsv.exec(e))return{h:t[1],s:t[2],v:t[3]};if(t=I.hsva.exec(e))return{h:t[1],s:t[2],v:t[3],a:t[4]};if(t=I.hex8.exec(e))return{r:j(t[1]),g:j(t[2]),b:j(t[3]),a:A(t[4]),format:n?\"name\":\"hex8\"};if(t=I.hex6.exec(e))return{r:j(t[1]),g:j(t[2]),b:j(t[3]),format:n?\"name\":\"hex\"};if(t=I.hex4.exec(e))return{r:j(t[1]+\"\"+t[1]),g:j(t[2]+\"\"+t[2]),b:j(t[3]+\"\"+t[3]),a:A(t[4]+\"\"+t[4]),format:n?\"name\":\"hex8\"};if(t=I.hex3.exec(e))return{r:j(t[1]+\"\"+t[1]),g:j(t[2]+\"\"+t[2]),b:j(t[3]+\"\"+t[3]),format:n?\"name\":\"hex\"};return!1}(e));\"object\"==typeof e&&(F(e.r)&&F(e.g)&&F(e.b)?(h=e.r,d=e.g,v=e.b,t={r:255*T(h,255),g:255*T(d,255),b:255*T(v,255)},f=!0,p=\"%\"===String(e.r).substr(-1)?\"prgb\":\"rgb\"):F(e.h)&&F(e.s)&&F(e.v)?(r=D(e.s),a=D(e.v),t=function(e,t,n){e=6*T(e,360),t=T(t,100),n=T(n,100);var r=o.floor(e),c=e-r,i=n*(1-t),a=n*(1-c*t),l=n*(1-(1-c)*t),u=r%6;return{r:255*[n,a,i,i,l,n][u],g:255*[l,n,n,a,i,i][u],b:255*[i,i,l,n,n,a][u]}}(e.h,r,a),f=!0,p=\"hsv\"):F(e.h)&&F(e.s)&&F(e.l)&&(r=D(e.s),l=D(e.l),t=function(e,t,n){var r,o,c;function i(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*(t-e)*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}if(e=T(e,360),t=T(t,100),n=T(n,100),0===t)r=o=c=n;else{var a=n<.5?n*(1+t):n+t-n*t,l=2*n-a;r=i(l,a,e+1/3),o=i(l,a,e),c=i(l,a,e-1/3)}return{r:255*r,g:255*o,b:255*c}}(e.h,r,l),f=!0,p=\"hsl\"),e.hasOwnProperty(\"a\")&&(n=e.a));var h,d,v;return n=V(n),{ok:f,format:e.format||p,r:u(255,s(t.r,0)),g:u(255,s(t.g,0)),b:u(255,s(t.b,0)),a:n}}(e);this._originalInput=e,this._r=n.r,this._g=n.g,this._b=n.b,this._a=n.a,this._roundA=l(100*this._a)/100,this._format=t.format||n.format,this._gradientType=t.gradientType,this._r<1&&(this._r=l(this._r)),this._g<1&&(this._g=l(this._g)),this._b<1&&(this._b=l(this._b)),this._ok=n.ok,this._tc_id=a++}function h(e,t,n){e=T(e,255),t=T(t,255),n=T(n,255);var r,o,c=s(e,t,n),i=u(e,t,n),a=(c+i)/2;if(c==i)r=o=0;else{var l=c-i;switch(o=a>.5?l/(2-c-i):l/(c+i),c){case e:r=(t-n)/l+(t<n?6:0);break;case t:r=(n-e)/l+2;break;case n:r=(e-t)/l+4}r/=6}return{h:r,s:o,l:a}}function d(e,t,n){e=T(e,255),t=T(t,255),n=T(n,255);var r,o,c=s(e,t,n),i=u(e,t,n),a=c,l=c-i;if(o=0===c?0:l/c,c==i)r=0;else{switch(c){case e:r=(t-n)/l+(t<n?6:0);break;case t:r=(n-e)/l+2;break;case n:r=(e-t)/l+4}r/=6}return{h:r,s:o,v:a}}function v(e,t,n,r){var o=[N(l(e).toString(16)),N(l(t).toString(16)),N(l(n).toString(16))];return r&&o[0].charAt(0)==o[0].charAt(1)&&o[1].charAt(0)==o[1].charAt(1)&&o[2].charAt(0)==o[2].charAt(1)?o[0].charAt(0)+o[1].charAt(0)+o[2].charAt(0):o.join(\"\")}function m(e,t,n,r){return[N(R(r)),N(l(e).toString(16)),N(l(t).toString(16)),N(l(n).toString(16))].join(\"\")}function y(e,t){t=0===t?0:t||10;var n=p(e).toHsl();return n.s-=t/100,n.s=L(n.s),p(n)}function b(e,t){t=0===t?0:t||10;var n=p(e).toHsl();return n.s+=t/100,n.s=L(n.s),p(n)}function g(e){return p(e).desaturate(100)}function w(e,t){t=0===t?0:t||10;var n=p(e).toHsl();return n.l+=t/100,n.l=L(n.l),p(n)}function z(e,t){t=0===t?0:t||10;var n=p(e).toRgb();return n.r=s(0,u(255,n.r-l(-t/100*255))),n.g=s(0,u(255,n.g-l(-t/100*255))),n.b=s(0,u(255,n.b-l(-t/100*255))),p(n)}function O(e,t){t=0===t?0:t||10;var n=p(e).toHsl();return n.l-=t/100,n.l=L(n.l),p(n)}function C(e,t){var n=p(e).toHsl(),r=(n.h+t)%360;return n.h=r<0?360+r:r,p(n)}function M(e){var t=p(e).toHsl();return t.h=(t.h+180)%360,p(t)}function S(e){var t=p(e).toHsl(),n=t.h;return[p(e),p({h:(n+120)%360,s:t.s,l:t.l}),p({h:(n+240)%360,s:t.s,l:t.l})]}function _(e){var t=p(e).toHsl(),n=t.h;return[p(e),p({h:(n+90)%360,s:t.s,l:t.l}),p({h:(n+180)%360,s:t.s,l:t.l}),p({h:(n+270)%360,s:t.s,l:t.l})]}function x(e){var t=p(e).toHsl(),n=t.h;return[p(e),p({h:(n+72)%360,s:t.s,l:t.l}),p({h:(n+216)%360,s:t.s,l:t.l})]}function k(e,t,n){t=t||6,n=n||30;var r=p(e).toHsl(),o=360/n,c=[p(e)];for(r.h=(r.h-(o*t>>1)+720)%360;--t;)r.h=(r.h+o)%360,c.push(p(r));return c}function H(e,t){t=t||6;for(var n=p(e).toHsv(),r=n.h,o=n.s,c=n.v,i=[],a=1/t;t--;)i.push(p({h:r,s:o,v:c})),c=(c+a)%1;return i}p.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var e=this.toRgb();return(299*e.r+587*e.g+114*e.b)/1e3},getLuminance:function(){var e,t,n,r=this.toRgb();return e=r.r/255,t=r.g/255,n=r.b/255,.2126*(e<=.03928?e/12.92:o.pow((e+.055)/1.055,2.4))+.7152*(t<=.03928?t/12.92:o.pow((t+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:o.pow((n+.055)/1.055,2.4))},setAlpha:function(e){return this._a=V(e),this._roundA=l(100*this._a)/100,this},toHsv:function(){var e=d(this._r,this._g,this._b);return{h:360*e.h,s:e.s,v:e.v,a:this._a}},toHsvString:function(){var e=d(this._r,this._g,this._b),t=l(360*e.h),n=l(100*e.s),r=l(100*e.v);return 1==this._a?\"hsv(\"+t+\", \"+n+\"%, \"+r+\"%)\":\"hsva(\"+t+\", \"+n+\"%, \"+r+\"%, \"+this._roundA+\")\"},toHsl:function(){var e=h(this._r,this._g,this._b);return{h:360*e.h,s:e.s,l:e.l,a:this._a}},toHslString:function(){var e=h(this._r,this._g,this._b),t=l(360*e.h),n=l(100*e.s),r=l(100*e.l);return 1==this._a?\"hsl(\"+t+\", \"+n+\"%, \"+r+\"%)\":\"hsla(\"+t+\", \"+n+\"%, \"+r+\"%, \"+this._roundA+\")\"},toHex:function(e){return v(this._r,this._g,this._b,e)},toHexString:function(e){return\"#\"+this.toHex(e)},toHex8:function(e){return function(e,t,n,r,o){var c=[N(l(e).toString(16)),N(l(t).toString(16)),N(l(n).toString(16)),N(R(r))];if(o&&c[0].charAt(0)==c[0].charAt(1)&&c[1].charAt(0)==c[1].charAt(1)&&c[2].charAt(0)==c[2].charAt(1)&&c[3].charAt(0)==c[3].charAt(1))return c[0].charAt(0)+c[1].charAt(0)+c[2].charAt(0)+c[3].charAt(0);return c.join(\"\")}(this._r,this._g,this._b,this._a,e)},toHex8String:function(e){return\"#\"+this.toHex8(e)},toRgb:function(){return{r:l(this._r),g:l(this._g),b:l(this._b),a:this._a}},toRgbString:function(){return 1==this._a?\"rgb(\"+l(this._r)+\", \"+l(this._g)+\", \"+l(this._b)+\")\":\"rgba(\"+l(this._r)+\", \"+l(this._g)+\", \"+l(this._b)+\", \"+this._roundA+\")\"},toPercentageRgb:function(){return{r:l(100*T(this._r,255))+\"%\",g:l(100*T(this._g,255))+\"%\",b:l(100*T(this._b,255))+\"%\",a:this._a}},toPercentageRgbString:function(){return 1==this._a?\"rgb(\"+l(100*T(this._r,255))+\"%, \"+l(100*T(this._g,255))+\"%, \"+l(100*T(this._b,255))+\"%)\":\"rgba(\"+l(100*T(this._r,255))+\"%, \"+l(100*T(this._g,255))+\"%, \"+l(100*T(this._b,255))+\"%, \"+this._roundA+\")\"},toName:function(){return 0===this._a?\"transparent\":!(this._a<1)&&(P[v(this._r,this._g,this._b,!0)]||!1)},toFilter:function(e){var t=\"#\"+m(this._r,this._g,this._b,this._a),n=t,r=this._gradientType?\"GradientType = 1, \":\"\";if(e){var o=p(e);n=\"#\"+m(o._r,o._g,o._b,o._a)}return\"progid:DXImageTransform.Microsoft.gradient(\"+r+\"startColorstr=\"+t+\",endColorstr=\"+n+\")\"},toString:function(e){var t=!!e;e=e||this._format;var n=!1,r=this._a<1&&this._a>=0;return t||!r||\"hex\"!==e&&\"hex6\"!==e&&\"hex3\"!==e&&\"hex4\"!==e&&\"hex8\"!==e&&\"name\"!==e?(\"rgb\"===e&&(n=this.toRgbString()),\"prgb\"===e&&(n=this.toPercentageRgbString()),\"hex\"!==e&&\"hex6\"!==e||(n=this.toHexString()),\"hex3\"===e&&(n=this.toHexString(!0)),\"hex4\"===e&&(n=this.toHex8String(!0)),\"hex8\"===e&&(n=this.toHex8String()),\"name\"===e&&(n=this.toName()),\"hsl\"===e&&(n=this.toHslString()),\"hsv\"===e&&(n=this.toHsvString()),n||this.toHexString()):\"name\"===e&&0===this._a?this.toName():this.toRgbString()},clone:function(){return p(this.toString())},_applyModification:function(e,t){var n=e.apply(null,[this].concat([].slice.call(t)));return this._r=n._r,this._g=n._g,this._b=n._b,this.setAlpha(n._a),this},lighten:function(){return this._applyModification(w,arguments)},brighten:function(){return this._applyModification(z,arguments)},darken:function(){return this._applyModification(O,arguments)},desaturate:function(){return this._applyModification(y,arguments)},saturate:function(){return this._applyModification(b,arguments)},greyscale:function(){return this._applyModification(g,arguments)},spin:function(){return this._applyModification(C,arguments)},_applyCombination:function(e,t){return e.apply(null,[this].concat([].slice.call(t)))},analogous:function(){return this._applyCombination(k,arguments)},complement:function(){return this._applyCombination(M,arguments)},monochromatic:function(){return this._applyCombination(H,arguments)},splitcomplement:function(){return this._applyCombination(x,arguments)},triad:function(){return this._applyCombination(S,arguments)},tetrad:function(){return this._applyCombination(_,arguments)}},p.fromRatio=function(e,t){if(\"object\"==typeof e){var n={};for(var r in e)e.hasOwnProperty(r)&&(n[r]=\"a\"===r?e[r]:D(e[r]));e=n}return p(e,t)},p.equals=function(e,t){return!(!e||!t)&&p(e).toRgbString()==p(t).toRgbString()},p.random=function(){return p.fromRatio({r:f(),g:f(),b:f()})},p.mix=function(e,t,n){n=0===n?0:n||50;var r=p(e).toRgb(),o=p(t).toRgb(),c=n/100;return p({r:(o.r-r.r)*c+r.r,g:(o.g-r.g)*c+r.g,b:(o.b-r.b)*c+r.b,a:(o.a-r.a)*c+r.a})},p.readability=function(e,t){var n=p(e),r=p(t);return(o.max(n.getLuminance(),r.getLuminance())+.05)/(o.min(n.getLuminance(),r.getLuminance())+.05)},p.isReadable=function(e,t,n){var r,o,c=p.readability(e,t);switch(o=!1,(r=function(e){var t,n;t=((e=e||{level:\"AA\",size:\"small\"}).level||\"AA\").toUpperCase(),n=(e.size||\"small\").toLowerCase(),\"AA\"!==t&&\"AAA\"!==t&&(t=\"AA\");\"small\"!==n&&\"large\"!==n&&(n=\"small\");return{level:t,size:n}}(n)).level+r.size){case\"AAsmall\":case\"AAAlarge\":o=c>=4.5;break;case\"AAlarge\":o=c>=3;break;case\"AAAsmall\":o=c>=7}return o},p.mostReadable=function(e,t,n){var r,o,c,i,a=null,l=0;o=(n=n||{}).includeFallbackColors,c=n.level,i=n.size;for(var u=0;u<t.length;u++)(r=p.readability(e,t[u]))>l&&(l=r,a=p(t[u]));return p.isReadable(e,a,{level:c,size:i})||!o?a:(n.includeFallbackColors=!1,p.mostReadable(e,[\"#fff\",\"#000\"],n))};var E=p.names={aliceblue:\"f0f8ff\",antiquewhite:\"faebd7\",aqua:\"0ff\",aquamarine:\"7fffd4\",azure:\"f0ffff\",beige:\"f5f5dc\",bisque:\"ffe4c4\",black:\"000\",blanchedalmond:\"ffebcd\",blue:\"00f\",blueviolet:\"8a2be2\",brown:\"a52a2a\",burlywood:\"deb887\",burntsienna:\"ea7e5d\",cadetblue:\"5f9ea0\",chartreuse:\"7fff00\",chocolate:\"d2691e\",coral:\"ff7f50\",cornflowerblue:\"6495ed\",cornsilk:\"fff8dc\",crimson:\"dc143c\",cyan:\"0ff\",darkblue:\"00008b\",darkcyan:\"008b8b\",darkgoldenrod:\"b8860b\",darkgray:\"a9a9a9\",darkgreen:\"006400\",darkgrey:\"a9a9a9\",darkkhaki:\"bdb76b\",darkmagenta:\"8b008b\",darkolivegreen:\"556b2f\",darkorange:\"ff8c00\",darkorchid:\"9932cc\",darkred:\"8b0000\",darksalmon:\"e9967a\",darkseagreen:\"8fbc8f\",darkslateblue:\"483d8b\",darkslategray:\"2f4f4f\",darkslategrey:\"2f4f4f\",darkturquoise:\"00ced1\",darkviolet:\"9400d3\",deeppink:\"ff1493\",deepskyblue:\"00bfff\",dimgray:\"696969\",dimgrey:\"696969\",dodgerblue:\"1e90ff\",firebrick:\"b22222\",floralwhite:\"fffaf0\",forestgreen:\"228b22\",fuchsia:\"f0f\",gainsboro:\"dcdcdc\",ghostwhite:\"f8f8ff\",gold:\"ffd700\",goldenrod:\"daa520\",gray:\"808080\",green:\"008000\",greenyellow:\"adff2f\",grey:\"808080\",honeydew:\"f0fff0\",hotpink:\"ff69b4\",indianred:\"cd5c5c\",indigo:\"4b0082\",ivory:\"fffff0\",khaki:\"f0e68c\",lavender:\"e6e6fa\",lavenderblush:\"fff0f5\",lawngreen:\"7cfc00\",lemonchiffon:\"fffacd\",lightblue:\"add8e6\",lightcoral:\"f08080\",lightcyan:\"e0ffff\",lightgoldenrodyellow:\"fafad2\",lightgray:\"d3d3d3\",lightgreen:\"90ee90\",lightgrey:\"d3d3d3\",lightpink:\"ffb6c1\",lightsalmon:\"ffa07a\",lightseagreen:\"20b2aa\",lightskyblue:\"87cefa\",lightslategray:\"789\",lightslategrey:\"789\",lightsteelblue:\"b0c4de\",lightyellow:\"ffffe0\",lime:\"0f0\",limegreen:\"32cd32\",linen:\"faf0e6\",magenta:\"f0f\",maroon:\"800000\",mediumaquamarine:\"66cdaa\",mediumblue:\"0000cd\",mediumorchid:\"ba55d3\",mediumpurple:\"9370db\",mediumseagreen:\"3cb371\",mediumslateblue:\"7b68ee\",mediumspringgreen:\"00fa9a\",mediumturquoise:\"48d1cc\",mediumvioletred:\"c71585\",midnightblue:\"191970\",mintcream:\"f5fffa\",mistyrose:\"ffe4e1\",moccasin:\"ffe4b5\",navajowhite:\"ffdead\",navy:\"000080\",oldlace:\"fdf5e6\",olive:\"808000\",olivedrab:\"6b8e23\",orange:\"ffa500\",orangered:\"ff4500\",orchid:\"da70d6\",palegoldenrod:\"eee8aa\",palegreen:\"98fb98\",paleturquoise:\"afeeee\",palevioletred:\"db7093\",papayawhip:\"ffefd5\",peachpuff:\"ffdab9\",peru:\"cd853f\",pink:\"ffc0cb\",plum:\"dda0dd\",powderblue:\"b0e0e6\",purple:\"800080\",rebeccapurple:\"663399\",red:\"f00\",rosybrown:\"bc8f8f\",royalblue:\"4169e1\",saddlebrown:\"8b4513\",salmon:\"fa8072\",sandybrown:\"f4a460\",seagreen:\"2e8b57\",seashell:\"fff5ee\",sienna:\"a0522d\",silver:\"c0c0c0\",skyblue:\"87ceeb\",slateblue:\"6a5acd\",slategray:\"708090\",slategrey:\"708090\",snow:\"fffafa\",springgreen:\"00ff7f\",steelblue:\"4682b4\",tan:\"d2b48c\",teal:\"008080\",thistle:\"d8bfd8\",tomato:\"ff6347\",turquoise:\"40e0d0\",violet:\"ee82ee\",wheat:\"f5deb3\",white:\"fff\",whitesmoke:\"f5f5f5\",yellow:\"ff0\",yellowgreen:\"9acd32\"},P=p.hexNames=function(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[e[n]]=n);return t}(E);function V(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function T(e,t){(function(e){return\"string\"==typeof e&&-1!=e.indexOf(\".\")&&1===parseFloat(e)})(e)&&(e=\"100%\");var n=function(e){return\"string\"===typeof e&&-1!=e.indexOf(\"%\")}(e);return e=u(t,s(0,parseFloat(e))),n&&(e=parseInt(e*t,10)/100),o.abs(e-t)<1e-6?1:e%t/parseFloat(t)}function L(e){return u(1,s(0,e))}function j(e){return parseInt(e,16)}function N(e){return 1==e.length?\"0\"+e:\"\"+e}function D(e){return e<=1&&(e=100*e+\"%\"),e}function R(e){return o.round(255*parseFloat(e)).toString(16)}function A(e){return j(e)/255}var I=function(){var e=\"(?:[-\\\\+]?\\\\d*\\\\.\\\\d+%?)|(?:[-\\\\+]?\\\\d+%?)\",t=\"[\\\\s|\\\\(]+(\"+e+\")[,|\\\\s]+(\"+e+\")[,|\\\\s]+(\"+e+\")\\\\s*\\\\)?\",n=\"[\\\\s|\\\\(]+(\"+e+\")[,|\\\\s]+(\"+e+\")[,|\\\\s]+(\"+e+\")[,|\\\\s]+(\"+e+\")\\\\s*\\\\)?\";return{CSS_UNIT:new RegExp(e),rgb:new RegExp(\"rgb\"+t),rgba:new RegExp(\"rgba\"+n),hsl:new RegExp(\"hsl\"+t),hsla:new RegExp(\"hsla\"+n),hsv:new RegExp(\"hsv\"+t),hsva:new RegExp(\"hsva\"+n),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/}}();function F(e){return!!I.CSS_UNIT.exec(e)}e.exports?e.exports=p:void 0===(r=function(){return p}.call(t,n,t,e))||(e.exports=r)}(Math)},function(e,t,n){},function(e,t,n){\"use strict\";var r=\"function\"===typeof Symbol&&Symbol.for,o=r?Symbol.for(\"react.element\"):60103,c=r?Symbol.for(\"react.portal\"):60106,i=r?Symbol.for(\"react.fragment\"):60107,a=r?Symbol.for(\"react.strict_mode\"):60108,l=r?Symbol.for(\"react.profiler\"):60114,u=r?Symbol.for(\"react.provider\"):60109,s=r?Symbol.for(\"react.context\"):60110,f=r?Symbol.for(\"react.async_mode\"):60111,p=r?Symbol.for(\"react.concurrent_mode\"):60111,h=r?Symbol.for(\"react.forward_ref\"):60112,d=r?Symbol.for(\"react.suspense\"):60113,v=r?Symbol.for(\"react.suspense_list\"):60120,m=r?Symbol.for(\"react.memo\"):60115,y=r?Symbol.for(\"react.lazy\"):60116,b=r?Symbol.for(\"react.block\"):60121,g=r?Symbol.for(\"react.fundamental\"):60117,w=r?Symbol.for(\"react.responder\"):60118,z=r?Symbol.for(\"react.scope\"):60119;function O(e){if(\"object\"===typeof e&&null!==e){var t=e.$$typeof;switch(t){case o:switch(e=e.type){case f:case p:case i:case l:case a:case d:return e;default:switch(e=e&&e.$$typeof){case s:case h:case y:case m:case u:return e;default:return t}}case c:return t}}}function C(e){return O(e)===p}t.AsyncMode=f,t.ConcurrentMode=p,t.ContextConsumer=s,t.ContextProvider=u,t.Element=o,t.ForwardRef=h,t.Fragment=i,t.Lazy=y,t.Memo=m,t.Portal=c,t.Profiler=l,t.StrictMode=a,t.Suspense=d,t.isAsyncMode=function(e){return C(e)||O(e)===f},t.isConcurrentMode=C,t.isContextConsumer=function(e){return O(e)===s},t.isContextProvider=function(e){return O(e)===u},t.isElement=function(e){return\"object\"===typeof e&&null!==e&&e.$$typeof===o},t.isForwardRef=function(e){return O(e)===h},t.isFragment=function(e){return O(e)===i},t.isLazy=function(e){return O(e)===y},t.isMemo=function(e){return O(e)===m},t.isPortal=function(e){return O(e)===c},t.isProfiler=function(e){return O(e)===l},t.isStrictMode=function(e){return O(e)===a},t.isSuspense=function(e){return O(e)===d},t.isValidElementType=function(e){return\"string\"===typeof e||\"function\"===typeof e||e===i||e===p||e===l||e===a||e===d||e===v||\"object\"===typeof e&&null!==e&&(e.$$typeof===y||e.$$typeof===m||e.$$typeof===u||e.$$typeof===s||e.$$typeof===h||e.$$typeof===g||e.$$typeof===w||e.$$typeof===z||e.$$typeof===b)},t.typeOf=O},function(e,t,n){(function(t){(function(){var n,r,o,c,i,a;\"undefined\"!==typeof performance&&null!==performance&&performance.now?e.exports=function(){return performance.now()}:\"undefined\"!==typeof t&&null!==t&&t.hrtime?(e.exports=function(){return(n()-i)/1e6},r=t.hrtime,c=(n=function(){var e;return 1e9*(e=r())[0]+e[1]})(),a=1e9*t.uptime(),i=c-a):Date.now?(e.exports=function(){return Date.now()-o},o=Date.now()):(e.exports=function(){return(new Date).getTime()-o},o=(new Date).getTime())}).call(this)}).call(this,n(135))},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function c(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function i(e,t){return(i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?l(e):t}function l(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}function u(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function s(e){return(s=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var f=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},p=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var h=f(n(0)),d=f(n(1)),v=p(n(15)),m=p(n(225)),y=p(n(136)),b=n(28),g=p(n(230)),w=p(n(55)),z=p(n(8)),O=n(12),C=n(54),M=p(n(296)),S=p(n(297)),_=p(n(315)),x=p(n(155)),k=p(n(156)),H=p(n(316)),E=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&i(e,t)}(d,e);var t,n,r,f,p=(t=d,function(){var e,n=s(t);if(u()){var r=s(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return a(this,e)});function d(e){var t;return function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,d),(t=p.call(this,e)).state={},t.getRowKey=function(e,n){var r=t.props.rowKey,o=\"function\"===typeof r?r(e,n):e[r];return y.default(void 0!==o,\"Each record in table should have a unique `key` prop,or set `rowKey` to an unique primary key.\"),void 0===o?n:o},t.handleWindowResize=function(){t.syncFixedTableRowHeight(),t.setScrollPositionClassName()},t.syncFixedTableRowHeight=function(){var e=t.tableNode.getBoundingClientRect();if(!(void 0!==e.height&&e.height<=0)){var n=t.props.prefixCls,r=t.headTable?t.headTable.querySelectorAll(\"thead\"):t.bodyTable.querySelectorAll(\"thead\"),o=t.bodyTable.querySelectorAll(\".\".concat(n,\"-row\"))||[],c=[].map.call(r,(function(e){return e.getBoundingClientRect().height||\"auto\"})),i=t.store.getState(),a=[].reduce.call(o,(function(e,t){var n=t.getAttribute(\"data-row-key\"),r=t.getBoundingClientRect().height||i.fixedColumnsBodyRowsHeight[n]||\"auto\";return e[n]=r,e}),{});v.default(i.fixedColumnsHeadRowsHeight,c)&&v.default(i.fixedColumnsBodyRowsHeight,a)||t.store.setState({fixedColumnsHeadRowsHeight:c,fixedColumnsBodyRowsHeight:a})}},t.handleBodyScrollLeft=function(e){if(e.currentTarget===e.target){var n=e.target,r=t.props.scroll,o=void 0===r?{}:r,c=l(t),i=c.headTable,a=c.bodyTable;n.scrollLeft!==t.lastScrollLeft&&o.x&&(n===a&&i?i.scrollLeft=n.scrollLeft:n===i&&a&&(a.scrollLeft=n.scrollLeft),t.setScrollPositionClassName()),t.lastScrollLeft=n.scrollLeft}},t.handleBodyScrollTop=function(e){var n=e.target;if(e.currentTarget===n){var r=t.props.scroll,o=void 0===r?{}:r,c=l(t),i=c.headTable,a=c.bodyTable,u=c.fixedColumnsBodyLeft,s=c.fixedColumnsBodyRight;if(n.scrollTop!==t.lastScrollTop&&o.y&&n!==i){var f=n.scrollTop;u&&n!==u&&(u.scrollTop=f),s&&n!==s&&(s.scrollTop=f),a&&n!==a&&(a.scrollTop=f)}t.lastScrollTop=n.scrollTop}},t.handleBodyScroll=function(e){t.handleBodyScrollLeft(e),t.handleBodyScrollTop(e)},t.handleWheel=function(e){var n=t.props.scroll,r=void 0===n?{}:n;if(window.navigator.userAgent.match(/Trident\\/7\\./)&&r.y){var o=e.deltaY,c=e.target,i=l(t),a=i.bodyTable,u=i.fixedColumnsBodyLeft,s=i.fixedColumnsBodyRight,f=0;f=t.lastScrollTop?t.lastScrollTop+o:o,u&&c!==u&&(e.preventDefault(),u.scrollTop=f),s&&c!==s&&(e.preventDefault(),s.scrollTop=f),a&&c!==a&&(e.preventDefault(),a.scrollTop=f)}},t.saveRef=function(e){return function(n){t[e]=n}},t.saveTableNodeRef=function(e){t.tableNode=e},[\"onRowClick\",\"onRowDoubleClick\",\"onRowContextMenu\",\"onRowMouseEnter\",\"onRowMouseLeave\"].forEach((function(t){y.default(void 0===e[t],\"\".concat(t,\" is deprecated, please use onRow instead.\"))})),y.default(void 0===e.getBodyWrapper,\"getBodyWrapper is deprecated, please use custom components instead.\"),t.columnManager=new M.default(e.columns,e.children),t.store=b.create({currentHoverKey:null,fixedColumnsHeadRowsHeight:[],fixedColumnsBodyRowsHeight:{}}),t.setScrollPosition(\"left\"),t.debouncedWindowResize=C.debounce(t.handleWindowResize,150),t}return n=d,f=[{key:\"getDerivedStateFromProps\",value:function(e,t){return e.columns&&e.columns!==t.columns?{columns:e.columns,children:null}:e.children!==t.children?{columns:null,children:e.children}:null}}],(r=[{key:\"getChildContext\",value:function(){return{table:{props:this.props,columnManager:this.columnManager,saveRef:this.saveRef,components:g.default({table:\"table\",header:{wrapper:\"thead\",row:\"tr\",cell:\"th\"},body:{wrapper:\"tbody\",row:\"tr\",cell:\"td\"}},this.props.components)}}}},{key:\"componentDidMount\",value:function(){this.columnManager.isAnyColumnsFixed()&&(this.handleWindowResize(),this.resizeEvent=m.default(window,\"resize\",this.debouncedWindowResize)),this.headTable&&(this.headTable.scrollLeft=0),this.bodyTable&&(this.bodyTable.scrollLeft=0)}},{key:\"componentDidUpdate\",value:function(e){this.columnManager.isAnyColumnsFixed()&&(this.handleWindowResize(),this.resizeEvent||(this.resizeEvent=m.default(window,\"resize\",this.debouncedWindowResize))),e.data.length>0&&0===this.props.data.length&&this.hasScrollX()&&this.resetScrollX()}},{key:\"componentWillUnmount\",value:function(){this.resizeEvent&&this.resizeEvent.remove(),this.debouncedWindowResize&&this.debouncedWindowResize.cancel()}},{key:\"setScrollPosition\",value:function(e){if(this.scrollPosition=e,this.tableNode){var t=this.props.prefixCls;\"both\"===e?w.default(this.tableNode).remove(new RegExp(\"^\".concat(t,\"-scroll-position-.+$\"))).add(\"\".concat(t,\"-scroll-position-left\")).add(\"\".concat(t,\"-scroll-position-right\")):w.default(this.tableNode).remove(new RegExp(\"^\".concat(t,\"-scroll-position-.+$\"))).add(\"\".concat(t,\"-scroll-position-\").concat(e))}}},{key:\"setScrollPositionClassName\",value:function(){var e=this.bodyTable,t=0===e.scrollLeft,n=e.scrollLeft+1>=e.children[0].getBoundingClientRect().width-e.getBoundingClientRect().width;t&&n?this.setScrollPosition(\"both\"):t?this.setScrollPosition(\"left\"):n?this.setScrollPosition(\"right\"):\"middle\"!==this.scrollPosition&&this.setScrollPosition(\"middle\")}},{key:\"isTableLayoutFixed\",value:function(){var e=this.props,t=e.tableLayout,n=e.columns,r=void 0===n?[]:n,o=e.useFixedHeader,c=e.scroll,i=void 0===c?{}:c;return\"undefined\"!==typeof t?\"fixed\"===t:!!r.some((function(e){return!!e.ellipsis}))||!(!o&&!i.y)||!(!i.x||!0===i.x||\"max-content\"===i.x)}},{key:\"resetScrollX\",value:function(){this.headTable&&(this.headTable.scrollLeft=0),this.bodyTable&&(this.bodyTable.scrollLeft=0)}},{key:\"hasScrollX\",value:function(){var e=this.props.scroll;return\"x\"in(void 0===e?{}:e)}},{key:\"renderMainTable\",value:function(){var e=this.props,t=e.scroll,n=e.prefixCls,r=this.columnManager.isAnyColumnsFixed(),o=r||t.x||t.y,c=[this.renderTable({columns:this.columnManager.groupedColumns(),isAnyColumnsFixed:r}),this.renderEmptyText(),this.renderFooter()];return o?h.createElement(\"div\",{className:\"\".concat(n,\"-scroll\")},c):c}},{key:\"renderLeftFixedTable\",value:function(){var e=this.props.prefixCls;return h.createElement(\"div\",{className:\"\".concat(e,\"-fixed-left\")},this.renderTable({columns:this.columnManager.leftColumns(),fixed:\"left\"}))}},{key:\"renderRightFixedTable\",value:function(){var e=this.props.prefixCls;return h.createElement(\"div\",{className:\"\".concat(e,\"-fixed-right\")},this.renderTable({columns:this.columnManager.rightColumns(),fixed:\"right\"}))}},{key:\"renderTable\",value:function(e){var t=e.columns,n=e.fixed,r=e.isAnyColumnsFixed,o=this.props,c=o.prefixCls,i=o.scroll,a=(void 0===i?{}:i).x||n?\"\".concat(c,\"-fixed\"):\"\";return[h.createElement(S.default,{key:\"head\",columns:t,fixed:n,tableClassName:a,handleBodyScrollLeft:this.handleBodyScrollLeft,expander:this.expander}),h.createElement(_.default,{key:\"body\",columns:t,fixed:n,tableClassName:a,getRowKey:this.getRowKey,handleWheel:this.handleWheel,handleBodyScroll:this.handleBodyScroll,expander:this.expander,isAnyColumnsFixed:r})]}},{key:\"renderTitle\",value:function(){var e=this.props,t=e.title,n=e.prefixCls;return t?h.createElement(\"div\",{className:\"\".concat(n,\"-title\"),key:\"title\"},t(this.props.data)):null}},{key:\"renderFooter\",value:function(){var e=this.props,t=e.footer,n=e.prefixCls;return t?h.createElement(\"div\",{className:\"\".concat(n,\"-footer\"),key:\"footer\"},t(this.props.data)):null}},{key:\"renderEmptyText\",value:function(){var e=this.props,t=e.emptyText,n=e.prefixCls;if(e.data.length)return null;var r=\"\".concat(n,\"-placeholder\");return h.createElement(\"div\",{className:r,key:\"emptyText\"},\"function\"===typeof t?t():t)}},{key:\"render\",value:function(){var e,t=this,n=this.props,r=n.prefixCls;this.state.columns?this.columnManager.reset(n.columns):this.state.children&&this.columnManager.reset(null,n.children);var c=z.default(n.prefixCls,n.className,(o(e={},\"\".concat(r,\"-fixed-header\"),n.useFixedHeader||n.scroll&&n.scroll.y),o(e,\"\".concat(r,\"-scroll-position-left \").concat(r,\"-scroll-position-right\"),\"both\"===this.scrollPosition),o(e,\"\".concat(r,\"-scroll-position-\").concat(this.scrollPosition),\"both\"!==this.scrollPosition),o(e,\"\".concat(r,\"-layout-fixed\"),this.isTableLayoutFixed()),e)),i=this.columnManager.isAnyColumnsLeftFixed(),a=this.columnManager.isAnyColumnsRightFixed(),l=C.getDataAndAriaProps(n);return h.createElement(b.Provider,{store:this.store},h.createElement(H.default,Object.assign({},n,{columnManager:this.columnManager,getRowKey:this.getRowKey}),(function(e){return t.expander=e,h.createElement(\"div\",Object.assign({ref:t.saveTableNodeRef,className:c,style:n.style,id:n.id},l),t.renderTitle(),h.createElement(\"div\",{className:\"\".concat(r,\"-content\")},t.renderMainTable(),i&&t.renderLeftFixedTable(),a&&t.renderRightFixedTable()))})))}}])&&c(n.prototype,r),f&&c(n,f),d}(h.Component);E.childContextTypes={table:d.any,components:d.any},E.Column=x.default,E.ColumnGroup=k.default,E.defaultProps={data:[],useFixedHeader:!1,rowKey:\"key\",rowClassName:function(){return\"\"},onRow:function(){},onHeaderRow:function(){},prefixCls:\"rc-table\",bodyStyle:{},style:{},showHeader:!0,scroll:{},rowRef:function(){return null},emptyText:function(){return\"No Data\"}},O.polyfill(E),t.default=E},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0}),t.default=function(e,t,n,c){var i=o.default.unstable_batchedUpdates?function(e){o.default.unstable_batchedUpdates(n,e)}:n;return(0,r.default)(e,t,i,c)};var r=c(n(119)),o=c(n(9));function c(e){return e&&e.__esModule?e:{default:e}}},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var r,o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),c=n(0),i=((r=c)&&r.__esModule,n(137));function a(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function l(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}var u=function(e){function t(){return a(this,t),l(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),o(t,[{key:\"getChildContext\",value:function(){return{miniStore:this.props.store}}},{key:\"render\",value:function(){return c.Children.only(this.props.children)}}]),t}(c.Component);u.propTypes={store:i.storeShape.isRequired},u.childContextTypes={miniStore:i.storeShape.isRequired},t.default=u},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();t.default=function(e){var t=!!e,n=e||p;return function(f){var p=function(c){function l(e,t){!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,l);var r=function(e,t){if(!e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return!t||\"object\"!==typeof t&&\"function\"!==typeof t?e:t}(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,e,t));return r.handleChange=function(){if(r.unsubscribe){var e=n(r.store.getState(),r.props);r.setState({subscribed:e})}},r.store=t.miniStore,r.state={subscribed:n(r.store.getState(),e),store:r.store,props:e},r}return function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function, not \"+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(l,c),o(l,null,[{key:\"getDerivedStateFromProps\",value:function(t,r){return e&&2===e.length&&t!==r.props?{subscribed:n(r.store.getState(),t),props:t}:{props:t}}}]),o(l,[{key:\"componentDidMount\",value:function(){this.trySubscribe()}},{key:\"componentWillUnmount\",value:function(){this.tryUnsubscribe()}},{key:\"shouldComponentUpdate\",value:function(e,t){return!(0,a.default)(this.props,e)||!(0,a.default)(this.state.subscribed,t.subscribed)}},{key:\"trySubscribe\",value:function(){t&&(this.unsubscribe=this.store.subscribe(this.handleChange),this.handleChange())}},{key:\"tryUnsubscribe\",value:function(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null)}},{key:\"getWrappedInstance\",value:function(){return this.wrappedInstance}},{key:\"render\",value:function(){var e=this,t=r({},this.props,this.state.subscribed,{store:this.store});return f.prototype.render&&(t=r({},t,{ref:function(t){return e.wrappedInstance=t}})),i.default.createElement(f,t)}}]),l}(c.Component);return p.displayName=\"Connect(\"+function(e){return e.displayName||e.name||\"Component\"}(f)+\")\",p.contextTypes={miniStore:s.storeShape.isRequired},(0,u.polyfill)(p),(0,l.default)(p,f)}};var c=n(0),i=f(c),a=f(n(15)),l=f(n(228)),u=n(12),s=n(137);function f(e){return e&&e.__esModule?e:{default:e}}var p=function(){return{}}},function(e,t,n){\"use strict\";var r={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},c=Object.defineProperty,i=Object.getOwnPropertyNames,a=Object.getOwnPropertySymbols,l=Object.getOwnPropertyDescriptor,u=Object.getPrototypeOf,s=u&&u(Object);e.exports=function e(t,n,f){if(\"string\"!==typeof n){if(s){var p=u(n);p&&p!==s&&e(t,p,f)}var h=i(n);a&&(h=h.concat(a(n)));for(var d=0;d<h.length;++d){var v=h[d];if(!r[v]&&!o[v]&&(!f||!f[v])){var m=l(n,v);try{c(t,v,m)}catch(y){}}}return t}return t}},function(e,t,n){\"use strict\";Object.defineProperty(t,\"__esModule\",{value:!0});var r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e};t.default=function(e){var t=e,n=[];return{setState:function(e){t=r({},t,e);for(var o=0;o<n.length;o++)n[o]()},getState:function(){return t},subscribe:function(e){return n.push(e),function(){var t=n.indexOf(e);n.splice(t,1)}}}}},function(e,t,n){var r=n(231),o=n(287)((function(e,t,n){r(e,t,n)}));e.exports=o},function(e,t,n){var r=n(232),o=n(141),c=n(262),i=n(264),a=n(34),l=n(150),u=n(149);e.exports=function e(t,n,s,f,p){t!==n&&c(n,(function(c,l){if(p||(p=new r),a(c))i(t,n,l,s,e,f,p);else{var h=f?f(u(t,l),c,l+\"\",t,n,p):void 0;void 0===h&&(h=c),o(t,l,h)}}),l)}},function(e,t,n){var r=n(79),o=n(238),c=n(239),i=n(240),a=n(241),l=n(242);function u(e){var t=this.__data__=new r(e);this.size=t.size}u.prototype.clear=o,u.prototype.delete=c,u.prototype.get=i,u.prototype.has=a,u.prototype.set=l,e.exports=u},function(e,t){e.exports=function(){this.__data__=[],this.size=0}},function(e,t,n){var r=n(80),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():o.call(t,n,1),--this.size,!0)}},function(e,t,n){var r=n(80);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},function(e,t,n){var r=n(80);e.exports=function(e){return r(this.__data__,e)>-1}},function(e,t,n){var r=n(80);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},function(e,t,n){var r=n(79);e.exports=function(){this.__data__=new r,this.size=0}},function(e,t){e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},function(e,t){e.exports=function(e){return this.__data__.get(e)}},function(e,t){e.exports=function(e){return this.__data__.has(e)}},function(e,t,n){var r=n(79),o=n(138),c=n(140);e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var i=n.__data__;if(!o||i.length<199)return i.push([e,t]),this.size=++n.size,this;n=this.__data__=new c(i)}return n.set(e,t),this.size=n.size,this}},function(e,t,n){var r=n(115),o=n(246),c=n(34),i=n(248),a=/^\\[object .+?Constructor\\]$/,l=Function.prototype,u=Object.prototype,s=l.toString,f=u.hasOwnProperty,p=RegExp(\"^\"+s.call(f).replace(/[\\\\^$.*+?()[\\]{}|]/g,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");e.exports=function(e){return!(!c(e)||o(e))&&(r(e)?p:a).test(i(e))}},function(e,t,n){var r=n(116),o=Object.prototype,c=o.hasOwnProperty,i=o.toString,a=r?r.toStringTag:void 0;e.exports=function(e){var t=c.call(e,a),n=e[a];try{e[a]=void 0;var r=!0}catch(l){}var o=i.call(e);return r&&(t?e[a]=n:delete e[a]),o}},function(e,t){var n=Object.prototype.toString;e.exports=function(e){return n.call(e)}},function(e,t,n){var r=n(247),o=function(){var e=/[^.]+$/.exec(r&&r.keys&&r.keys.IE_PROTO||\"\");return e?\"Symbol(src)_1.\"+e:\"\"}();e.exports=function(e){return!!o&&o in e}},function(e,t,n){var r=n(44)[\"__core-js_shared__\"];e.exports=r},function(e,t){var n=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return n.call(e)}catch(t){}try{return e+\"\"}catch(t){}}return\"\"}},function(e,t){e.exports=function(e,t){return null==e?void 0:e[t]}},function(e,t,n){var r=n(251),o=n(79),c=n(138);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(c||o),string:new r}}},function(e,t,n){var r=n(252),o=n(253),c=n(254),i=n(255),a=n(256);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}l.prototype.clear=r,l.prototype.delete=o,l.prototype.get=c,l.prototype.has=i,l.prototype.set=a,e.exports=l},function(e,t,n){var r=n(82);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},function(e,t){e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},function(e,t,n){var r=n(82),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return\"__lodash_hash_undefined__\"===n?void 0:n}return o.call(t,e)?t[e]:void 0}},function(e,t,n){var r=n(82),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},function(e,t,n){var r=n(82);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?\"__lodash_hash_undefined__\":t,this}},function(e,t,n){var r=n(83);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},function(e,t){e.exports=function(e){var t=typeof e;return\"string\"==t||\"number\"==t||\"symbol\"==t||\"boolean\"==t?\"__proto__\"!==e:null===e}},function(e,t,n){var r=n(83);e.exports=function(e){return r(this,e).get(e)}},function(e,t,n){var r=n(83);e.exports=function(e){return r(this,e).has(e)}},function(e,t,n){var r=n(83);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},function(e,t,n){var r=n(263)();e.exports=r},function(e,t){e.exports=function(e){return function(t,n,r){for(var o=-1,c=Object(t),i=r(t),a=i.length;a--;){var l=i[e?a:++o];if(!1===n(c[l],l,c))break}return t}}},function(e,t,n){var r=n(141),o=n(265),c=n(266),i=n(269),a=n(270),l=n(145),u=n(65),s=n(274),f=n(147),p=n(115),h=n(34),d=n(276),v=n(148),m=n(149),y=n(280);e.exports=function(e,t,n,b,g,w,z){var O=m(e,n),C=m(t,n),M=z.get(C);if(M)r(e,n,M);else{var S=w?w(O,C,n+\"\",e,t,z):void 0,_=void 0===S;if(_){var x=u(C),k=!x&&f(C),H=!x&&!k&&v(C);S=C,x||k||H?u(O)?S=O:s(O)?S=i(O):k?(_=!1,S=o(C,!0)):H?(_=!1,S=c(C,!0)):S=[]:d(C)||l(C)?(S=O,l(O)?S=y(O):h(O)&&!p(O)||(S=a(C))):_=!1}_&&(z.set(C,S),g(S,C,b,w,z),z.delete(C)),r(e,n,S)}}},function(e,t,n){(function(e){var r=n(44),o=t&&!t.nodeType&&t,c=o&&\"object\"==typeof e&&e&&!e.nodeType&&e,i=c&&c.exports===o?r.Buffer:void 0,a=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var n=e.length,r=a?a(n):new e.constructor(n);return e.copy(r),r}}).call(this,n(72)(e))},function(e,t,n){var r=n(267);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}},function(e,t,n){var r=n(268);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},function(e,t,n){var r=n(44).Uint8Array;e.exports=r},function(e,t){e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n<r;)t[n]=e[n];return t}},function(e,t,n){var r=n(271),o=n(143),c=n(144);e.exports=function(e){return\"function\"!=typeof e.constructor||c(e)?{}:r(o(e))}},function(e,t,n){var r=n(34),o=Object.create,c=function(){function e(){}return function(t){if(!r(t))return{};if(o)return o(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();e.exports=c},function(e,t){e.exports=function(e,t){return function(n){return e(t(n))}}},function(e,t,n){var r=n(64),o=n(53);e.exports=function(e){return o(e)&&\"[object Arguments]\"==r(e)}},function(e,t,n){var r=n(118),o=n(53);e.exports=function(e){return o(e)&&r(e)}},function(e,t){e.exports=function(){return!1}},function(e,t,n){var r=n(64),o=n(143),c=n(53),i=Function.prototype,a=Object.prototype,l=i.toString,u=a.hasOwnProperty,s=l.call(Object);e.exports=function(e){if(!c(e)||\"[object Object]\"!=r(e))return!1;var t=o(e);if(null===t)return!0;var n=u.call(t,\"constructor\")&&t.constructor;return\"function\"==typeof n&&n instanceof n&&l.call(n)==s}},function(e,t,n){var r=n(64),o=n(146),c=n(53),i={};i[\"[object Float32Array]\"]=i[\"[object Float64Array]\"]=i[\"[object Int8Array]\"]=i[\"[object Int16Array]\"]=i[\"[object Int32Array]\"]=i[\"[object Uint8Array]\"]=i[\"[object Uint8ClampedArray]\"]=i[\"[object Uint16Array]\"]=i[\"[object Uint32Array]\"]=!0,i[\"[object Arguments]\"]=i[\"[object Array]\"]=i[\"[object ArrayBuffer]\"]=i[\"[object Boolean]\"]=i[\"[object DataView]\"]=i[\"[object Date]\"]=i[\"[object Error]\"]=i[\"[object Function]\"]=i[\"[object Map]\"]=i[\"[object Number]\"]=i[\"[object Object]\"]=i[\"[object RegExp]\"]=i[\"[object Set]\"]=i[\"[object String]\"]=i[\"[object WeakMap]\"]=!1,e.exports=function(e){return c(e)&&o(e.length)&&!!i[r(e)]}},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){(function(e){var r=n(139),o=t&&!t.nodeType&&t,c=o&&\"object\"==typeof e&&e&&!e.nodeType&&e,i=c&&c.exports===o&&r.process,a=function(){try{var e=c&&c.require&&c.require(\"util\").types;return e||i&&i.binding&&i.binding(\"util\")}catch(t){}}();e.exports=a}).call(this,n(72)(e))},function(e,t,n){var r=n(281),o=n(150);e.exports=function(e){return r(e,o(e))}},function(e,t,n){var r=n(282),o=n(117);e.exports=function(e,t,n,c){var i=!n;n||(n={});for(var a=-1,l=t.length;++a<l;){var u=t[a],s=c?c(n[u],e[u],u,n,e):void 0;void 0===s&&(s=e[u]),i?o(n,u,s):r(n,u,s)}return n}},function(e,t,n){var r=n(117),o=n(81),c=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var i=e[t];c.call(e,t)&&o(i,n)&&(void 0!==n||t in e)||r(e,t,n)}},function(e,t,n){var r=n(284),o=n(145),c=n(65),i=n(147),a=n(151),l=n(148),u=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=c(e),s=!n&&o(e),f=!n&&!s&&i(e),p=!n&&!s&&!f&&l(e),h=n||s||f||p,d=h?r(e.length,String):[],v=d.length;for(var m in e)!t&&!u.call(e,m)||h&&(\"length\"==m||f&&(\"offset\"==m||\"parent\"==m)||p&&(\"buffer\"==m||\"byteLength\"==m||\"byteOffset\"==m)||a(m,v))||d.push(m);return d}},function(e,t){e.exports=function(e,t){for(var n=-1,r=Array(e);++n<e;)r[n]=t(n);return r}},function(e,t,n){var r=n(34),o=n(144),c=n(286),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return c(e);var t=o(e),n=[];for(var a in e)(\"constructor\"!=a||!t&&i.call(e,a))&&n.push(a);return n}},function(e,t){e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},function(e,t,n){var r=n(288),o=n(295);e.exports=function(e){return r((function(t,n){var r=-1,c=n.length,i=c>1?n[c-1]:void 0,a=c>2?n[2]:void 0;for(i=e.length>3&&\"function\"==typeof i?(c--,i):void 0,a&&o(n[0],n[1],a)&&(i=c<3?void 0:i,c=1),t=Object(t);++r<c;){var l=n[r];l&&e(t,l,r,i)}return t}))}},function(e,t,n){var r=n(152),o=n(289),c=n(291);e.exports=function(e,t){return c(o(e,t,r),e+\"\")}},function(e,t,n){var r=n(290),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var c=arguments,i=-1,a=o(c.length-t,0),l=Array(a);++i<a;)l[i]=c[t+i];i=-1;for(var u=Array(t+1);++i<t;)u[i]=c[i];return u[t]=n(l),r(e,this,u)}}},function(e,t){e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},function(e,t,n){var r=n(292),o=n(294)(r);e.exports=o},function(e,t,n){var r=n(293),o=n(142),c=n(152),i=o?function(e,t){return o(e,\"toString\",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:c;e.exports=i},function(e,t){e.exports=function(e){return function(){return e}}},function(e,t){var n=Date.now;e.exports=function(e){var t=0,r=0;return function(){var o=n(),c=16-(o-r);if(r=o,c>0){if(++t>=800)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}},function(e,t,n){var r=n(81),o=n(118),c=n(151),i=n(34);e.exports=function(e,t,n){if(!i(n))return!1;var a=typeof t;return!!(\"number\"==a?o(n)&&c(t,n.length):\"string\"==a&&t in n)&&r(n[t],e)}},function(e,t,n){\"use strict\";function r(e){return function(e){if(Array.isArray(e))return o(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if(\"string\"===typeof e)return o(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);\"Object\"===n&&e.constructor&&(n=e.constructor.name);if(\"Map\"===n||\"Set\"===n)return Array.from(n);if(\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return o(e,t)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function c(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?c(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):c(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var u=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t};Object.defineProperty(t,\"__esModule\",{value:!0});var s=u(n(0)),f=function(){function e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,e),this._cached={},this.columns=t||this.normalize(n)}var t,n,o;return t=e,(n=[{key:\"isAnyColumnsFixed\",value:function(){var e=this;return this._cache(\"isAnyColumnsFixed\",(function(){return e.columns.some((function(e){return!!e.fixed}))}))}},{key:\"isAnyColumnsLeftFixed\",value:function(){var e=this;return this._cache(\"isAnyColumnsLeftFixed\",(function(){return e.columns.some((function(e){return\"left\"===e.fixed||!0===e.fixed}))}))}},{key:\"isAnyColumnsRightFixed\",value:function(){var e=this;return this._cache(\"isAnyColumnsRightFixed\",(function(){return e.columns.some((function(e){return\"right\"===e.fixed}))}))}},{key:\"leftColumns\",value:function(){var e=this;return this._cache(\"leftColumns\",(function(){return e.groupedColumns().filter((function(e){return\"left\"===e.fixed||!0===e.fixed}))}))}},{key:\"rightColumns\",value:function(){var e=this;return this._cache(\"rightColumns\",(function(){return e.groupedColumns().filter((function(e){return\"right\"===e.fixed}))}))}},{key:\"leafColumns\",value:function(){var e=this;return this._cache(\"leafColumns\",(function(){return e._leafColumns(e.columns)}))}},{key:\"leftLeafColumns\",value:function(){var e=this;return this._cache(\"leftLeafColumns\",(function(){return e._leafColumns(e.leftColumns())}))}},{key:\"rightLeafColumns\",value:function(){var e=this;return this._cache(\"rightLeafColumns\",(function(){return e._leafColumns(e.rightColumns())}))}},{key:\"groupedColumns\",value:function(){var e=this;return this._cache(\"groupedColumns\",(function(){return function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];o[n]=o[n]||[];var c=[],a=function(e){var t=o.length-n;e&&!e.children&&t>1&&(!e.rowSpan||e.rowSpan<t)&&(e.rowSpan=t)};return t.forEach((function(l,u){var s=i({},l);o[n].push(s),r.colSpan=r.colSpan||0,s.children&&s.children.length>0?(s.children=e(s.children,n+1,s,o),r.colSpan+=s.colSpan):r.colSpan+=1;for(var f=0;f<o[n].length-1;f+=1)a(o[n][f]);u+1===t.length&&a(s),c.push(s)})),c}(e.columns)}))}},{key:\"normalize\",value:function(e){var t=this,n=[];return s.Children.forEach(e,(function(e){if(s.isValidElement(e)){var r=i({},e.props);e.key&&(r.key=e.key),e.type.isTableColumnGroup&&(r.children=t.normalize(r.children)),n.push(r)}})),n}},{key:\"reset\",value:function(e,t){this.columns=e||this.normalize(t),this._cached={}}},{key:\"_cache\",value:function(e,t){return e in this._cached||(this._cached[e]=t()),this._cached[e]}},{key:\"_leafColumns\",value:function(e){var t=this,n=[];return e.forEach((function(e){e.children?n.push.apply(n,r(t._leafColumns(e.children))):n.push(e)})),n}}])&&l(t.prototype,n),o&&l(t,o),e}();t.default=f},function(e,t,n){\"use strict\";var r=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var c=r(n(0)),i=r(n(1)),a=o(n(8)),l=n(54),u=o(n(153));function s(e,t){var n,r,o,i=t.table,s=i.props,f=s.prefixCls,p=s.scroll,h=s.showHeader,d=e.columns,v=e.fixed,m=e.tableClassName,y=e.handleBodyScrollLeft,b=e.expander,g=i.saveRef,w=i.props.useFixedHeader,z={},O=l.measureScrollbar({direction:\"vertical\"});if(p.y){w=!0;var C=l.measureScrollbar({direction:\"horizontal\",prefixCls:f});C>0&&!v&&(z.marginBottom=\"-\".concat(C,\"px\"),z.paddingBottom=\"0px\",z.minWidth=\"\".concat(O,\"px\"),z.overflowX=\"scroll\",z.overflowY=0===O?\"hidden\":\"scroll\")}return w&&h?c.createElement(\"div\",{key:\"headTable\",ref:v?null:g(\"headTable\"),className:a.default(\"\".concat(f,\"-header\"),(n={},r=\"\".concat(f,\"-hide-scrollbar\"),o=O>0,r in n?Object.defineProperty(n,r,{value:o,enumerable:!0,configurable:!0,writable:!0}):n[r]=o,n)),style:z,onScroll:y},c.createElement(u.default,{tableClassName:m,hasHead:!0,hasBody:!1,fixed:v,columns:d,expander:b})):null}t.default=s,s.contextTypes={table:i.any}},function(e,t,n){\"use strict\";var r=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t};Object.defineProperty(t,\"__esModule\",{value:!0});var o=r(n(0)),c=r(n(1)),i=n(54),a=function(e,t){var n,r=t.table,c=r.props,a=c.prefixCls,l=c.expandIconAsCell,u=e.fixed,s=[];return l&&\"right\"!==u&&s.push(o.createElement(\"col\",{className:\"\".concat(a,\"-expand-icon-col\"),key:\"rc-table-expand-icon-col\"})),n=\"left\"===u?r.columnManager.leftLeafColumns():\"right\"===u?r.columnManager.rightLeafColumns():r.columnManager.leafColumns(),s=s.concat(n.map((function(e){var t=e.key,n=e.dataIndex,r=e.width,c=e[i.INTERNAL_COL_DEFINE],a=void 0!==t?t:n;return o.createElement(\"col\",Object.assign({key:a,style:{width:r,minWidth:r}},c))}))),o.createElement(\"colgroup\",null,s)};a.contextTypes={table:c.any},t.default=a},function(e,t,n){\"use strict\";var r=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var c=r(n(0)),i=r(n(1)),a=o(n(300));function l(e){var t=e.columns,n=void 0===t?[]:t,r=e.currentRow,o=void 0===r?0:r,c=e.rows,i=void 0===c?[]:c,a=e.isLast,u=void 0===a||a;return i[o]=i[o]||[],n.forEach((function(e,t){if(e.rowSpan&&i.length<e.rowSpan)for(;i.length<e.rowSpan;)i.push([]);var r=u&&t===n.length-1,c={key:e.key,className:e.className||\"\",children:e.title,isLast:r,column:e};e.children&&l({columns:e.children,currentRow:o+1,rows:i,isLast:r}),\"colSpan\"in e&&(c.colSpan=e.colSpan),\"rowSpan\"in e&&(c.rowSpan=e.rowSpan),0!==c.colSpan&&i[o].push(c)})),i.filter((function(e){return e.length>0}))}var u=function(e,t){var n=t.table,r=n.components,o=n.props,i=o.prefixCls,u=o.showHeader,s=o.onHeaderRow,f=e.expander,p=e.columns,h=e.fixed;if(!u)return null;var d=l({columns:p});f.renderExpandIndentCell(d,h);var v=r.header.wrapper;return c.createElement(v,{className:\"\".concat(i,\"-thead\")},d.map((function(e,t){return c.createElement(a.default,{prefixCls:i,key:t,index:t,fixed:h,columns:p,rows:d,row:e,components:r,onHeaderRow:s})})))};u.contextTypes={table:i.any},t.default=u},function(e,t,n){\"use strict\";function r(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},c=Object.keys(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var c=Object.getOwnPropertySymbols(e);for(r=0;r<c.length;r++)n=c[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var a=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},l=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var u=a(n(0)),s=n(28),f=l(n(8));function p(e,t){var n=e.fixedColumnsHeadRowsHeight,r=t.columns,o=t.rows,c=t.fixed,i=n[0];return c&&i&&r?\"auto\"===i?\"auto\":i/o.length:null}t.default=s.connect((function(e,t){return{height:p(e,t)}}))((function(e){var t=e.row,n=e.index,o=e.height,a=e.components,l=e.onHeaderRow,s=e.prefixCls,p=a.header.row,h=a.header.cell,d=l(t.map((function(e){return e.column})),n),v=d?d.style:{},m=c({height:t.length>1&&0===n&&o&&\"auto\"!==o?parseInt(o.toString(),10):o},v);return u.createElement(p,Object.assign({},d,{style:m}),t.map((function(e,t){var n,o=e.column,a=e.isLast,l=r(e,[\"column\",\"isLast\"]),p=o.onHeaderCell?o.onHeaderCell(o):{};return o.align&&(p.style=c({},p.style,{textAlign:o.align})),p.className=f.default(p.className,o.className,(i(n={},\"\".concat(s,\"-align-\").concat(o.align),!!o.align),i(n,\"\".concat(s,\"-row-cell-ellipsis\"),!!o.ellipsis),i(n,\"\".concat(s,\"-row-cell-break-word\"),!!o.width),i(n,\"\".concat(s,\"-row-cell-last\"),a),n)),u.createElement(h,Object.assign({},l,p,{key:o.key||o.dataIndex||t}))})))}))},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function l(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function u(e,t){return(u=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function s(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function f(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function p(e){return(p=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var h=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},d=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var v=h(n(0)),m=d(n(8)),y=d(n(302));function b(e){return e&&!v.isValidElement(e)&&\"[object Object]\"===Object.prototype.toString.call(e)}var g=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&u(e,t)}(d,e);var t,n,r,o,h=(t=d,function(){var e,n=p(t);if(f()){var r=p(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return s(this,e)});function d(){var e;return a(this,d),(e=h.apply(this,arguments)).handleClick=function(t){var n=e.props,r=n.record,o=n.column.onCellClick;o&&o(r,t)},e}return n=d,(r=[{key:\"render\",value:function(){var e,t,n=this.props,r=n.record,o=n.indentSize,a=n.prefixCls,l=n.indent,u=n.index,s=n.expandIcon,f=n.column,p=n.component,h=f.dataIndex,d=f.render,g=f.className,w=void 0===g?\"\":g;t=\"number\"===typeof h||h&&0!==h.length?y.default(r,h):r;var z,O,C={};if(d&&b(t=d(t,r,u))){var M=C=t.props||C;z=M.colSpan,O=M.rowSpan,t=t.children}f.onCell&&(C=c({},C,{},f.onCell(r,u))),b(t)&&(t=null);var S=s?v.createElement(\"span\",{style:{paddingLeft:\"\".concat(o*l,\"px\")},className:\"\".concat(a,\"-indent indent-level-\").concat(l)}):null;if(0===O||0===z)return null;f.align&&(C.style=c({textAlign:f.align},C.style));var _=m.default(w,(i(e={},\"\".concat(a,\"-cell-ellipsis\"),!!f.ellipsis),i(e,\"\".concat(a,\"-cell-break-word\"),!!f.width),e));if(f.ellipsis)if(\"string\"===typeof t)C.title=t;else if(t){var x=t.props;x&&x.children&&\"string\"===typeof x.children&&(C.title=x.children)}return v.createElement(p,Object.assign({className:_,onClick:this.handleClick},C),S,s,t)}}])&&l(n.prototype,r),o&&l(n,o),d}(v.Component);t.default=g},function(e,t,n){var r=n(303);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},function(e,t,n){var r=n(304),o=n(312);e.exports=function(e,t){for(var n=0,c=(t=r(t,e)).length;null!=e&&n<c;)e=e[o(t[n++])];return n&&n==c?e:void 0}},function(e,t,n){var r=n(65),o=n(305),c=n(306),i=n(309);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:c(i(e))}},function(e,t,n){var r=n(65),o=n(84),c=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,i=/^\\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!(\"number\"!=n&&\"symbol\"!=n&&\"boolean\"!=n&&null!=e&&!o(e))||(i.test(e)||!c.test(e)||null!=t&&e in Object(t))}},function(e,t,n){var r=n(307),o=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,c=/\\\\(\\\\)?/g,i=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(\"\"),e.replace(o,(function(e,n,r,o){t.push(r?o.replace(c,\"$1\"):n||e)})),t}));e.exports=i},function(e,t,n){var r=n(308);e.exports=function(e){var t=r(e,(function(e){return 500===n.size&&n.clear(),e})),n=t.cache;return t}},function(e,t,n){var r=n(140);function o(e,t){if(\"function\"!=typeof e||null!=t&&\"function\"!=typeof t)throw new TypeError(\"Expected a function\");var n=function n(){var r=arguments,o=t?t.apply(this,r):r[0],c=n.cache;if(c.has(o))return c.get(o);var i=e.apply(this,r);return n.cache=c.set(o,i)||c,i};return n.cache=new(o.Cache||r),n}o.Cache=r,e.exports=o},function(e,t,n){var r=n(310);e.exports=function(e){return null==e?\"\":r(e)}},function(e,t,n){var r=n(116),o=n(311),c=n(65),i=n(84),a=r?r.prototype:void 0,l=a?a.toString:void 0;e.exports=function e(t){if(\"string\"==typeof t)return t;if(c(t))return o(t,e)+\"\";if(i(t))return l?l.call(t):\"\";var n=t+\"\";return\"0\"==n&&1/t==-Infinity?\"-0\":n}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}},function(e,t,n){var r=n(84);e.exports=function(e){if(\"string\"==typeof e||r(e))return e;var t=e+\"\";return\"0\"==t&&1/e==-Infinity?\"-0\":t}},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function c(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function i(e,t){return(i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function l(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var s=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},f=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var p=s(n(0)),h=n(28),d=f(n(314)),v=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&i(e,t)}(h,e);var t,n,r,s,f=(t=h,function(){var e,n=u(t);if(l()){var r=u(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return a(this,e)});function h(){var e;return o(this,h),(e=f.apply(this,arguments)).hasExpandIcon=function(t){var n=e.props,r=n.expandRowByClick,o=n.expandIcon;return!e.expandIconAsCell&&t===e.expandIconColumnIndex&&(!!o||!r)},e.handleExpandChange=function(t,n){var r=e.props,o=r.onExpandedChange,c=r.expanded,i=r.rowKey;e.expandable&&o(!c,t,n,i)},e.handleRowClick=function(t,n,r){var o=e.props,c=o.expandRowByClick,i=o.onRowClick;c&&e.handleExpandChange(t,r),i&&i(t,n,r)},e.renderExpandIcon=function(){var t=e.props,n=t.prefixCls,r=t.expanded,o=t.record,c=t.needIndentSpaced,i=t.expandIcon;return i?i({prefixCls:n,expanded:r,record:o,needIndentSpaced:c,expandable:e.expandable,onExpand:e.handleExpandChange}):p.createElement(d.default,{expandable:e.expandable,prefixCls:n,onExpand:e.handleExpandChange,needIndentSpaced:c,expanded:r,record:o})},e.renderExpandIconCell=function(t){if(e.expandIconAsCell){var n=e.props.prefixCls;t.push(p.createElement(\"td\",{className:\"\".concat(n,\"-expand-icon-cell\"),key:\"rc-table-expand-icon-cell\"},e.renderExpandIcon()))}},e}return n=h,(r=[{key:\"componentWillUnmount\",value:function(){this.handleDestroy()}},{key:\"handleDestroy\",value:function(){var e=this.props,t=e.onExpandedChange,n=e.rowKey,r=e.record;this.expandable&&t(!1,r,null,n,!0)}},{key:\"render\",value:function(){var e=this.props,t=e.childrenColumnName,n=e.expandedRowRender,r=e.indentSize,o=e.record,c=e.fixed,i=e.expanded;this.expandIconAsCell=\"right\"!==c&&this.props.expandIconAsCell,this.expandIconColumnIndex=\"right\"!==c?this.props.expandIconColumnIndex:-1;var a=o[t];this.expandable=!(!a&&!n);var l={indentSize:r,expanded:i,onRowClick:this.handleRowClick,hasExpandIcon:this.hasExpandIcon,renderExpandIcon:this.renderExpandIcon,renderExpandIconCell:this.renderExpandIconCell};return this.props.children(l)}}])&&c(n.prototype,r),s&&c(n,s),h}(p.Component);t.default=h.connect((function(e,t){var n=e.expandedRowKeys,r=void 0===n?[]:n,o=t.rowKey;return{expanded:r.includes(o)}}))(v)},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function c(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function i(e,t){return(i=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function a(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function l(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var s=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},f=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var p=s(n(0)),h=f(n(15)),d=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&i(e,t)}(d,e);var t,n,r,s,f=(t=d,function(){var e,n=u(t);if(l()){var r=u(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return a(this,e)});function d(){return o(this,d),f.apply(this,arguments)}return n=d,(r=[{key:\"shouldComponentUpdate\",value:function(e){return!h.default(e,this.props)}},{key:\"render\",value:function(){var e=this.props,t=e.expandable,n=e.prefixCls,r=e.onExpand,o=e.needIndentSpaced,c=e.expanded,i=e.record;if(t){var a=c?\"expanded\":\"collapsed\";return p.createElement(\"span\",{className:\"\".concat(n,\"-expand-icon \").concat(n,\"-\").concat(a),onClick:function(e){return r(i,e)}})}return o?p.createElement(\"span\",{className:\"\".concat(n,\"-expand-icon \").concat(n,\"-spaced\")}):null}}])&&c(n.prototype,r),s&&c(n,s),d}(p.Component);t.default=d},function(e,t,n){\"use strict\";function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){c(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var l=i(n(0)),u=i(n(1)),s=n(54),f=a(n(153));function p(e,t){var n=t.table,r=n.props,c=r.prefixCls,i=r.scroll,a=e.columns,u=e.fixed,p=e.tableClassName,h=e.getRowKey,d=e.handleBodyScroll,v=e.handleWheel,m=e.expander,y=e.isAnyColumnsFixed,b=n.saveRef,g=n.props.useFixedHeader,w=o({},n.props.bodyStyle),z={};if((i.x||u)&&(w.overflowX=w.overflowX||\"scroll\",w.WebkitTransform=\"translate3d (0, 0, 0)\"),i.y){u?(z.maxHeight=w.maxHeight||i.y,z.overflowY=w.overflowY||\"scroll\"):w.maxHeight=w.maxHeight||i.y,w.overflowY=w.overflowY||\"scroll\",g=!0;var O=s.measureScrollbar({direction:\"vertical\"});O>0&&u&&(w.marginBottom=\"-\".concat(O,\"px\"),w.paddingBottom=\"0px\")}var C,M=l.createElement(f.default,{tableClassName:p,hasHead:!g,hasBody:!0,fixed:u,columns:a,expander:m,getRowKey:h,isAnyColumnsFixed:y});if(u&&a.length)return\"left\"===a[0].fixed||!0===a[0].fixed?C=\"fixedColumnsBodyLeft\":\"right\"===a[0].fixed&&(C=\"fixedColumnsBodyRight\"),delete w.overflowX,delete w.overflowY,l.createElement(\"div\",{key:\"bodyTable\",className:\"\".concat(c,\"-body-outer\"),style:o({},w)},l.createElement(\"div\",{className:\"\".concat(c,\"-body-inner\"),style:z,ref:b(C),onWheel:v,onScroll:d},M));var S=i&&(i.x||i.y);return l.createElement(\"div\",{tabIndex:S?-1:void 0,key:\"bodyTable\",className:\"\".concat(c,\"-body\"),style:w,ref:b(\"bodyTable\"),onWheel:v,onScroll:d},M)}t.default=p,p.contextTypes={table:u.any}},function(e,t,n){\"use strict\";function r(e){return(r=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?\"symbol\":typeof e})(e)}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e){return function(e){if(Array.isArray(e))return a(e)}(e)||function(e){if(\"undefined\"!==typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if(\"string\"===typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);\"Object\"===n&&e.constructor&&(n=e.constructor.name);if(\"Map\"===n||\"Set\"===n)return Array.from(n);if(\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(e,t)}(e)||function(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}()}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function l(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function u(e,t){return(u=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function s(e,t){return!t||\"object\"!==r(t)&&\"function\"!==typeof t?function(e){if(void 0===e)throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\");return e}(e):t}function f(){if(\"undefined\"===typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if(\"function\"===typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function p(e){return(p=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}var h=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t},d=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});var v=h(n(0)),m=n(28),y=n(12),b=d(n(15)),g=d(n(154)),w=n(54),z=function(e){!function(e,t){if(\"function\"!==typeof t&&null!==t)throw new TypeError(\"Super expression must either be null or a function\");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&u(e,t)}(d,e);var t,n,r,a,h=(t=d,function(){var e,n=p(t);if(f()){var r=p(this).constructor;e=Reflect.construct(n,arguments,r)}else e=n.apply(this,arguments);return s(this,e)});function d(e){var t;!function(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}(this,d),(t=h.call(this,e)).handleExpandChange=function(e,n,r,o){var c=arguments.length>4&&void 0!==arguments[4]&&arguments[4];r&&r.stopPropagation();var a=t.props,l=a.onExpandedRowsChange,u=a.onExpand,s=t.store.getState(),f=s.expandedRowKeys;if(e)f=[].concat(i(f),[o]);else{var p=f.indexOf(o);-1!==p&&(f=w.remove(f,o))}t.props.expandedRowKeys||t.store.setState({expandedRowKeys:f}),t.latestExpandedRows&&b.default(t.latestExpandedRows,f)||(t.latestExpandedRows=f,l(f)),c||u(e,n)},t.renderExpandIndentCell=function(e,n){var r=t.props,i=r.prefixCls;if(r.expandIconAsCell&&\"right\"!==n&&e.length){var a={key:\"rc-table-expand-icon-cell\",className:\"\".concat(i,\"-expand-icon-th\"),title:\"\",rowSpan:e.length};e[0].unshift(function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){c(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},a,{column:a}))}},t.renderRows=function(e,n,r,o,c,a,l,u){var s=t.props,f=s.expandedRowClassName,p=s.expandedRowRender,h=r[s.childrenColumnName],d=[].concat(i(u),[l]),v=c+1;p&&n.push(t.renderExpandedRow(r,o,p,f(r,o,c),d,v,a)),h&&n.push.apply(n,i(e(h,v,d)))};var n=e.data,r=e.childrenColumnName,a=e.defaultExpandAllRows,l=e.expandedRowKeys,u=e.defaultExpandedRowKeys,s=e.getRowKey,f=[],p=i(n);if(a)for(var v=0;v<p.length;v+=1){var m=p[v];f.push(s(m,v)),p=p.concat(m[r]||[])}else f=l||u;return t.columnManager=e.columnManager,t.store=e.store,t.store.setState({expandedRowsHeight:{},expandedRowKeys:f}),t}return n=d,(r=[{key:\"componentDidMount\",value:function(){this.handleUpdated()}},{key:\"componentDidUpdate\",value:function(){\"expandedRowKeys\"in this.props&&this.store.setState({expandedRowKeys:this.props.expandedRowKeys}),this.handleUpdated()}},{key:\"handleUpdated\",value:function(){this.latestExpandedRows=null}},{key:\"renderExpandedRow\",value:function(e,t,n,r,o,c,i){var a,l=this,u=this.props,s=u.prefixCls,f=u.expandIconAsCell,p=u.indentSize,h=o[o.length-1],d=\"\".concat(h,\"-extra-row\");a=\"left\"===i?this.columnManager.leftLeafColumns().length:\"right\"===i?this.columnManager.rightLeafColumns().length:this.columnManager.leafColumns().length;var m=[{key:\"extra-row\",render:function(){var r=l.store.getState().expandedRowKeys,o=(void 0===r?[]:r).includes(h);return{props:{colSpan:a},children:\"right\"!==i?n(e,t,c,o):\"&nbsp;\"}}}];return f&&\"right\"!==i&&m.unshift({key:\"expand-icon-placeholder\",render:function(){return null}}),v.createElement(g.default,{key:d,columns:m,className:r,rowKey:d,ancestorKeys:o,prefixCls:\"\".concat(s,\"-expanded-row\"),indentSize:p,indent:c,fixed:i,components:{body:{row:\"tr\",cell:\"td\"}},expandedRow:!0})}},{key:\"render\",value:function(){var e=this.props,t=e.data,n=e.childrenColumnName,r=e.children,o=t.some((function(e){return e[n]}));return r({props:this.props,needIndentSpaced:o,renderRows:this.renderRows,handleExpandChange:this.handleExpandChange,renderExpandIndentCell:this.renderExpandIndentCell})}}])&&l(n.prototype,r),a&&l(n,a),d}(v.Component);z.defaultProps={expandIconAsCell:!1,expandedRowClassName:function(){return\"\"},expandIconColumnIndex:0,defaultExpandAllRows:!1,defaultExpandedRowKeys:[],childrenColumnName:\"children\",indentSize:15,onExpand:function(){},onExpandedRowsChange:function(){}},y.polyfill(z),t.default=m.connect()(z)},function(e,t,n){var r=n(44);e.exports=function(){return r.Date.now()}},function(e,t,n){var r=n(319),o=n(34),c=n(84),i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,u=parseInt;e.exports=function(e){if(\"number\"==typeof e)return e;if(c(e))return NaN;if(o(e)){var t=\"function\"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+\"\":t}if(\"string\"!=typeof e)return 0===e?e:+e;e=r(e);var n=a.test(e);return n||l.test(e)?u(e.slice(2),n?2:8):i.test(e)?NaN:+e}},function(e,t,n){var r=n(320),o=/^\\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,\"\"):e}},function(e,t){var n=/\\s/;e.exports=function(e){for(var t=e.length;t--&&n.test(e.charAt(t)););return t}},function(e,t){window.MutationObserver||(window.MutationObserver=function(e){function t(e){this.i=[],this.m=e}function n(t){var n,r={type:null,target:null,addedNodes:[],removedNodes:[],previousSibling:null,nextSibling:null,attributeName:null,attributeNamespace:null,oldValue:null};for(n in t)r[n]!==e&&t[n]!==e&&(r[n]=t[n]);return r}function r(t,r){var a=c(t,r);return function(u){var s=u.length;if(r.a&&3===t.nodeType&&t.nodeValue!==a.a&&u.push(new n({type:\"characterData\",target:t,oldValue:a.a})),r.b&&a.b&&o(u,t,a.b,r.f),r.c||r.g)var f=function(t,r,c,a){function u(e,r,c,i,l){var u,f,p,h=e.length-1;for(l=-~((h-l)/2);p=e.pop();)u=c[p.j],f=i[p.l],a.c&&l&&Math.abs(p.j-p.l)>=h&&(t.push(n({type:\"childList\",target:r,addedNodes:[u],removedNodes:[u],nextSibling:u.nextSibling,previousSibling:u.previousSibling})),l--),a.b&&f.b&&o(t,u,f.b,a.f),a.a&&3===u.nodeType&&u.nodeValue!==f.a&&t.push(n({type:\"characterData\",target:u,oldValue:f.a})),a.g&&s(u,f)}function s(r,c){for(var p,h,d,v,m,y=r.childNodes,b=c.c,g=y.length,w=b?b.length:0,z=0,O=0,C=0;O<g||C<w;)(v=y[O])===(m=(d=b[C])&&d.node)?(a.b&&d.b&&o(t,v,d.b,a.f),a.a&&d.a!==e&&v.nodeValue!==d.a&&t.push(n({type:\"characterData\",target:v,oldValue:d.a})),h&&u(h,r,y,b,z),a.g&&(v.childNodes.length||d.c&&d.c.length)&&s(v,d),O++,C++):(f=!0,p||(p={},h=[]),v&&(p[d=i(v)]||(p[d]=!0,-1===(d=l(b,v,C,\"node\"))?a.c&&(t.push(n({type:\"childList\",target:r,addedNodes:[v],nextSibling:v.nextSibling,previousSibling:v.previousSibling})),z++):h.push({j:O,l:d})),O++),m&&m!==y[O]&&(p[d=i(m)]||(p[d]=!0,-1===(d=l(y,m,O))?a.c&&(t.push(n({type:\"childList\",target:c.node,removedNodes:[m],nextSibling:b[C+1],previousSibling:b[C-1]})),z--):h.push({j:d,l:C})),C++));h&&u(h,r,y,b,z)}var f;return s(r,c),f}(u,t,a,r);(f||u.length!==s)&&(a=c(t,r))}}function o(t,r,o,c){for(var i,a,l={},u=r.attributes,f=u.length;f--;)a=(i=u[f]).name,c&&c[a]===e||(s(r,i)!==o[a]&&t.push(n({type:\"attributes\",target:r,attributeName:a,oldValue:o[a],attributeNamespace:i.namespaceURI})),l[a]=!0);for(a in o)l[a]||t.push(n({target:r,type:\"attributes\",attributeName:a,oldValue:o[a]}))}function c(e,t){var n=!0;return function e(r){var o={node:r};return!t.a||3!==r.nodeType&&8!==r.nodeType?(t.b&&n&&1===r.nodeType&&(o.b=a(r.attributes,(function(e,n){return t.f&&!t.f[n.name]||(e[n.name]=s(r,n)),e}),{})),n&&(t.c||t.a||t.b&&t.g)&&(o.c=function(e,t){for(var n=[],r=0;r<e.length;r++)n[r]=t(e[r],r,e);return n}(r.childNodes,e)),n=t.g):o.a=r.nodeValue,o}(e)}function i(e){try{return e.id||(e.mo_id=e.mo_id||f++)}catch(t){try{return e.nodeValue}catch(n){return f++}}}function a(e,t,n){for(var r=0;r<e.length;r++)n=t(n,e[r],r,e);return n}function l(e,t,n,r){for(;n<e.length;n++)if((r?e[n][r]:e[n])===t)return n;return-1}t._period=30,t.prototype={observe:function(e,n){for(var o={b:!!(n.attributes||n.attributeFilter||n.attributeOldValue),c:!!n.childList,g:!!n.subtree,a:!(!n.characterData&&!n.characterDataOldValue)},c=this.i,i=0;i<c.length;i++)c[i].s===e&&c.splice(i,1);n.attributeFilter&&(o.f=a(n.attributeFilter,(function(e,t){return e[t]=!0,e}),{})),c.push({s:e,o:r(e,o)}),this.h||function(e){!function n(){var r=e.takeRecords();r.length&&e.m(r,e),e.h=setTimeout(n,t._period)}()}(this)},takeRecords:function(){for(var e=[],t=this.i,n=0;n<t.length;n++)t[n].o(e);return e},disconnect:function(){this.i=[],clearTimeout(this.h),this.h=null}};var u=document.createElement(\"i\");u.style.top=0;var s=(u=\"null\"!=u.attributes.style.value)?function(e,t){return t.value}:function(e,t){return\"style\"!==t.name?t.value:e.style.cssText},f=1;return t}(void 0))},function(e,t,n){\"use strict\";var r=n(323);e.exports=function(e,t,n){n=n||{},9===t.nodeType&&(t=r.getWindow(t));var o=n.allowHorizontalScroll,c=n.onlyScrollIfNeeded,i=n.alignWithTop,a=n.alignWithLeft,l=n.offsetTop||0,u=n.offsetLeft||0,s=n.offsetBottom||0,f=n.offsetRight||0;o=void 0===o||o;var p=r.isWindow(t),h=r.offset(e),d=r.outerHeight(e),v=r.outerWidth(e),m=void 0,y=void 0,b=void 0,g=void 0,w=void 0,z=void 0,O=void 0,C=void 0,M=void 0,S=void 0;p?(O=t,S=r.height(O),M=r.width(O),C={left:r.scrollLeft(O),top:r.scrollTop(O)},w={left:h.left-C.left-u,top:h.top-C.top-l},z={left:h.left+v-(C.left+M)+f,top:h.top+d-(C.top+S)+s},g=C):(m=r.offset(t),y=t.clientHeight,b=t.clientWidth,g={left:t.scrollLeft,top:t.scrollTop},w={left:h.left-(m.left+(parseFloat(r.css(t,\"borderLeftWidth\"))||0))-u,top:h.top-(m.top+(parseFloat(r.css(t,\"borderTopWidth\"))||0))-l},z={left:h.left+v-(m.left+b+(parseFloat(r.css(t,\"borderRightWidth\"))||0))+f,top:h.top+d-(m.top+y+(parseFloat(r.css(t,\"borderBottomWidth\"))||0))+s}),w.top<0||z.top>0?!0===i?r.scrollTop(t,g.top+w.top):!1===i?r.scrollTop(t,g.top+z.top):w.top<0?r.scrollTop(t,g.top+w.top):r.scrollTop(t,g.top+z.top):c||((i=void 0===i||!!i)?r.scrollTop(t,g.top+w.top):r.scrollTop(t,g.top+z.top)),o&&(w.left<0||z.left>0?!0===a?r.scrollLeft(t,g.left+w.left):!1===a?r.scrollLeft(t,g.left+z.left):w.left<0?r.scrollLeft(t,g.left+w.left):r.scrollLeft(t,g.left+z.left):c||((a=void 0===a||!!a)?r.scrollLeft(t,g.left+w.left):r.scrollLeft(t,g.left+z.left)))}},function(e,t,n){\"use strict\";var r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},o=\"function\"===typeof Symbol&&\"symbol\"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&\"function\"===typeof Symbol&&e.constructor===Symbol?\"symbol\":typeof e};function c(e,t){var n=e[\"page\"+(t?\"Y\":\"X\")+\"Offset\"],r=\"scroll\"+(t?\"Top\":\"Left\");if(\"number\"!==typeof n){var o=e.document;\"number\"!==typeof(n=o.documentElement[r])&&(n=o.body[r])}return n}function i(e){return c(e)}function a(e){return c(e,!0)}function l(e){var t=function(e){var t,n=void 0,r=void 0,o=e.ownerDocument,c=o.body,i=o&&o.documentElement;return n=(t=e.getBoundingClientRect()).left,r=t.top,{left:n-=i.clientLeft||c.clientLeft||0,top:r-=i.clientTop||c.clientTop||0}}(e),n=e.ownerDocument,r=n.defaultView||n.parentWindow;return t.left+=i(r),t.top+=a(r),t}var u=new RegExp(\"^(\"+/[\\-+]?(?:\\d*\\.|)\\d+(?:[eE][\\-+]?\\d+|)/.source+\")(?!px)[a-z%]+$\",\"i\"),s=/^(top|right|bottom|left)$/;var f=void 0;function p(e,t){for(var n=0;n<e.length;n++)t(e[n])}function h(e){return\"border-box\"===f(e,\"boxSizing\")}\"undefined\"!==typeof window&&(f=window.getComputedStyle?function(e,t,n){var r=\"\",o=e.ownerDocument,c=n||o.defaultView.getComputedStyle(e,null);return c&&(r=c.getPropertyValue(t)||c[t]),r}:function(e,t){var n=e.currentStyle&&e.currentStyle[t];if(u.test(n)&&!s.test(t)){var r=e.style,o=r.left,c=e.runtimeStyle.left;e.runtimeStyle.left=e.currentStyle.left,r.left=\"fontSize\"===t?\"1em\":n||0,n=r.pixelLeft+\"px\",r.left=o,e.runtimeStyle.left=c}return\"\"===n?\"auto\":n});var d=[\"margin\",\"border\",\"padding\"];function v(e,t,n){var r={},o=e.style,c=void 0;for(c in t)t.hasOwnProperty(c)&&(r[c]=o[c],o[c]=t[c]);for(c in n.call(e),t)t.hasOwnProperty(c)&&(o[c]=r[c])}function m(e,t,n){var r=0,o=void 0,c=void 0,i=void 0;for(c=0;c<t.length;c++)if(o=t[c])for(i=0;i<n.length;i++){var a=void 0;a=\"border\"===o?o+n[i]+\"Width\":o+n[i],r+=parseFloat(f(e,a))||0}return r}function y(e){return null!=e&&e==e.window}var b={};function g(e,t,n){if(y(e))return\"width\"===t?b.viewportWidth(e):b.viewportHeight(e);if(9===e.nodeType)return\"width\"===t?b.docWidth(e):b.docHeight(e);var r=\"width\"===t?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],o=\"width\"===t?e.offsetWidth:e.offsetHeight,c=(f(e),h(e)),i=0;(null==o||o<=0)&&(o=void 0,(null==(i=f(e,t))||Number(i)<0)&&(i=e.style[t]||0),i=parseFloat(i)||0),void 0===n&&(n=c?1:-1);var a=void 0!==o||c,l=o||i;if(-1===n)return a?l-m(e,[\"border\",\"padding\"],r):i;if(a){var u=2===n?-m(e,[\"border\"],r):m(e,[\"margin\"],r);return l+(1===n?0:u)}return i+m(e,d.slice(n),r)}p([\"Width\",\"Height\"],(function(e){b[\"doc\"+e]=function(t){var n=t.document;return Math.max(n.documentElement[\"scroll\"+e],n.body[\"scroll\"+e],b[\"viewport\"+e](n))},b[\"viewport\"+e]=function(t){var n=\"client\"+e,r=t.document,o=r.body,c=r.documentElement[n];return\"CSS1Compat\"===r.compatMode&&c||o&&o[n]||c}}));var w={position:\"absolute\",visibility:\"hidden\",display:\"block\"};function z(e){var t=void 0,n=arguments;return 0!==e.offsetWidth?t=g.apply(void 0,n):v(e,w,(function(){t=g.apply(void 0,n)})),t}function O(e,t,n){var r=n;if(\"object\"!==(\"undefined\"===typeof t?\"undefined\":o(t)))return\"undefined\"!==typeof r?(\"number\"===typeof r&&(r+=\"px\"),void(e.style[t]=r)):f(e,t);for(var c in t)t.hasOwnProperty(c)&&O(e,c,t[c])}p([\"width\",\"height\"],(function(e){var t=e.charAt(0).toUpperCase()+e.slice(1);b[\"outer\"+t]=function(t,n){return t&&z(t,e,n?0:1)};var n=\"width\"===e?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"];b[e]=function(t,r){if(void 0===r)return t&&z(t,e,-1);if(t){f(t);return h(t)&&(r+=m(t,[\"padding\",\"border\"],n)),O(t,e,r)}}})),e.exports=r({getWindow:function(e){var t=e.ownerDocument||e;return t.defaultView||t.parentWindow},offset:function(e,t){if(\"undefined\"===typeof t)return l(e);!function(e,t){\"static\"===O(e,\"position\")&&(e.style.position=\"relative\");var n=l(e),r={},o=void 0,c=void 0;for(c in t)t.hasOwnProperty(c)&&(o=parseFloat(O(e,c))||0,r[c]=o+t[c]-n[c]);O(e,r)}(e,t)},isWindow:y,each:p,css:O,clone:function(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);if(e.overflow)for(var n in e)e.hasOwnProperty(n)&&(t.overflow[n]=e.overflow[n]);return t},scrollLeft:function(e,t){if(y(e)){if(void 0===t)return i(e);window.scrollTo(t,a(e))}else{if(void 0===t)return e.scrollLeft;e.scrollLeft=t}},scrollTop:function(e,t){if(y(e)){if(void 0===t)return a(e);window.scrollTo(i(e),t)}else{if(void 0===t)return e.scrollTop;e.scrollTop=t}},viewportWidth:0,viewportHeight:0},b)},function(e,t,n){\"use strict\";e.exports=function(e,t){var n=window.Element.prototype,r=n.matches||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector;if(!e||1!==e.nodeType)return!1;var o=e.parentNode;if(r)return r.call(e,t);for(var c=o.querySelectorAll(t),i=c.length,a=0;a<i;a++)if(c[a]===e)return!0;return!1}},function(e,t,n){var r=n(326);e.exports=new r},function(e,t,n){var r=n(327),o=n(157),c=o.each,i=o.isFunction,a=o.isArray;function l(){if(!window.matchMedia)throw new Error(\"matchMedia not present, legacy browsers require a polyfill\");this.queries={},this.browserIsIncapable=!window.matchMedia(\"only all\").matches}l.prototype={constructor:l,register:function(e,t,n){var o=this.queries,l=n&&this.browserIsIncapable;return o[e]||(o[e]=new r(e,l)),i(t)&&(t={match:t}),a(t)||(t=[t]),c(t,(function(t){i(t)&&(t={match:t}),o[e].addHandler(t)})),this},unregister:function(e,t){var n=this.queries[e];return n&&(t?n.removeHandler(t):(n.clear(),delete this.queries[e])),this}},e.exports=l},function(e,t,n){var r=n(328),o=n(157).each;function c(e,t){this.query=e,this.isUnconditional=t,this.handlers=[],this.mql=window.matchMedia(e);var n=this;this.listener=function(e){n.mql=e.currentTarget||e,n.assess()},this.mql.addListener(this.listener)}c.prototype={constuctor:c,addHandler:function(e){var t=new r(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var t=this.handlers;o(t,(function(n,r){if(n.equals(e))return n.destroy(),!t.splice(r,1)}))},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){o(this.handlers,(function(e){e.destroy()})),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?\"on\":\"off\";o(this.handlers,(function(t){t[e]()}))}},e.exports=c},function(e,t){function n(e){this.options=e,!e.deferSetup&&this.setup()}n.prototype={constructor:n,setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},e.exports=n},function(e,t,n){\"use strict\";n(25),n.p,n.p,n.p,n.p,n.p,n.p,n.p,n.p,n.p},function(e,t,n){\"use strict\";n(25),n.p,n.p,n.p},,function(e,t,n){\"use strict\";n.p},function(e,t,n){\"use strict\";n(25),n.p},function(e,t,n){\"use strict\";n(25),n.p},function(e,t,n){\"use strict\";n(25),n.p},function(e,t,n){\"use strict\";n(25),n.p},function(e,t,n){\"use strict\";n(25),n.p},function(e,t,n){\"use strict\";var r=n(23),o=Object(r.a)((function(e,t){return null==t||t!==t?e:t}));t.a=o},function(e,t,n){\"use strict\";var r=n(21),o=Object(r.a)((function(e){return null==e}));t.a=o},function(e,t,n){\"use strict\";var r=n(97),o=n(21),c=n(47),i=Object(o.a)((function(e){return Object(c.a)(e.length,(function(){var t=0,n=arguments[0],o=arguments[arguments.length-1],c=Array.prototype.slice.call(arguments,0);return c[0]=function(){var e=n.apply(this,Object(r.a)(arguments,[t,o]));return t+=1,e},e.apply(this,c)}))}));t.a=i},function(e,t,n){\"use strict\";var r=n(21),o=n(91),c=n(57);var i=n(92),a=Object(r.a)((function(e){return null!=e&&\"function\"===typeof e[\"fantasy-land/empty\"]?e[\"fantasy-land/empty\"]():null!=e&&null!=e.constructor&&\"function\"===typeof e.constructor[\"fantasy-land/empty\"]?e.constructor[\"fantasy-land/empty\"]():null!=e&&\"function\"===typeof e.empty?e.empty():null!=e&&null!=e.constructor&&\"function\"===typeof e.constructor.empty?e.constructor.empty():Object(c.a)(e)?[]:Object(i.a)(e)?\"\":function(e){return\"[object Object]\"===Object.prototype.toString.call(e)}(e)?{}:Object(o.a)(e)?function(){return arguments}():void 0})),l=n(23);function u(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}function s(e,t,n){for(var r=0,o=n.length;r<o;){if(e(t,n[r]))return!0;r+=1}return!1}var f=n(46);var p=\"function\"===typeof Object.is?Object.is:function(e,t){return e===t?0!==e||1/e===1/t:e!==e&&t!==t},h=n(69),d=Object(r.a)((function(e){return null===e?\"Null\":void 0===e?\"Undefined\":Object.prototype.toString.call(e).slice(8,-1)}));function v(e,t,n,r){var o=u(e);function c(e,t){return m(e,t,n.slice(),r.slice())}return!s((function(e,t){return!s(c,t,e)}),u(t),o)}function m(e,t,n,r){if(p(e,t))return!0;var o=d(e);if(o!==d(t))return!1;if(null==e||null==t)return!1;if(\"function\"===typeof e[\"fantasy-land/equals\"]||\"function\"===typeof t[\"fantasy-land/equals\"])return\"function\"===typeof e[\"fantasy-land/equals\"]&&e[\"fantasy-land/equals\"](t)&&\"function\"===typeof t[\"fantasy-land/equals\"]&&t[\"fantasy-land/equals\"](e);if(\"function\"===typeof e.equals||\"function\"===typeof t.equals)return\"function\"===typeof e.equals&&e.equals(t)&&\"function\"===typeof t.equals&&t.equals(e);switch(o){case\"Arguments\":case\"Array\":case\"Object\":if(\"function\"===typeof e.constructor&&\"Promise\"===function(e){var t=String(e).match(/^function (\\w*)/);return null==t?\"\":t[1]}(e.constructor))return e===t;break;case\"Boolean\":case\"Number\":case\"String\":if(typeof e!==typeof t||!p(e.valueOf(),t.valueOf()))return!1;break;case\"Date\":if(!p(e.valueOf(),t.valueOf()))return!1;break;case\"Error\":return e.name===t.name&&e.message===t.message;case\"RegExp\":if(e.source!==t.source||e.global!==t.global||e.ignoreCase!==t.ignoreCase||e.multiline!==t.multiline||e.sticky!==t.sticky||e.unicode!==t.unicode)return!1}for(var c=n.length-1;c>=0;){if(n[c]===e)return r[c]===t;c-=1}switch(o){case\"Map\":return e.size===t.size&&v(e.entries(),t.entries(),n.concat([e]),r.concat([t]));case\"Set\":return e.size===t.size&&v(e.values(),t.values(),n.concat([e]),r.concat([t]));case\"Arguments\":case\"Array\":case\"Object\":case\"Boolean\":case\"Number\":case\"String\":case\"Date\":case\"Error\":case\"RegExp\":case\"Int8Array\":case\"Uint8Array\":case\"Uint8ClampedArray\":case\"Int16Array\":case\"Uint16Array\":case\"Int32Array\":case\"Uint32Array\":case\"Float32Array\":case\"Float64Array\":case\"ArrayBuffer\":break;default:return!1}var i=Object(h.a)(e);if(i.length!==Object(h.a)(t).length)return!1;var a=n.concat([e]),l=r.concat([t]);for(c=i.length-1;c>=0;){var u=i[c];if(!Object(f.a)(u,t)||!m(t[u],e[u],a,l))return!1;c-=1}return!0}var y=Object(l.a)((function(e,t){return m(e,t,[],[])})),b=Object(r.a)((function(e){return null!=e&&y(e,a(e))}));t.a=b},function(e,t,n){\"use strict\";var r=n(23);var o=n(21),c=n(60),i=n(97),a=n(99),l=Object(r.a)((function(e,t){return\"function\"===typeof t[\"fantasy-land/ap\"]?t[\"fantasy-land/ap\"](e):\"function\"===typeof e.ap?e.ap(t):\"function\"===typeof e?function(n){return e(n)(t(n))}:Object(c.a)((function(e,n){return Object(i.a)(e,Object(a.a)(n,t))}),[],e)})),u=n(47),s=Object(r.a)((function(e,t){var n=Object(u.a)(e,t);return Object(u.a)(e,(function(){return Object(c.a)(l,Object(a.a)(n,arguments[0]),Array.prototype.slice.call(arguments,1))}))})),f=Object(o.a)((function(e){return s(e.length,e)})),p=Object(r.a)((function(e,t){return e||t})),h=Object(r.a)((function(e,t){return function(e){var t=Object.prototype.toString.call(e);return\"[object Function]\"===t||\"[object AsyncFunction]\"===t||\"[object GeneratorFunction]\"===t||\"[object AsyncGeneratorFunction]\"===t}(e)?function(){return e.apply(this,arguments)||t.apply(this,arguments)}:f(p)(e,t)}));t.a=h}]]);\n//# sourceMappingURL=2.0ca9bd94.chunk.js.map"
  },
  {
    "path": "package_hub/template/inspection_html/static/js/2.0ca9bd94.chunk.js.LICENSE.txt",
    "content": "/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n/*!\n  Copyright (c) 2017 Jed Watson.\n  Licensed under the MIT License (MIT), see\n  http://jedwatson.github.io/classnames\n*/\n\n/*!\n  Copyright (c) 2018 Jed Watson.\n  Licensed under the MIT License (MIT), see\n  http://jedwatson.github.io/classnames\n*/\n\n/** @license React v0.20.2\n * scheduler.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/** @license React v16.13.1\n * react-is.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/** @license React v17.0.2\n * react-dom.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/** @license React v17.0.2\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/** @license React v17.0.2\n * react.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n//! moment.js\n\n//! moment.js locale configuration\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/js/main.e4ade54a.chunk.js",
    "content": "(this[\"webpackJsonpomp-fontend\"] = this[\"webpackJsonpomp-fontend\"] || []).push([\n  [0],\n  {\n    172: function (a, r, t) {},\n    22: function (a) {\n      a.exports = '!@#$';\n    },\n    331: function (a, r, t) {\n      \"use strict\";\n      t.r(r);\n      var e = t(9),\n        o = t.n(e),\n        p = (t(332), t(17)),\n        i = t(161),\n        d = (t(171), t(330), t(160)),\n        l = t(163),\n        n = (t(333), t(122)),\n        c = (t(329), t(41)),\n        s = (t(334), t(13)),\n        b = t(70),\n        j = t(4),\n        m = (t(335), t(100)),\n        u = (t(172), t(162), t(336), t(61), t(337), t(98)),\n        g = t(24),\n        v = t.n(g),\n        S = t(338),\n        E = t.p + \"static/media/index.module.b57695f6.less\",\n        _ = t(2),\n        f = function (a) {\n          return \"null\" === String(a) || \"\" === a || void 0 === a;\n        };\n      function k(a, r, t) {\n        return f(a)\n          ? \"-\"\n          : 0 === a || \"active\" === a || !0 === a\n          ? Object(_.jsxs)(\"div\", {\n              style: { fontSize: 14 },\n              children: [\n                R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                \"\\u6b63\\u5e38\",\n              ],\n            })\n          : 1 === a || \"unactive\" === a || !1 === a\n          ? Object(_.jsxs)(\"div\", {\n              style: { fontSize: 14 },\n              children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5f02\\u5e38\"],\n            })\n          : \"CREATE\" === a\n          ? Object(_.jsx)(\"div\", {\n              style: { fontSize: 14 },\n              children: \"\\u589e\\u52a0\",\n            })\n          : \"UPDATE\" === a\n          ? Object(_.jsx)(\"div\", {\n              style: { fontSize: 14 },\n              children: \"\\u66f4\\u65b0\",\n            })\n          : \"DELETE\" === a\n          ? Object(_.jsx)(\"div\", {\n              style: { fontSize: 14 },\n              children: \"\\u5220\\u9664\",\n            })\n          : a;\n      }\n      function A(a, r, t) {\n        if (f(a)) return \"-\";\n        var e = \"\",\n          o = Math.round(Number(a)),\n          p = Math.floor(o / 86400),\n          i = Math.floor((o % 86400) / 3600),\n          d = Math.floor(((o % 86400) % 3600) / 60),\n          l = Math.floor(((o % 86400) % 3600) % 60);\n        return (\n          p > 0\n            ? (e = p + \"\\u5929\" + i + \"\\u5c0f\\u65f6\")\n            : i > 0\n            ? (e = i + \"\\u5c0f\\u65f6\" + d + \"\\u5206\")\n            : d > 0\n            ? (e = d + \"\\u5206\")\n            : l > 0 && (e = l + \"\\u79d2\"),\n          e\n        );\n      }\n      var R = function (a, r) {\n        return Object(_.jsx)(\"span\", {\n          style: {\n            width: 10,\n            height: 10,\n            borderStyle: \"solid\",\n            borderWidth: 2,\n            borderColor: a,\n            backgroundColor: r,\n            display: \"inline-block\",\n            marginRight: 5,\n            borderRadius: \"50%\",\n          },\n        });\n      };\n      function D(a) {\n        var r = a.backgroundColor,\n          t = a.borderColor,\n          e = a.text,\n          o = a.top,\n          p = a.width,\n          i = void 0 === p ? 55 : p;\n        return Object(_.jsx)(\"div\", {\n          style: {\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            width: i,\n            height: 20,\n            margin: \"0 auto\",\n            color: \"rgba(0, 0, 0, 0.6)\",\n            fontSize: \"12px\",\n            borderRadius: \"4px\",\n            border: \"1px solid #fff\",\n            backgroundColor: r,\n            borderColor: t,\n            position: \"relative\",\n            top: o,\n          },\n          children: e,\n        });\n      }\n      var L = {\n        sortIP: function (a, r) {\n          return a.ip && r.ip\n            ? a.ip\n                .split(\".\")\n                .map(function (a) {\n                  return a.padStart(3, \"0\");\n                })\n                .join(\"\") -\n                r.ip\n                  .split(\".\")\n                  .map(function (a) {\n                    return a.padStart(3, \"0\");\n                  })\n                  .join(\"\")\n            : 0;\n        },\n        sortAlertIP: function (a, r) {\n          return a.alert_host_ip && r.alert_host_ip\n            ? a.alert_host_ip\n                .split(\".\")\n                .map(function (a) {\n                  return a.padStart(3, \"0\");\n                })\n                .join(\"\") -\n                r.alert_host_ip\n                  .split(\".\")\n                  .map(function (a) {\n                    return a.padStart(3, \"0\");\n                  })\n                  .join(\"\")\n            : 0;\n        },\n        sortUsageRate: function (a, r) {\n          return Number(f(a) ? 0 : a) - Number(f(r) ? 0 : r);\n        },\n      };\n      function y(a) {\n        return Object(_.jsx)(u.a, {\n          title: a,\n          children: Object(_.jsx)(\"div\", {\n            style: {\n              overflow: \"hidden\",\n              whiteSpace: \"nowrap\",\n              textOverflow: \"ellipsis\",\n            },\n            children: a,\n          }),\n        });\n      }\n      var x = {\n          idx: {\n            title: \"\\u5e8f\\u5217\",\n            key: \"index\",\n            render: function (a, r, t) {\n              return \"\".concat(t + 1);\n            },\n            align: \"center\",\n            width: 60,\n          },\n          product_name: {\n            title: \"\\u670d\\u52a1\\u7c7b\\u578b\",\n            key: \"product_name\",\n            dataIndex: \"product_name\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.product_name),\n                e = S.a(\" \", r.product_name);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          functional_module: {\n            title: \"\\u529f\\u80fd\\u6a21\\u5757\",\n            width: 120,\n            key: \"product_cn_name\",\n            dataIndex: \"product_cn_name\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.product_cn_name),\n                e = S.a(\" \", r.product_cn_name);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          alert_service_type: {\n            title: \"\\u529f\\u80fd\\u6a21\\u5757\",\n            width: 120,\n            key: \"alert_service_type\",\n            dataIndex: \"alert_service_type\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.alert_service_type),\n                e = S.a(\" \", r.alert_service_type);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          service_name: {\n            title: \"\\u670d\\u52a1\\u540d\\u79f0\",\n            width: 180,\n            key: \"service_name\",\n            dataIndex: \"service_name\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.service_name),\n                e = S.a(\" \", r.service_name);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          alert_service_name: {\n            title: \"\\u670d\\u52a1\\u540d\\u79f0\",\n            width: 160,\n            key: \"alert_service_name\",\n            dataIndex: \"alert_service_name\",\n            ellipsis: !0,\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.alert_service_name),\n                e = S.a(\" \", r.alert_service_name);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          ip: {\n            title: \"IP\\u5730\\u5740\",\n            width: 160,\n            key: \"ip\",\n            dataIndex: \"ip\",\n            sorter: L.sortIP,\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          thirdParty_ip: {\n            title: \"\\u8fde\\u63a5\\u5730\\u5740\",\n            key: \"ip\",\n            dataIndex: \"ip\",\n            sorter: L.sortIP,\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          alert_host_ip: {\n            title: \"IP\\u5730\\u5740\",\n            width: 80,\n            key: \"alert_host_ip\",\n            dataIndex: \"alert_host_ip\",\n            sorter: L.sortAlertIP,\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          alert_level: {\n            title: \"\\u544a\\u8b66\\u7ea7\\u522b\",\n            width: 100,\n            key: \"alert_level\",\n            dataIndex: \"alert_level\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.alert_level),\n                e = S.a(\" \", r.alert_level);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            render: function (a, r, t) {\n              switch (r.alert_level) {\n                case \"critical\":\n                  return Object(_.jsx)(D, {\n                    borderColor: \"#da4e48\",\n                    backgroundColor: \"#fbe7e6\",\n                    text: \"\\u4e25\\u91cd\",\n                  });\n                case \"warning\":\n                  return Object(_.jsx)(D, {\n                    borderColor: \"#f5c773\",\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    text: \"\\u8b66\\u544a\",\n                  });\n                default:\n                  return \"-\";\n              }\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n          },\n          alert_describe: {\n            title: \"\\u544a\\u8b66\\u63cf\\u8ff0\",\n            key: \"alert_describe\",\n            dataIndex: \"alert_describe\",\n            width: 280,\n            align: \"center\",\n            ellipsis: !0,\n            render: y,\n          },\n          alert_time: {\n            title: \"\\u544a\\u8b66\\u65f6\\u95f4\",\n            width: 140,\n            key: \"alert_time\",\n            dataIndex: \"alert_time\",\n            sorter: function (a, r) {\n              return v()(a.alert_time).valueOf() - v()(r.alert_time).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          warning_record_alert_time: {\n            title: \"\\u544a\\u8b66\\u65f6\\u95f4\",\n            width: 180,\n            key: \"alert_time\",\n            dataIndex: \"create_time\",\n            sorter: function (a, r) {\n              return (\n                v()(a.create_time).valueOf() - v()(r.create_time).valueOf()\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          alert_receiver: {\n            title: \"\\u544a\\u8b66\\u63a8\\u9001\",\n            key: \"alert_receiver\",\n            dataIndex: \"alert_receiver\",\n            align: \"center\",\n            render: y,\n          },\n          alert_resolve: {\n            title: \"\\u89e3\\u51b3\\u65b9\\u6848\",\n            key: \"alert_resolve\",\n            dataIndex: \"alert_resolve\",\n            align: \"center\",\n            render: k,\n          },\n          operating_system: {\n            title: \"\\u64cd\\u4f5c\\u7cfb\\u7edf\",\n            key: \"operating_system\",\n            dataIndex: \"operating_system\",\n            align: \"center\",\n            render: k,\n          },\n          alert_host_system: {\n            title: \"\\u64cd\\u4f5c\\u7cfb\\u7edf\",\n            key: \"alert_host_system\",\n            dataIndex: \"alert_host_system\",\n            align: \"center\",\n            render: k,\n          },\n          port: {\n            title: \"\\u7aef\\u53e3\",\n            key: \"port\",\n            dataIndex: \"port\",\n            sorter: function (a, r) {\n              return a.port - r.port;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          service_port: {\n            title: \"\\u7aef\\u53e3\",\n            key: \"service_port\",\n            dataIndex: \"service_port\",\n            sorter: function (a, r) {\n              return a.service_port - r.service_port;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          service_version: {\n            title: \"\\u670d\\u52a1\\u7248\\u672c\",\n            key: \"service_version\",\n            dataIndex: \"service_version\",\n            align: \"center\",\n            render: k,\n          },\n          configuration_information: {\n            title: \"\\u914d\\u7f6e\\u4fe1\\u606f\",\n            key: \"configuration_information\",\n            dataIndex: \"configuration_information\",\n            align: \"center\",\n            render: function (a, r, t) {\n              var e = r.cpu,\n                o = r.memory,\n                p = r.disk,\n                i = 1073741824,\n                d = f(e) ? \"-\" : \"\".concat(e, \"C\"),\n                l = f(o) ? \"-\" : \"\".concat((o / i).toFixed(1), \"G\"),\n                n = f(p) ? \"-\" : \"\".concat((p / i).toFixed(1), \"G\");\n              return \"-\" === d && \"-\" === l && \"-\" === n\n                ? \"-\"\n                : \"\".concat(d, \"|\").concat(l, \"|\").concat(n);\n            },\n          },\n          cpu_rate: {\n            title: \"CPU\\u4f7f\\u7528\\u7387\",\n            width: 110,\n            key: \"cpu_rate\",\n            dataIndex: \"cpu_rate\",\n            sorter: function (a, r) {\n              return L.sortUsageRate(a.cpu_rate, r.cpu_rate);\n            },\n            render: function (a, r, t) {\n              if (f(a)) return Object(_.jsx)(\"div\", { children: \"-\" });\n              var e = Number(Number(a).toFixed(2));\n              return \"normal\" === r.cpu_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"critical\" === r.cpu_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"#fbe7e6\",\n                    borderColor: \"#da4e48\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"warning\" === r.cpu_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    borderColor: \"#f5c773\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  });\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n          },\n          disk_rate: {\n            title: \"(\\u6839\\u5206\\u533a)\\u4f7f\\u7528\\u7387\",\n            width: 150,\n            key: \"disk_rate\",\n            dataIndex: \"disk_rate\",\n            sorter: function (a, r) {\n              return L.sortUsageRate(a.disk_rate, r.disk_rate);\n            },\n            render: function (a, r, t) {\n              if (f(a)) return Object(_.jsx)(\"div\", { children: \"-\" });\n              var e = Number(Number(a).toFixed(2));\n              return \"normal\" === r.disk_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    top: 1,\n                    text: \"\".concat(e, \"%\"),\n                  })\n                : \"critical\" === r.disk_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"#fbe7e6\",\n                    borderColor: \"#da4e48\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"warning\" === r.disk_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    borderColor: \"#f5c773\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  });\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n          },\n          disk_data_rate: {\n            title: \"(\\u6570\\u636e\\u5206\\u533a)\\u4f7f\\u7528\\u7387\",\n            width: 160,\n            key: \"disk_data_rate\",\n            dataIndex: \"disk_data_rate\",\n            sorter: function (a, r) {\n              return L.sortUsageRate(a.disk_data_rate, r.disk_data_rate);\n            },\n            render: function (a, r, t) {\n              if (f(a)) return Object(_.jsx)(\"div\", { children: \"-\" });\n              var e = Number(Number(a).toFixed(2));\n              return \"normal\" === r.disk_data_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"critical\" === r.disk_data_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"#fbe7e6\",\n                    borderColor: \"#da4e48\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"warning\" === r.disk_data_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    borderColor: \"#f5c773\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  });\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n          },\n          memory_rate: {\n            title: \"\\u5185\\u5b58\\u4f7f\\u7528\\u7387\",\n            width: 100,\n            key: \"memory_rate\",\n            dataIndex: \"memory_rate\",\n            sorter: function (a, r) {\n              return L.sortUsageRate(a.memory_rate, r.memory_rate);\n            },\n            render: function (a, r, t) {\n              if (f(a)) return Object(_.jsx)(\"div\", { children: \"-\" });\n              var e = Number(Number(a).toFixed(2));\n              return \"normal\" === r.memory_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"critical\" === r.memory_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"#fbe7e6\",\n                    borderColor: \"#da4e48\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : \"warning\" === r.memory_rate_check\n                ? Object(_.jsx)(D, {\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    borderColor: \"#f5c773\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  })\n                : Object(_.jsx)(D, {\n                    backgroundColor: \"rgb(238, 250, 244)\",\n                    borderColor: \"rgb(84, 187, 166)\",\n                    text: \"\".concat(e, \"%\"),\n                    top: 1,\n                  });\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n          },\n          running_time: {\n            title: \"\\u8fd0\\u884c\\u65f6\\u95f4\",\n            key: \"running_time\",\n            width: 120,\n            dataIndex: \"running_time\",\n            sorter: function (a, r) {\n              return (\n                Number(f(a.running_time) ? 0 : a.running_time) -\n                Number(f(r.running_time) ? 0 : r.running_time)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: A,\n          },\n          ssh_state: {\n            title: \"SSH\\u72b6\\u6001\",\n            width: 140,\n            key: \"ssh_state\",\n            dataIndex: \"ssh_state\",\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 0 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                      \"\\u542f\\u7528\",\n                    ],\n                  })\n                : 1 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u7981\\u7528\"],\n                  })\n                : a;\n            },\n          },\n          agent_state: {\n            title: \"Agent\\u72b6\\u6001\",\n            width: 140,\n            key: \"agent_state\",\n            dataIndex: \"agent_state\",\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 0 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                      \"\\u6b63\\u5e38\",\n                    ],\n                  })\n                : 1 === a\n                ? \"\\u5b89\\u88c5\\u4e2d\"\n                : 2 === a\n                ? \"\\u672a\\u5b89\\u88c5\"\n                : 3 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5f02\\u5e38\"],\n                  })\n                : a;\n            },\n          },\n          cluster_name: {\n            title: \"\\u96c6\\u7fa4\\u540d\\u79f0\",\n            key: \"cluster_name\",\n            dataIndex: \"cluster_name\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.cluster_name),\n                e = S.a(\" \", r.cluster_name);\n              return (\n                t.toLowerCase().charCodeAt(0) - e.toLowerCase().charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          linkAddress: {\n            title: \"\\u8fde\\u63a5\\u5730\\u5740\",\n            key: \"linkAddress\",\n            dataIndex: \"linkAddress\",\n            align: \"center\",\n            render: k,\n          },\n          cluster_mode: {\n            title: \"\\u96c6\\u7fa4\\u6a21\\u5f0f\",\n            key: \"cluster_mode\",\n            dataIndex: \"cluster_mode\",\n            align: \"center\",\n            render: k,\n          },\n          quote: {\n            title: \"\\u5df2\\u5f15\\u7528\",\n            key: \"quote\",\n            dataIndex: \"quote\",\n            align: \"center\",\n            render: function (a) {\n              return 0 === a ? \"\\u5426\" : \"\\u662f\";\n            },\n          },\n          created_at: {\n            title: \"\\u6dfb\\u52a0\\u65f6\\u95f4\",\n            key: \"created_at\",\n            dataIndex: \"created_at\",\n            sorter: function (a, r) {\n              return a - r;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          username: {\n            title: \"\\u7528\\u6237\\u540d\",\n            width: 120,\n            key: \"username\",\n            dataIndex: \"username\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.username),\n                e = S.a(\" \", r.username);\n              return t.charCodeAt(0) - e.charCodeAt(0);\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          role: {\n            title: \"\\u89d2\\u8272\",\n            width: 120,\n            key: \"role\",\n            dataIndex: \"role\",\n            sorter: function (a, r) {\n              var t = S.a(\" \", a.role),\n                e = S.a(\" \", r.role);\n              return t.charCodeAt(0) - e.charCodeAt(0);\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          login_time: {\n            title: \"\\u767b\\u5165\\u65f6\\u95f4\",\n            width: 180,\n            key: \"login_time\",\n            dataIndex: \"login_time\",\n            sorter: function (a, r) {\n              return v()(a.login_time).valueOf() - v()(r.login_time).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          datetime: {\n            title: \"\\u64cd\\u4f5c\\u65f6\\u95f4\",\n            width: 150,\n            key: \"datetime\",\n            dataIndex: \"datetime\",\n            sorter: function (a, r) {\n              return v()(a.datetime).valueOf() - v()(r.datetime).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          status: {\n            title: \"\\u7528\\u6237\\u72b6\\u6001\",\n            width: 160,\n            key: \"status\",\n            dataIndex: \"status\",\n            align: \"center\",\n            render: k,\n          },\n          date_joined: {\n            title: \"\\u521b\\u5efa\\u65f6\\u95f4\",\n            width: 120,\n            key: \"date_joined\",\n            dataIndex: \"date_joined\",\n            align: \"center\",\n            render: k,\n          },\n          desc: {\n            title: \"\\u63cf\\u8ff0\",\n            width: 220,\n            key: \"desc\",\n            dataIndex: \"desc\",\n            align: \"center\",\n            render: k,\n          },\n          action: {\n            title: \"\\u64cd\\u4f5c\\u7c7b\\u578b\",\n            width: 120,\n            key: \"action\",\n            dataIndex: \"action\",\n            align: \"center\",\n            render: k,\n          },\n          permission_count: {\n            title: \"\\u6743\\u9650\\u4e2a\\u6570\",\n            width: 120,\n            key: \"permission_count\",\n            dataIndex: \"permission_count\",\n            align: \"center\",\n            render: k,\n          },\n          inspection_operator: {\n            title: \"\\u64cd\\u4f5c\\u5458\",\n            width: 80,\n            key: \"inspection_operator\",\n            dataIndex: \"inspection_operator\",\n            sorter: function (a, r) {\n              return (\n                a.inspection_operator.charCodeAt(0) -\n                r.inspection_operator.charCodeAt(0)\n              );\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          inspection_status: {\n            title: \"\\u5de1\\u68c0\\u7ed3\\u679c\",\n            width: 150,\n            key: \"inspection_status\",\n            dataIndex: \"inspection_status\",\n            sorter: function (a, r) {\n              return a.inspection_status - r.inspection_status;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 2 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#6cbe7b\", \"#e8f5eb\"), \"\\u6210\\u529f\"],\n                  })\n                : 1 === a\n                ? \"\\u8fdb\\u884c\\u4e2d\"\n                : 0 === a\n                ? \"\\u672a\\u5f00\\u59cb\"\n                : 3 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5931\\u8d25\"],\n                  })\n                : a;\n            },\n          },\n          run_status: {\n            title: \"\\u6267\\u884c\\u7ed3\\u679c\",\n            width: 120,\n            key: \"inspection_status\",\n            dataIndex: \"inspection_status\",\n            sorter: function (a, r) {\n              return a.inspection_status - r.inspection_status;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 2 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#6cbe7b\", \"#e8f5eb\"), \"\\u6210\\u529f\"],\n                  })\n                : 1 === a\n                ? \"\\u8fdb\\u884c\\u4e2d\"\n                : 0 === a\n                ? \"\\u672a\\u5f00\\u59cb\"\n                : 3 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5931\\u8d25\"],\n                  })\n                : a;\n            },\n          },\n          service_status: {\n            title: \"\\u4e1a\\u52a1\\u72b6\\u6001\",\n            key: \"service_status\",\n            dataIndex: \"service_status\",\n            sorter: function (a, r) {\n              return a.service_status - r.service_status;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          product_service_status: {\n            title: \"\\u8fd0\\u884c\\u72b6\\u6001\",\n            width: 120,\n            key: \"product_service_status\",\n            dataIndex: \"service_status\",\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 0 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u672a\\u5b89\\u88c5\",\n                    ],\n                  })\n                : 1 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u5b89\\u88c5\\u4e2d\",\n                    ],\n                  })\n                : 2 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                      \"\\u6b63\\u5e38\",\n                    ],\n                  })\n                : 3 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5f02\\u5e38\"],\n                  })\n                : 4 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u505c\\u6b62\"],\n                  })\n                : 5 == a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u542f\\u52a8\\u4e2d\",\n                    ],\n                  })\n                : 6 == a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u505c\\u6b62\\u4e2d\",\n                    ],\n                  })\n                : 7 == a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u91cd\\u542f\\u4e2d\",\n                    ],\n                  })\n                : -1 == a\n                ? r.is_web_service\n                  ? Object(_.jsxs)(\"div\", {\n                      children: [\n                        R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                        \"\\u6b63\\u5e38\",\n                      ],\n                    })\n                  : Object(_.jsxs)(\"div\", {\n                      children: [\n                        R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                        \"\\u672a\\u76d1\\u63a7\",\n                      ],\n                    })\n                : a;\n            },\n          },\n          product_thrityPart_status: {\n            title: \"\\u8fd0\\u884c\\u72b6\\u6001\",\n            key: \"product_service_status\",\n            dataIndex: \"state\",\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a)\n                ? \"-\"\n                : 1 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                      \"\\u6b63\\u5e38\",\n                    ],\n                  })\n                : 2 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [\n                      R(\"#f5c773\", \"rgba(247, 231, 24,.2)\"),\n                      \"\\u5f02\\u5e38\",\n                    ],\n                  })\n                : 0 === a\n                ? Object(_.jsxs)(\"div\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u505c\\u6b62\"],\n                  })\n                : a;\n            },\n          },\n          host_risk: {\n            title: \"\\u4e3b\\u673a\\u98ce\\u9669\",\n            key: \"host_risk\",\n            dataIndex: \"host_risk\",\n            sorter: function (a, r) {\n              return a.host_risk - r.host_risk;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a) ? \"-\" : \"\".concat(a, \"\\u4e2a\");\n            },\n          },\n          service_risk: {\n            title: \"\\u670d\\u52a1\\u98ce\\u9669\",\n            key: \"service_risk\",\n            dataIndex: \"service_risk\",\n            sorter: function (a, r) {\n              return a.service_risk - r.service_risk;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a, r, t) {\n              return f(a) ? \"-\" : \"\".concat(a, \"\\u4e2a\");\n            },\n          },\n          start_time: {\n            title: \"\\u5f00\\u59cb\\u65f6\\u95f4\",\n            width: 160,\n            key: \"start_time\",\n            dataIndex: \"start_time\",\n            sorter: function (a, r) {\n              return v()(a.start_time).valueOf() - v()(r.start_time).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          patrol_start_time: {\n            title: \"\\u5f00\\u59cb\\u65f6\\u95f4\",\n            width: 200,\n            key: \"patrol_start_time\",\n            dataIndex: \"start_time\",\n            sorter: function (a, r) {\n              return v()(a.start_time).valueOf() - v()(r.start_time).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          patrol_end_time: {\n            title: \"\\u7ed3\\u675f\\u65f6\\u95f4\",\n            width: 160,\n            key: \"patrol_end_time\",\n            dataIndex: \"end_time\",\n            sorter: function (a, r) {\n              return v()(a.end_time).valueOf() - v()(r.end_time).valueOf();\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: k,\n          },\n          duration: {\n            title: \"\\u7528\\u65f6\",\n            width: 100,\n            key: \"duration\",\n            dataIndex: \"duration\",\n            sorter: function (a, r) {\n              return a.duration - r.duration;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: A,\n          },\n          report_system: {\n            title: \"\\u64cd\\u4f5c\\u7cfb\\u7edf\",\n            key: \"report_system\",\n            dataIndex: \"system\",\n            render: k,\n            align: \"center\",\n          },\n          report_risk_level: {\n            title: \"\\u98ce\\u9669\\u7ea7\\u522b\",\n            key: \"report_risk_level\",\n            dataIndex: \"risk_level\",\n            render: function (a, r, t) {\n              switch (r.risk_level) {\n                case \"critical\":\n                  return Object(_.jsx)(D, {\n                    backgroundColor: \"#fbe7e6\",\n                    borderColor: \"#da4e48\",\n                    text: \"\\u4e25\\u91cd\",\n                  });\n                case \"warning\":\n                  return Object(_.jsx)(D, {\n                    backgroundColor: \"rgba(247, 231, 24,.2)\",\n                    borderColor: \"#f5c773\",\n                    text: \"\\u8b66\\u544a\",\n                  });\n                default:\n                  return \"-\";\n              }\n            },\n            align: \"center\",\n          },\n          report_risk_describe: {\n            title: \"\\u98ce\\u9669\\u63cf\\u8ff0\",\n            key: \"report_risk_describe\",\n            dataIndex: \"risk_describe\",\n            ellipsis: !0,\n            render: k,\n            align: \"center\",\n            width: 400,\n          },\n          report_resolve_info: {\n            title: \"\\u89e3\\u51b3\\u65b9\\u6848\",\n            key: \"report_resolve_info\",\n            dataIndex: \"resolve_info\",\n            render: k,\n            align: \"center\",\n          },\n          report_release_version: {\n            title: \"\\u64cd\\u4f5c\\u7cfb\\u7edf\",\n            key: \"report_release_version\",\n            dataIndex: \"release_version\",\n            render: k,\n            align: \"center\",\n          },\n          report_host_massage: {\n            title: \"\\u914d\\u7f6e\\u4fe1\\u606f\",\n            key: \"report_host_massage\",\n            dataIndex: \"host_massage\",\n            render: k,\n            align: \"center\",\n          },\n          report_disk_usage_root: {\n            title: \"\\u6839\\u5206\\u533a\\u4f7f\\u7528\\u7387\",\n            width: 150,\n            key: \"report_disk_usage_root\",\n            dataIndex: \"disk_usage_root\",\n            render: k,\n            align: \"center\",\n          },\n          report_disk_usage_data: {\n            title: \"\\u6570\\u636e\\u5206\\u533a\\u4f7f\\u7528\\u7387\",\n            width: 130,\n            key: \"report_disk_usage_data\",\n            dataIndex: \"disk_usage_data\",\n            render: k,\n            align: \"center\",\n          },\n          report_sys_load: {\n            title: \"\\u5e73\\u5747\\u8d1f\\u8f7d\",\n            key: \"report_sys_load\",\n            dataIndex: \"sys_load\",\n            render: k,\n            align: \"center\",\n          },\n          report_idx: {\n            title: \"\\u5e8f\\u5217\",\n            key: \"index\",\n            width: 50,\n            render: function (a, r, t) {\n              return \"\".concat(t + 1);\n            },\n            align: \"center\",\n          },\n          report_host_ip: {\n            title: \"IP\\u5730\\u5740\",\n            key: \"report_host_ip\",\n            dataIndex: \"host_ip\",\n            width: 150,\n            render: k,\n            align: \"center\",\n          },\n          report_log_level: {\n            title: \"\\u65e5\\u5fd7\\u7b49\\u7ea7\",\n            key: \"report_log_level\",\n            dataIndex: \"log_level\",\n            render: k,\n            align: \"center\",\n          },\n          report_mem_usage: {\n            title: \"\\u5185\\u5b58\\u4f7f\\u7528\\u7387\",\n            key: \"report_mem_usage\",\n            dataIndex: \"mem_usage\",\n            width: 100,\n            render: k,\n            align: \"center\",\n          },\n          report_cpu_usage: {\n            title: \"CPU\\u4f7f\\u7528\\u7387\",\n            key: \"report_cpu_usage\",\n            dataIndex: \"cpu_usage\",\n            width: 110,\n            render: k,\n            align: \"center\",\n          },\n          report_service_name: {\n            title: \"\\u670d\\u52a1\\u540d\\u79f0\",\n            key: \"report_service_name\",\n            dataIndex: \"service_name\",\n            render: k,\n            align: \"center\",\n          },\n          report_service_port: {\n            title: \"\\u7aef\\u53e3\\u53f7\",\n            key: \"report_service_port\",\n            dataIndex: \"service_port\",\n            render: function (a, r, t) {\n              return a || \"-\";\n            },\n            align: \"center\",\n          },\n          report_service_status: {\n            title: \"\\u8fd0\\u884c\\u72b6\\u6001\",\n            key: \"report_service_status\",\n            dataIndex: \"service_status\",\n            render: k,\n            align: \"center\",\n          },\n          report_run_time: {\n            title: \"\\u8fd0\\u884c\\u65f6\\u95f4\",\n            width: 120,\n            key: \"report_run_time\",\n            dataIndex: \"run_time\",\n            render: k,\n            align: \"center\",\n          },\n          report_cluster_name: {\n            title: \"\\u96c6\\u7fa4\\u540d\\u79f0\",\n            key: \"report_cluster_name\",\n            dataIndex: \"cluster_name\",\n            render: k,\n            align: \"center\",\n          },\n          operator: {\n            title: \"\\u64cd\\u4f5c\\u4eba\\u5458\",\n            key: \"operator\",\n            dataIndex: \"operator\",\n            align: \"center\",\n            width: 80,\n          },\n          install_process: {\n            title: \"\\u5b89\\u88c5\\u8fdb\\u5ea6\",\n            key: \"install_process\",\n            dataIndex: \"install_process\",\n            align: \"center\",\n            width: 80,\n            render: function (a) {\n              return \"0%\" == a\n                ? Object(_.jsxs)(\"span\", {\n                    children: [R(\"#da4e48\", \"#fbe7e6\"), \"\\u5931\\u8d25\"],\n                  })\n                : \"100%\" == a\n                ? Object(_.jsxs)(\"span\", {\n                    children: [\n                      R(\"rgb(84, 187, 166)\", \"rgb(238, 250, 244)\"),\n                      \"\\u6210\\u529f\",\n                    ],\n                  })\n                : a;\n            },\n          },\n          verson_start_time: {\n            title: \"\\u5f00\\u59cb\\u65f6\\u95f4\",\n            key: \"start_time\",\n            dataIndex: \"start_time\",\n            align: \"center\",\n            width: 160,\n          },\n          verson_end_time: {\n            title: \"\\u7ed3\\u675f\\u65f6\\u95f4\",\n            key: \"end_time\",\n            dataIndex: \"end_time\",\n            align: \"center\",\n            width: 150,\n          },\n          use_time: {\n            title: \"\\u7528\\u65f6\",\n            key: \"duration\",\n            dataIndex: \"duration\",\n            render: function (a) {\n              if (a && \"-\" !== a) {\n                var r = v.a.duration(a, \"seconds\"),\n                  t = r.hours(),\n                  e = t ? \"\".concat(t, \"\\u5c0f\\u65f6\") : \"\",\n                  o = r.minutes(),\n                  p = o % 60 ? \"\".concat(o % 60, \"\\u5206\\u949f\") : \"\",\n                  i = r.seconds(),\n                  d = i % 60 ? \"\".concat(i % 60, \"\\u79d2\") : \"\";\n                return \"\".concat(e, \" \").concat(p, \" \").concat(d);\n              }\n              return \"-\";\n            },\n            align: \"center\",\n            width: 100,\n          },\n          execution_mdoal: {\n            title: \"\\u6267\\u884c\\u65b9\\u5f0f\",\n            align: \"center\",\n            dataIndex: \"execute_type\",\n            key: \"execute_type\",\n            render: function (a) {\n              return \"man\" == a\n                ? \"\\u624b\\u52a8\\u6267\\u884c\"\n                : \"auto\" == a\n                ? \"\\u5b9a\\u65f6\\u6267\\u884c\"\n                : \"-\";\n            },\n            width: 80,\n          },\n          machine_idx: {\n            title: \"\\u5e8f\\u5217\",\n            key: \"index\",\n            render: function (a, r, t) {\n              return \"\".concat(r._idx);\n            },\n            align: \"center\",\n            width: 60,\n          },\n          service_idx: {\n            title: \"\\u5e8f\\u5217\",\n            key: \"index\",\n            dataIndex: \"_idx\",\n            align: \"center\",\n            width: 60,\n          },\n          service_port_new: {\n            title: \"\\u7aef\\u53e3\",\n            width: 100,\n            key: \"service_port\",\n            dataIndex: \"service_port\",\n            sorter: function (a, r) {\n              return a.service_port - r.service_port;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a) {\n              return a || \"-\";\n            },\n          },\n          _port_new: {\n            title: \"\\u7aef\\u53e3\",\n            key: \"port\",\n            dataIndex: \"port\",\n            sorter: function (a, r) {\n              return a.port - r.port;\n            },\n            sortDirections: [\"descend\", \"ascend\"],\n            align: \"center\",\n            render: function (a) {\n              return a || \"-\";\n            },\n          },\n        },\n        w = [\n          {\n            title: \"\\u670d\\u52a1\",\n            dataIndex: \"name\",\n            className: E._bigfontSize,\n          },\n          {\n            title: \"IP\\u5730\\u5740\",\n            dataIndex: \"ip\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n          {\n            title: \"\\u7aef\\u53e3\",\n            dataIndex: \"port\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n          {\n            title: \"\\u8fde\\u901a\\u6027\",\n            dataIndex: \"status\",\n            align: \"center\",\n            className: E._bigfontSize,\n            render: function (a) {\n              return Object(_.jsx)(\"div\", {\n                style: { color: \"False\" == a ? \"rgb(207, 19, 34)\" : null },\n                children: a,\n              });\n            },\n          },\n        ],\n        h = [\n          { title: \"Topic\", dataIndex: \"topic\", className: E._bigfontSize },\n          {\n            title: \"\\u5206\\u533a\\u6570\",\n            dataIndex: \"partition\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n          {\n            title: \"\\u526f\\u672c\\u6570\",\n            dataIndex: \"replication\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n        ],\n        P = [\n          { title: \"Group\", dataIndex: \"group\", className: E._bigfontSize },\n          { title: \"Topic\", dataIndex: \"topic\", className: E._bigfontSize },\n          {\n            title: \"Log Offset\",\n            dataIndex: \"log_offset\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n          {\n            title: \"Lag Offset\",\n            dataIndex: \"lag_offset\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n        ],\n        C = [\n          { title: \"Topic\", dataIndex: \"topic\", className: E._bigfontSize },\n          {\n            title: \"Size\",\n            dataIndex: \"size\",\n            align: \"center\",\n            className: E._bigfontSize,\n          },\n        ];\n      var M = t(341),\n        X = t(342),\n        G = t(339),\n        O = t(340),\n        B = t(99),\n        U = t(69),\n        N = t(0),\n        T = (t(221), t(22)),\n        F = m.a.Panel,\n        z = [\n          Object(j.a)(\n            Object(j.a)({}, x.report_service_name),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_host_ip),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_service_port),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_service_status),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_cpu_usage),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_mem_usage),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_run_time),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n          Object(j.a)(\n            Object(j.a)({}, x.report_log_level),\n            {},\n            { className: \"_bigfontSize\" }\n          ),\n        ];\n      function I() {\n        var a = Object(N.useState)(!1),\n          r = Object(b.a)(a, 2),\n          t = r[0],\n          e = r[1],\n          o = Object(N.useState)(\"\"),\n          p = Object(b.a)(o, 2),\n          i = p[0],\n          d = p[1],\n          l = Object(N.useState)([]),\n          u = Object(b.a)(l, 2);\n        u[0], u[1];\n        return Object(_.jsxs)(\"div\", {\n          id: \"reportContent\",\n          className: \"reportContent\",\n          children: [\n            Object(_.jsx)(\"div\", {\n              className: \"reportTitle\",\n              children: Object(_.jsxs)(\"div\", {\n                children: [\" \", \"\\u5de1\\u68c0\\u62a5\\u544a\"],\n              }),\n            }),\n            Object(_.jsx)(\"div\", {\n              children: Object(_.jsxs)(m.a, {\n                bordered: !1,\n                defaultActiveKey: [\n                  \"overview\",\n                  \"risk\",\n                  \"map\",\n                  \"host\",\n                  \"database\",\n                  \"component\",\n                  \"service\",\n                ],\n                style: { marginTop: 10 },\n                expandIcon: function (a) {\n                  var r = a.isActive;\n                  return Object(_.jsx)(s.a, {\n                    type: \"caret-right\",\n                    rotate: r ? 90 : 0,\n                  });\n                },\n                children: [\n                  Object(_.jsx)(\n                    F,\n                    {\n                      header: \"\\u6982\\u8ff0\\u4fe1\\u606f\",\n                      className: \"panelItem\",\n                      children: Object(_.jsxs)(\"div\", {\n                        className: \"overviewItemWrapper\",\n                        children: [\n                          Object(_.jsx)(Z, {\n                            data: T.summary.task_info,\n                            type: \"task_info\",\n                          }),\n                          Object(_.jsx)(Z, {\n                            data: T.summary.time_info,\n                            type: \"time_info\",\n                          }),\n                          Object(_.jsx)(Z, {\n                            data: T.summary.scan_info,\n                            type: \"scan_info\",\n                          }),\n                          Object(_.jsx)(Z, {\n                            data: T.summary.scan_result,\n                            type: \"scan_result\",\n                          }),\n                        ],\n                      }),\n                    },\n                    \"overview\"\n                  ),\n                  T.risks &&\n                    (!M.a(T.risks.host_list) || !M.a(T.risks.service_list)) &&\n                    Object(_.jsxs)(\n                      F,\n                      {\n                        header: \"\\u98ce\\u9669\\u6307\\u6807\",\n                        className: \"panelItem\",\n                        children: [\n                          T.risks.host_list.length > 0 &&\n                            Object(_.jsx)(c.a, {\n                              style: { marginTop: 20 },\n                              size: \"small\",\n                              pagination: !1,\n                              rowKey: function (a, r) {\n                                return r;\n                              },\n                              columns: [\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_idx),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_host_ip),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_system),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_risk_level),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_risk_describe),\n                                  {},\n                                  {\n                                    className: \"_bigfontSize\",\n                                    render: function (a) {\n                                      return Object(_.jsx)(\"span\", {\n                                        style: { cursor: \"pointer\" },\n                                        onClick: function () {\n                                          console.log(a), d(a), e(!0);\n                                        },\n                                        children: a,\n                                      });\n                                    },\n                                  }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_resolve_info),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                              ],\n                              title: function () {\n                                return \"\\u4e3b\\u673a\\u6307\\u6807\";\n                              },\n                              dataSource: T.risks.host_list,\n                            }),\n                          T.risks.service_list.length > 0 &&\n                            Object(_.jsx)(c.a, {\n                              style: { marginTop: 20 },\n                              size: \"small\",\n                              pagination: !1,\n                              rowKey: function (a, r) {\n                                return r;\n                              },\n                              columns: [\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_idx),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_service_name),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_host_ip),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_service_port),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_risk_level),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_risk_describe),\n                                  {},\n                                  {\n                                    className: \"_bigfontSize\",\n                                    render: function (a) {\n                                      return Object(_.jsx)(\"span\", {\n                                        style: { cursor: \"pointer\" },\n                                        onClick: function () {\n                                          console.log(a), d(a), e(!0);\n                                        },\n                                        children: a,\n                                      });\n                                    },\n                                  }\n                                ),\n                                Object(j.a)(\n                                  Object(j.a)({}, x.report_resolve_info),\n                                  {},\n                                  { className: \"_bigfontSize\" }\n                                ),\n                              ],\n                              title: function () {\n                                return \"\\u670d\\u52a1\\u6307\\u6807\";\n                              },\n                              dataSource: T.risks.service_list,\n                            }),\n                        ],\n                      },\n                      \"risk\"\n                    ),\n                  !X.a(G.a, M.a)(T.service_topology) &&\n                    Object(_.jsx)(\n                      F,\n                      {\n                        header: \"\\u670d\\u52a1\\u5e73\\u9762\\u56fe\",\n                        className: \"panelItem\",\n                        children: Object(_.jsx)(\"div\", {\n                          style: {\n                            display: \"flex\",\n                            flexFlow: \"row wrap\",\n                            margin: 10,\n                          },\n                          children: O.a(B.a)(function (a, r) {\n                            return Object(_.jsx)(\n                              J,\n                              {\n                                title: a.host_ip,\n                                list: a.service_list,\n                                data: T,\n                              },\n                              r\n                            );\n                          }, T.service_topology),\n                        }),\n                      },\n                      \"map\"\n                    ),\n                  !X.a(G.a, M.a)(T.detail_dict.host) &&\n                    Object(_.jsx)(\n                      F,\n                      {\n                        header: \"\\u4e3b\\u673a\\u5217\\u8868\",\n                        className: \"panelItem\",\n                        children: Object(_.jsx)(c.a, {\n                          size: \"small\",\n                          style: { marginTop: 20 },\n                          scroll: { x: 1100 },\n                          pagination: !1,\n                          rowKey: function (a) {\n                            return a.id;\n                          },\n                          columns: [\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_idx),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_host_ip),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_release_version),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_host_massage),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_cpu_usage),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_mem_usage),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_disk_usage_root),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_disk_usage_data),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_run_time),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                            Object(j.a)(\n                              Object(j.a)({}, x.report_sys_load),\n                              {},\n                              { className: \"_bigfontSize\" }\n                            ),\n                          ],\n                          expandedRowRender: function () {\n                            for (\n                              var a = arguments.length, r = new Array(a), o = 0;\n                              o < a;\n                              o++\n                            )\n                              r[o] = arguments[o];\n                            return (\n                              (r[0].basic = r[0].basic.filter(function (a) {\n                                return \"cluster_ip\" !== a.name;\n                              })),\n                              K.apply(void 0, r.concat([t, e, i, d]))\n                            );\n                          },\n                          dataSource: T.detail_dict.host,\n                        }),\n                      },\n                      \"host\"\n                    ),\n                  !X.a(G.a, M.a)(T.detail_dict.database) &&\n                    Object(_.jsx)(\n                      F,\n                      {\n                        header: \"\\u6570\\u636e\\u5e93\\u5217\\u8868\",\n                        className: \"panelItem\",\n                        children: Object(_.jsx)(c.a, {\n                          size: \"small\",\n                          style: { marginTop: 20 },\n                          pagination: !1,\n                          rowKey: function (a, r) {\n                            return a.id;\n                          },\n                          columns: z,\n                          expandedRowRender: function () {\n                            for (\n                              var a = arguments.length, r = new Array(a), o = 0;\n                              o < a;\n                              o++\n                            )\n                              r[o] = arguments[o];\n                            (r[0].basic = r[0].basic.filter(function (a) {\n                              return \"cluster_ip\" !== a.name;\n                            })),\n                              K.apply(void 0, r.concat([t, e, i, d]));\n                          },\n                          dataSource: T.detail_dict.database,\n                        }),\n                      },\n                      \"database\"\n                    ),\n                  !X.a(G.a, M.a)(T.detail_dict.component) &&\n                    Object(_.jsx)(\n                      F,\n                      {\n                        header: \"\\u7ec4\\u4ef6\\u5217\\u8868\",\n                        className: \"panelItem\",\n                        children: Object(_.jsx)(c.a, {\n                          size: \"small\",\n                          style: { marginTop: 20 },\n                          pagination: !1,\n                          rowKey: function (a, r) {\n                            return a.id;\n                          },\n                          columns: z,\n                          expandedRowRender: function () {\n                            for (\n                              var a = arguments.length, r = new Array(a), o = 0;\n                              o < a;\n                              o++\n                            )\n                              r[o] = arguments[o];\n                            return (\n                              (r[0].basic = r[0].basic.filter(function (a) {\n                                return \"cluster_ip\" !== a.name;\n                              })),\n                              K.apply(void 0, r.concat([t, e, i, d]))\n                            );\n                          },\n                          dataSource: T.detail_dict.component,\n                        }),\n                      },\n                      \"component\"\n                    ),\n                  !X.a(G.a, M.a)(T.detail_dict.service) &&\n                    Object(_.jsx)(\n                      F,\n                      {\n                        header: \"\\u670d\\u52a1\\u5217\\u8868\",\n                        className: \"panelItem\",\n                        children: Object(_.jsx)(c.a, {\n                          size: \"small\",\n                          style: { marginTop: 20 },\n                          pagination: !1,\n                          rowKey: function (a, r) {\n                            return a.id;\n                          },\n                          columns: z,\n                          expandedRowRender: function () {\n                            for (\n                              var a = arguments.length, r = new Array(a), o = 0;\n                              o < a;\n                              o++\n                            )\n                              r[o] = arguments[o];\n                            return (\n                              (r[0].basic = r[0].basic.filter(function (a) {\n                                return \"cluster_ip\" !== a.name;\n                              })),\n                              K.apply(void 0, r.concat([t, e, i, d]))\n                            );\n                          },\n                          dataSource: T.detail_dict.service,\n                        }),\n                      },\n                      \"service\"\n                    ),\n                ],\n              }),\n            }),\n            Object(_.jsx)(n.a, {\n              title: \"\\u8fdb\\u7a0b\\u65e5\\u5fd7\",\n              placement: \"right\",\n              closable: !1,\n              onClose: function () {\n                return e(!1);\n              },\n              visible: t,\n              width: 720,\n              destroyOnClose: !0,\n              children: i,\n            }),\n          ],\n        });\n      }\n      function H() {\n        var a =\n            arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 0,\n          r = a,\n          t = Math.round(Number(a)),\n          e = Math.floor(t / 86400),\n          o = Math.floor((t % 86400) / 3600),\n          p = Math.floor(((t % 86400) % 3600) / 60),\n          i = Math.floor(((t % 86400) % 3600) % 60);\n        return (\n          e > 0\n            ? (r =\n                e + \"\\u5929\" + o + \"\\u5c0f\\u65f6\" + p + \"\\u5206\" + i + \"\\u79d2\")\n            : o > 0\n            ? (r = o + \"\\u5c0f\\u65f6\" + p + \"\\u5206\" + i + \"\\u79d2\")\n            : p > 0\n            ? (r = p + \"\\u5206\" + i + \"\\u79d2\")\n            : i > 0 && (r = i + \"\\u79d2\"),\n          r\n        );\n      }\n      function Z(a) {\n        var r = a.data;\n        switch (a.type) {\n          case \"task_info\":\n            return Object(_.jsxs)(\"div\", {\n              className: \"overviewItem\",\n              children: [\n                Object(_.jsx)(\"div\", { children: \"\\u4efb\\u52a1\\u4fe1\\u606f\" }),\n                Object(_.jsxs)(\"div\", {\n                  children: [\n                    Object(_.jsxs)(\"div\", {\n                      children: [\"\\u4efb\\u52a1\\u540d\\u79f0\\uff1a\", r.task_name],\n                    }),\n                    Object(_.jsxs)(\"div\", {\n                      children: [\"\\u64cd\\u4f5c\\u4eba\\u5458\\uff1a\", r.operator],\n                    }),\n                    Object(_.jsxs)(\"div\", {\n                      children: [\n                        \"\\u4efb\\u52a1\\u72b6\\u6001\\uff1a\",\n                        2 === r.task_status\n                          ? \"\\u5df2\\u5b8c\\u6210\"\n                          : \"\\u5931\\u8d25\",\n                      ],\n                    }),\n                  ],\n                }),\n              ],\n            });\n          case \"time_info\":\n            return Object(_.jsxs)(\"div\", {\n              className: \"overviewItem\",\n              children: [\n                Object(_.jsx)(\"div\", { children: \"\\u65f6\\u95f4\\u7edf\\u8ba1\" }),\n                Object(_.jsxs)(\"div\", {\n                  children: [\n                    Object(_.jsxs)(\"div\", {\n                      children: [\n                        \"\\u5f00\\u59cb\\u65f6\\u95f4\\uff1a\",\n                        r.start_time,\n                      ],\n                    }),\n                    Object(_.jsxs)(\"div\", {\n                      children: [\"\\u7ed3\\u675f\\u65f6\\u95f4\\uff1a\", r.end_time],\n                    }),\n                    Object(_.jsxs)(\"div\", {\n                      children: [\"\\u4efb\\u52a1\\u8017\\u65f6\\uff1a\", H(r.cost)],\n                    }),\n                  ],\n                }),\n              ],\n            });\n          case \"scan_info\":\n            return Object(_.jsxs)(\"div\", {\n              className: \"overviewItem\",\n              children: [\n                Object(_.jsx)(\"div\", { children: \"\\u626b\\u63cf\\u7edf\\u8ba1\" }),\n                Object(_.jsx)(\"div\", {\n                  style: { display: \"flex\", alignItems: \"center\" },\n                  children: Object(_.jsxs)(\"div\", {\n                    children: [\n                      r.host > 0 &&\n                        Object(_.jsxs)(\"div\", {\n                          children: [\n                            \"\\u4e3b\\u673a\\u4e2a\\u6570\\uff1a\",\n                            r.host,\n                            \"\\u53f0\",\n                          ],\n                        }),\n                      r.component > 0 &&\n                        Object(_.jsxs)(\"div\", {\n                          children: [\n                            \"\\u7ec4\\u4ef6\\u4e2a\\u6570\\uff1a\",\n                            r.component,\n                            \"\\u4e2a\",\n                          ],\n                        }),\n                      r.service > 0 &&\n                        Object(_.jsxs)(\"div\", {\n                          children: [\n                            \"\\u670d\\u52a1\\u4e2a\\u6570\\uff1a\",\n                            r.service,\n                            \"\\u4e2a\",\n                          ],\n                        }),\n                    ],\n                  }),\n                }),\n              ],\n            });\n          case \"scan_result\":\n            return Object(_.jsxs)(\"div\", {\n              className: \"overviewItem\",\n              children: [\n                Object(_.jsx)(\"div\", { children: \"\\u5206\\u6790\\u7ed3\\u679c\" }),\n                Object(_.jsx)(\"div\", {\n                  style: { display: \"flex\", alignItems: \"center\" },\n                  children: Object(_.jsxs)(\"div\", {\n                    children: [\n                      Object(_.jsxs)(\"div\", {\n                        children: [\n                          \"\\u603b\\u6307\\u6807\\u6570\\uff1a\",\n                          r.all_target_num,\n                        ],\n                      }),\n                      Object(_.jsxs)(\"div\", {\n                        children: [\n                          \"\\u5f02\\u5e38\\u6307\\u6807\\uff1a\",\n                          r.abnormal_target,\n                        ],\n                      }),\n                    ],\n                  }),\n                }),\n              ],\n            });\n        }\n      }\n      function J(a) {\n        var r = a.title,\n          t = a.list;\n        a.data;\n        return Object(_.jsxs)(\"div\", {\n          className: \"planChartWrapper\",\n          children: [\n            Object(_.jsxs)(\"div\", {\n              className: \"planChartTitle\",\n              children: [\n                Object(_.jsx)(\"span\", { className: \"planChartTitleCircular\" }),\n                r,\n              ],\n            }),\n            Object(_.jsx)(\"div\", {\n              className: \"planChartBlockWrapper\",\n              children: t.map(function (a) {\n                return Object(_.jsx)(\n                  \"div\",\n                  {\n                    className: \"stateButton\",\n                    children: Object(_.jsx)(\"div\", { children: a }),\n                  },\n                  a\n                );\n              }),\n            }),\n          ],\n        });\n      }\n      function K(a) {\n        for (\n          var r = a.basic,\n            t =\n              (a.host_ip,\n              a.service_status,\n              a.run_time,\n              a.log_level,\n              a.mem_usage,\n              a.cpu_usage,\n              a.service_name,\n              a.service_port,\n              a.cluster_name,\n              a.release_version,\n              a.host_massage,\n              a.disk_usage_root,\n              a.disk_usage_data,\n              a.sys_load,\n              Object(l.a)(a, [\n                \"basic\",\n                \"host_ip\",\n                \"service_status\",\n                \"run_time\",\n                \"log_level\",\n                \"mem_usage\",\n                \"cpu_usage\",\n                \"service_name\",\n                \"service_port\",\n                \"cluster_name\",\n                \"release_version\",\n                \"host_massage\",\n                \"disk_usage_root\",\n                \"disk_usage_data\",\n                \"sys_load\",\n              ])),\n            e =\n              (t.topic_partition,\n              t.kafka_offsets,\n              t.topic_size,\n              arguments.length),\n            o = new Array(e > 1 ? e - 1 : 0),\n            p = 1;\n          p < e;\n          p++\n        )\n          o[p - 1] = arguments[p];\n        var i = o.slice(-4),\n          d = Object(b.a)(i, 4),\n          j = d[0],\n          u = d[1],\n          g = d[2],\n          v = d[3],\n          S = Object.entries(t).filter(function (a) {\n            return Array.isArray(a[1]);\n          }),\n          E = [\n            {\n              title: \"TOP\",\n              dataIndex: \"TOP\",\n              width: 50,\n              className: \"_bigfontSize\",\n            },\n            {\n              title: \"PID\",\n              dataIndex: \"PID\",\n              align: \"center\",\n              width: 100,\n              className: \"_bigfontSize\",\n            },\n            {\n              title: \"\\u4f7f\\u7528\\u7387\",\n              dataIndex: \"P_RATE\",\n              align: \"center\",\n              width: 100,\n              className: \"_bigfontSize\",\n            },\n            {\n              title: \"\\u8fdb\\u7a0b\",\n              dataIndex: \"P_CMD\",\n              ellipsis: !0,\n              className: \"_bigfontSize\",\n              render: function (a) {\n                return Object(_.jsx)(\"span\", {\n                  style: { cursor: \"pointer\" },\n                  onClick: function () {\n                    v(a), u(!0);\n                  },\n                  children: a,\n                });\n              },\n            },\n          ],\n          f = {\n            port_connectivity: {\n              columns: w,\n              dataSource: t.port_connectivity,\n              title: \"\\u7aef\\u53e3\\u8fde\\u901a\\u6027\",\n            },\n            memory_top: {\n              columns: E,\n              dataSource: t.memory_top,\n              title: \"\\u5185\\u5b58\\u4f7f\\u7528\\u7387Top10\\u8fdb\\u7a0b\",\n            },\n            cpu_top: {\n              columns: E,\n              dataSource: t.cpu_top,\n              title: \"cpu\\u4f7f\\u7528\\u7387Top10\\u8fdb\\u7a0b\",\n            },\n            kernel_parameters: {\n              columns: [],\n              dataSource: t.kernel_parameters,\n              title: \"\\u5185\\u6838\\u53c2\\u6570\",\n            },\n            topic_partition: {\n              columns: h,\n              dataSource: t.topic_partition,\n              title: \"\\u5206\\u533a\\u4fe1\\u606f\",\n            },\n            kafka_offsets: {\n              columns: P,\n              dataSource: t.kafka_offsets,\n              title: \"\\u6d88\\u8d39\\u4f4d\\u79fb\\u4fe1\\u606f\",\n            },\n            topic_size: {\n              columns: C,\n              dataSource: t.topic_size,\n              title: \"Topic\\u6d88\\u606f\\u5927\\u5c0f\",\n            },\n          };\n        return Object(_.jsxs)(\"div\", {\n          className: \"expandedRowWrapper\",\n          children: [\n            Array.isArray(r) && Object(_.jsx)(q, { basic: r }),\n            S.length > 0 &&\n              Object(_.jsx)(m.a, {\n                defaultActiveKey: U.a(t),\n                style: { marginTop: 10 },\n                expandIcon: function (a) {\n                  var r = a.isActive;\n                  return Object(_.jsx)(s.a, {\n                    type: \"caret-right\",\n                    rotate: r ? 90 : 0,\n                  });\n                },\n                children: S.map(function (a, r) {\n                  var t = f[a[0]];\n                  if (!G.a(t))\n                    return Object(_.jsx)(\n                      F,\n                      {\n                        header: t.title,\n                        children:\n                          t.columns.length > 0\n                            ? Object(_.jsx)(c.a, {\n                                rowKey: function (a, r) {\n                                  return r;\n                                },\n                                size: \"small\",\n                                columns: t.columns,\n                                dataSource: t.dataSource,\n                                pagination: !1,\n                              })\n                            : Object(_.jsx)(\"div\", {\n                                className: \"basicCardWrapper\",\n                                children: t.dataSource.map(function (a, r) {\n                                  return Object(_.jsx)(\n                                    \"div\",\n                                    { className: \"basicCardItem\", children: a },\n                                    r\n                                  );\n                                }),\n                              }),\n                      },\n                      a[0]\n                    );\n                  console.log(\"\\u672a\\u914d\\u7f6e\\u7684\\u6570\\u636e\\u9879\", a);\n                }),\n              }),\n            Object(_.jsx)(n.a, {\n              title: \"\\u8fdb\\u7a0b\\u65e5\\u5fd7\",\n              placement: \"right\",\n              closable: !1,\n              onClose: function () {\n                return u(!1);\n              },\n              visible: j,\n              width: 720,\n              destroyOnClose: !0,\n              children: g,\n            }),\n          ],\n        });\n      }\n      function q(a) {\n        var r = a.basic;\n        return Object(_.jsx)(d.a, {\n          children: Object(_.jsx)(\"div\", {\n            className: \"basicCardWrapper\",\n            children: r.map(function (a, r) {\n              return Object(_.jsxs)(\n                \"div\",\n                {\n                  className: \"basicCardItem\",\n                  children: [\n                    Object(_.jsxs)(\"span\", {\n                      style: { color: \"#333\" },\n                      children: [a.name_cn, \": \"],\n                    }),\n                    Object(_.jsx)(\"span\", {\n                      children: k(JSON.stringify(a.value)),\n                    }),\n                  ],\n                },\n                r\n              );\n            }),\n          }),\n        });\n      }\n      var W = function () {\n        return Object(_.jsx)(p.a, {\n          locale: i.a,\n          children: Object(_.jsx)(I, {}),\n        });\n      };\n      o.a.render(Object(_.jsx)(W, {}), document.getElementById(\"root\"));\n    },\n  },\n  [[331, 1, 2]],\n]);\n//# sourceMappingURL=main.e4ade54a.chunk.js.map\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/js/runtime-main.da7bcbe2.js",
    "content": "!function(e){function t(t){for(var n,f,l=t[0],p=t[1],i=t[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);for(a&&a(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var p=r[l];0!==o[p]&&(n=!1)}n&&(u.splice(t--,1),e=f(f.s=r[0]))}return e}var n={},o={1:0},u=[];function f(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=e,f.c=n,f.d=function(e,t,r){f.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},f.r=function(e){\"undefined\"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},f.t=function(e,t){if(1&t&&(e=f(e)),8&t)return e;if(4&t&&\"object\"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(f.r(r),Object.defineProperty(r,\"default\",{enumerable:!0,value:e}),2&t&&\"string\"!=typeof e)for(var n in e)f.d(r,n,function(t){return e[t]}.bind(null,n));return r},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,\"a\",t),t},f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},f.p=\"/\";var l=this[\"webpackJsonpomp-fontend\"]=this[\"webpackJsonpomp-fontend\"]||[],p=l.push.bind(l);l.push=t,l=l.slice();for(var i=0;i<l.length;i++)t(l[i]);var a=p;r()}([]);\n//# sourceMappingURL=runtime-main.da7bcbe2.js.map"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.02867153.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import './card-style';\n\n@tab-prefix-cls: ~'@{ant-prefix}-tabs';\n\n// Hidden content\n.tabs-hidden-content() {\n  height: 0;\n  padding: 0 !important;\n  overflow: hidden;\n  opacity: 0;\n  pointer-events: none;\n  input {\n    visibility: hidden;\n  }\n}\n\n.@{tab-prefix-cls} {\n  .reset-component;\n\n  position: relative;\n  overflow: hidden;\n  .clearfix;\n\n  &-ink-bar {\n    position: absolute;\n    bottom: 1px;\n    left: 0;\n    z-index: 1;\n    box-sizing: border-box;\n    width: 0;\n    height: 2px;\n    background-color: @tabs-ink-bar-color;\n    transform-origin: 0 0;\n  }\n\n  &-bar {\n    margin: @tabs-bar-margin;\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    outline: none;\n    transition: padding 0.3s @ease-in-out;\n  }\n\n  &-nav-container {\n    position: relative;\n    box-sizing: border-box;\n    margin-bottom: -1px;\n    overflow: hidden;\n    font-size: @tabs-title-font-size;\n    line-height: @line-height-base;\n    white-space: nowrap;\n    transition: padding 0.3s @ease-in-out;\n    .clearfix;\n\n    &-scrolling {\n      padding-right: @tabs-scrolling-size;\n      padding-left: @tabs-scrolling-size;\n    }\n  }\n\n  // https://github.com/ant-design/ant-design/issues/9104\n  &-bottom &-bottom-bar {\n    margin-top: 16px;\n    margin-bottom: 0;\n    border-top: @border-width-base @border-style-base @border-color-split;\n    border-bottom: none;\n  }\n\n  &-bottom &-bottom-bar &-ink-bar {\n    top: 1px;\n    bottom: auto;\n  }\n\n  &-bottom &-bottom-bar &-nav-container {\n    margin-top: -1px;\n    margin-bottom: 0;\n  }\n\n  &-tab-prev,\n  &-tab-next {\n    position: absolute;\n    z-index: 2;\n    width: 0;\n    height: 100%;\n    color: @text-color-secondary;\n    text-align: center;\n    background-color: transparent;\n    border: 0;\n    cursor: pointer;\n    opacity: 0;\n    transition: width 0.3s @ease-in-out, opacity 0.3s @ease-in-out, color 0.3s @ease-in-out;\n    user-select: none;\n    pointer-events: none;\n\n    &.@{tab-prefix-cls}-tab-arrow-show {\n      width: @tabs-scrolling-size;\n      height: 100%;\n      opacity: 1;\n      pointer-events: auto;\n    }\n\n    &:hover {\n      color: @text-color;\n    }\n\n    &-icon {\n      position: absolute;\n      top: 50%;\n      left: 50%;\n      font-weight: bold;\n      font-style: normal;\n      font-variant: normal;\n      line-height: inherit;\n      text-align: center;\n      text-transform: none;\n      transform: translate(-50%, -50%);\n\n      &-target {\n        display: block;\n        .iconfont-size-under-12px(10px);\n      }\n    }\n  }\n\n  &-tab-btn-disabled {\n    cursor: not-allowed;\n    &,\n    &:hover {\n      color: @disabled-color;\n    }\n  }\n\n  &-tab-next {\n    right: 2px;\n  }\n\n  &-tab-prev {\n    left: 0;\n    :root & {\n      filter: none;\n    }\n  }\n\n  &-nav-wrap {\n    margin-bottom: -1px;\n    overflow: hidden;\n  }\n\n  &-nav-scroll {\n    overflow: hidden;\n    white-space: nowrap;\n  }\n\n  &-nav {\n    position: relative;\n    display: inline-block;\n    box-sizing: border-box;\n    margin: 0;\n    padding-left: 0;\n    list-style: none;\n    transition: transform 0.3s @ease-in-out;\n\n    &::before,\n    &::after {\n      display: table;\n      content: ' ';\n    }\n\n    &::after {\n      clear: both;\n    }\n\n    .@{tab-prefix-cls}-tab {\n      position: relative;\n      display: inline-block;\n      box-sizing: border-box;\n      height: 100%;\n      margin: @tabs-horizontal-margin;\n      padding: @tabs-horizontal-padding;\n      text-decoration: none;\n      cursor: pointer;\n      transition: color 0.3s @ease-in-out;\n\n      &::before {\n        position: absolute;\n        top: -1px;\n        left: 0;\n        width: 100%;\n        border-top: 2px solid transparent;\n        border-radius: @border-radius-base @border-radius-base 0 0;\n        transition: all 0.3s;\n        content: '';\n        pointer-events: none;\n      }\n\n      &:last-child {\n        margin-right: 0;\n      }\n\n      &:hover {\n        color: @tabs-hover-color;\n      }\n\n      &:active {\n        color: @tabs-active-color;\n      }\n\n      .@{iconfont-css-prefix} {\n        margin-right: 8px;\n      }\n\n      &-active {\n        color: @tabs-highlight-color;\n        font-weight: 500;\n      }\n\n      &-disabled {\n        &,\n        &:hover {\n          color: @disabled-color;\n          cursor: not-allowed;\n        }\n      }\n    }\n  }\n\n  .@{tab-prefix-cls}-large-bar {\n    .@{tab-prefix-cls}-nav-container {\n      font-size: @tabs-title-font-size-lg;\n    }\n    .@{tab-prefix-cls}-tab {\n      padding: @tabs-horizontal-padding-lg;\n    }\n  }\n\n  .@{tab-prefix-cls}-small-bar {\n    .@{tab-prefix-cls}-nav-container {\n      font-size: @tabs-title-font-size-sm;\n    }\n    .@{tab-prefix-cls}-tab {\n      padding: @tabs-horizontal-padding-sm;\n    }\n  }\n\n  // Create an empty element to avoid margin collapsing\n  // https://github.com/ant-design/ant-design/issues/18103\n  &-content::before {\n    display: block;\n    overflow: hidden;\n    content: '';\n  }\n\n  // Horizontal Content\n  .@{tab-prefix-cls}-top-content,\n  .@{tab-prefix-cls}-bottom-content {\n    width: 100%;\n    > .@{tab-prefix-cls}-tabpane {\n      flex-shrink: 0;\n      width: 100%;\n      -webkit-backface-visibility: hidden;\n      opacity: 1;\n      transition: opacity 0.45s;\n    }\n\n    > .@{tab-prefix-cls}-tabpane-inactive {\n      .tabs-hidden-content();\n    }\n\n    &.@{tab-prefix-cls}-content-animated {\n      display: flex;\n      flex-direction: row;\n      transition: margin-left 0.3s @ease-in-out;\n      will-change: margin-left;\n    }\n  }\n\n  // Vertical Bar\n  .@{tab-prefix-cls}-left-bar,\n  .@{tab-prefix-cls}-right-bar {\n    height: 100%;\n    border-bottom: 0;\n\n    .@{tab-prefix-cls}-tab-arrow-show {\n      width: 100%;\n      height: @tabs-scrolling-size;\n    }\n\n    .@{tab-prefix-cls}-tab {\n      display: block;\n      float: none;\n      margin: @tabs-vertical-margin;\n      padding: @tabs-vertical-padding;\n\n      &:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    .@{tab-prefix-cls}-extra-content {\n      text-align: center;\n    }\n\n    .@{tab-prefix-cls}-nav-scroll {\n      width: auto;\n    }\n\n    .@{tab-prefix-cls}-nav-container,\n    .@{tab-prefix-cls}-nav-wrap {\n      height: 100%;\n    }\n\n    .@{tab-prefix-cls}-nav-container {\n      margin-bottom: 0;\n\n      &.@{tab-prefix-cls}-nav-container-scrolling {\n        padding: @tabs-scrolling-size 0;\n      }\n    }\n\n    .@{tab-prefix-cls}-nav-wrap {\n      margin-bottom: 0;\n    }\n\n    .@{tab-prefix-cls}-nav {\n      width: 100%;\n    }\n\n    .@{tab-prefix-cls}-ink-bar {\n      top: 0;\n      bottom: auto;\n      left: auto;\n      width: 2px;\n      height: 0;\n    }\n\n    .@{tab-prefix-cls}-tab-next {\n      right: 0;\n      bottom: 0;\n      width: 100%;\n      height: @tabs-scrolling-size;\n    }\n\n    .@{tab-prefix-cls}-tab-prev {\n      top: 0;\n      width: 100%;\n      height: @tabs-scrolling-size;\n    }\n  }\n\n  // Vertical Content\n  .@{tab-prefix-cls}-left-content,\n  .@{tab-prefix-cls}-right-content {\n    width: auto;\n    margin-top: 0 !important;\n    overflow: hidden;\n  }\n\n  // Vertical - Left\n  .@{tab-prefix-cls}-left-bar {\n    float: left;\n    margin-right: -1px;\n    margin-bottom: 0;\n    border-right: @border-width-base @border-style-base @border-color-split;\n    .@{tab-prefix-cls}-tab {\n      text-align: right;\n    }\n    .@{tab-prefix-cls}-nav-container {\n      margin-right: -1px;\n    }\n    .@{tab-prefix-cls}-nav-wrap {\n      margin-right: -1px;\n    }\n    .@{tab-prefix-cls}-ink-bar {\n      right: 1px;\n    }\n  }\n  .@{tab-prefix-cls}-left-content {\n    padding-left: 24px;\n    border-left: @border-width-base @border-style-base @border-color-split;\n  }\n\n  // Vertical - Right\n  .@{tab-prefix-cls}-right-bar {\n    float: right;\n    margin-bottom: 0;\n    margin-left: -1px;\n    border-left: @border-width-base @border-style-base @border-color-split;\n    .@{tab-prefix-cls}-nav-container {\n      margin-left: -1px;\n    }\n    .@{tab-prefix-cls}-nav-wrap {\n      margin-left: -1px;\n    }\n    .@{tab-prefix-cls}-ink-bar {\n      left: 1px;\n    }\n  }\n  .@{tab-prefix-cls}-right-content {\n    padding-right: 24px;\n    border-right: @border-width-base @border-style-base @border-color-split;\n  }\n}\n\n.@{tab-prefix-cls}-top .@{tab-prefix-cls}-ink-bar-animated,\n.@{tab-prefix-cls}-bottom .@{tab-prefix-cls}-ink-bar-animated {\n  transition: transform 0.3s @ease-in-out, width 0.2s @ease-in-out, left 0.3s @ease-in-out;\n}\n\n.@{tab-prefix-cls}-left .@{tab-prefix-cls}-ink-bar-animated,\n.@{tab-prefix-cls}-right .@{tab-prefix-cls}-ink-bar-animated {\n  transition: transform 0.3s @ease-in-out, height 0.2s @ease-in-out, top 0.3s @ease-in-out;\n}\n\n// No animation\n.tabs-no-animation() {\n  > .@{tab-prefix-cls}-content-animated {\n    margin-left: 0 !important;\n    transform: none !important;\n  }\n  > .@{tab-prefix-cls}-tabpane-inactive {\n    .tabs-hidden-content();\n  }\n}\n\n.no-flex,\n.@{tab-prefix-cls}-no-animation {\n  > .@{tab-prefix-cls}-content {\n    .tabs-no-animation();\n  }\n}\n\n.@{tab-prefix-cls}-left-content,\n.@{tab-prefix-cls}-right-content {\n  .tabs-no-animation();\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.2041a1d4.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@collapse-prefix-cls: ~'@{ant-prefix}-collapse';\n\n.@{collapse-prefix-cls} {\n  .reset-component;\n\n  background-color: @collapse-header-bg;\n  border: @border-width-base @border-style-base @border-color-base;\n  border-bottom: 0;\n  border-radius: @collapse-panel-border-radius;\n\n  & > &-item {\n    border-bottom: @border-width-base @border-style-base @border-color-base;\n\n    &:last-child {\n      &,\n      & > .@{collapse-prefix-cls}-header {\n        border-radius: 0 0 @collapse-panel-border-radius @collapse-panel-border-radius;\n      }\n    }\n\n    > .@{collapse-prefix-cls}-header {\n      position: relative;\n      padding: @collapse-header-padding;\n      padding-left: @collapse-header-padding-extra;\n      color: @heading-color;\n      line-height: 22px;\n      cursor: pointer;\n      transition: all 0.3s;\n\n      .@{collapse-prefix-cls}-arrow {\n        .iconfont-mixin();\n\n        position: absolute;\n        top: 50%;\n        left: @padding-md;\n        display: inline-block;\n        font-size: @font-size-sm;\n        transform: translateY(-50%);\n\n        & svg {\n          transition: transform 0.24s;\n        }\n      }\n\n      .@{collapse-prefix-cls}-extra {\n        float: right;\n      }\n\n      &:focus {\n        outline: none;\n      }\n    }\n\n    &.@{collapse-prefix-cls}-no-arrow {\n      > .@{collapse-prefix-cls}-header {\n        padding-left: 12px;\n      }\n    }\n  }\n\n  // Expand Icon right\n  &-icon-position-right {\n    & > .@{collapse-prefix-cls}-item {\n      > .@{collapse-prefix-cls}-header {\n        padding: @collapse-header-padding;\n        padding-right: @collapse-header-padding-extra;\n\n        .@{collapse-prefix-cls}-arrow {\n          right: @padding-md;\n          left: auto;\n        }\n      }\n    }\n  }\n\n  &-anim-active {\n    transition: height 0.2s @ease-out;\n  }\n\n  &-content {\n    overflow: hidden;\n    color: @text-color;\n    background-color: @collapse-content-bg;\n    border-top: @border-width-base @border-style-base @border-color-base;\n\n    & > &-box {\n      padding: @collapse-content-padding;\n    }\n\n    &-inactive {\n      display: none;\n    }\n  }\n\n  &-item:last-child {\n    > .@{collapse-prefix-cls}-content {\n      border-radius: 0 0 @collapse-panel-border-radius @collapse-panel-border-radius;\n    }\n  }\n\n  &-borderless {\n    background-color: @collapse-header-bg;\n    border: 0;\n  }\n\n  &-borderless > &-item {\n    border-bottom: 1px solid @border-color-base;\n  }\n\n  &-borderless > &-item:last-child,\n  &-borderless > &-item:last-child &-header {\n    border-radius: 0;\n  }\n\n  &-borderless > &-item > &-content {\n    background-color: transparent;\n    border-top: 0;\n  }\n\n  &-borderless > &-item > &-content > &-content-box {\n    padding-top: 4px;\n  }\n\n  & &-item-disabled > &-header {\n    &,\n    & > .arrow {\n      color: @disabled-color;\n      cursor: not-allowed;\n    }\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.2f186d27.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@message-prefix-cls: ~'@{ant-prefix}-message';\n\n.@{message-prefix-cls} {\n  .reset-component;\n\n  position: fixed;\n  top: 16px;\n  left: 0;\n  z-index: @zindex-message;\n  width: 100%;\n  pointer-events: none;\n\n  &-notice {\n    padding: 8px;\n    text-align: center;\n    &:first-child {\n      margin-top: -8px;\n    }\n  }\n\n  &-notice-content {\n    display: inline-block;\n    padding: @message-notice-content-padding;\n    background: @component-background;\n    border-radius: @border-radius-base;\n    box-shadow: @shadow-2;\n    pointer-events: all;\n  }\n\n  &-success .@{iconfont-css-prefix} {\n    color: @success-color;\n  }\n\n  &-error .@{iconfont-css-prefix} {\n    color: @error-color;\n  }\n\n  &-warning .@{iconfont-css-prefix} {\n    color: @warning-color;\n  }\n\n  &-info .@{iconfont-css-prefix},\n  &-loading .@{iconfont-css-prefix} {\n    color: @info-color;\n  }\n\n  .@{iconfont-css-prefix} {\n    position: relative;\n    top: 1px;\n    margin-right: 8px;\n    font-size: @font-size-lg;\n  }\n\n  &-notice.move-up-leave.move-up-leave-active {\n    overflow: hidden;\n    animation-name: MessageMoveOut;\n    animation-duration: 0.3s;\n  }\n}\n\n@keyframes MessageMoveOut {\n  0% {\n    max-height: 150px;\n    padding: 8px;\n    opacity: 1;\n  }\n  100% {\n    max-height: 0;\n    padding: 0;\n    opacity: 0;\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.32dc937e.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import './mixin';\n\n@btn-prefix-cls: ~'@{ant-prefix}-btn';\n\n// for compatible\n@btn-ghost-color: @text-color;\n@btn-ghost-bg: transparent;\n@btn-ghost-border: @border-color-base;\n\n// Button styles\n// -----------------------------\n.@{btn-prefix-cls} {\n  // Fixing https://github.com/ant-design/ant-design/issues/12978\n  // Fixing https://github.com/ant-design/ant-design/issues/20058\n  // Fixing https://github.com/ant-design/ant-design/issues/19972\n  // Fixing https://github.com/ant-design/ant-design/issues/12978\n  // Fixing https://github.com/ant-design/ant-design/issues/18107\n  // Fixing https://github.com/ant-design/ant-design/issues/13214\n  // It is a render problem of chrome, which is only happened in the codesandbox demo\n  // 0.001px solution works and I don't why\n  line-height: @line-height-base - 0.001;\n  .btn;\n  .btn-default;\n\n  // Make sure that the target of Button's click event always be `button`\n  // Ref: https://github.com/ant-design/ant-design/issues/7034\n  > i,\n  > span {\n    display: inline-block;\n    transition: margin-left 0.3s @ease-in-out;\n    pointer-events: none;\n  }\n\n  &-primary {\n    .btn-primary;\n\n    .@{btn-prefix-cls}-group &:not(:first-child):not(:last-child) {\n      border-right-color: @btn-group-border;\n      border-left-color: @btn-group-border;\n\n      &:disabled {\n        border-color: @btn-default-border;\n      }\n    }\n\n    .@{btn-prefix-cls}-group &:first-child {\n      &:not(:last-child) {\n        border-right-color: @btn-group-border;\n        &[disabled] {\n          border-right-color: @btn-default-border;\n        }\n      }\n    }\n\n    .@{btn-prefix-cls}-group &:last-child:not(:first-child),\n    .@{btn-prefix-cls}-group & + & {\n      border-left-color: @btn-group-border;\n      &[disabled] {\n        border-left-color: @btn-default-border;\n      }\n    }\n  }\n\n  &-ghost {\n    .btn-ghost;\n  }\n\n  &-dashed {\n    .btn-dashed;\n  }\n\n  &-danger {\n    .btn-danger;\n  }\n\n  &-link {\n    .btn-link;\n  }\n\n  &-icon-only {\n    .btn-square(@btn-prefix-cls);\n\n    > i {\n      vertical-align: middle;\n    }\n  }\n\n  &-round {\n    .btn-round(@btn-prefix-cls);\n    &.@{btn-prefix-cls}-icon-only {\n      width: auto;\n    }\n  }\n\n  &-circle,\n  &-circle-outline {\n    .btn-circle(@btn-prefix-cls);\n  }\n\n  &::before {\n    position: absolute;\n    top: -1px;\n    right: -1px;\n    bottom: -1px;\n    left: -1px;\n    z-index: 1;\n    display: none;\n    background: @component-background;\n    border-radius: inherit;\n    opacity: 0.35;\n    transition: opacity 0.2s;\n    content: '';\n    pointer-events: none;\n  }\n\n  .@{iconfont-css-prefix} {\n    transition: margin-left 0.3s @ease-in-out;\n\n    // Follow icon blur under windows. Change the render.\n    // https://github.com/ant-design/ant-design/issues/13924\n    &.@{iconfont-css-prefix}-plus,\n    &.@{iconfont-css-prefix}-minus {\n      > svg {\n        shape-rendering: optimizeSpeed;\n      }\n    }\n  }\n\n  &&-loading {\n    position: relative;\n    &:not([disabled]) {\n      pointer-events: none;\n    }\n  }\n\n  &&-loading::before {\n    display: block;\n  }\n\n  &&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {\n    padding-left: 29px;\n    .@{iconfont-css-prefix}:not(:last-child) {\n      margin-left: -14px;\n    }\n  }\n\n  &-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {\n    padding-left: 24px;\n    .@{iconfont-css-prefix} {\n      margin-left: -17px;\n    }\n  }\n\n  &-group {\n    .btn-group(@btn-prefix-cls);\n  }\n\n  // http://stackoverflow.com/a/21281554/3040605\n  &:focus > span,\n  &:active > span {\n    position: relative;\n  }\n\n  // To ensure that a space will be placed between character and `Icon`.\n  > .@{iconfont-css-prefix} + span,\n  > span + .@{iconfont-css-prefix} {\n    margin-left: 8px;\n  }\n\n  &-background-ghost {\n    color: @component-background;\n    background: transparent !important;\n    border-color: @component-background;\n  }\n\n  &-background-ghost&-primary {\n    .button-variant-ghost(@btn-primary-bg);\n  }\n\n  &-background-ghost&-danger {\n    .button-variant-ghost(@btn-danger-border);\n  }\n\n  &-background-ghost&-link {\n    .button-variant-ghost(@link-color; transparent);\n\n    color: @component-background;\n  }\n\n  &-two-chinese-chars::first-letter {\n    letter-spacing: 0.34em;\n  }\n\n  &-two-chinese-chars > *:not(.@{iconfont-css-prefix}) {\n    margin-right: -0.34em;\n    letter-spacing: 0.34em;\n  }\n\n  &-block {\n    width: 100%;\n  }\n\n  // https://github.com/ant-design/ant-design/issues/12681\n  &:empty {\n    vertical-align: top;\n  }\n}\n\na.@{btn-prefix-cls} {\n  // Fixing https://github.com/ant-design/ant-design/issues/12978\n  // It is a render problem of chrome, which is only happened in the codesandbox demo\n  // 0.1px for padding-top solution works and I don't why\n  padding-top: 0.1px;\n  line-height: @btn-height-base - 2px;\n\n  &-lg {\n    line-height: @btn-height-lg - 2px;\n  }\n  &-sm {\n    line-height: @btn-height-sm - 2px;\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.383af9c4.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@icon-prefix-cls: ~'@{ant-prefix}-icon';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.51825487.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import './drawer';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.60c6e3ea.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@empty-prefix-cls: ~'@{ant-prefix}-empty';\n\n.@{empty-prefix-cls} {\n  margin: 0 8px;\n  font-size: @empty-font-size;\n  line-height: 22px;\n  text-align: center;\n\n  &-image {\n    height: 100px;\n    margin-bottom: 8px;\n\n    img {\n      height: 100%;\n    }\n\n    svg {\n      height: 100%;\n      margin: auto;\n    }\n  }\n\n  &-description {\n    margin: 0;\n  }\n\n  &-footer {\n    margin-top: 16px;\n  }\n\n  // antd internal empty style\n  &-normal {\n    margin: 32px 0;\n    color: @disabled-color;\n\n    .@{empty-prefix-cls}-image {\n      height: 40px;\n    }\n  }\n\n  &-small {\n    margin: 8px 0;\n    color: @disabled-color;\n\n    .@{empty-prefix-cls}-image {\n      height: 35px;\n    }\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.67101e84.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@table-prefix-cls: ~'@{ant-prefix}-table';\n@table-header-icon-color: #bfbfbf;\n@table-header-sort-active-bg: darken(@table-header-bg, 3%);\n@table-header-filter-active-bg: darken(@table-header-sort-active-bg, 5%);\n@table-selection-column-width: 60px;\n\n.@{table-prefix-cls}-wrapper {\n  .clearfix;\n}\n\n.@{table-prefix-cls} {\n  .reset-component;\n\n  position: relative;\n  clear: both;\n\n  &-body {\n    transition: opacity 0.3s;\n  }\n\n  &-empty &-body {\n    // https://github.com/ant-design/ant-design/issues/11135\n    overflow-x: auto !important;\n    // https://github.com/ant-design/ant-design/issues/17175\n    overflow-y: hidden !important;\n  }\n\n  // https://github.com/ant-design/ant-design/issues/17611\n  table {\n    width: 100%;\n    text-align: left;\n    border-radius: @table-border-radius-base @table-border-radius-base 0 0;\n    border-collapse: separate;\n    border-spacing: 0;\n  }\n\n  &-layout-fixed table {\n    table-layout: fixed;\n  }\n\n  &-thead > tr > th {\n    color: @table-header-color;\n    font-weight: 500;\n    text-align: left;\n    background: @table-header-bg;\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    transition: background 0.3s ease;\n\n    &[colspan]:not([colspan='1']) {\n      text-align: center;\n    }\n\n    .@{iconfont-css-prefix}-filter,\n    .@{table-prefix-cls}-filter-icon {\n      position: absolute;\n      top: 0;\n      right: 0;\n      width: 28px;\n      height: 100%;\n      color: @table-header-icon-color;\n      font-size: @font-size-sm;\n      text-align: center;\n      cursor: pointer;\n      transition: all 0.3s;\n\n      > svg {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        margin-top: -@font-size-sm / 2 + 1px;\n        margin-left: -@font-size-sm / 2;\n      }\n    }\n\n    .@{table-prefix-cls}-filter-selected.@{iconfont-css-prefix} {\n      color: @primary-color;\n    }\n\n    .@{table-prefix-cls}-column-sorter {\n      display: table-cell;\n      vertical-align: middle;\n\n      .@{table-prefix-cls}-column-sorter-inner {\n        height: 1em;\n        margin-top: 0.35em;\n        margin-left: 0.57142857em;\n        color: @table-header-icon-color;\n        line-height: 1em;\n        text-align: center;\n        transition: all 0.3s;\n\n        .@{table-prefix-cls}-column-sorter-up,\n        .@{table-prefix-cls}-column-sorter-down {\n          .iconfont-size-under-12px(11px);\n\n          display: block;\n          height: 1em;\n          line-height: 1em;\n          transition: all 0.3s;\n          &.on {\n            color: @primary-color;\n          }\n        }\n\n        &-full {\n          margin-top: -0.15em;\n\n          .@{table-prefix-cls}-column-sorter-up,\n          .@{table-prefix-cls}-column-sorter-down {\n            height: 0.5em;\n            line-height: 0.5em;\n          }\n\n          .@{table-prefix-cls}-column-sorter-down {\n            margin-top: 0.125em;\n          }\n        }\n      }\n    }\n\n    &.@{table-prefix-cls}-column-has-actions {\n      position: relative;\n      background-clip: padding-box; // For Firefox background bug, https://github.com/ant-design/ant-design/issues/12628\n      /* stylelint-disable-next-line */\n      -webkit-background-clip: border-box; // For Chrome extra space: https://github.com/ant-design/ant-design/issues/14926\n\n      &.@{table-prefix-cls}-column-has-filters {\n        // https://github.com/ant-design/ant-design/issues/12650\n        padding-right: 30px !important;\n\n        .@{iconfont-css-prefix}-filter,\n        .@{table-prefix-cls}-filter-icon {\n          &.@{table-prefix-cls}-filter-open {\n            color: @text-color-secondary;\n            background: @table-header-filter-active-bg;\n          }\n        }\n        // Very complicated styles logic but necessary\n        &:hover {\n          .@{iconfont-css-prefix}-filter,\n          .@{table-prefix-cls}-filter-icon {\n            &:hover {\n              color: @text-color-secondary;\n              background: @table-header-filter-active-bg;\n            }\n            &:active {\n              color: @text-color;\n            }\n          }\n        }\n      }\n\n      &.@{table-prefix-cls}-column-has-sorters {\n        cursor: pointer;\n        &:hover {\n          background: @table-header-sort-active-bg;\n          .@{iconfont-css-prefix}-filter,\n          .@{table-prefix-cls}-filter-icon {\n            background: @table-header-sort-active-bg;\n          }\n        }\n        &:active {\n          .@{table-prefix-cls}-column-sorter-up:not(.on),\n          .@{table-prefix-cls}-column-sorter-down:not(.on) {\n            color: @text-color-secondary;\n          }\n        }\n      }\n    }\n\n    .@{table-prefix-cls}-header-column {\n      display: inline-block;\n      max-width: 100%;\n      vertical-align: top;\n\n      .@{table-prefix-cls}-column-sorters {\n        display: table;\n\n        > .@{table-prefix-cls}-column-title {\n          display: table-cell;\n          vertical-align: middle;\n        }\n\n        > *:not(.@{table-prefix-cls}-column-sorter) {\n          position: relative;\n        }\n        &::before {\n          position: absolute;\n          top: 0;\n          right: 0;\n          bottom: 0;\n          left: 0;\n          background: transparent;\n          transition: all 0.3s;\n          content: '';\n        }\n        &:hover::before {\n          background: rgba(0, 0, 0, 0.04);\n        }\n      }\n    }\n\n    &.@{table-prefix-cls}-column-has-sorters {\n      user-select: none;\n    }\n  }\n\n  &-thead > tr:first-child > th {\n    &:first-child {\n      border-top-left-radius: @table-border-radius-base;\n    }\n\n    &:last-child {\n      border-top-right-radius: @table-border-radius-base;\n    }\n  }\n\n  &-thead > tr:not(:last-child) > th {\n    &[colspan] {\n      border-bottom: 0;\n    }\n  }\n\n  &-tbody > tr > td {\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    transition: all 0.3s, border 0s;\n  }\n\n  &-thead > tr,\n  &-tbody > tr {\n    transition: all 0.3s, height 0s;\n    &.@{table-prefix-cls}-row-hover,\n    &:hover {\n      &:not(.@{table-prefix-cls}-expanded-row):not(.@{table-prefix-cls}-row-selected) > td {\n        background: @table-row-hover-bg;\n      }\n    }\n    &.@{table-prefix-cls}-row-selected > td {\n      &.@{table-prefix-cls}-column-sort {\n        background: @table-body-selected-sort-bg;\n      }\n    }\n    &:hover {\n      &.@{table-prefix-cls}-row-selected > td {\n        background: @table-selected-row-hover-bg;\n        &.@{table-prefix-cls}-column-sort {\n          background: @table-body-selected-sort-bg;\n        }\n      }\n    }\n  }\n\n  &-thead > tr:hover {\n    background: none;\n  }\n\n  &-footer {\n    position: relative;\n    padding: @table-padding-vertical @table-padding-horizontal;\n    color: @table-footer-color;\n    background: @table-footer-bg;\n    border-top: @border-width-base @border-style-base @border-color-split;\n    border-radius: 0 0 @table-border-radius-base @table-border-radius-base;\n    &::before {\n      position: absolute;\n      top: -1px;\n      left: 0;\n      width: 100%;\n      height: 1px;\n      background: @table-footer-bg;\n      content: '';\n    }\n  }\n\n  &.@{table-prefix-cls}-bordered &-footer {\n    border: @border-width-base @border-style-base @border-color-split;\n  }\n\n  &-title {\n    position: relative;\n    top: 1px;\n    padding: @table-padding-vertical 0;\n    border-radius: @table-border-radius-base @table-border-radius-base 0 0;\n  }\n\n  &.@{table-prefix-cls}-bordered &-title {\n    padding-right: @table-padding-horizontal;\n    padding-left: @table-padding-horizontal;\n    border: @border-width-base @border-style-base @border-color-split;\n  }\n\n  &-title + &-content {\n    position: relative;\n    border-radius: @table-border-radius-base @table-border-radius-base 0 0;\n\n    .@{table-prefix-cls}-bordered & {\n      &,\n      table,\n      .@{table-prefix-cls}-thead > tr:first-child > th {\n        border-radius: 0;\n      }\n    }\n  }\n\n  // https://github.com/ant-design/ant-design/issues/4373\n  &-without-column-header &-title + &-content,\n  &-without-column-header table {\n    border-radius: 0;\n  }\n\n  // https://github.com/ant-design/ant-design/issues/14834\n  &-without-column-header&-bordered&-empty &-placeholder {\n    border-top: 1px solid @border-color-split;\n    border-radius: @border-radius-base;\n  }\n\n  &-tbody > tr.@{table-prefix-cls}-row-selected td {\n    color: @table-selected-row-color;\n    background: @table-selected-row-bg;\n  }\n\n  &-thead > tr > th.@{table-prefix-cls}-column-sort {\n    background: @table-header-sort-bg;\n  }\n\n  &-tbody > tr > td.@{table-prefix-cls}-column-sort {\n    background: @table-body-sort-bg;\n  }\n\n  &-thead > tr > th,\n  &-tbody > tr > td {\n    padding: @table-padding-vertical @table-padding-horizontal;\n    overflow-wrap: break-word;\n  }\n\n  &-expand-icon-th,\n  &-row-expand-icon-cell {\n    width: 50px;\n    min-width: 50px;\n    text-align: center;\n  }\n\n  &-header {\n    overflow: hidden;\n    background: @table-header-bg;\n  }\n\n  &-header table {\n    border-radius: @table-border-radius-base @table-border-radius-base 0 0;\n  }\n\n  &-loading {\n    position: relative;\n    .@{table-prefix-cls}-body {\n      background: @component-background;\n      opacity: 0.5;\n    }\n    .@{table-prefix-cls}-spin-holder {\n      position: absolute;\n      top: 50%;\n      left: 50%;\n      height: 20px;\n      margin-left: -30px;\n      line-height: 20px;\n    }\n    .@{table-prefix-cls}-with-pagination {\n      margin-top: -20px;\n    }\n    .@{table-prefix-cls}-without-pagination {\n      margin-top: 10px;\n    }\n  }\n\n  &-bordered {\n    .@{table-prefix-cls}-header > table,\n    .@{table-prefix-cls}-body > table,\n    .@{table-prefix-cls}-fixed-left table,\n    .@{table-prefix-cls}-fixed-right table {\n      border: @border-width-base @border-style-base @border-color-split;\n      border-right: 0;\n      border-bottom: 0;\n    }\n\n    &.@{table-prefix-cls}-empty {\n      .@{table-prefix-cls}-placeholder {\n        border-right: @border-width-base @border-style-base @border-color-split;\n        border-left: @border-width-base @border-style-base @border-color-split;\n      }\n    }\n\n    &.@{table-prefix-cls}-fixed-header {\n      .@{table-prefix-cls}-header > table {\n        border-bottom: 0;\n      }\n\n      .@{table-prefix-cls}-body > table {\n        border-top-left-radius: 0;\n        border-top-right-radius: 0;\n      }\n\n      .@{table-prefix-cls}-header + .@{table-prefix-cls}-body > table,\n      .@{table-prefix-cls}-body-inner > table {\n        border-top: 0;\n      }\n    }\n\n    .@{table-prefix-cls}-thead > tr:not(:last-child) > th {\n      border-bottom: @border-width-base @border-style-base @border-color-split;\n    }\n\n    .@{table-prefix-cls}-thead > tr > th,\n    .@{table-prefix-cls}-tbody > tr > td {\n      border-right: @border-width-base @border-style-base @border-color-split;\n    }\n  }\n\n  &-placeholder {\n    position: relative;\n    z-index: 1;\n    margin-top: -1px;\n    padding: @table-padding-vertical @table-padding-horizontal;\n    color: @disabled-color;\n    font-size: @font-size-base;\n    text-align: center;\n    background: @component-background;\n    border-top: @border-width-base @border-style-base @border-color-split;\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    border-radius: 0 0 @border-radius-base @border-radius-base;\n  }\n\n  &-pagination.@{ant-prefix}-pagination {\n    float: right;\n    margin: 16px 0;\n  }\n\n  &-filter-dropdown {\n    position: relative;\n    min-width: 96px;\n    margin-left: -8px;\n    background: @component-background;\n    border-radius: @border-radius-base;\n    box-shadow: @box-shadow-base;\n\n    .@{ant-prefix}-dropdown-menu {\n      // https://github.com/ant-design/ant-design/issues/4916\n      // https://github.com/ant-design/ant-design/issues/19542\n      max-height: ~'calc(100vh - 130px)';\n      overflow-x: hidden;\n      border: 0;\n      border-radius: @border-radius-base @border-radius-base 0 0;\n      box-shadow: none;\n\n      &-item > label + span {\n        padding-right: 0;\n      }\n\n      &-sub {\n        border-radius: @border-radius-base;\n        box-shadow: @box-shadow-base;\n      }\n\n      .@{ant-prefix}-dropdown-submenu-contain-selected {\n        .@{ant-prefix}-dropdown-menu-submenu-title::after {\n          color: @primary-color;\n          font-weight: bold;\n          text-shadow: 0 0 2px @primary-2;\n        }\n      }\n    }\n\n    .@{ant-prefix}-dropdown-menu-item {\n      overflow: hidden;\n    }\n\n    > .@{ant-prefix}-dropdown-menu > .@{ant-prefix}-dropdown-menu-item:last-child,\n    > .@{ant-prefix}-dropdown-menu\n      > .@{ant-prefix}-dropdown-menu-submenu:last-child\n      .@{ant-prefix}-dropdown-menu-submenu-title {\n      border-radius: 0;\n    }\n\n    &-btns {\n      padding: 7px 8px;\n      overflow: hidden;\n      border-top: @border-width-base @border-style-base @border-color-split;\n    }\n\n    &-link {\n      color: @link-color;\n      &:hover {\n        color: @link-hover-color;\n      }\n      &:active {\n        color: @link-active-color;\n      }\n      &.confirm {\n        float: left;\n      }\n      &.clear {\n        float: right;\n      }\n    }\n  }\n\n  &-selection {\n    white-space: nowrap;\n\n    &-select-all-custom {\n      margin-right: 4px !important;\n    }\n\n    .@{iconfont-css-prefix}-down {\n      color: @table-header-icon-color;\n      transition: all 0.3s;\n    }\n\n    &-menu {\n      min-width: 96px;\n      margin-top: 5px;\n      margin-left: -30px;\n      background: @component-background;\n      border-radius: @border-radius-base;\n      box-shadow: @box-shadow-base;\n\n      .@{ant-prefix}-action-down {\n        color: @table-header-icon-color;\n      }\n    }\n\n    &-down {\n      display: inline-block;\n      padding: 0;\n      line-height: 1;\n      cursor: pointer;\n      &:hover .@{iconfont-css-prefix}-down {\n        color: fade(@black, 60%);\n      }\n    }\n  }\n\n  &-row {\n    &-expand-icon {\n      .operation-unit();\n\n      display: inline-block;\n      width: 17px;\n      height: 17px;\n      color: inherit;\n      line-height: 13px;\n      text-align: center;\n      background: @component-background;\n      border: @border-width-base @border-style-base @border-color-split;\n      border-radius: @border-radius-sm;\n      outline: none;\n      transition: all 0.3s;\n      user-select: none;\n\n      &:focus,\n      &:hover,\n      &:active {\n        border-color: currentColor;\n      }\n    }\n\n    &-expanded::after {\n      content: '-';\n    }\n\n    &-collapsed::after {\n      content: '+';\n    }\n\n    &-spaced {\n      visibility: hidden;\n      &::after {\n        content: '.';\n      }\n    }\n\n    &-cell-ellipsis,\n    &-cell-ellipsis .@{table-prefix-cls}-column-title {\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n\n    &-cell-ellipsis .@{table-prefix-cls}-column-title {\n      display: block;\n    }\n\n    &-cell-break-word {\n      word-wrap: break-word;\n      word-break: break-word;\n    }\n  }\n\n  tr&-expanded-row {\n    &,\n    &:hover {\n      background: @table-expanded-row-bg;\n    }\n\n    td > .@{table-prefix-cls}-wrapper {\n      margin: -@table-padding-vertical -@table-padding-horizontal -@table-padding-vertical - 1px;\n    }\n  }\n\n  .@{table-prefix-cls}-row-indent + .@{table-prefix-cls}-row-expand-icon {\n    margin-right: 8px;\n  }\n\n  &-scroll {\n    overflow: auto;\n    overflow-x: hidden;\n    table {\n      min-width: 100%;\n\n      // https://github.com/ant-design/ant-design/issues/14545\n      // https://github.com/ant-design/ant-design/issues/19491\n      .@{table-prefix-cls}-fixed-columns-in-body:not([colspan]) {\n        color: transparent;\n        & > * {\n          visibility: hidden;\n        }\n      }\n    }\n  }\n\n  &-body-inner {\n    height: 100%;\n  }\n\n  &-fixed-header > &-content > &-scroll > &-body {\n    position: relative;\n    background: @component-background;\n  }\n\n  &-fixed-header &-body-inner {\n    overflow: scroll;\n  }\n\n  &-fixed-header &-scroll &-header {\n    margin-bottom: -20px;\n    padding-bottom: 20px;\n    overflow: scroll;\n    // Workaround for additional scroll bar on the table header\n    // https://github.com/ant-design/ant-design/issues/6515#issuecomment-419634369\n    opacity: 0.9999;\n\n    &::-webkit-scrollbar {\n      border: 1px solid @border-color-split;\n      border-width: 0 0 1px 0;\n    }\n  }\n\n  &-hide-scrollbar {\n    // https://github.com/ant-design/ant-design/issues/4637\n    // https://stackoverflow.com/a/54101063\n    // https://github.com/react-component/table/pull/333\n    scrollbar-color: transparent transparent;\n    min-width: unset;\n\n    &::-webkit-scrollbar {\n      // set min width to window chrome scrollbar\n      // https://github.com/ant-design/ant-design/issues/19952#issuecomment-559367149\n      min-width: inherit;\n      background-color: transparent;\n    }\n  }\n\n  // optimize header style of borderd table after hide extra scrollbar\n  &-bordered&-fixed-header &-scroll &-header {\n    &::-webkit-scrollbar {\n      border: 1px solid @border-color-split;\n      border-width: 1px 1px 1px 0;\n    }\n    &.@{table-prefix-cls}-hide-scrollbar\n      .@{table-prefix-cls}-thead\n      > tr:only-child\n      > th:last-child {\n      border-right-color: transparent;\n    }\n  }\n\n  &-fixed-left,\n  &-fixed-right {\n    position: absolute;\n    top: 0;\n    z-index: @zindex-table-fixed;\n    overflow: hidden;\n    border-radius: 0;\n    transition: box-shadow 0.3s ease;\n    table {\n      width: auto;\n      background: @component-background;\n    }\n  }\n\n  &-fixed-header &-fixed-left &-body-outer &-fixed,\n  &-fixed-header &-fixed-right &-body-outer &-fixed {\n    border-radius: 0;\n  }\n\n  &-fixed-left {\n    left: 0;\n    box-shadow: 6px 0 6px -4px @shadow-color;\n    .@{table-prefix-cls}-header {\n      overflow-y: hidden;\n    }\n    // hide scrollbar in left fixed columns\n    .@{table-prefix-cls}-body-inner {\n      margin-right: -20px;\n      padding-right: 20px;\n    }\n    .@{table-prefix-cls}-fixed-header & .@{table-prefix-cls}-body-inner {\n      padding-right: 0;\n    }\n    &,\n    table {\n      border-radius: @table-border-radius-base 0 0 0;\n    }\n    .@{table-prefix-cls}-thead > tr > th:last-child {\n      border-top-right-radius: 0;\n    }\n  }\n\n  &-fixed-right {\n    right: 0;\n    box-shadow: -6px 0 6px -4px @shadow-color;\n    &,\n    table {\n      border-radius: 0 @table-border-radius-base 0 0;\n    }\n    // hide expand row content in right-fixed Table\n    // https://github.com/ant-design/ant-design/issues/1898\n    .@{table-prefix-cls}-expanded-row {\n      color: transparent;\n      pointer-events: none;\n    }\n    .@{table-prefix-cls}-thead > tr > th:first-child {\n      border-top-left-radius: 0;\n    }\n  }\n\n  &&-scroll-position-left &-fixed-left {\n    box-shadow: none;\n  }\n\n  &&-scroll-position-right &-fixed-right {\n    box-shadow: none;\n  }\n\n  // ========================== Row Selection ==========================\n  colgroup {\n    > col.@{table-prefix-cls}-selection-col {\n      width: @table-selection-column-width;\n    }\n  }\n\n  &-thead > tr > th.@{table-prefix-cls}-selection-column-custom {\n    .@{table-prefix-cls}-selection {\n      margin-right: -15px;\n    }\n  }\n\n  &-thead > tr > th.@{table-prefix-cls}-selection-column,\n  &-tbody > tr > td.@{table-prefix-cls}-selection-column {\n    text-align: center;\n\n    .@{ant-prefix}-radio-wrapper {\n      margin-right: 0;\n    }\n  }\n\n  &-row[class*='@{table-prefix-cls}-row-level-0'] .@{table-prefix-cls}-selection-column > span {\n    display: inline-block;\n  }\n}\n\n.@{table-prefix-cls}-filter-dropdown,\n.@{table-prefix-cls}-filter-dropdown-submenu {\n  .@{ant-prefix}-checkbox-wrapper + span {\n    padding-left: 8px;\n  }\n}\n\n/**\n* Another fix of Firefox:\n*/\n@supports (-moz-appearance: meterbar) {\n  // https://github.com/ant-design/ant-design/issues/12628\n  .@{table-prefix-cls}-thead > tr > th.@{table-prefix-cls}-column-has-actions {\n    background-clip: padding-box;\n  }\n}\n\n@import './size';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.68b48da1.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';\n\n.@{dropdown-prefix-cls} {\n  .reset-component;\n\n  position: absolute;\n  top: -9999px;\n  left: -9999px;\n  z-index: @zindex-dropdown;\n  display: block;\n\n  &::before {\n    position: absolute;\n    top: -7px;\n    right: 0;\n    bottom: -7px;\n    left: -7px;\n    z-index: -9999;\n    opacity: 0.0001;\n    content: ' ';\n  }\n\n  &-wrap {\n    position: relative;\n\n    .@{ant-prefix}-btn > .@{iconfont-css-prefix}-down {\n      .iconfont-size-under-12px(10px);\n    }\n\n    .@{iconfont-css-prefix}-down::before {\n      transition: transform 0.2s;\n    }\n  }\n\n  &-wrap-open {\n    .@{iconfont-css-prefix}-down::before {\n      transform: rotate(180deg);\n    }\n  }\n\n  &-hidden,\n  &-menu-hidden {\n    display: none;\n  }\n\n  &-menu {\n    position: relative;\n    margin: 0;\n    padding: @dropdown-edge-child-vertical-padding 0;\n    text-align: left;\n    list-style-type: none;\n    background-color: @component-background;\n    background-clip: padding-box;\n    border-radius: @border-radius-base;\n    outline: none;\n    box-shadow: @box-shadow-base;\n    -webkit-transform: translate3d(0, 0, 0);\n\n    &-item-group-title {\n      padding: 5px @control-padding-horizontal;\n      color: @text-color-secondary;\n      transition: all 0.3s;\n    }\n\n    &-submenu-popup {\n      position: absolute;\n      z-index: @zindex-dropdown;\n\n      > .@{dropdown-prefix-cls}-menu {\n        transform-origin: 0 0;\n      }\n\n      ul,\n      li {\n        list-style: none;\n      }\n\n      ul {\n        margin-right: 0.3em;\n        margin-left: 0.3em;\n        padding: 0;\n      }\n    }\n\n    &-item,\n    &-submenu-title {\n      clear: both;\n      margin: 0;\n      padding: @dropdown-vertical-padding @control-padding-horizontal;\n      color: @text-color;\n      font-weight: normal;\n      font-size: @dropdown-font-size;\n      line-height: @dropdown-line-height;\n      white-space: nowrap;\n      cursor: pointer;\n      transition: all 0.3s;\n\n      > .anticon:first-child,\n      > span > .anticon:first-child {\n        min-width: 12px;\n        margin-right: 8px;\n        font-size: @font-size-sm;\n      }\n\n      > a {\n        display: block;\n        margin: -5px -@control-padding-horizontal;\n        padding: 5px @control-padding-horizontal;\n        color: @text-color;\n        transition: all 0.3s;\n      }\n\n      &:first-child {\n        & when (@dropdown-edge-child-vertical-padding = 0) {\n          border-radius: @border-radius-base @border-radius-base 0 0;\n        }\n      }\n\n      &:last-child {\n        & when (@dropdown-edge-child-vertical-padding = 0) {\n          border-radius: 0 0 @border-radius-base @border-radius-base;\n        }\n      }\n\n      &-selected,\n      &-selected > a {\n        color: @dropdown-selected-color;\n        background-color: @item-active-bg;\n      }\n\n      &:hover {\n        background-color: @item-hover-bg;\n      }\n\n      &-disabled {\n        color: @disabled-color;\n        cursor: not-allowed;\n\n        &:hover {\n          color: @disabled-color;\n          background-color: @component-background;\n          cursor: not-allowed;\n        }\n      }\n\n      &-divider {\n        height: 1px;\n        margin: 4px 0;\n        overflow: hidden;\n        line-height: 0;\n        background-color: @border-color-split;\n      }\n\n      .@{dropdown-prefix-cls}-menu-submenu-arrow {\n        position: absolute;\n        right: @padding-xs;\n        &-icon {\n          color: @text-color-secondary;\n          font-style: normal;\n          .iconfont-size-under-12px(10px);\n        }\n      }\n    }\n\n    &-item-group-list {\n      margin: 0 8px;\n      padding: 0;\n      list-style: none;\n    }\n\n    &-submenu-title {\n      padding-right: 26px;\n    }\n\n    &-submenu-vertical {\n      position: relative;\n    }\n\n    &-submenu-vertical > & {\n      position: absolute;\n      top: 0;\n      left: 100%;\n      min-width: 100%;\n      margin-left: 4px;\n      transform-origin: 0 0;\n    }\n\n    &-submenu&-submenu-disabled .@{dropdown-prefix-cls}-menu-submenu-title {\n      &,\n      .@{dropdown-prefix-cls}-menu-submenu-arrow-icon {\n        color: @disabled-color;\n        background-color: @component-background;\n        cursor: not-allowed;\n      }\n    }\n\n    // https://github.com/ant-design/ant-design/issues/19264\n    &-submenu-selected &-submenu-title {\n      color: @primary-color;\n    }\n  }\n\n  &.slide-down-enter.slide-down-enter-active&-placement-bottomLeft,\n  &.slide-down-appear.slide-down-appear-active&-placement-bottomLeft,\n  &.slide-down-enter.slide-down-enter-active&-placement-bottomCenter,\n  &.slide-down-appear.slide-down-appear-active&-placement-bottomCenter,\n  &.slide-down-enter.slide-down-enter-active&-placement-bottomRight,\n  &.slide-down-appear.slide-down-appear-active&-placement-bottomRight {\n    animation-name: antSlideUpIn;\n  }\n\n  &.slide-up-enter.slide-up-enter-active&-placement-topLeft,\n  &.slide-up-appear.slide-up-appear-active&-placement-topLeft,\n  &.slide-up-enter.slide-up-enter-active&-placement-topCenter,\n  &.slide-up-appear.slide-up-appear-active&-placement-topCenter,\n  &.slide-up-enter.slide-up-enter-active&-placement-topRight,\n  &.slide-up-appear.slide-up-appear-active&-placement-topRight {\n    animation-name: antSlideDownIn;\n  }\n\n  &.slide-down-leave.slide-down-leave-active&-placement-bottomLeft,\n  &.slide-down-leave.slide-down-leave-active&-placement-bottomCenter,\n  &.slide-down-leave.slide-down-leave-active&-placement-bottomRight {\n    animation-name: antSlideUpOut;\n  }\n\n  &.slide-up-leave.slide-up-leave-active&-placement-topLeft,\n  &.slide-up-leave.slide-up-leave-active&-placement-topCenter,\n  &.slide-up-leave.slide-up-leave-active&-placement-topRight {\n    animation-name: antSlideDownOut;\n  }\n}\n\n.@{dropdown-prefix-cls}-trigger,\n.@{dropdown-prefix-cls}-link {\n  > .@{iconfont-css-prefix}.@{iconfont-css-prefix}-down {\n    .iconfont-size-under-12px(10px);\n  }\n}\n\n.@{dropdown-prefix-cls}-button {\n  white-space: nowrap;\n\n  &.@{ant-prefix}-btn-group > .@{ant-prefix}-btn:last-child:not(:first-child) {\n    padding-right: @padding-xs;\n    padding-left: @padding-xs;\n  }\n  .@{iconfont-css-prefix}.@{iconfont-css-prefix}-down {\n    .iconfont-size-under-12px(10px);\n  }\n}\n\n// https://github.com/ant-design/ant-design/issues/4903\n.@{dropdown-prefix-cls}-menu-dark {\n  &,\n  .@{dropdown-prefix-cls}-menu {\n    background: @menu-dark-bg;\n  }\n  .@{dropdown-prefix-cls}-menu-item,\n  .@{dropdown-prefix-cls}-menu-submenu-title,\n  .@{dropdown-prefix-cls}-menu-item > a {\n    color: @text-color-secondary-dark;\n    .@{dropdown-prefix-cls}-menu-submenu-arrow::after {\n      color: @text-color-secondary-dark;\n    }\n    &:hover {\n      color: @text-color-inverse;\n      background: transparent;\n    }\n  }\n  .@{dropdown-prefix-cls}-menu-item-selected {\n    &,\n    &:hover,\n    > a {\n      color: @text-color-inverse;\n      background: @primary-color;\n    }\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.73987a8f.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import '../../input/style/mixin';\n\n@pagination-prefix-cls: ~'@{ant-prefix}-pagination';\n\n.@{pagination-prefix-cls} {\n  .reset-component;\n\n  ul,\n  ol {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  &::after {\n    display: block;\n    clear: both;\n    height: 0;\n    overflow: hidden;\n    visibility: hidden;\n    content: ' ';\n  }\n\n  &-total-text {\n    display: inline-block;\n    height: @pagination-item-size;\n    margin-right: 8px;\n    line-height: @pagination-item-size - 2px;\n    vertical-align: middle;\n  }\n\n  &-item {\n    display: inline-block;\n    min-width: @pagination-item-size;\n    height: @pagination-item-size;\n    margin-right: 8px;\n    font-family: @pagination-font-family;\n    line-height: @pagination-item-size - 2px;\n    text-align: center;\n    vertical-align: middle;\n    list-style: none;\n    background-color: @component-background;\n    border: @border-width-base @border-style-base @border-color-base;\n    border-radius: @border-radius-base;\n    outline: 0;\n    cursor: pointer;\n    user-select: none;\n\n    a {\n      display: block;\n      padding: 0 6px;\n      color: @text-color;\n      transition: none;\n\n      &:hover {\n        text-decoration: none;\n      }\n    }\n\n    &:focus,\n    &:hover {\n      border-color: @primary-color;\n      transition: all 0.3s;\n      a {\n        color: @primary-color;\n      }\n    }\n\n    &-active {\n      font-weight: @pagination-font-weight-active;\n      background: @pagination-item-bg-active;\n      border-color: @primary-color;\n\n      a {\n        color: @primary-color;\n      }\n\n      &:focus,\n      &:hover {\n        border-color: @primary-5;\n      }\n\n      &:focus a,\n      &:hover a {\n        color: @primary-5;\n      }\n    }\n  }\n\n  &-jump-prev,\n  &-jump-next {\n    outline: 0;\n    .@{pagination-prefix-cls}-item-container {\n      position: relative;\n\n      .@{pagination-prefix-cls}-item-link-icon {\n        .iconfont-size-under-12px(12px);\n\n        color: @primary-color;\n        letter-spacing: -1px;\n        opacity: 0;\n        transition: all 0.2s;\n        &-svg {\n          top: 0;\n          right: 0;\n          bottom: 0;\n          left: 0;\n          margin: auto;\n        }\n      }\n\n      .@{pagination-prefix-cls}-item-ellipsis {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n        display: block;\n        margin: auto;\n        color: @disabled-color;\n        letter-spacing: 2px;\n        text-align: center;\n        text-indent: 0.13em;\n        opacity: 1;\n        transition: all 0.2s;\n      }\n    }\n\n    &:focus,\n    &:hover {\n      .@{pagination-prefix-cls}-item-link-icon {\n        opacity: 1;\n      }\n      .@{pagination-prefix-cls}-item-ellipsis {\n        opacity: 0;\n      }\n    }\n  }\n\n  &-prev,\n  &-jump-prev,\n  &-jump-next {\n    margin-right: 8px;\n  }\n  &-prev,\n  &-next,\n  &-jump-prev,\n  &-jump-next {\n    display: inline-block;\n    min-width: @pagination-item-size;\n    height: @pagination-item-size;\n    color: @text-color;\n    font-family: @pagination-font-family;\n    line-height: @pagination-item-size;\n    text-align: center;\n    vertical-align: middle;\n    list-style: none;\n    border-radius: @border-radius-base;\n    cursor: pointer;\n    transition: all 0.3s;\n  }\n\n  &-prev,\n  &-next {\n    outline: 0;\n\n    a {\n      color: @text-color;\n      user-select: none;\n    }\n\n    &:hover a {\n      border-color: @primary-5;\n    }\n\n    .@{pagination-prefix-cls}-item-link {\n      display: block;\n      height: 100%;\n      font-size: 12px;\n      text-align: center;\n      background-color: @component-background;\n      border: @border-width-base @border-style-base @border-color-base;\n      border-radius: @border-radius-base;\n      outline: none;\n      transition: all 0.3s;\n    }\n\n    &:focus .@{pagination-prefix-cls}-item-link,\n    &:hover .@{pagination-prefix-cls}-item-link {\n      color: @primary-color;\n      border-color: @primary-color;\n    }\n  }\n\n  &-disabled {\n    &,\n    &:hover,\n    &:focus {\n      cursor: not-allowed;\n      a,\n      .@{pagination-prefix-cls}-item-link {\n        color: @disabled-color;\n        border-color: @border-color-base;\n        cursor: not-allowed;\n      }\n    }\n  }\n\n  &-slash {\n    margin: 0 10px 0 5px;\n  }\n\n  &-options {\n    display: inline-block;\n    margin-left: 16px;\n    vertical-align: middle;\n\n    &-size-changer.@{ant-prefix}-select {\n      display: inline-block;\n      width: auto;\n      margin-right: 8px;\n    }\n\n    &-quick-jumper {\n      display: inline-block;\n      height: @input-height-base;\n      line-height: @input-height-base;\n      vertical-align: top;\n\n      input {\n        .input;\n\n        width: 50px;\n        margin: 0 8px;\n      }\n    }\n  }\n\n  &-simple &-prev,\n  &-simple &-next {\n    height: @pagination-item-size-sm;\n    line-height: @pagination-item-size-sm;\n    vertical-align: top;\n    .@{pagination-prefix-cls}-item-link {\n      height: @pagination-item-size-sm;\n      border: 0;\n      &::after {\n        height: @pagination-item-size-sm;\n        line-height: @pagination-item-size-sm;\n      }\n    }\n  }\n\n  &-simple &-simple-pager {\n    display: inline-block;\n    height: @pagination-item-size-sm;\n    margin-right: 8px;\n\n    input {\n      box-sizing: border-box;\n      height: 100%;\n      margin-right: 8px;\n      padding: 0 6px;\n      text-align: center;\n      background-color: @component-background;\n      border: @border-width-base @border-style-base @border-color-base;\n      border-radius: @border-radius-base;\n      outline: none;\n      transition: border-color 0.3s;\n\n      &:hover {\n        border-color: @primary-color;\n      }\n    }\n  }\n\n  &.mini &-total-text,\n  &.mini &-simple-pager {\n    height: @pagination-item-size-sm;\n    line-height: @pagination-item-size-sm;\n  }\n\n  &.mini &-item {\n    min-width: @pagination-item-size-sm;\n    height: @pagination-item-size-sm;\n    margin: 0;\n    line-height: @pagination-item-size-sm - 2px;\n  }\n\n  &.mini &-item:not(&-item-active) {\n    background: transparent;\n    border-color: transparent;\n  }\n\n  &.mini &-prev,\n  &.mini &-next {\n    min-width: @pagination-item-size-sm;\n    height: @pagination-item-size-sm;\n    margin: 0;\n    line-height: @pagination-item-size-sm;\n  }\n\n  &.mini &-prev &-item-link,\n  &.mini &-next &-item-link {\n    background: transparent;\n    border-color: transparent;\n    &::after {\n      height: @pagination-item-size-sm;\n      line-height: @pagination-item-size-sm;\n    }\n  }\n\n  &.mini &-jump-prev,\n  &.mini &-jump-next {\n    height: @pagination-item-size-sm;\n    margin-right: 0;\n    line-height: @pagination-item-size-sm;\n  }\n\n  &.mini &-options {\n    margin-left: 2px;\n    &-quick-jumper {\n      height: @pagination-item-size-sm;\n      line-height: @pagination-item-size-sm;\n\n      input {\n        .input-sm;\n\n        width: 44px;\n      }\n    }\n  }\n\n  // ============================ Disabled ============================\n  &&-disabled {\n    cursor: not-allowed;\n\n    .@{pagination-prefix-cls}-item {\n      background: @disabled-bg;\n      border-color: @border-color-base;\n      cursor: not-allowed;\n\n      a {\n        color: @disabled-color;\n        background: transparent;\n        border: none;\n        cursor: not-allowed;\n      }\n\n      &-active {\n        background: darken(@disabled-bg, 10%);\n        border-color: transparent;\n        a {\n          color: #fff;\n        }\n      }\n    }\n\n    .@{pagination-prefix-cls}-item-link {\n      &,\n      &:hover,\n      &:focus {\n        color: @text-color-secondary;\n        background: @disabled-bg;\n        border-color: @border-color-base;\n        cursor: not-allowed;\n      }\n    }\n\n    .@{pagination-prefix-cls}-jump-prev,\n    .@{pagination-prefix-cls}-jump-next {\n      &:focus,\n      &:hover {\n        .@{pagination-prefix-cls}-item-link-icon {\n          opacity: 0;\n        }\n        .@{pagination-prefix-cls}-item-ellipsis {\n          opacity: 1;\n        }\n      }\n    }\n  }\n}\n\n@media only screen and (max-width: @screen-lg) {\n  .@{pagination-prefix-cls}-item {\n    &-after-jump-prev,\n    &-before-jump-next {\n      display: none;\n    }\n  }\n}\n\n@media only screen and (max-width: @screen-sm) {\n  .@{pagination-prefix-cls}-options {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.8372475c.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@tooltip-prefix-cls: ~'@{ant-prefix}-tooltip';\n\n@tooltip-arrow-shadow-width: 3px;\n\n@tooltip-arrow-rotate-width: sqrt(@tooltip-arrow-width * @tooltip-arrow-width * 2) +\n  @tooltip-arrow-shadow-width * 2;\n\n@tooltip-arrow-offset-vertical: 5px; // 8 - 3px\n@tooltip-arrow-offset-horizontal: 13px; // 16 - 3px\n\n// Base class\n.@{tooltip-prefix-cls} {\n  .reset-component;\n\n  position: absolute;\n  z-index: @zindex-tooltip;\n  display: block;\n  max-width: @tooltip-max-width;\n  visibility: visible;\n\n  &-hidden {\n    display: none;\n  }\n\n  &-placement-top,\n  &-placement-topLeft,\n  &-placement-topRight {\n    padding-bottom: @tooltip-distance;\n  }\n\n  &-placement-right,\n  &-placement-rightTop,\n  &-placement-rightBottom {\n    padding-left: @tooltip-distance;\n  }\n\n  &-placement-bottom,\n  &-placement-bottomLeft,\n  &-placement-bottomRight {\n    padding-top: @tooltip-distance;\n  }\n\n  &-placement-left,\n  &-placement-leftTop,\n  &-placement-leftBottom {\n    padding-right: @tooltip-distance;\n  }\n\n  // Wrapper for the tooltip content\n  &-inner {\n    min-width: 30px;\n    min-height: 32px;\n    padding: 6px 8px;\n    color: @tooltip-color;\n    text-align: left;\n    text-decoration: none;\n    word-wrap: break-word;\n    background-color: @tooltip-bg;\n    border-radius: @border-radius-base;\n    box-shadow: @box-shadow-base;\n  }\n\n  // Arrows\n  &-arrow {\n    position: absolute;\n    display: block;\n    width: @tooltip-arrow-rotate-width;\n    height: @tooltip-arrow-rotate-width;\n    overflow: hidden;\n    background: transparent;\n    pointer-events: none;\n\n    &::before {\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      left: 0;\n      display: block;\n      width: @tooltip-arrow-width;\n      height: @tooltip-arrow-width;\n      margin: auto;\n      background-color: @tooltip-bg;\n      content: '';\n      pointer-events: auto;\n    }\n  }\n\n  &-placement-top &-arrow,\n  &-placement-topLeft &-arrow,\n  &-placement-topRight &-arrow {\n    bottom: @tooltip-distance - @tooltip-arrow-rotate-width;\n\n    &::before {\n      box-shadow: @tooltip-arrow-shadow-width @tooltip-arrow-shadow-width 7px fade(@black, 7%);\n      transform: translateY(-@tooltip-arrow-rotate-width / 2) rotate(45deg);\n    }\n  }\n\n  &-placement-top &-arrow {\n    left: 50%;\n    transform: translateX(-50%);\n  }\n\n  &-placement-topLeft &-arrow {\n    left: @tooltip-arrow-offset-horizontal;\n  }\n\n  &-placement-topRight &-arrow {\n    right: @tooltip-arrow-offset-horizontal;\n  }\n\n  &-placement-right &-arrow,\n  &-placement-rightTop &-arrow,\n  &-placement-rightBottom &-arrow {\n    left: @tooltip-distance - @tooltip-arrow-rotate-width;\n\n    &::before {\n      box-shadow: -@tooltip-arrow-shadow-width @tooltip-arrow-shadow-width 7px fade(@black, 7%);\n      transform: translateX(@tooltip-arrow-rotate-width / 2) rotate(45deg);\n    }\n  }\n\n  &-placement-right &-arrow {\n    top: 50%;\n    transform: translateY(-50%);\n  }\n\n  &-placement-rightTop &-arrow {\n    top: @tooltip-arrow-offset-vertical;\n  }\n\n  &-placement-rightBottom &-arrow {\n    bottom: @tooltip-arrow-offset-vertical;\n  }\n\n  &-placement-left &-arrow,\n  &-placement-leftTop &-arrow,\n  &-placement-leftBottom &-arrow {\n    right: @tooltip-distance - @tooltip-arrow-rotate-width;\n\n    &::before {\n      box-shadow: @tooltip-arrow-shadow-width -@tooltip-arrow-shadow-width 7px fade(@black, 7%);\n      transform: translateX(-@tooltip-arrow-rotate-width / 2) rotate(45deg);\n    }\n  }\n\n  &-placement-left &-arrow {\n    top: 50%;\n    transform: translateY(-50%);\n  }\n\n  &-placement-leftTop &-arrow {\n    top: @tooltip-arrow-offset-vertical;\n  }\n\n  &-placement-leftBottom &-arrow {\n    bottom: @tooltip-arrow-offset-vertical;\n  }\n\n  &-placement-bottom &-arrow,\n  &-placement-bottomLeft &-arrow,\n  &-placement-bottomRight &-arrow {\n    top: @tooltip-distance - @tooltip-arrow-rotate-width;\n\n    &::before {\n      box-shadow: -@tooltip-arrow-shadow-width -@tooltip-arrow-shadow-width 7px fade(@black, 7%);\n      transform: translateY(@tooltip-arrow-rotate-width / 2) rotate(45deg);\n    }\n  }\n\n  &-placement-bottom &-arrow {\n    left: 50%;\n    transform: translateX(-50%);\n  }\n\n  &-placement-bottomLeft &-arrow {\n    left: @tooltip-arrow-offset-horizontal;\n  }\n\n  &-placement-bottomRight &-arrow {\n    right: @tooltip-arrow-offset-horizontal;\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.85c775e4.less",
    "content": "@import '../../style/themes/index';\n@import './mixin';\n\n.antCheckboxFn();\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.8c12967b.less",
    "content": "// placeholder\n@import '../../style/themes/index';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.976fe83e.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@spin-prefix-cls: ~'@{ant-prefix}-spin';\n@spin-dot-default: @text-color-secondary;\n\n.@{spin-prefix-cls} {\n  .reset-component;\n\n  position: absolute;\n  display: none;\n  color: @primary-color;\n  text-align: center;\n  vertical-align: middle;\n  opacity: 0;\n  transition: transform 0.3s @ease-in-out-circ;\n\n  &-spinning {\n    position: static;\n    display: inline-block;\n    opacity: 1;\n  }\n\n  &-nested-loading {\n    position: relative;\n    > div > .@{spin-prefix-cls} {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: 4;\n      display: block;\n      width: 100%;\n      height: 100%;\n      max-height: 400px;\n      .@{spin-prefix-cls}-dot {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        margin: -@spin-dot-size / 2;\n      }\n      .@{spin-prefix-cls}-text {\n        position: absolute;\n        top: 50%;\n        width: 100%;\n        padding-top: (@spin-dot-size - @font-size-base) / 2 + 2px;\n        text-shadow: 0 1px 2px @shadow-color-inverse;\n      }\n      &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {\n        margin-top: -@spin-dot-size / 2 - 10px;\n      }\n    }\n\n    > div > .@{spin-prefix-cls}-sm {\n      .@{spin-prefix-cls}-dot {\n        margin: -@spin-dot-size-sm / 2;\n      }\n      .@{spin-prefix-cls}-text {\n        padding-top: (@spin-dot-size-sm - @font-size-base) / 2 + 2px;\n      }\n      &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {\n        margin-top: -@spin-dot-size-sm / 2 - 10px;\n      }\n    }\n\n    > div > .@{spin-prefix-cls}-lg {\n      .@{spin-prefix-cls}-dot {\n        margin: -@spin-dot-size-lg / 2;\n      }\n      .@{spin-prefix-cls}-text {\n        padding-top: (@spin-dot-size-lg - @font-size-base) / 2 + 2px;\n      }\n      &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot {\n        margin-top: -@spin-dot-size-lg / 2 - 10px;\n      }\n    }\n  }\n\n  &-container {\n    position: relative;\n    transition: opacity 0.3s;\n\n    &::after {\n      position: absolute;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      left: 0;\n      z-index: 10;\n      display: ~'none \\9';\n      width: 100%;\n      height: 100%;\n      background: @component-background;\n      opacity: 0;\n      transition: all 0.3s;\n      content: '';\n      pointer-events: none;\n    }\n  }\n\n  &-blur {\n    clear: both;\n    overflow: hidden;\n    opacity: 0.5;\n    user-select: none;\n    pointer-events: none;\n\n    &::after {\n      opacity: 0.4;\n      pointer-events: auto;\n    }\n  }\n\n  // tip\n  // ------------------------------\n  &-tip {\n    color: @spin-dot-default;\n  }\n\n  // dots\n  // ------------------------------\n\n  &-dot {\n    position: relative;\n    display: inline-block;\n    font-size: @spin-dot-size;\n\n    .square(1em);\n\n    &-item {\n      position: absolute;\n      display: block;\n      width: 9px;\n      height: 9px;\n      background-color: @primary-color;\n      border-radius: 100%;\n      transform: scale(0.75);\n      transform-origin: 50% 50%;\n      opacity: 0.3;\n      animation: antSpinMove 1s infinite linear alternate;\n\n      &:nth-child(1) {\n        top: 0;\n        left: 0;\n      }\n      &:nth-child(2) {\n        top: 0;\n        right: 0;\n        animation-delay: 0.4s;\n      }\n      &:nth-child(3) {\n        right: 0;\n        bottom: 0;\n        animation-delay: 0.8s;\n      }\n      &:nth-child(4) {\n        bottom: 0;\n        left: 0;\n        animation-delay: 1.2s;\n      }\n    }\n\n    &-spin {\n      transform: rotate(45deg);\n      animation: antRotate 1.2s infinite linear;\n    }\n  }\n\n  // Sizes\n  // ------------------------------\n\n  // small\n  &-sm &-dot {\n    font-size: @spin-dot-size-sm;\n\n    i {\n      width: 6px;\n      height: 6px;\n    }\n  }\n\n  // large\n  &-lg &-dot {\n    font-size: @spin-dot-size-lg;\n\n    i {\n      width: 14px;\n      height: 14px;\n    }\n  }\n\n  &&-show-text &-text {\n    display: block;\n  }\n}\n\n@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {\n  /* IE10+ */\n  .@{spin-prefix-cls}-blur {\n    background: @component-background;\n    opacity: 0.5;\n  }\n}\n\n@keyframes antSpinMove {\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes antRotate {\n  to {\n    transform: rotate(405deg);\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.cae8fdaf.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import './mixin';\n\n// Grid system\n.@{ant-prefix}-row {\n  .make-row();\n\n  display: block;\n  box-sizing: border-box;\n}\n\n.@{ant-prefix}-row + .@{ant-prefix}-row::before {\n  clear: both;\n}\n\n.@{ant-prefix}-row-flex {\n  display: flex;\n  flex-flow: row wrap;\n\n  &::before,\n  &::after {\n    display: flex;\n  }\n}\n\n// x轴原点\n.@{ant-prefix}-row-flex-start {\n  justify-content: flex-start;\n}\n\n// x轴居中\n.@{ant-prefix}-row-flex-center {\n  justify-content: center;\n}\n\n// x轴反方向\n.@{ant-prefix}-row-flex-end {\n  justify-content: flex-end;\n}\n\n// x轴平分\n.@{ant-prefix}-row-flex-space-between {\n  justify-content: space-between;\n}\n\n// x轴有间隔地平分\n.@{ant-prefix}-row-flex-space-around {\n  justify-content: space-around;\n}\n\n// 顶部对齐\n.@{ant-prefix}-row-flex-top {\n  align-items: flex-start;\n}\n\n// 居中对齐\n.@{ant-prefix}-row-flex-middle {\n  align-items: center;\n}\n\n// 底部对齐\n.@{ant-prefix}-row-flex-bottom {\n  align-items: flex-end;\n}\n\n.@{ant-prefix}-col {\n  position: relative;\n  // Prevent columns from collapsing when empty\n  min-height: 1px;\n}\n\n.make-grid-columns();\n.make-grid();\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(-xs);\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n  .make-grid(-sm);\n}\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n  .make-grid(-md);\n}\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n  .make-grid(-lg);\n}\n\n// Extra Large grid\n//\n// Columns, offsets, pushes, and pulls for the full hd device range.\n\n@media (min-width: @screen-xl-min) {\n  .make-grid(-xl);\n}\n\n// Extra Extra Large grid\n//\n// Columns, offsets, pushes, and pulls for the full hd device range.\n\n@media (min-width: @screen-xxl-min) {\n  .make-grid(-xxl);\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.d15ddbc9.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@card-prefix-cls: ~'@{ant-prefix}-card';\n@card-head-height: 48px;\n@card-hover-border: fade(@black, 9%);\n@card-action-icon-size: 16px;\n\n@gradient-min: fade(@card-skeleton-bg, 20%);\n@gradient-max: fade(@card-skeleton-bg, 40%);\n\n.@{card-prefix-cls} {\n  .reset-component;\n\n  position: relative;\n  background: @card-background;\n  border-radius: @card-radius;\n  transition: all 0.3s;\n\n  &-hoverable {\n    cursor: pointer;\n    &:hover {\n      border-color: @card-hover-border;\n      box-shadow: @card-shadow;\n    }\n  }\n\n  &-bordered {\n    border: @border-width-base @border-style-base @border-color-split;\n  }\n\n  &-head {\n    min-height: @card-head-height;\n    margin-bottom: -1px; // Fix card grid overflow bug: https://gw.alipayobjects.com/zos/rmsportal/XonYxBikwpgbqIQBeuhk.png\n    padding: 0 @card-padding-base;\n    color: @card-head-color;\n    font-weight: 500;\n    font-size: @font-size-lg;\n    background: @card-head-background;\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    border-radius: @card-radius @card-radius 0 0;\n    .clearfix;\n\n    &-wrapper {\n      display: flex;\n      align-items: center;\n    }\n\n    &-title {\n      display: inline-block;\n      flex: 1;\n      padding: @card-head-padding 0;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n\n    .@{ant-prefix}-tabs {\n      clear: both;\n      margin-bottom: -17px;\n      color: @text-color;\n      font-weight: normal;\n      font-size: @font-size-base;\n\n      &-bar {\n        border-bottom: @border-width-base @border-style-base @border-color-split;\n      }\n    }\n  }\n\n  &-extra {\n    float: right;\n    // https://stackoverflow.com/a/22429853/3040605\n    margin-left: auto;\n    padding: @card-head-padding 0;\n    color: @text-color;\n    font-weight: normal;\n    font-size: @font-size-base;\n  }\n\n  &-body {\n    padding: @card-padding-base;\n    .clearfix;\n  }\n\n  &-contain-grid:not(&-loading) &-body {\n    margin: -1px 0 0 -1px;\n    padding: 0;\n  }\n\n  &-grid {\n    float: left;\n    width: 33.33%;\n    padding: @card-padding-base;\n    border: 0;\n    border-radius: 0;\n    box-shadow: 1px 0 0 0 @border-color-split, 0 1px 0 0 @border-color-split,\n      1px 1px 0 0 @border-color-split, 1px 0 0 0 @border-color-split inset,\n      0 1px 0 0 @border-color-split inset;\n    transition: all 0.3s;\n    &-hoverable {\n      &:hover {\n        position: relative;\n        z-index: 1;\n        box-shadow: @box-shadow-base;\n      }\n    }\n  }\n\n  &-contain-tabs > &-head &-head-title {\n    min-height: @card-head-height - @card-head-padding;\n    padding-bottom: 0;\n  }\n\n  &-contain-tabs > &-head &-extra {\n    padding-bottom: 0;\n  }\n\n  &-cover {\n    > * {\n      display: block;\n      width: 100%;\n    }\n    img {\n      border-radius: @card-radius @card-radius 0 0;\n    }\n  }\n\n  &-actions {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    background: @card-actions-background;\n    border-top: @border-width-base @border-style-base @border-color-split;\n    .clearfix;\n\n    & > li {\n      float: left;\n      margin: 12px 0;\n      color: @text-color-secondary;\n      text-align: center;\n\n      > span {\n        position: relative;\n        display: block;\n        min-width: 32px;\n        font-size: @font-size-base;\n        line-height: 22px;\n        cursor: pointer;\n\n        &:hover {\n          color: @primary-color;\n          transition: color 0.3s;\n        }\n\n        a:not(.@{ant-prefix}-btn),\n        > .anticon {\n          display: inline-block;\n          width: 100%;\n          color: @text-color-secondary;\n          line-height: 22px;\n          transition: color 0.3s;\n\n          &:hover {\n            color: @primary-color;\n          }\n        }\n\n        > .anticon {\n          font-size: @card-action-icon-size;\n          line-height: 22px;\n        }\n      }\n\n      &:not(:last-child) {\n        border-right: @border-width-base @border-style-base @border-color-split;\n      }\n    }\n  }\n\n  &-type-inner &-head {\n    padding: 0 @card-padding-base;\n    background: @background-color-light;\n\n    &-title {\n      padding: @card-inner-head-padding 0;\n      font-size: @font-size-base;\n    }\n  }\n\n  &-type-inner &-body {\n    padding: 16px @card-padding-base;\n  }\n\n  &-type-inner &-extra {\n    padding: @card-inner-head-padding + 1.5px 0;\n  }\n\n  &-meta {\n    margin: -4px 0;\n    .clearfix;\n\n    &-avatar {\n      float: left;\n      padding-right: 16px;\n    }\n\n    &-detail {\n      overflow: hidden;\n      > div:not(:last-child) {\n        margin-bottom: 8px;\n      }\n    }\n\n    &-title {\n      overflow: hidden;\n      color: @card-head-color;\n      font-weight: 500;\n      font-size: @font-size-lg;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n\n    &-description {\n      color: @text-color-secondary;\n    }\n  }\n\n  &-loading {\n    overflow: hidden;\n  }\n\n  &-loading &-body {\n    user-select: none;\n  }\n\n  &-loading-content {\n    p {\n      margin: 0;\n    }\n  }\n\n  &-loading-block {\n    height: 14px;\n    margin: 4px 0;\n    background: linear-gradient(90deg, @gradient-min, @gradient-max, @gradient-min);\n    background-size: 600% 600%;\n    border-radius: @card-radius;\n    animation: card-loading 1.4s ease infinite;\n  }\n}\n\n@keyframes card-loading {\n  0%,\n  100% {\n    background-position: 0 50%;\n  }\n  50% {\n    background-position: 100% 50%;\n  }\n}\n\n@import './size';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.d61ddb9a.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n\n@radio-prefix-cls: ~'@{ant-prefix}-radio';\n@radio-group-prefix-cls: ~'@{radio-prefix-cls}-group';\n@radio-inner-prefix-cls: ~'@{radio-prefix-cls}-inner';\n@radio-duration: 0.3s;\n@radio-focused-outline: 3px solid fade(@radio-dot-color, 6%);\n\n.@{radio-group-prefix-cls} {\n  .reset-component;\n\n  display: inline-block;\n}\n\n// 一般状态\n.@{radio-prefix-cls}-wrapper {\n  .reset-component;\n\n  position: relative;\n  display: inline-block;\n  margin-right: 8px;\n  white-space: nowrap;\n  cursor: pointer;\n}\n\n.@{radio-prefix-cls} {\n  .reset-component;\n\n  position: relative;\n  display: inline-block;\n  line-height: 1;\n  white-space: nowrap;\n  vertical-align: sub;\n  outline: none;\n  cursor: pointer;\n\n  .@{radio-prefix-cls}-wrapper:hover &,\n  &:hover .@{radio-inner-prefix-cls},\n  &-input:focus + .@{radio-inner-prefix-cls} {\n    border-color: @radio-dot-color;\n  }\n\n  &-input:focus + .@{radio-inner-prefix-cls} {\n    box-shadow: 0 0 0 3px fade(@radio-dot-color, 8%);\n  }\n\n  &-checked::after {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border: 1px solid @radio-dot-color;\n    border-radius: 50%;\n    visibility: hidden;\n    animation: antRadioEffect 0.36s ease-in-out;\n    animation-fill-mode: both;\n    content: '';\n  }\n\n  &:hover::after,\n  .@{radio-prefix-cls}-wrapper:hover &::after {\n    visibility: visible;\n  }\n\n  &-inner {\n    &::after {\n      @radio-dot-size: @radio-size - 8px;\n\n      position: absolute;\n      top: (@radio-size - @radio-dot-size) / 2 - 1px;\n      left: (@radio-size - @radio-dot-size) / 2 - 1px;\n      display: table;\n      width: @radio-dot-size;\n      height: @radio-dot-size;\n      background-color: @radio-dot-color;\n      border-top: 0;\n      border-left: 0;\n      border-radius: @radio-dot-size;\n      transform: scale(0);\n      opacity: 0;\n      transition: all @radio-duration @ease-in-out-circ;\n      content: ' ';\n    }\n\n    position: relative;\n    top: 0;\n    left: 0;\n    display: block;\n    width: @radio-size;\n    height: @radio-size;\n    background-color: @radio-button-bg;\n    border-color: @border-color-base;\n    border-style: solid;\n    border-width: 1px;\n    border-radius: 100px;\n    transition: all @radio-duration;\n  }\n\n  &-input {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    z-index: 1;\n    cursor: pointer;\n    opacity: 0;\n  }\n}\n\n// 选中状态\n.@{radio-prefix-cls}-checked {\n  .@{radio-inner-prefix-cls} {\n    border-color: @radio-dot-color;\n    &::after {\n      transform: scale(1);\n      opacity: 1;\n      transition: all @radio-duration @ease-in-out-circ;\n    }\n  }\n}\n\n.@{radio-prefix-cls}-disabled {\n  .@{radio-inner-prefix-cls} {\n    background-color: @input-disabled-bg;\n    border-color: @border-color-base !important;\n    cursor: not-allowed;\n    &::after {\n      background-color: fade(@black, 20%);\n    }\n  }\n\n  .@{radio-prefix-cls}-input {\n    cursor: not-allowed;\n  }\n\n  & + span {\n    color: @disabled-color;\n    cursor: not-allowed;\n  }\n}\n\nspan.@{radio-prefix-cls} + * {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n\n.@{radio-prefix-cls}-button-wrapper {\n  position: relative;\n  display: inline-block;\n  height: @btn-height-base;\n  margin: 0;\n  padding: 0 @padding-md - 1px;\n  color: @radio-button-color;\n  line-height: @btn-height-base - 2px;\n  background: @radio-button-bg;\n  border: @border-width-base @border-style-base @border-color-base;\n  // strange align fix for chrome but works\n  // https://gw.alipayobjects.com/zos/rmsportal/VFTfKXJuogBAXcvfAUWJ.gif\n  border-top-width: @border-width-base + 0.02px;\n  border-left: 0;\n  cursor: pointer;\n  transition: color 0.3s, background 0.3s, border-color 0.3s;\n\n  a {\n    color: @radio-button-color;\n  }\n\n  > .@{radio-prefix-cls}-button {\n    display: block;\n    width: 0;\n    height: 0;\n    margin-left: 0;\n  }\n\n  .@{radio-group-prefix-cls}-large & {\n    height: @input-height-lg;\n    font-size: @font-size-lg;\n    line-height: @input-height-lg - 2px;\n  }\n\n  .@{radio-group-prefix-cls}-small & {\n    height: @input-height-sm;\n    padding: 0 @control-padding-horizontal-sm - 1px;\n    line-height: @input-height-sm - 2px;\n  }\n\n  &:not(:first-child) {\n    &::before {\n      position: absolute;\n      top: 0;\n      left: -1px;\n      display: block;\n      width: 1px;\n      height: 100%;\n      background-color: @border-color-base;\n      content: '';\n    }\n  }\n  &:first-child {\n    border-left: @border-width-base @border-style-base @border-color-base;\n    border-radius: @border-radius-base 0 0 @border-radius-base;\n  }\n\n  &:last-child {\n    border-radius: 0 @border-radius-base @border-radius-base 0;\n  }\n\n  &:first-child:last-child {\n    border-radius: @border-radius-base;\n  }\n\n  &:hover {\n    position: relative;\n    color: @radio-dot-color;\n  }\n\n  &:focus-within {\n    outline: @radio-focused-outline;\n  }\n\n  .@{radio-prefix-cls}-inner,\n  input[type='checkbox'],\n  input[type='radio'] {\n    width: 0;\n    height: 0;\n    opacity: 0;\n    pointer-events: none;\n  }\n\n  &-checked:not(&-disabled) {\n    z-index: 1;\n    color: @radio-dot-color;\n    background: @radio-button-checked-bg;\n    border-color: @radio-dot-color;\n    box-shadow: -1px 0 0 0 @radio-dot-color;\n\n    &::before {\n      background-color: @radio-dot-color !important;\n      opacity: 0.1;\n    }\n\n    &:first-child {\n      border-color: @radio-dot-color;\n      box-shadow: none !important;\n    }\n\n    &:hover {\n      color: @radio-button-hover-color;\n      border-color: @radio-button-hover-color;\n      box-shadow: -1px 0 0 0 @radio-button-hover-color;\n    }\n\n    &:active {\n      color: @radio-button-active-color;\n      border-color: @radio-button-active-color;\n      box-shadow: -1px 0 0 0 @radio-button-active-color;\n    }\n\n    &:focus-within {\n      outline: @radio-focused-outline;\n    }\n  }\n\n  .@{radio-group-prefix-cls}-solid &-checked:not(&-disabled) {\n    color: @component-background;\n    background: @radio-dot-color;\n    border-color: @radio-dot-color;\n    &:hover {\n      color: @component-background;\n      background: @radio-button-hover-color;\n      border-color: @radio-button-hover-color;\n    }\n    &:active {\n      color: @component-background;\n      background: @radio-button-active-color;\n      border-color: @radio-button-active-color;\n    }\n    &:focus-within {\n      outline: @radio-focused-outline;\n    }\n  }\n\n  &-disabled {\n    color: @disabled-color;\n    background-color: @input-disabled-bg;\n    border-color: @border-color-base;\n    cursor: not-allowed;\n\n    &:first-child,\n    &:hover {\n      color: @disabled-color;\n      background-color: @input-disabled-bg;\n      border-color: @border-color-base;\n    }\n    &:first-child {\n      border-left-color: @border-color-base;\n    }\n  }\n\n  &-disabled&-checked {\n    color: @text-color-inverse;\n    background-color: tint(@black, 90%);\n    border-color: @border-color-base;\n    box-shadow: none;\n  }\n}\n\n@keyframes antRadioEffect {\n  0% {\n    transform: scale(1);\n    opacity: 0.5;\n  }\n  100% {\n    transform: scale(1.6);\n    opacity: 0;\n  }\n}\n\n// Firefox hack\n@supports (-moz-appearance: meterbar) and (background-blend-mode: difference, normal) {\n  .@{radio-prefix-cls} {\n    vertical-align: text-bottom;\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.e1e14bcc.less",
    "content": "@import '../../style/themes/index';\n@import '../../style/mixins/index';\n@import '../../input/style/mixin';\n\n@select-prefix-cls: ~'@{ant-prefix}-select';\n\n.selection__clear() {\n  position: absolute;\n  top: 50%;\n  right: @control-padding-horizontal - 1px;\n  z-index: 1;\n  display: inline-block;\n  width: 12px;\n  height: 12px;\n  margin-top: -6px;\n  color: @disabled-color;\n  font-size: @font-size-sm;\n  font-style: normal;\n  line-height: 12px;\n  text-align: center;\n  text-transform: none;\n  background: @component-background;\n  cursor: pointer;\n  opacity: 0;\n  transition: color 0.3s ease, opacity 0.15s ease;\n  text-rendering: auto;\n  &::before {\n    display: block;\n  }\n  &:hover {\n    color: @text-color-secondary;\n  }\n}\n\n.@{select-prefix-cls} {\n  .reset-component;\n\n  position: relative;\n  display: inline-block;\n  outline: 0;\n\n  ul,\n  ol {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  > ul > li > a {\n    padding: 0;\n    background-color: @component-background;\n  }\n\n  // arrow\n  &-arrow {\n    .iconfont-mixin();\n\n    position: absolute;\n    top: 50%;\n    right: @control-padding-horizontal - 1px;\n    margin-top: -@font-size-sm / 2;\n    color: @disabled-color;\n    font-size: @font-size-sm;\n    line-height: 1;\n    transform-origin: 50% 50%;\n\n    & &-icon svg {\n      transition: transform 0.3s;\n    }\n  }\n\n  &-selection {\n    display: block;\n    box-sizing: border-box;\n    background-color: @select-background;\n    border: @border-width-base @border-style-base @select-border-color;\n    // strange align fix for chrome but works\n    // https://gw.alipayobjects.com/zos/rmsportal/VFTfKXJuogBAXcvfAUWJ.gif\n    border-top-width: @border-width-base + 0.02px;\n    border-radius: @border-radius-base;\n    outline: none;\n    transition: all 0.3s @ease-in-out;\n    user-select: none;\n\n    &:hover {\n      .hover;\n    }\n\n    .@{select-prefix-cls}-focused &,\n    &:focus,\n    &:active {\n      .active;\n    }\n\n    &__clear {\n      .selection__clear();\n    }\n\n    &:hover &__clear {\n      opacity: 1;\n    }\n\n    &-selected-value {\n      float: left;\n      max-width: 100%;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n  }\n\n  &-no-arrow &-selection-selected-value {\n    padding-right: 0;\n  }\n\n  &-disabled {\n    color: @disabled-color;\n  }\n\n  &-disabled &-selection {\n    background: @input-disabled-bg;\n    cursor: not-allowed;\n    &:hover,\n    &:focus,\n    &:active {\n      border-color: @select-border-color;\n      box-shadow: none;\n    }\n\n    &__clear {\n      display: none;\n      visibility: hidden;\n      pointer-events: none;\n    }\n  }\n\n  &-disabled &-selection--multiple &-selection__choice {\n    padding-right: 10px;\n    color: fade(@black, 33%);\n    background: @background-color-base;\n    &__remove {\n      display: none;\n    }\n  }\n\n  &-selection--single {\n    position: relative;\n    height: @input-height-base;\n    cursor: pointer;\n\n    .@{select-prefix-cls}-selection__rendered {\n      margin-right: 24px;\n    }\n  }\n\n  &-no-arrow {\n    .@{select-prefix-cls}-selection__rendered {\n      margin-right: @control-padding-horizontal - 1px;\n    }\n  }\n\n  &-selection__rendered {\n    position: relative;\n    display: block;\n    margin-right: @control-padding-horizontal - 1px;\n    margin-left: @control-padding-horizontal - 1px;\n    line-height: @input-height-base - 2px;\n    // https://github.com/ant-design/ant-design/issues/3481#issuecomment-254721026\n    &::after {\n      display: inline-block;\n      width: 0;\n      visibility: hidden;\n      content: '.';\n      pointer-events: none;\n    }\n  }\n\n  &-lg {\n    font-size: @font-size-lg;\n    .@{select-prefix-cls}-selection--single {\n      height: @input-height-lg;\n    }\n    .@{select-prefix-cls}-selection__rendered {\n      line-height: @input-height-lg - 2px;\n    }\n    .@{select-prefix-cls}-selection--multiple {\n      min-height: @input-height-lg;\n      .@{select-prefix-cls}-selection__rendered {\n        li {\n          height: @input-height-lg - 8px;\n          line-height: @input-height-lg - 8px;\n        }\n      }\n      .@{select-prefix-cls}-selection__clear,\n      .@{select-prefix-cls}-arrow {\n        top: @input-height-lg / 2;\n      }\n    }\n  }\n\n  &-sm {\n    .@{select-prefix-cls}-selection--single {\n      height: @input-height-sm;\n    }\n    .@{select-prefix-cls}-selection__rendered {\n      margin-left: @control-padding-horizontal-sm - 1px;\n      line-height: @input-height-sm - 2px;\n    }\n    .@{select-prefix-cls}-selection--multiple {\n      min-height: @input-height-sm;\n      .@{select-prefix-cls}-selection__rendered {\n        li {\n          height: @input-height-sm - 8px;\n          line-height: @input-height-sm - 10px;\n        }\n      }\n      .@{select-prefix-cls}-selection__clear,\n      .@{select-prefix-cls}-arrow {\n        top: @input-height-sm / 2;\n      }\n    }\n    .@{select-prefix-cls}-selection__clear,\n    .@{select-prefix-cls}-arrow {\n      right: @control-padding-horizontal-sm;\n    }\n  }\n\n  &-disabled &-selection__choice__remove {\n    color: @disabled-color;\n    cursor: default;\n    &:hover {\n      color: @disabled-color;\n    }\n  }\n\n  &-search__field__wrap {\n    position: relative;\n    display: inline-block;\n  }\n\n  &-selection__placeholder,\n  &-search__field__placeholder {\n    // for TreeSelect compatibility\n    position: absolute;\n    top: 50%;\n    right: 9px;\n    left: 0;\n    max-width: 100%;\n    height: 20px;\n    margin-top: -10px;\n    overflow: hidden;\n    color: @input-placeholder-color;\n    line-height: 20px;\n    white-space: nowrap;\n    text-align: left;\n    text-overflow: ellipsis;\n  }\n\n  &-search__field__placeholder {\n    left: @control-padding-horizontal;\n  }\n\n  &-search__field__mirror {\n    position: absolute;\n    top: 0;\n    left: 0;\n    white-space: pre;\n    opacity: 0;\n    pointer-events: none;\n  }\n\n  &-search--inline {\n    position: absolute;\n    width: 100%;\n    height: 100%;\n\n    .@{select-prefix-cls}-search__field__wrap {\n      width: 100%;\n      height: 100%;\n    }\n\n    .@{select-prefix-cls}-search__field {\n      width: 100%;\n      height: 100%;\n      font-size: 100%;\n      line-height: 1;\n      background: transparent;\n      border-width: 0;\n      border-radius: @border-radius-base;\n      outline: 0;\n    }\n\n    > i {\n      float: right;\n    }\n  }\n\n  &-selection--multiple {\n    min-height: @input-height-base;\n    padding-bottom: 3px;\n    cursor: text;\n    .clearfix;\n\n    .@{select-prefix-cls}-search--inline {\n      position: static;\n      float: left;\n      width: auto;\n      max-width: 100%;\n      padding: 0;\n      .@{select-prefix-cls}-search__field {\n        width: 0.75em;\n        max-width: 100%;\n        padding: 1px;\n      }\n    }\n\n    .@{select-prefix-cls}-selection__rendered {\n      height: auto;\n      margin-bottom: -3px;\n      margin-left: 5px;\n    }\n\n    .@{select-prefix-cls}-selection__placeholder {\n      margin-left: 6px;\n    }\n\n    > ul > li,\n    .@{select-prefix-cls}-selection__rendered > ul > li {\n      height: @input-height-base - 8px;\n      // for tree-select\n      margin-top: 3px;\n      line-height: @input-height-base - 8px - 2px;\n    }\n\n    .@{select-prefix-cls}-selection__choice {\n      position: relative;\n      float: left;\n      max-width: 99%;\n      margin-right: 4px;\n      padding: 0 20px 0 10px;\n      overflow: hidden;\n      color: @tag-default-color;\n      background-color: @tag-default-bg;\n      border: 1px solid @border-color-split;\n      border-radius: @border-radius-sm;\n      cursor: default;\n      transition: padding 0.3s @ease-in-out;\n      &__disabled {\n        padding: 0 10px;\n      }\n    }\n\n    .@{select-prefix-cls}-selection__choice__content {\n      display: inline-block;\n      max-width: 100%;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      transition: margin 0.3s @ease-in-out;\n    }\n\n    .@{select-prefix-cls}-selection__choice__remove {\n      .iconfont-mixin();\n\n      position: absolute;\n      right: 4px;\n      display: inline-block;\n      color: @text-color-secondary;\n      font-weight: bold;\n      font-size: @font-size-sm;\n      line-height: inherit;\n      cursor: pointer;\n      transition: all 0.3s;\n      .iconfont-size-under-12px(10px);\n      &:hover {\n        color: @icon-color-hover;\n      }\n    }\n\n    .@{select-prefix-cls}-selection__clear,\n    .@{select-prefix-cls}-arrow {\n      top: @input-height-base / 2;\n    }\n  }\n\n  &-allow-clear &-selection--multiple &-selection__rendered,\n  &-show-arrow &-selection--multiple &-selection__rendered {\n    margin-right: 20px; // In case that clear button will overlap content\n  }\n\n  &-open {\n    .@{select-prefix-cls}-arrow {\n      &-icon svg {\n        transform: rotate(180deg);\n      }\n    }\n    .@{select-prefix-cls}-selection {\n      .active();\n    }\n  }\n\n  &-combobox {\n    .@{select-prefix-cls}-arrow {\n      display: none;\n    }\n    .@{select-prefix-cls}-search--inline {\n      float: none;\n      width: 100%;\n      height: 100%;\n    }\n    .@{select-prefix-cls}-search__field__wrap {\n      width: 100%;\n      height: 100%;\n    }\n    .@{select-prefix-cls}-search__field {\n      position: relative;\n      z-index: 1;\n      width: 100%;\n      height: 100%;\n      box-shadow: none;\n      transition: all 0.3s @ease-in-out, height 0s;\n    }\n  }\n  &-combobox&-allow-clear &-selection:hover &-selection__rendered,\n  &-combobox&-show-arrow &-selection:hover &-selection__rendered {\n    margin-right: 20px; // In case that clear button will overlap content\n  }\n}\n\n.@{select-prefix-cls}-dropdown {\n  .reset-component;\n\n  position: absolute;\n  top: -9999px;\n  left: -9999px;\n  z-index: @zindex-dropdown;\n  box-sizing: border-box;\n  font-size: @font-size-base;\n  // Fix select render lag of long text in chrome\n  // https://github.com/ant-design/ant-design/issues/11456\n  // https://github.com/ant-design/ant-design/issues/11843\n  font-variant: initial;\n  background-color: @select-dropdown-bg;\n  border-radius: @border-radius-base;\n  outline: none;\n  box-shadow: @box-shadow-base;\n\n  &.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,\n  &.slide-up-appear.slide-up-appear-active&-placement-bottomLeft {\n    animation-name: antSlideUpIn;\n  }\n\n  &.slide-up-enter.slide-up-enter-active&-placement-topLeft,\n  &.slide-up-appear.slide-up-appear-active&-placement-topLeft {\n    animation-name: antSlideDownIn;\n  }\n\n  &.slide-up-leave.slide-up-leave-active&-placement-bottomLeft {\n    animation-name: antSlideUpOut;\n  }\n\n  &.slide-up-leave.slide-up-leave-active&-placement-topLeft {\n    animation-name: antSlideDownOut;\n  }\n\n  &-hidden {\n    display: none;\n  }\n\n  &-menu {\n    max-height: 250px;\n    margin-bottom: 0;\n    padding: @select-dropdown-edge-child-vertical-padding 0; //Change\n    padding-left: 0; // Override default ul/ol\n    overflow: auto;\n    list-style: none;\n    outline: none;\n\n    &-item-group-list {\n      margin: 0;\n      padding: 0;\n\n      > .@{select-prefix-cls}-dropdown-menu-item {\n        padding-left: 20px;\n      }\n    }\n\n    &-item-group-title {\n      height: 32px;\n      padding: 0 @control-padding-horizontal;\n      color: @text-color-secondary;\n      font-size: @font-size-sm;\n      line-height: 32px;\n    }\n\n    &-item-group-list &-item:first-child:not(:last-child),\n    &-item-group:not(:last-child) &-item-group-list &-item:last-child {\n      border-radius: 0;\n    }\n\n    &-item {\n      position: relative;\n      display: block;\n      padding: @select-dropdown-vertical-padding @control-padding-horizontal;\n      overflow: hidden;\n      color: @text-color;\n      font-weight: normal;\n      font-size: @select-dropdown-font-size;\n      line-height: @select-dropdown-line-height;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      cursor: pointer;\n      transition: background 0.3s ease;\n\n      &:hover:not(&-disabled) {\n        background-color: @item-hover-bg;\n      }\n\n      &:first-child {\n        & when (@select-dropdown-edge-child-vertical-padding = 0) {\n          border-radius: @border-radius-base @border-radius-base 0 0;\n        }\n      }\n\n      &:last-child {\n        & when (@select-dropdown-edge-child-vertical-padding = 0) {\n          border-radius: 0 0 @border-radius-base @border-radius-base;\n        }\n      }\n\n      &-selected {\n        color: @text-color;\n        font-weight: @select-item-selected-font-weight;\n        background-color: @select-item-selected-bg;\n      }\n\n      &-disabled {\n        color: @disabled-color;\n        cursor: not-allowed;\n\n        &:hover {\n          color: @disabled-color;\n          cursor: not-allowed;\n        }\n      }\n\n      &-active:not(&-disabled) {\n        background-color: @select-item-active-bg;\n      }\n\n      &-divider {\n        height: 1px;\n        margin: 1px 0;\n        overflow: hidden;\n        line-height: 0;\n        background-color: @border-color-split;\n      }\n    }\n  }\n\n  &&--multiple {\n    .@{select-prefix-cls}-dropdown-menu-item {\n      padding-right: @control-padding-horizontal + 20;\n      & .@{select-prefix-cls}-selected-icon {\n        position: absolute;\n        top: 50%;\n        right: @control-padding-horizontal;\n        color: transparent;\n        font-weight: bold;\n        font-size: 12px;\n        text-shadow: 0 0.1px 0, 0.1px 0 0, 0 -0.1px 0, -0.1px 0;\n        transform: translateY(-50%);\n        transition: all 0.2s;\n      }\n\n      &:hover .@{select-prefix-cls}-selected-icon {\n        color: fade(@black, 87%);\n      }\n\n      &-disabled .@{select-prefix-cls}-selected-icon {\n        display: none;\n      }\n\n      &-selected .@{select-prefix-cls}-selected-icon,\n      &-selected:hover .@{select-prefix-cls}-selected-icon {\n        display: inline-block;\n        color: @primary-color;\n      }\n    }\n  }\n\n  // Patch for popup adjust\n  // https://github.com/ant-design/ant-design/issues/14422\n  &--empty&--multiple &-menu-item {\n    padding-right: @control-padding-horizontal;\n  }\n\n  &-container-open,\n  &-open {\n    .@{select-prefix-cls}-dropdown {\n      display: block;\n    }\n  }\n}\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.e90871b5.less",
    "content": "@import './themes/index';\n@import './core/index';\n"
  },
  {
    "path": "package_hub/template/inspection_html/static/media/index.module.b57695f6.less",
    "content": "._bigfontSize{\n    font-size: 14px;\n  }\n\n  .listButton {\n    display: flex;\n    justify-content: center;\n    color: #1890ff;\n    cursor: pointer;\n  \n    & > div:not(:last-child) {\n      margin-right: 10px;\n    }\n  }"
  },
  {
    "path": "package_hub/template/template.md",
    "content": "# OMP 社区版-应用商店发布说明文档\n\n[TOC]\n\n## 1. 组件包 规范\n\n### 1.1. 目录结构\n\n**以mysql为例：**\n\n```shell\n$ tree ./mysql\n./mysql\n├── mysql.svg\t\t\t\t\t\t# 平台展示组件图标，请使用 “组件名称.svg ” 命名\n├── mysql_binary\t\t\t\t# 组件包程序目录\n├── mysql.yaml\t\t\t\t\t# 组件配置文件, 记录该组件安装信息,请使用 “组件名称.yaml” 命名\n└── scripts\t\t\t\t\t\t\t# 组件的安装、启动等控制脚本\n│   ├── Core.py\n│   ├── bash\n│   ├── init.py\n│   ├── install.py\n│   ├── mysql\n│   ├── mysql_backup.sh\n│   └── sql\n```\n\n**备注:**\n\n1. 组件图标请使用svg格式图片，如不添加会显示平台缺省图标\n2. 确保包名称(mysql)、包配置文件(mysql.yaml) 、包图标(mysql.svg) 名称统一, 上传安装包时，平台会校验 “包名.yaml”文件\n3. 确保安装包解压后是一个整体目录\n\n### 1.2. 发布包命名规范\n\n请使用 `{packageName}-{packageVersion}-{others}-{packageMD5}.tar.gz`  格式进行打包命名\n\n> packageName:   安装包名称，建议字符:  `英文`  `数字`   `_`\n> packageVersion: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n> others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n> packageMD5:  安装包MD5 值\n> 例如：`mysql-5.7.31-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz`\n\n```shell\n$ tar czf mysql-5.7.31.tar.gz mysql\n$ md5sum mysql-5.7.31.tar.gz\n8e955b24fefe7061eb79cfc61a9a02a1\n$ mv mysql-5.7.31.tar.gz mysql-5.7.31-8e955b24fefe7061eb79cfc61a9a02a1.tar.gz\n```\n\n### 1.3.  配置文件yaml说明\n\n保留KEY值说明:\n\n| KEY          | 说明         | 备注                                   |\n| ------------ | ------------ | -------------------------------------- |\n| service_port | 服务端口     | 供其他程序连接的端口号                 |\n| base_dir     | 应用安装目录 |                                        |\n| log_dir      | 应用日志目录 | 服务的日志采集会采集该目录下*.log 文件 |\n| username     | 用户名       |                                        |\n| password     | 密码         |                                        |\n\n```yaml\n# 类型 (必填)：\n# - product 产品\n  # - service 服务\n  # - upgrade 升级\n# - component 组件\nkind: component\n# 名称:  上传后显示的名称，请确保相同组件名称一致\nname: mysql\n# 版本:  上传后显示的版本，支持： 数字、字母、- 、.\nversion: 5.7.31\n# 描述 长度256字符，请针对组件书写贴切的描述文字\ndescription: \"MySQL是一个关系型数据库管理系统，由瑞典MySQL AB 公司开发，属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一，在 WEB 应用方面，MySQL是最好的 RDBMS (Relational Database Management System，关系数据库管理系统) 应用软件之一。\"\n# 标签 ，请针对组件功能设置准确标签\nlabels:\n  - 数据库\n# 自动启动 true/flase，安装完后是否需要启动服务\nauto_launch: false\n# 是否为基础环境组件，如 jdk,  该类组件以基础环境方式安装\nbase_env: flase\n# 监控相关\nmonitor:\n  # 监控进程名称\n  process_name:  \"mysqld\"\n  # 监控端口号，请根据 ports 中的变量设置\n  metric_port: {service_port}\n# 定义服务使用的端口号\nports:\n    # 名称，在表单中显示的标题\n  - name: 服务端口\n    # 协议\n    protocol: TCP\n    # 关键词，传入到 安装脚本中\n    key: service_port  # service_port 为保留关键词，表示 为 提供服务的端口\n    # 服务默认端口\n    default: 3306\n# 部署方式\ndeploy:\n  single:\n    - name: 单实例\n      key: single\n  complex:\n    - name: 主从模式\n      key: master_slave\n      nodes:\n        start: 2    # 初始节点数量\n        step: 1     # 增加节点步长\n# 依赖项 选填\ndependencies:\n# 资源需求\nresources:\n  cpu: 1000m\n  memory: 500m\n# 脚本所需变量参数 选填\ninstall:\n  - name: \"安装目录\"\n    key: base_dir\n    default: \"{data_path}/mysql\"\n  - name: \"数据目录\"\n    key: data_dir\n    default: \"{data_path}/mysql/data\"\n  - name: \"日志目录\"\n    key: log_dir\n    default: \"{data_path}/mysql/log\"\n  - name: \"用户名\"\n    key: username\n    default: root\n  - name: \"密码\"\n    key: password\n    default: \"123456\"\n# 程序控制脚本相对路径\ncontrol:\n  start: \"./bin/start.sh\"\n  stop: \"./bin/stop.sh\"\n  restart: \"./bin/restart.sh\"\n  reload: \"./bin/reload.sh\"\n  install: \"./scripts/install.sh\"\n  init:  \"./scripts/init.sh\"\n```\n\n\n\n## 2. 应用服务包规范\n\n### 2.1. 目录结构\n\n```shell\n$ tree prod\nprod\n├── prod.svg  # 应用 图标svg\n├── prod\n│   └── service_name.yaml # 服务yaml配置文件\n├── service_name-2.303.2-5d1ac8ce87323fc399506d1335ae5c98.tar.gz  # 服务压缩包\n└── prod.yaml\t# 应用yaml配置文件\n\n1 directory, 3 files\n```\n\n### 2.2. 包命名规范\n\n请使用 `{packageName}-{packageVersion}-{others}-{packageMD5}.tar.gz`  格式进行打包命名\n\n> packageName:   安装包名称，建议字符:  `英文`  `数字`   `_`\n> packageVersion: 安装包版本，建议字符:  `英文` `数字` `_`   `.`\n> others:  其他信息，建议字符:  `英文` `数字` `_`   `.`\n> packageMD5:  安装包MD5 值\n> 例如：`prod-2.303.2-5d1ac8ce87323fc399506d1335ae5c98.tar.gz`\n\n### 2.3. 配置文件yaml说明\n\n#### 2.3.1. 应用包yaml说明\n\n```yaml\nkind: product     # 类型\nname: prod     # 名称\nversion: 2.303.2  # 版本\ndescription: \"应用详细描述信息\"\nlabels:          # 标签\n  - 小程序服务\n# 只能依赖 product 类型\ndependencies:\nservice:  #  指定服务，按照安装启动顺序\n  - name: service_name\n    version: 2.303.2\n```\n\n#### 2.3.2. 服务包yaml说明\n\n```yaml\n# 类型 (必填)：\n# - product 产品\n  # - service 服务\n  # - upgrade 升级\n# - component 组件\nkind: service\n# 名称\nname: service_name\n# 版本\nversion: 2.303.2\n# 描述\ndescription: \"服务描述内容...\"\n# 自动自动\nauto_launch: true\nbase_env: false\n# 进程名称，监控该名称\nmonitor:\n  process_name: \"service_name\"\n  metrics_port: {service_port}\n# 使用端口号\nports:\n    # 名称\n  - name: 服务端口\n    # 协议\n    protocol: TCP\n    # 关键词，传入到 install脚本\n    key: service_port\n    # 端口\n    default: 8080\n# 部署方式\n# 依赖项 选填\ndependencies:\n  - name: jdk\n    version: 8u211\n# 资源需求\nresources:\n  cpu: 1000m\n  memory: 2000m\n# 脚本所需变量参数 选填\ninstall:\n  - name: \"安装目录\"\n    key: base_dir\n    default: \"{data_path}/service_path\"\n  - name: \"日志目录\"\n    key: log_dir\n    default: \"{data_path}/service_path/logs\"\n# 程序控制脚本路径\ncontrol:\n  start: \"./bin/start.sh\"\n  stop: \"./bin/stop.sh\"\n  restart: \"./bin/restart.sh\"\n  reload: \"./bin/reload.sh\"\n  install: \"./scripts/install.sh\"\n  init:  \"./scripts/init.sh\"\n```\n\n"
  },
  {
    "path": "package_hub/tmp_end_verified/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/tool/download_data/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/tool/folder/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/tool/tar/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/tool/upload_data/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/tool/verify_tar/.gitkeep",
    "content": ""
  },
  {
    "path": "package_hub/verified/.gitkeep",
    "content": ""
  },
  {
    "path": "salt/master",
    "content": "interface: 0.0.0.0\npublish_port: 19004\nret_port: 19005\nuser: root\nenable_ssh_minions: False\npresence_events: True\nauto_accept: True\ntimeout: 30\nroot_dir: /data/omp/data/salt\nconf_file: /data/omp/config/salt/master\nfile_roots:\n  base:\n    - /data/omp/package_hub\nfile_recv: True\nfile_recv_max_size: 524288\n\n"
  },
  {
    "path": "salt/minion",
    "content": "master: ${MASTER_IP}\nmaster_port: ${MASTER_PORT}\nuser: ${USER}\nid: ${AGENT_ID}\nroot_dir: ${AGENT_DIR}/data/salt\nconf_file: ${AGENT_DIR}/config/salt/minion\n"
  },
  {
    "path": "salt/minion.d/_schedule.conf",
    "content": "schedule:\n  __mine_interval: {enabled: true, function: mine.update, jid_include: true, maxrunning: 2,\n    minutes: 60, return_job: false, run_on_start: true}\n"
  },
  {
    "path": "salt/minion.template",
    "content": "master: ${MASTER_IP}\nmaster_port: ${MASTER_PORT}\nuser: ${USER}\nid: ${AGENT_ID}\nroot_dir: ${AGENT_DIR}/data/salt\nconf_file: ${AGENT_DIR}/config/salt/minion\n"
  },
  {
    "path": "scripts/cmd_manager",
    "content": "#!/bin/bash\n\nHELP_MSG=\"\n此脚本具体使用命令如下:\\n\n1. 删除已纳管的主机及服务操作:\\n\n\\t bash cmd_manager uninstall\\n\n2. 产品安装操作(使用excel模板进行安装)\\n\n\\t bash cmd_manager install [excel的绝对路径(不填写默认寻找与OMP同级目录下的deployment.xlsx)]\\n\n3. 产品的升级(仅升级单个服务)\\n\n\\t bash cmd_manager upgrade [服务安装包(如: mysql-xxxxx.tar.gz)] [服务所在ip(不填默认升级该服务所有实例)]\\n\n4. 产品的回滚(仅回滚单个服务最近一次升级记录)\\n\n\\t bash cmd_manager rollback [服务名(如: mysql)] [服务所在ip(不填默认回滚该服务所有实例)]\\n\n5. 产品的服务管理(仅支持非中间态服务（安装中，删除中，升级中，会滚中均为中间态）状态管理)\\n\n\\t bash cmd_manager service [执行动作如:（'start','stop','status'）] [执行ip:(选填)] [执行服务名: (选填)]\\n\n6. 产品上传安装包(需要将安装包上传至omp/package_hub/tmp_end_verified,建议并发数小于3)\\n\n\\t bash cmd_manager scan \\n\n7. omp自身服务升级 \\n\n\\t bash cmd_manager omp_upgrade [必填参数：升级目标路径(如:/data/omp，注意此处路径末尾无/)] [选填参数:从某个断点处升级,默认开头] \\n\n8. omp自身回滚 \\n\n\\t bash cmd_manager omp_rollback [必填参数：升级目标路径(如:/data/omp，注意此处路径末尾无/)] [选填参数:从某个断点处升级,默认开头]\n\"\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname ${CURRENT_DIR})\"\n\n# 解决openssl依赖的问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\ncd ${PROJECT_FOLDER}\n\nPYTHON3=\"${PROJECT_FOLDER}/component/env/bin/python3\"\nUNINSTALL_SERVICE_PATH=\"${PROJECT_FOLDER}/scripts/source/uninstall_services.py\"\nINSTALL_SERVICE_PATH=\"${PROJECT_FOLDER}/scripts/source/cmd_install_entrance.py\"\nUPGRADE_SERVICE_PATH=\"${PROJECT_FOLDER}/scripts/source/upgrade_service.py\"\nROLLBACK_SERVICE_PATH=\"${PROJECT_FOLDER}/scripts/source/rollback_service.py\"\nSERVICE_MANAGER_PATH=\"${PROJECT_FOLDER}/scripts/source/service_manager.py\"\nSCAN_TAR_FILE_PATH=\"${PROJECT_FOLDER}/scripts/source/scan_tar_file.py\"\nOMP_UPGRADE_FILE_PATH=\"${PROJECT_FOLDER}/scripts/source/omp_upgrade.py\"\nOMP_ROLLBACK_FILE_PATH=\"${PROJECT_FOLDER}/scripts/source/omp_rollback.py\"\n\n\nfunction help() {\n  echo -e $HELP_MSG\n}\n\nfunction install() {\n  $PYTHON3 $INSTALL_SERVICE_PATH --excel_path=$1\n}\n\nfunction upgrade() {\n  $PYTHON3 $UPGRADE_SERVICE_PATH upgrade $1\n}\n\nfunction rollback() {\n  $PYTHON3 $UPGRADE_SERVICE_PATH rollback $1\n}\n\nfunction service() {\n  $PYTHON3 $SERVICE_MANAGER_PATH $@\n}\n\nfunction scan() {\n  $PYTHON3 $SCAN_TAR_FILE_PATH\n}\n\nfunction omp_upgrade() {\n  $PYTHON3 $OMP_UPGRADE_FILE_PATH $@\n}\n\nfunction omp_rollback() {\n  $PYTHON3 $OMP_ROLLBACK_FILE_PATH $@\n}\n\n\nfunction uninstall_all_service_and_host() {\n  for i in $(seq 1 3)\n  do\n    read -p \"删除已部署的服务?确认请第${i}请再次输入delete or DELETE > \" ask\n    if [[ \"$ask\" == \"delete\" ]] || [[ \"$ask\" == \"DELETE\" ]];then\n      continue\n    fi\n    exit 1\n  done\n  $PYTHON3 $UNINSTALL_SERVICE_PATH\n  if [[ $? -ne 0 ]];then\n    echo \"卸载已部署的服务失败\"\n    exit 1\n  fi\n  echo \"卸载已部署的服务成功\"\n}\n\nif [[ $# -eq 0 ]]; then\n  # echo \"bash cmd_manager [uninstall|install|upgrade|rollback] [...]\"\n  help\nelse\n  case $1 in\n  help)\n    shift\n    help \"$@\"\n    ;;\n  uninstall)\n    shift\n    uninstall_all_service_and_host \"$@\"\n    ;;\n  install)\n    shift\n    install \"$@\"\n    ;;\n  upgrade)\n    shift\n    upgrade \"$@\"\n    ;;\n  rollback)\n    shift\n    rollback \"$@\"\n    ;;\n  service)\n    shift\n    service \"$@\"\n    ;;\n  scan)\n    shift\n    scan \"$@\"\n    ;;\n  omp_upgrade)\n    shift\n    omp_upgrade \"$@\"\n    ;;\n  omp_rollback)\n    shift\n    omp_rollback \"$@\"\n    ;;\n  *)\n    shift\n    help \"#@\"\n  esac\nfi\n"
  },
  {
    "path": "scripts/install.sh",
    "content": "#!/bin/bash\n\n# 项目初始化、安装\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname ${CURRENT_DIR})\"\n\n# 解决openssl依赖的问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nPYTHON3=\"${PROJECT_FOLDER}/component/env/bin/python3\"\nOMP_SCRIPT=\"${PROJECT_FOLDER}/scripts/omp\"\nTMP_LOG_PATH=\"${PROJECT_FOLDER}/tmp/install_omp.log\"\nTMP_CRONTAB_TXT_PATH=\"${PROJECT_FOLDER}/tmp/crontab.txt\"\n\ntest -d \"${PROJECT_FOLDER}/tmp\" || mkdir \"${PROJECT_FOLDER}/tmp\"\n\nget_local_ip() {\n  ips=$(ip a | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}' | grep -v \"127.0.0.1\")\n  k=0\n  echo \"通过命令获取本机ip如下：\"\n  for ip in $ips; do\n    ip_list[k]=$ip\n    echo \"若选择ip: $ip 请输入id: $k\"\n    (( k++ ))\n  done\n  echo \"\"\n  read -p \"请选择本机ip对应的id，如果ip不在上述输出中请输入N > \" index\n  if [[ $index == \"N\" ]] || [[ $index == \"n\" ]]; then\n    read -p \"请输入本机ip:\" local_ip\n    local_ip_grep=$(echo \"$local_ip\" | grep -oP \"([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})\")\n    if [ \"$local_ip_grep\" != \"$local_ip\" ]; then\n      echo \"ip: $local_ip 格式不正确！\"\n      exit 1\n    fi\n  elif [ -z \"$(echo $index| sed -n \"/^[0-9]\\+$/p\")\" ];then\n    echo \"输入的id: $index 非法,请重新执行命令选择！\"\n    exit 1\n  else\n    local_ip=${ip_list[$index]}\n    if [ ! -n \"$local_ip\" ]; then\n      echo \"选择的ip不存在!\"\n      exit 1\n    fi\n  fi\n\n}\n\nfunction echo_omp_url() {\n    echo \" \"\n    echo \"=========OMP平台访问信息================\"\n    echo \"平台访问地址：http://$local_ip:19001\"\n    echo \"平台默认用户：admin\"\n    echo \"平台默认密码：Yunweiguanli@OMP_123\"\n    echo \"=======================================\"\n    echo \" \"\n}\n\n# 平台端安装逻辑\nfunction install_omp() {\n  ack=\"N\"\n  while [[ \"$ack\" == \"N\" ]] || [[ \"$ack\" == \"n\" ]]; do\n    get_local_ip\n    read -p \"确认当前主机ip为【$local_ip】请输入 Y, 重新选择请输入N > \" ack\n  done\n  update_conf_path=\"${PROJECT_FOLDER}/scripts/source/update_conf.py\"\n  run_user=$(whoami)\n  $PYTHON3 $update_conf_path $local_ip $run_user\n  if [[ $? -ne 0 ]]; then\n    echo \"OMP配置更新失败\"\n    exit 1\n  fi\n  install_mysql_redis_path=\"${PROJECT_FOLDER}/scripts/source/install_mysql_redis.py\"\n  $PYTHON3 $install_mysql_redis_path\n  if [[ $? -ne 0 ]]; then\n   echo \"mysql+redis安装失败\"\n   exit 1\n  fi\n  MANAGE_PATH=\"${PROJECT_FOLDER}/omp_server/manage.py\"\n  $PYTHON3 $MANAGE_PATH migrate >> $TMP_LOG_PATH\n  UPDATE_DATA_PATH=\"${PROJECT_FOLDER}/scripts/source/update_data.py\"\n  $PYTHON3 $UPDATE_DATA_PATH\n  bash $OMP_SCRIPT all start\n}\n\nfunction check_grafana_up() {\n  CONF_PATH=\"${PROJECT_FOLDER}/config/omp.yaml\"\n  port_flag=$(cat ${CONF_PATH} |grep 'grafana: ' | tr \"grafana:\" \" \")\n  port=${port_flag// /}\n  grafana_url=\"http://127.0.0.1:${port}/proxy/v1/grafana/login\"\n  # 等待grafana在5分钟内启动成功\n  i=0\n  while [ $i -le 10 ]\n  do\n    curl -s -H \"Content-Type: application/json\" -X POST -d '{\"user\": \"admin\", \"password\": \"Yunweiguanli@OMP_123\"}' \"${grafana_url}\" |grep 'Logged in'\n    if [[ $? -eq 0 ]]; then\n      return 0\n    fi\n    sleep 30\n    if [[ $i -eq 5 ]]; then\n      bash $OMP_SCRIPT grafana start\n    fi\n    let i++\n  done\n  echo \"Grafana start failed in 300s, please check!\"\n  exit 1\n}\n\n# 监控端安装逻辑\nfunction install_monitor_server() {\n  bash $OMP_SCRIPT grafana start\n  # 确认grafana能够成功启动后，再更新grafana的数据\n  check_grafana_up\n  update_grafana_path=\"${PROJECT_FOLDER}/scripts/source/update_grafana.py\"\n  $PYTHON3 $update_grafana_path $local_ip\n  if [[ $? -ne 0 ]]; then\n    echo \"Grafana配置更新失败\"\n    exit 1\n  fi\n}\n\n# 添加omp server端保活定时任务\nfunction omp_keep_alive() {\n  crontab -l 2>/dev/null > $TMP_CRONTAB_TXT_PATH\n  echo \"*/5 * * * * bash $OMP_SCRIPT all start &>/dev/null\" >> $TMP_CRONTAB_TXT_PATH\n  oka_crontab=$(crontab -l | grep \"omp all start\")\n  if test -z \"$oka_crontab\"\n  then\n    crontab < $TMP_CRONTAB_TXT_PATH\n    echo \"omp server端保活任务添加成功\"\n  else\n    echo \"omp server端保活任务添加成功\"\n  fi\n}\n\n\ninstall_omp \"$@\"\ninstall_monitor_server \"$@\"\nomp_keep_alive\necho_omp_url\n"
  },
  {
    "path": "scripts/omp",
    "content": "#!/bin/bash\n\n# 启动停止控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname ${CURRENT_DIR})\"\n\nfunction check_user() {\n  CONF_PATH=\"${PROJECT_FOLDER}/config/omp.yaml\"\n  user_flag=$(cat ${CONF_PATH} |grep 'global_user: ')\n  user=${user_flag#*: }\n  if [ `whoami` != \"${user}\" ];then\n    echo \"You must use *** ${user} *** to execute this script!\"\n    exit 1\n  fi\n}\n\nfunction mysql() {\n  test -d $PROJECT_FOLDER/component/mysql || return 0\n  bash $PROJECT_FOLDER/component/mysql/scripts/mysql $1\n}\n\nfunction redis() {\n  test -d $PROJECT_FOLDER/component/redis || return 0\n  bash $PROJECT_FOLDER/component/redis/scripts/redis $1\n}\n\nfunction tengine() {\n  bash $PROJECT_FOLDER/scripts/source/tengine $1\n}\n\nfunction uwsgi() {\n  bash $PROJECT_FOLDER/scripts/source/uwsgi $1\n}\n\nfunction worker() {\n  bash $PROJECT_FOLDER/scripts/source/worker $1\n}\n\nfunction cron() {\n  bash $PROJECT_FOLDER/scripts/source/cron $1\n}\n\nfunction salt() {\n  bash $PROJECT_FOLDER/scripts/source/salt $1\n}\n\nfunction prometheus() {\n  bash $PROJECT_FOLDER/component/prometheus/scripts/prometheus $1\n}\n\nfunction alertmanager() {\n  bash $PROJECT_FOLDER/component/alertmanager/scripts/alertmanager $1\n}\n\nfunction grafana() {\n  bash $PROJECT_FOLDER/component/grafana/scripts/grafana $1\n}\n\nfunction loki() {\n  bash $PROJECT_FOLDER/component/loki/scripts/loki $1\n}\n\nfunction all() {\n  mysql $1\n  redis $1\n  tengine $1\n  uwsgi $1\n  worker $1\n  cron $1\n  salt $1\n  alertmanager $1\n  grafana $1\n  loki $1\n  prometheus $1\n}\n\ncheck_user\n\nif [[ $# -eq 0 ]]; then\n  echo \"bash omp [all|mysql|redis|tengine|uwsgi|worker|cron|salt|prometheus|alertmanager|grafana|loki] [status|start|stop|restart]\"\nelse\n  case $1 in\n  all)\n    shift\n    all \"$@\"\n    ;;\n  mysql)\n    shift\n    mysql \"$@\"\n    ;;\n  redis)\n    shift\n    redis \"$@\"\n    ;;\n  tengine)\n    shift\n    tengine \"$@\"\n    ;;\n  uwsgi)\n    shift\n    uwsgi \"$@\"\n    ;;\n  worker)\n    shift\n    worker \"$@\"\n    ;;\n  cron)\n    shift\n    cron \"$@\"\n    ;;\n  salt)\n    shift\n    salt \"$@\"\n    ;;\n  prometheus)\n    shift\n    prometheus \"$@\"\n    ;;\n  alertmanager)\n    shift\n    alertmanager \"$@\"\n    ;;\n  grafana)\n    shift\n    grafana \"$@\"\n    ;;\n  loki)\n    shift\n    loki \"$@\"\n    ;;\n  esac\nfi\n"
  },
  {
    "path": "scripts/source/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: __init__.py\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-15 13:59\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n"
  },
  {
    "path": "scripts/source/add_readonly_user.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: add_readonly_user\n# Create time: 2023/1/10 3:36 下午\n\nimport django\nimport os\nimport sys\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nPYTHON_PATH = os.path.join(PROJECT_DIR, \"component/env/bin/python3\")\nsys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import UserProfile\n\n\ndef add_read_only_user():\n    \"\"\"增加只读用户\"\"\"\n    read_only_username = \"omp\"\n    read_only_password = \"Yunweiguanli@OMP_123\"\n    if UserProfile.objects.filter(username=read_only_username).count() == 0:\n        UserProfile.objects.create_user(\n            username=read_only_username,\n            password=read_only_password,\n            email=\"omp@cloudwise.com\",\n            role=\"ReadOnlyUser\"\n        )\n    admin_user = UserProfile.objects.get(username=\"admin\")\n    admin_user.role = \"SuperUser\"\n    admin_user.save()\n\n\nif __name__ == '__main__':\n    add_read_only_user()\n"
  },
  {
    "path": "scripts/source/cmd_install_entrance.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: cmd_install_entrance\n# Author: jon.liu@yunzhihui.com\n# Create time: 2022-01-07 15:16\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n通过命令行安装应用、产品的主体逻辑\n# TODO 主机Agent安装错误时处理逻辑需要补充进来\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport datetime\nimport argparse\nimport time\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom rest_framework.test import APIClient\n\nfrom db_models.models import UserProfile\nfrom db_models.models import MainInstallHistory\nfrom db_models.models import DetailInstallHistory\nfrom app_store.cmd_install_utils import ReadDeploymentExcel\nfrom utils.plugin.public_utils import get_file_md5\n\nHOST_AGENT_WAIT_TIME = 60 * 10\nINFO_FRESH_SECOND = 5\n\n\nclass DjangoClient(object):\n    \"\"\" 模拟请求到Django的客户端 \"\"\"\n\n    def __init__(self):\n        \"\"\" 初始化 \"\"\"\n        user = UserProfile.objects.filter(username=\"admin\").last()\n        self.client = APIClient()\n        self.client.force_authenticate(user)\n\n    def get(self, url, data=None):\n        \"\"\"\n        通过client发送get请求\n        :param url: 请求url\n        :param data: 请求参数\n        :return:\n        \"\"\"\n        return self.client.get(url, data=data).json()\n\n    def post(self, url, data):\n        \"\"\"\n        通过client发送post请求\n        :param url: 请求url\n        :param data: 请求数据\n        :return:\n        \"\"\"\n        res = self.client.post(\n            url,\n            data=json.dumps(data),\n            content_type=\"application/json\"\n        ).json()\n        return res\n\n\nclass MainProcess(object):\n    \"\"\" 命令行主体安装执行类 \"\"\"\n\n    def __init__(self, excel_path, excel_md5_value):\n        self.excel_path = excel_path\n        self.operation_uuid = excel_md5_value\n        self.host = list()\n        self.service = dict()\n        self.client = DjangoClient()\n\n    @staticmethod\n    def print_log(msg, error=False):\n        \"\"\"\n        打印输出日志，后续会结合log进行操作\n        :param msg:\n        :param error: 是否为错误信息，打印时增加颜色显示\n        :return:\n        \"\"\"\n        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n        if error:\n            print(f\"\\033[0;31;47m{now} {msg}\\033[0m\")\n        else:\n            print(f\"{now} {msg}\")\n\n    def step_1(self):\n        \"\"\"\n        解析excel数据\n        :return:\n        \"\"\"\n        _flag, _data = ReadDeploymentExcel(\n            excel_path=self.excel_path\n        ).read_excel()\n        if not _flag:\n            return False, _data\n        self.host = _data[\"host\"]\n        self.service = _data[\"service\"]\n        return True, \"成功解析表格数据\"\n\n    def step_2(self):\n        \"\"\"\n        调用主机校验接口\n        :return:\n        \"\"\"\n        data = {\"host_list\": self.host}\n        res = self.client.post(url=\"/api/hosts/batchValidate/\", data=data)\n        if res.get(\"code\") != 0:\n            return False, res.get(\"message\")\n        error_lst = res.get(\"data\", {}).get(\"error\", [])\n        if not error_lst:\n            return True, \"success\"\n        for item in error_lst:\n            self.print_log(\n                f\"主机 [{item.get('ip')}] 校验失败 [{item.get('validate_error')}]\",\n                error=True\n            )\n        return False, \"主机数据存在异常, 请查看上述错误信息进行排查后重试\"\n\n    def step_3(self):\n        \"\"\"\n        调用服务分布校验接口\n        :return:\n        \"\"\"\n        res = self.client.post(\n            url=\"/api/appStore/deploymentPlanValidate/\",\n            data=self.service\n        )\n        if res.get(\"code\") != 0:\n            return False, res.get(\"message\")\n        error_lst = res.get(\"data\", {}).get(\"error\", [])\n        if not error_lst:\n            return True, \"success\"\n        for item in error_lst:\n            self.print_log(\n                f\"服务分布信息校验失败 [{item.get('validate_error')}]\",\n                error=True\n            )\n        return False, \"服务分布信息校验失败, 请查看上述错误信息进行排查后重试\"\n\n    def step_4(self):\n        \"\"\"\n        调用主机批量添加接口\n        :return:\n        \"\"\"\n        data = {\"host_list\": self.host}\n        res = self.client.post(url=\"/api/hosts/batchImport/\", data=data)\n        if res.get(\"code\") != 0:\n            return False, res.get(\"message\")\n        return True, \"批量添加主机成功\"\n\n    def step_5(self):\n        \"\"\"\n        调用服务入库接口\n        :return:\n        \"\"\"\n        instance_info_ls = list()\n        for item in self.host:\n            instance_info_ls.append({\n                \"instance_name\": item.get(\"instance_name\"),\n                \"run_user\": item.get(\"run_user\")\n            })\n        data = {\n            \"instance_info_ls\": instance_info_ls,\n            \"service_data_ls\": self.service.get(\"service_data_ls\"),\n            \"operation_uuid\": self.operation_uuid\n        }\n        res = self.client.post(\n            url=\"/api/appStore/deploymentPlanImport/\",\n            data=data\n        )\n        if res.get(\"code\") != 0:\n            return False, res.get(\"message\")\n        return True, \"服务分布信息入库成功\"\n\n    def step_6(self):\n        \"\"\"\n        调用Agent状态监控接口\n        :return:\n        \"\"\"\n        data = {\"ip_list\": [el[\"ip\"] for el in self.host]}\n        step_count = 0\n        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n        while True:\n            step_count += 1\n            _msg = \".\" * step_count\n            if step_count == 1:\n                print(f\"{now} 等待Agent安装成功{_msg}\", end=\"\", flush=True)\n            else:\n                print(\".\", end=\"\", flush=True)\n            res = self.client.post(\n                url=\"/api/hosts/hostsAgentStatus/\",\n                data=data\n            )\n            if res.get(\"code\") != 0:\n                return False, res.get(\"message\")\n            agent_status = res.get(\"data\")\n            if agent_status is False:\n                time.sleep(INFO_FRESH_SECOND)\n                continue\n            break\n        print()\n        if agent_status is True:\n            return True, \"主机Agent状态已恢复正常\"\n        return False, f\"主机Agent状态经{HOST_AGENT_WAIT_TIME}s后仍处于不可用状态\"\n\n    def step_7(self):\n        \"\"\"\n        调用安装部署执行接口\n        :return:\n        \"\"\"\n        data = {\"unique_key\": self.operation_uuid}\n        res = self.client.post(\n            url=\"/api/appStore/retryInstall/\",\n            data=data\n        )\n        if res.get(\"code\") != 0:\n            return False, res.get(\"message\")\n        # 增加 5s 等待时间\n        time.sleep(10)\n        return True, \"执行安装操作成功\"\n\n    def step_8(self):\n        \"\"\"\n        调用安装进度显示接口\n        :return:\n        \"\"\"\n        status_dic = self.get_service_install_status()\n        status_map = {\n            DetailInstallHistory.INSTALL_STATUS_READY: \"待安装\",\n            DetailInstallHistory.INSTALL_STATUS_INSTALLING: \"安装中\",\n            DetailInstallHistory.INSTALL_STATUS_SUCCESS: \"安装成功\",\n            DetailInstallHistory.INSTALL_STATUS_FAILED: \"安装失败\",\n        }\n        break_flag = False\n        continue_flag = True\n        # 第一次获取状态，并输出状态\n        for key, value in status_dic.items():\n            if value == DetailInstallHistory.INSTALL_STATUS_FAILED:\n                self.check_failed_status(key)\n                break_flag = True\n            else:\n                self.print_log(f\"{key} 当前状态为: {status_map[value]}\")\n\n        while continue_flag:\n            time.sleep(INFO_FRESH_SECOND)\n            _status_dic = self.get_service_install_status()\n            for key, value in _status_dic.items():\n                if key not in status_dic or status_dic.get(key) == value:\n                    continue\n                if value == DetailInstallHistory.INSTALL_STATUS_FAILED:\n                    self.check_failed_status(key)\n                    break_flag = True\n                else:\n                    self.print_log(\n                        f\"{key} 当前状态为: {status_map[value]}\"\n                    )\n            status_dic = _status_dic\n            if all(\n                    value == DetailInstallHistory.INSTALL_STATUS_SUCCESS\n                    for value in status_dic.values()\n            ):\n                self.print_log(\"***** 所有服务安装成功 *****\")\n                break\n            if break_flag and not MainInstallHistory.objects.filter(\n                operation_uuid=self.operation_uuid,\n                install_status=MainInstallHistory.INSTALL_STATUS_INSTALLING\n            ).exists():\n                continue_flag = False\n        if break_flag:\n            return False, \"安装过程出现问题，请查看上述报错信息\"\n        return True, \"安装进度显示成功\"\n\n    def step_9(self):\n        \"\"\"\n        查询post_action操作及结果\n        :return:\n        \"\"\"\n        post_action_queryset = DetailInstallHistory.objects.filter(\n            main_install_history__operation_uuid=self.operation_uuid\n        ).exclude(post_action_flag=4)\n        for item in post_action_queryset:\n            self.print_log(\n                f\"服务 [{item.service.service_instance_name}] 执行 \"\n                f\"[post_action] 结果为: \\n{item.post_action_msg}\"\n            )\n        return True, \"\"\n\n    def check_failed_status(self, service_instance_name):\n        \"\"\"\n        检查安装错误的服务的信息\n        :param service_instance_name: 服务实例名称\n        :return:\n        \"\"\"\n        detail_obj = DetailInstallHistory.objects.filter(\n            main_install_history__operation_uuid=self.operation_uuid,\n            service__service_instance_name=service_instance_name\n        ).first()\n        self.print_log(\n            f\"{service_instance_name} 当前状态为: 安装失败, 详情如下:\",\n            error=True\n        )\n        error_msg = \\\n            detail_obj.send_msg + \\\n            detail_obj.unzip_msg + \\\n            detail_obj.install_msg + \\\n            detail_obj.init_msg + \\\n            detail_obj.start_msg\n        self.print_log(error_msg, error=True)\n\n    def get_service_install_status(self):\n        \"\"\"\n        获取服务的安装状态\n        :return:\n        \"\"\"\n        queryset = DetailInstallHistory.objects.filter(\n            main_install_history__operation_uuid=self.operation_uuid\n        ).exclude(service__service_split=2).values(\n            \"install_step_status\", \"service__service_instance_name\")\n        status_dic = {\n            el[\"service__service_instance_name\"]:\n                el[\"install_step_status\"] for el in queryset\n        }\n        return status_dic\n\n    def _run(self, start_step=1):\n        \"\"\"\n        安装主体程序运行入口\n        :return:\n        \"\"\"\n        step_map_dic = {\n            \"step_1\": \"解析excel数据\",\n            \"step_2\": \"调用主机校验接口\",\n            \"step_3\": \"调用服务分布校验接口\",\n            \"step_4\": \"调用主机批量添加接口\",\n            \"step_5\": \"调用服务入库接口\",\n            \"step_6\": \"调用Agent状态监控接口\",\n            \"step_7\": \"调用安装部署执行接口\",\n            \"step_8\": \"调用安装进度显示接口\",\n            \"step_9\": \"查询post_action(服务注册)结果\"\n        }\n        self.print_log(\"开始进入安装流程\")\n        for i in range(start_step, 20):\n            if not hasattr(self, f\"step_{i}\"):\n                break\n            step_pre_msg = step_map_dic[f\"step_{i}\"]\n            self.print_log(f\"开始执行[{step_pre_msg}]\")\n            flag, msg = getattr(self, f\"step_{i}\")()\n            if not flag:\n                self.print_log(\n                    f\"[{step_pre_msg}]执行失败, 原因为: {msg}\", error=True)\n                self.print_log(\"安装失败, 程序结束!\")\n                sys.exit(1)\n            self.print_log(f\"[{step_pre_msg}]执行成功\")\n        self.print_log(\"安装成功, 程序结束!\")\n\n    def run(self):\n        \"\"\"\n        初次安装操作\n        :return:\n        \"\"\"\n        self._run(start_step=1)\n\n    def run_retry(self):\n        \"\"\"\n        重试安装操作\n        :return:\n        \"\"\"\n        self._run(start_step=7)\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        \"--excel_path\", \"-excel_path\",\n        required=False, help=\"excel文件位置\",\n        default=os.path.join(os.path.dirname(PROJECT_DIR), \"deployment.xlsx\")\n    )\n    param = parser.parse_args()\n    excel_path_arg = param.excel_path\n    if not excel_path_arg:\n        default_path = os.path.join(\n            os.path.dirname(PROJECT_DIR), \"deployment.xlsx\")\n        MainProcess(\n            excel_path=excel_path_arg, excel_md5_value=None\n        ).print_log(\n            f\"excel 文件不存在, 请输入excel绝对路径或将excel放置在{default_path}!\",\n            error=True\n        )\n        sys.exit(1)\n    if not os.path.exists(excel_path_arg):\n        MainProcess(\n            excel_path=excel_path_arg, excel_md5_value=None\n        ).print_log(\n            f\"文件{excel_path_arg}不存在!\", error=True\n        )\n        sys.exit(1)\n    _, excel_md5 = get_file_md5(excel_path_arg)\n    if MainInstallHistory.objects.filter(operation_uuid=excel_md5).exists():\n        MainProcess(\n            excel_path=excel_path_arg, excel_md5_value=excel_md5).run_retry()\n    else:\n        MainProcess(\n            excel_path=excel_path_arg, excel_md5_value=excel_md5).run()\n"
  },
  {
    "path": "scripts/source/cron",
    "content": "#!/bin/bash\n\n# celery worker的控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nAPP_NAME=\"cron\"\n\nstart() {\n  real_status\n  if [ $? -eq 0 ];then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    cd $PROJECT_FOLDER/omp_server\n    nohup $PROJECT_FOLDER/component/env/bin/python3 $PROJECT_FOLDER/component/env/bin/celery -A omp_server \\\n    beat --scheduler django --loglevel=ERROR >$PROJECT_FOLDER/logs/celery_cron.log 2>&1 &\n    echo \"${APP_NAME} [running]\"\n  fi\n}\n\nstop() {\n  real_status\n  if [ $? -eq 1 ]; then\n    echo \"${APP_NAME} [not running]\"\n    return 0\n  else\n    ps -ef | grep \"${PROJECT_FOLDER}/component/env/bin/celery\" |grep scheduler | grep -v grep | awk '{print $2}' | xargs kill -9\n    sleep 3\n    real_status\n    if [ $? -eq 0 ]; then\n      echo \"${APP_NAME} [running]\"\n      return 1\n    else\n      echo \"${APP_NAME} [not running]\"\n      return 0\n    fi\n  fi\n}\n\nreal_status() {\n  worker_status=$(ps -ef | grep ${PROJECT_FOLDER}/component/env/bin/celery | grep scheduler | grep -v grep)\n  if [ -n \"$worker_status\" ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nstatus() {\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 1\n  fi\n}\n\ncase $1 in\nstart) start ;;\nstop) stop ;;\nrestart)\n  stop\n  start\n  ;;\nstatus)\n  status\n  ;;\n*)\n  echo \"usage: $0 [start|stop|restart|status]\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/source/features.py",
    "content": "from django.db import ProgrammingError\nfrom django.utils.functional import cached_property\n\n\nclass BaseDatabaseFeatures:\n    gis_enabled = False\n    allows_group_by_pk = False\n    allows_group_by_selected_pks = False\n    empty_fetchmany_value = []\n    update_can_self_select = True\n\n    # Does the backend distinguish between '' and None?\n    interprets_empty_strings_as_nulls = False\n\n    # Does the backend allow inserting duplicate NULL rows in a nullable\n    # unique field? All core backends implement this correctly, but other\n    # databases such as SQL Server do not.\n    supports_nullable_unique_constraints = True\n\n    # Does the backend allow inserting duplicate rows when a unique_together\n    # constraint exists and some fields are nullable but not all of them?\n    supports_partially_nullable_unique_constraints = True\n    # Does the backend support initially deferrable unique constraints?\n    supports_deferrable_unique_constraints = False\n\n    can_use_chunked_reads = True\n    can_return_columns_from_insert = False\n    can_return_rows_from_bulk_insert = False\n    has_bulk_insert = True\n    uses_savepoints = True\n    can_release_savepoints = False\n\n    # If True, don't use integer foreign keys referring to, e.g., positive\n    # integer primary keys.\n    related_fields_match_type = False\n    allow_sliced_subqueries_with_in = True\n    has_select_for_update = False\n    has_select_for_update_nowait = False\n    has_select_for_update_skip_locked = False\n    has_select_for_update_of = False\n    # Does the database's SELECT FOR UPDATE OF syntax require a column rather\n    # than a table?\n    select_for_update_of_column = False\n\n    # Does the default test database allow multiple connections?\n    # Usually an indication that the test database is in-memory\n    test_db_allows_multiple_connections = True\n\n    # Can an object be saved without an explicit primary key?\n    supports_unspecified_pk = False\n\n    # Can a fixture contain forward references? i.e., are\n    # FK constraints checked at the end of transaction, or\n    # at the end of each save operation?\n    supports_forward_references = True\n\n    # Does the backend truncate names properly when they are too long?\n    truncates_names = False\n\n    # Is there a REAL datatype in addition to floats/doubles?\n    has_real_datatype = False\n    supports_subqueries_in_group_by = True\n\n    # Is there a true datatype for uuid?\n    has_native_uuid_field = False\n\n    # Is there a true datatype for timedeltas?\n    has_native_duration_field = False\n\n    # Does the database driver supports same type temporal data subtraction\n    # by returning the type used to store duration field?\n    supports_temporal_subtraction = False\n\n    # Does the __regex lookup support backreferencing and grouping?\n    supports_regex_backreferencing = True\n\n    # Can date/datetime lookups be performed using a string?\n    supports_date_lookup_using_string = True\n\n    # Can datetimes with timezones be used?\n    supports_timezones = True\n\n    # Does the database have a copy of the zoneinfo database?\n    has_zoneinfo_database = True\n\n    # When performing a GROUP BY, is an ORDER BY NULL required\n    # to remove any ordering?\n    requires_explicit_null_ordering_when_grouping = False\n\n    # Does the backend order NULL values as largest or smallest?\n    nulls_order_largest = False\n\n    # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY?\n    supports_order_by_nulls_modifier = True\n\n    # Does the backend orders NULLS FIRST by default?\n    order_by_nulls_first = False\n\n    # The database's limit on the number of query parameters.\n    max_query_params = None\n\n    # Can an object have an autoincrement primary key of 0? MySQL says No.\n    allows_auto_pk_0 = True\n\n    # Do we need to NULL a ForeignKey out, or can the constraint check be\n    # deferred\n    can_defer_constraint_checks = False\n\n    # date_interval_sql can properly handle mixed Date/DateTime fields and timedeltas\n    supports_mixed_date_datetime_comparisons = True\n\n    # Does the backend support tablespaces? Default to False because it isn't\n    # in the SQL standard.\n    supports_tablespaces = False\n\n    # Does the backend reset sequences between tests?\n    supports_sequence_reset = True\n\n    # Can the backend introspect the default value of a column?\n    can_introspect_default = True\n\n    # Confirm support for introspected foreign keys\n    # Every database can do this reliably, except MySQL,\n    # which can't do it for MyISAM tables\n    can_introspect_foreign_keys = True\n\n    # Can the backend introspect an AutoField, instead of an IntegerField?\n    can_introspect_autofield = False\n\n    # Can the backend introspect a BigIntegerField, instead of an IntegerField?\n    can_introspect_big_integer_field = True\n\n    # Can the backend introspect an BinaryField, instead of an TextField?\n    can_introspect_binary_field = True\n\n    # Can the backend introspect an DecimalField, instead of an FloatField?\n    can_introspect_decimal_field = True\n\n    # Can the backend introspect a DurationField, instead of a BigIntegerField?\n    can_introspect_duration_field = True\n\n    # Can the backend introspect an IPAddressField, instead of an CharField?\n    can_introspect_ip_address_field = False\n\n    # Can the backend introspect a PositiveIntegerField, instead of an IntegerField?\n    can_introspect_positive_integer_field = False\n\n    # Can the backend introspect a SmallIntegerField, instead of an IntegerField?\n    can_introspect_small_integer_field = False\n\n    # Can the backend introspect a TimeField, instead of a DateTimeField?\n    can_introspect_time_field = True\n\n    # Some backends may not be able to differentiate BigAutoField or\n    # SmallAutoField from other fields such as AutoField.\n    introspected_big_auto_field_type = 'BigAutoField'\n    introspected_small_auto_field_type = 'SmallAutoField'\n\n    # Some backends may not be able to differentiate BooleanField from other\n    # fields such as IntegerField.\n    introspected_boolean_field_type = 'BooleanField'\n\n    # Can the backend introspect the column order (ASC/DESC) for indexes?\n    supports_index_column_ordering = True\n\n    # Does the backend support introspection of materialized views?\n    can_introspect_materialized_views = False\n\n    # Support for the DISTINCT ON clause\n    can_distinct_on_fields = False\n\n    # Does the backend prevent running SQL queries in broken transactions?\n    atomic_transactions = True\n\n    # Can we roll back DDL in a transaction?\n    can_rollback_ddl = False\n\n    # Does it support operations requiring references rename in a transaction?\n    supports_atomic_references_rename = True\n\n    # Can we issue more than one ALTER COLUMN clause in an ALTER TABLE?\n    supports_combined_alters = False\n\n    # Does it support foreign keys?\n    supports_foreign_keys = True\n\n    # Can it create foreign key constraints inline when adding columns?\n    can_create_inline_fk = True\n\n    # Does it support CHECK constraints?\n    supports_column_check_constraints = True\n    supports_table_check_constraints = True\n    # Does the backend support introspection of CHECK constraints?\n    can_introspect_check_constraints = True\n\n    # Does the backend support 'pyformat' style (\"... %(name)s ...\", {'name': value})\n    # parameter passing? Note this can be provided by the backend even if not\n    # supported by the Python driver\n    supports_paramstyle_pyformat = True\n\n    # Does the backend require literal defaults, rather than parameterized ones?\n    requires_literal_defaults = False\n\n    # Does the backend require a connection reset after each material schema change?\n    connection_persists_old_columns = False\n\n    # What kind of error does the backend throw when accessing closed cursor?\n    closed_cursor_error_class = ProgrammingError\n\n    # Does 'a' LIKE 'A' match?\n    has_case_insensitive_like = True\n\n    # Suffix for backends that don't support \"SELECT xxx;\" queries.\n    bare_select_suffix = ''\n\n    # If NULL is implied on columns without needing to be explicitly specified\n    implied_column_null = False\n\n    # Does the backend support \"select for update\" queries with limit (and offset)?\n    supports_select_for_update_with_limit = True\n\n    # Does the backend ignore null expressions in GREATEST and LEAST queries unless\n    # every expression is null?\n    greatest_least_ignores_nulls = False\n\n    # Can the backend clone databases for parallel test execution?\n    # Defaults to False to allow third-party backends to opt-in.\n    can_clone_databases = False\n\n    # Does the backend consider table names with different casing to\n    # be equal?\n    ignores_table_name_case = False\n\n    # Place FOR UPDATE right after FROM clause. Used on MSSQL.\n    for_update_after_from = False\n\n    # Combinatorial flags\n    supports_select_union = True\n    supports_select_intersection = True\n    supports_select_difference = True\n    supports_slicing_ordering_in_compound = False\n    supports_parentheses_in_compound = True\n\n    # Does the database support SQL 2003 FILTER (WHERE ...) in aggregate\n    # expressions?\n    supports_aggregate_filter_clause = False\n\n    # Does the backend support indexing a TextField?\n    supports_index_on_text_field = True\n\n    # Does the backend support window expressions (expression OVER (...))?\n    supports_over_clause = False\n    supports_frame_range_fixed_distance = False\n    only_supports_unbounded_with_preceding_and_following = False\n\n    # Does the backend support CAST with precision?\n    supports_cast_with_precision = True\n\n    # How many second decimals does the database return when casting a value to\n    # a type with time?\n    time_cast_precision = 6\n\n    # SQL to create a procedure for use by the Django test suite. The\n    # functionality of the procedure isn't important.\n    create_test_procedure_without_params_sql = None\n    create_test_procedure_with_int_param_sql = None\n\n    # Does the backend support keyword parameters for cursor.callproc()?\n    supports_callproc_kwargs = False\n\n    # Convert CharField results from bytes to str in database functions.\n    db_functions_convert_bytes_to_str = False\n\n    # What formats does the backend EXPLAIN syntax support?\n    supported_explain_formats = set()\n\n    # Does DatabaseOperations.explain_query_prefix() raise ValueError if\n    # unknown kwargs are passed to QuerySet.explain()?\n    validates_explain_options = True\n\n    # Does the backend support the default parameter in lead() and lag()?\n    supports_default_in_lead_lag = True\n\n    # Does the backend support ignoring constraint or uniqueness errors during\n    # INSERT?\n    supports_ignore_conflicts = True\n\n    # Does this backend require casting the results of CASE expressions used\n    # in UPDATE statements to ensure the expression has the correct type?\n    requires_casted_case_in_updates = False\n\n    # Does the backend support partial indexes (CREATE INDEX ... WHERE ...)?\n    supports_partial_indexes = True\n    supports_functions_in_partial_indexes = True\n\n    # Does the database allow more than one constraint or index on the same\n    # field(s)?\n    allows_multiple_constraints_on_same_fields = True\n\n    # Does the backend support boolean expressions in SELECT and GROUP BY\n    # clauses?\n    supports_boolean_expr_in_select_clause = True\n\n    # Does the backend support JSONField?\n    supports_json_field = True\n    # Can the backend introspect a JSONField?\n    can_introspect_json_field = True\n    # Does the backend support primitives in JSONField?\n    supports_primitives_in_json_field = True\n    # Is there a true datatype for JSON?\n    has_native_json_field = False\n    # Does the backend use PostgreSQL-style JSON operators like '->'?\n    has_json_operators = False\n    # Does the backend support __contains and __contained_by lookups for\n    # a JSONField?\n    supports_json_field_contains = True\n    # Does value__d__contains={'f': 'g'} (without a list around the dict) match\n    # {'d': [{'f': 'g'}]}?\n    json_key_contains_list_matching_requires_list = False\n\n    def __init__(self, connection):\n        self.connection = connection\n        # unsafe version Adaptation\n        self.connection.mysql_version = (5,7,37)\n\n    @cached_property\n    def supports_explaining_query_execution(self):\n        \"\"\"Does this backend support explaining query execution?\"\"\"\n        return self.connection.ops.explain_prefix is not None\n\n    @cached_property\n    def supports_transactions(self):\n        \"\"\"Confirm support for transactions.\"\"\"\n        with self.connection.cursor() as cursor:\n            cursor.execute('CREATE TABLE ROLLBACK_TEST (X INT)')\n            self.connection.set_autocommit(False)\n            cursor.execute('INSERT INTO ROLLBACK_TEST (X) VALUES (8)')\n            self.connection.rollback()\n            self.connection.set_autocommit(True)\n            cursor.execute('SELECT COUNT(X) FROM ROLLBACK_TEST')\n            count, = cursor.fetchone()\n            cursor.execute('DROP TABLE ROLLBACK_TEST')\n        return count == 0\n\n    def allows_group_by_selected_pks_on_model(self, model):\n        if not self.allows_group_by_selected_pks:\n            return False\n        return model._meta.managed\n"
  },
  {
    "path": "scripts/source/install_mysql_redis.py",
    "content": "# -*- coding: utf-8 -*-\r\n# Project: install_mysql_redis\r\n# Author: jon.liu@yunzhihui.com\r\n# Create time: 2021-12-04 18:42\r\n# IDE: PyCharm\r\n# Version: 1.0\r\n# Introduction:\r\n\r\nimport os\r\nimport sys\r\nimport yaml\r\nimport time\r\nimport shutil\r\nimport subprocess\r\n\r\n\r\nCURRENT_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\r\nPROJECT_FOLDER = os.path.dirname(os.path.dirname(CURRENT_FILE_PATH))\r\n\r\nconfig_path = os.path.join(PROJECT_FOLDER, \"config/omp.yaml\")\r\nPROJECT_DATA_PATH = os.path.join(PROJECT_FOLDER, \"data\")\r\nPROJECT_LOG_PATH = os.path.join(PROJECT_FOLDER, \"logs\")\r\n\r\n\r\ndef cmd(command):\r\n    \"\"\"执行shell 命令\"\"\"\r\n    p = subprocess.Popen(\r\n        command,\r\n        stdout=subprocess.PIPE,\r\n        stderr=subprocess.PIPE,\r\n        shell=True,\r\n    )\r\n    stdout, stderr = p.communicate()\r\n    _out, _err, _code = stdout, stderr, p.returncode\r\n    return _out.decode(), _err.decode(), _code\r\n\r\n\r\ndef get_config_dic():\r\n    \"\"\"\r\n    获取配置文件详细信息\r\n    :return:\r\n    \"\"\"\r\n    with open(config_path, \"r\", encoding=\"utf8\") as fp:\r\n        return yaml.load(fp, Loader=yaml.FullLoader)\r\n\r\n\r\ndef replace_placeholder(file_path, data):\r\n    \"\"\"\r\n    替换占位符\r\n    :param file_path: 文件路径\r\n    :param data: 占位符映射关系\r\n    :type data: dict\r\n    :return:\r\n    \"\"\"\r\n    if not os.path.exists(file_path):\r\n        print(f\"无法找到文件{file_path}\")\r\n        sys.exit(1)\r\n    with open(file_path, \"r\", encoding=\"utf8\") as fp:\r\n        content = fp.read()\r\n    for key, value in data.items():\r\n        content = content.replace(key, str(value))\r\n    with open(file_path, \"w\", encoding=\"utf8\") as fp:\r\n        fp.write(content)\r\n\r\n\r\ndef get_run_user():\r\n    \"\"\"\r\n    获取程序运行用户\r\n    :return:\r\n    \"\"\"\r\n    global_user = get_config_dic().get(\"global_user\")\r\n    if global_user != \"root\":\r\n        return global_user\r\n    default_user = \"omp\"\r\n    cmd(f\"id {default_user} || useradd -s /bin/bash {default_user}\")\r\n    return default_user\r\n\r\n\r\ndef install_mysql():\r\n    \"\"\"\r\n    安装 mysql 逻辑\r\n    :return:\r\n    \"\"\"\r\n    mysql_config = get_config_dic().get(\"mysql\")\r\n    _dic = {\r\n        \"CW_MYSQL_USERNAME\": mysql_config.get(\"username\"),\r\n        \"CW_MYSQL_PASSWORD\": mysql_config.get(\"password\"),\r\n        \"CW_MYSQL_PORT\": mysql_config.get(\"port\"),\r\n        \"CW_MYSQL_RUN_USER\": get_run_user(),\r\n        \"CW_MYSQL_DATA_DIR\": os.path.join(PROJECT_DATA_PATH, \"mysql\"),\r\n        \"CW_MYSQL_ERROR_LOG_DIR\": os.path.join(PROJECT_LOG_PATH, \"mysql\"),\r\n        \"CW_MYSQL_BASE_DIR\": os.path.join(PROJECT_FOLDER, \"component/mysql\")\r\n    }\r\n    # 创建日志目录\r\n    if not os.path.exists(_dic[\"CW_MYSQL_ERROR_LOG_DIR\"]):\r\n        cmd(f\"mkdir -p {_dic['CW_MYSQL_ERROR_LOG_DIR']}\")\r\n    # 复制数据到目标目录\r\n    shutil.copytree(\r\n        os.path.join(_dic[\"CW_MYSQL_BASE_DIR\"], \"data\"),\r\n        os.path.join(PROJECT_DATA_PATH, \"mysql\")\r\n    )\r\n    # 替换占位符\r\n    # my.cnf\r\n    replace_placeholder(\r\n        os.path.join(_dic[\"CW_MYSQL_BASE_DIR\"], \"my.cnf\"), _dic)\r\n    # scripts/mysql\r\n    _mysql_path = os.path.join(_dic[\"CW_MYSQL_BASE_DIR\"], \"scripts/mysql\")\r\n    replace_placeholder(_mysql_path, _dic)\r\n    # 启动服务\r\n    out, _, code = cmd(f\"bash {_mysql_path} start\")\r\n    if \"mysql  [running]\" not in out:\r\n        print(f\"mysql启动失败: {out}\")\r\n        sys.exit(1)\r\n    time.sleep(30)\r\n    # 确保mysql启动成功并可用\r\n    _mysql_cli = os.path.join(_dic[\"CW_MYSQL_BASE_DIR\"], \"bin/mysql\")\r\n    _mysql_cli = f\"{_mysql_cli} -S {os.path.join(_dic['CW_MYSQL_DATA_DIR'], 'mysql.sock')}\"\r\n    try_times = 0\r\n    while try_times < 10:\r\n        out, _, _ = cmd(f\"{_mysql_cli} -e 'SHOW DATABASES;'\")\r\n        if \"information_schema\" in out:\r\n            break\r\n        try_times += 1\r\n        time.sleep(10)\r\n    else:\r\n        print(\"mysql启动失败\")\r\n        sys.exit(1)\r\n    # 创建数据库\r\n    create = \"create database omp default charset utf8 collate utf8_general_ci;\"\r\n    cmd(f\"{_mysql_cli} -e '{create}'\")\r\n    _u = _dic[\"CW_MYSQL_USERNAME\"]\r\n    _p = _dic[\"CW_MYSQL_PASSWORD\"]\r\n    cmd(f\"\"\" {_mysql_cli} -e 'grant all privileges on `omp`.* to \"{_u}\"@\"%\" identified by \"{_p}\" with grant option;' \"\"\")\r\n    flush = \"flush privileges;\"\r\n    cmd(f\"{_mysql_cli} -e '{flush}'\")\r\n\r\n\r\ndef install_redis():\r\n    \"\"\"\r\n    安装 redis 逻辑\r\n    :return:\r\n    \"\"\"\r\n    redis_config = get_config_dic().get(\"redis\")\r\n    _dic = {\r\n        \"CW_REDIS_PORT\": redis_config.get(\"port\"),\r\n        \"CW_REDIS_PASSWORD\": redis_config.get(\"password\"),\r\n        \"CW_REDIS_BASE_DIR\": os.path.join(PROJECT_FOLDER, \"component/redis\"),\r\n        \"CW_REDIS_LOG_DIR\": os.path.join(PROJECT_LOG_PATH, \"redis\"),\r\n        \"CW_REDIS_DATA_DIR\": os.path.join(PROJECT_DATA_PATH, \"redis\"),\r\n        \"CW_LOCAL_IP\": get_config_dic().get(\"local_ip\"),\r\n        \"CW_REDIS_RUN_USER\": get_config_dic().get(\"global_user\")\r\n    }\r\n    cmd(f\"mkdir -p {_dic['CW_REDIS_LOG_DIR']}\")\r\n    cmd(f\"mkdir -p {_dic['CW_REDIS_DATA_DIR']}\")\r\n    _redis_path = os.path.join(_dic[\"CW_REDIS_BASE_DIR\"], \"scripts/redis\")\r\n    _redis_conf = os.path.join(_dic[\"CW_REDIS_BASE_DIR\"], \"redis.conf\")\r\n    replace_placeholder(_redis_path, _dic)\r\n    replace_placeholder(_redis_conf, _dic)\r\n    cmd(f\"bash {_redis_path} start\")\r\n\r\n\r\nif __name__ == '__main__':\r\n    install_mysql()\r\n    install_redis()\r\n"
  },
  {
    "path": "scripts/source/install_or_update.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: install_or_update\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-17 17:01\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n安装或更新server端代码，前端+后端\n\"\"\"\n\nimport os\nimport sys\nimport subprocess\n\n# 获取当前项目目录，优先从环境变量中获取\n# 如果从环境变量中无法获取的话，则从当前文件寻找其家目录\nOMP_HOME = os.environ.get(\"OMP_HOME_PATH\", \"\")\nUPDATE_HOME = os.environ.get(\"OMP_UPDATE_HOME_PATH\", \"\")\nif not OMP_HOME or not UPDATE_HOME:\n    OMP_HOME = os.path.dirname(\n        os.path.dirname(\n            os.path.dirname(\n                os.path.realpath(__file__)\n            )\n        )\n    )\n    UPDATE_HOME = os.path.join(os.path.dirname(OMP_HOME), \"omp_update\")\nOMP_PYTHON_PATH = os.path.join(OMP_HOME, \"component/env/bin/python3\")\nOMP_MANAGE_PATH = os.path.join(OMP_HOME, \"omp_server/manage.py\")\nOMP_SHELL_PATH = os.path.join(OMP_HOME, \"scripts/omp\")\nOMP_UPDATE_DATA_PATH = os.path.join(OMP_HOME, \"scripts/source/update_data.py\")\nOMP_UPDATE_CONFIG_FILE = os.path.join(\n    OMP_HOME, \"scripts/source/update_conf.py\")\n\n\ndef cmd(command):\n    \"\"\"执行shell 命令\"\"\"\n    p = subprocess.Popen(\n        command,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        shell=True,\n    )\n    stdout, stderr = p.communicate()\n    _out, _err, _code = stdout, stderr, p.returncode\n    print(\n        \"Execute Command: {0}\\n\"\n        \"RETCODE: {1}\\nSTDOUT: {2}\\nSTDERR: {3}\\n\".format(\n            command, _code, _out, _err\n        ))\n    return _out, _err, _code\n\n\ndef install(pack_path, ip_address):\n    \"\"\"\n    omp安装流程\n    :param pack_path: 包路径\n    :param ip_address: ip地址\n    :return:\n    \"\"\"\n    # 解压源码包\n    cmd(\"tar -xmf {0} -C {1}\".format(pack_path, os.path.dirname(OMP_HOME)))\n    # 执行安装脚本\n    cmd(\"bash {0} {1}\".format(os.path.join(\n        OMP_HOME, 'scripts/install.sh'), ip_address))\n    # 执行数据库迁移\n    cmd(\"{0} {1} migrate\".format(OMP_PYTHON_PATH, OMP_MANAGE_PATH))\n    # 启动OMP服务\n    cmd(\"bash {0} all start\".format(OMP_SHELL_PATH))\n\n\ndef update_omp_platform(pack_path, update_file_folder):\n    \"\"\"\n    更新omp平台端文件\n    :param pack_path: omp包路径\n    :param update_file_folder: 更新文件路径\n    :return:\n    \"\"\"\n    # 解压安装包\n    cmd(\"tar -xmf {0} -C {1}\".format(pack_path, update_file_folder))\n    # 备份服务端文件\n    omp_server_old = os.path.join(OMP_HOME, 'omp_server')\n    omp_server_bak = os.path.join(update_file_folder, 'omp_server')\n    cmd(\"test -d {0} || cp -rf {1} {2}\".format(\n        omp_server_bak,\n        omp_server_old, update_file_folder))\n    # 备份scripts脚本目录\n    omp_scripts_old = os.path.join(OMP_HOME, 'scripts')\n    omp_scripts_bak = os.path.join(update_file_folder, 'scripts')\n    cmd(\"test -d {0} || cp -rf {1} {2}\".format(\n        omp_scripts_bak,\n        omp_scripts_old, update_file_folder))\n    # 备份前端文件目录\n    omp_web_old = os.path.join(OMP_HOME, 'omp_web')\n    omp_web_bak = os.path.join(update_file_folder, 'omp_web')\n    cmd(\"test -d {0} || cp -rf {1} {2}\".format(\n        omp_web_bak,\n        omp_web_old, update_file_folder))\n    # 备份package_hub\n    package_hub_old = os.path.join(OMP_HOME, 'package_hub')\n    package_hub_bak = os.path.join(update_file_folder, 'package_hub')\n    cmd(\"test -d {0} || cp -rf {1} {2}\".format(\n        package_hub_bak,\n        package_hub_old, update_file_folder))\n    # 更新服务端文件\n    _new_server = os.path.join(update_file_folder, \"omp/omp_server\")\n    cmd(\"rm -rf {0} && cp -rf {1} {2}\".format(\n        omp_server_old,\n        _new_server, omp_server_old))\n    _new_web = os.path.join(update_file_folder, \"omp/omp_web\")\n    cmd(\"rm -rf {0} && cp -rf {1} {2}\".format(\n        omp_web_old, _new_web, omp_web_old))\n    _new_scripts = os.path.join(update_file_folder, \"omp/scripts\")\n    cmd(\"rm -rf {0} && cp -rf {1} {2}\".format(\n        omp_scripts_old,\n        _new_scripts, omp_scripts_old))\n    _new_package_hub = os.path.join(update_file_folder, \"omp/package_hub\")\n    cmd(\"rm -rf {0} && cp -rf {1} {2}\".format(\n        package_hub_old,\n        _new_package_hub, package_hub_old))\n    cmd(\"cp -rf {}/omp_monitor_agent* {}/\".format(\n        package_hub_bak, package_hub_old))\n\n\ndef update_config(update_file_folder):\n    \"\"\"\n    更新配置文件\n    :param update_file_folder:\n    :return:\n    \"\"\"\n    from ruamel.yaml import YAML\n    # 复制配置文件\n    config_old = os.path.join(OMP_HOME, \"config\")\n    config_bak = os.path.join(update_file_folder, \"config\")\n    config_new = os.path.join(update_file_folder, \"omp/config/omp.yaml\")\n    cmd(\"test -d {0} || cp -rf {1} {2}\".format(config_bak,\n                                               config_old, config_bak))\n\n    # 提取原有配置\n    with open(os.path.join(config_old, \"omp.yaml\"), \"r\",\n              encoding=\"utf8\") as fp:\n        content_old = fp.read()\n    my_yaml = YAML()\n    old_config_dic = my_yaml.load(content_old)\n\n    # 提取最新配置\n    with open(config_new, \"r\", encoding=\"utf8\") as fp:\n        content_new = fp.read()\n    my_yaml = YAML()\n    new_config_dic = my_yaml.load(content_new)\n\n    # 合并并重写配置\n    new_config_dic.update(old_config_dic)\n    with open(os.path.join(config_old, \"omp.yaml\"), \"w\",\n              encoding=\"utf8\") as fp:\n        my_yaml.dump(new_config_dic, fp)\n\n    _local_ip = new_config_dic.get(\"local_ip\")\n    _run_user = new_config_dic.get(\"global_user\")\n\n    # 更新配置文件\n    cmd(\"{0} {1} {2} {3}\".format(\n        OMP_PYTHON_PATH, OMP_UPDATE_CONFIG_FILE,\n        _local_ip, _run_user)\n        )\n\n\ndef update(pack_path, ip_address):\n    \"\"\"\n    omp更新流程\n    :param pack_path: 包路径\n    :param ip_address: ip地址\n    :return:\n    \"\"\"\n    # 创建升级文件夹\n    cmd(\"mkdir -p {0}\".format(UPDATE_HOME))\n    # 获取安装包md5值\n    _out, _, _ = cmd(\"md5sum {0}\".format(pack_path))\n    package_md5 = _out.strip().split()[0].strip().decode()\n    update_file_folder = os.path.join(UPDATE_HOME, \"{0}\".format(package_md5))\n    cmd(\"test -d {0} && rm -rf {0}\".format(update_file_folder))\n    cmd(\"mkdir -p {0}\".format(update_file_folder))\n    # # 停止相关服务\n    # cmd(\"bash {0} all stop\".format(OMP_SHELL_PATH))\n    # 更新平台端文件\n    update_omp_platform(pack_path, update_file_folder)\n    # 更新配置文件\n    update_config(update_file_folder)\n    # 执行数据库迁移\n    cmd(\"{0} {1} migrate\".format(OMP_PYTHON_PATH, OMP_MANAGE_PATH))\n    # 重启相关服务\n    cmd(\"bash {0} all restart\".format(OMP_SHELL_PATH))\n\n\ndef main(pack_path, ip_address):\n    \"\"\"\n    运行主方法\n    :param pack_path: 包路径\n    :param ip_address: 本机ip地址\n    :return:\n    \"\"\"\n    if os.path.exists(OMP_HOME):\n        update(pack_path, ip_address)\n    else:\n        install(pack_path, ip_address)\n    # 更新默认数据\n    cmd(\"{0} {1}\".format(OMP_PYTHON_PATH, OMP_UPDATE_DATA_PATH))\n    print(\"All Process Finish\")\n\n\nif __name__ == '__main__':\n    sys_args = sys.argv[1:]\n    if len(sys_args) != 2:\n        print(\"Please use: python install_or_update.py package_path local_ip\")\n    package_path, local_ip = sys_args\n    if not os.path.exists(package_path):\n        print(\"{0} Package Not Exist!\".format(package_path))\n        exit(1)\n    main(pack_path=package_path, ip_address=local_ip)\n"
  },
  {
    "path": "scripts/source/omp_rollback.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nimport time\nimport sys\nimport logging\nfrom abc import abstractmethod, ABCMeta\n\nCURRENT_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\n# 升级包路径\nPROJECT_FOLDER = os.path.dirname(os.path.dirname(CURRENT_FILE_PATH))\n\n\nclass Logging:\n\n    @staticmethod\n    def log_new(app_name, path_log):\n        logger = logging.getLogger(app_name)\n        formatter = logging.Formatter('[%(asctime)s-%(levelname)s]: %(message)s')\n        logger.setLevel(level=logging.INFO)\n        file_handler = logging.FileHandler(path_log)\n        file_handler.setLevel(logging.INFO)\n        file_handler.setFormatter(formatter)\n        logger.addHandler(file_handler)\n        return logger\n\n\n# 流程 检查状态 关闭进程前准备 关闭进程 备份 升级 升级整体恢复\nclass Common(metaclass=ABCMeta):\n\n    def __init__(self, target_dir):\n        \"\"\"\n        target_dir 目标跟路径\n        base_dir 目标安装路径\n        backup_dir 升级备份路径\n        \"\"\"\n        self.base_dir = None\n        self.bin = None\n        self.target_dir = target_dir\n        self.check_target_dir()\n        self.target_data = os.path.join(self.target_dir, \"data\")\n        self.target_log = os.path.join(self.target_dir, \"logs\")\n        self.omp_conf = os.path.join(self.target_dir, \"config/omp.yaml\")\n        self.backup_dir = os.path.join(PROJECT_FOLDER, 'backup', self.get_class_name())\n        self.target_backup = os.path.join(self.target_dir, \"back_new\")\n        _name = f\"omp_rollback_{self.get_class_name()}\"\n        self.logger = Logging.log_new(_name,\n                                      os.path.join(PROJECT_FOLDER, f\"logs/{_name}.log\"))\n        if not os.path.exists(self.target_backup):\n            self.sys_cmd('mkdir -p {0}'.format(self.target_backup))\n        if not os.path.exists(self.backup_dir) and \\\n                self.backup_dir.rsplit(\"/\", 1)[1] not in [\"PreUpdate\", \"PostUpdate\"]:\n            print(f\"备份路径不存在:{self.backup_dir},无法恢复\")\n            sys.exit(1)\n\n    @abstractmethod\n    def get_class_name(self):\n        raise NotImplementedError(\"Must override get_class_name\")\n\n    def sys_cmd(self, cmd):\n        \"\"\"\n        shell脚本输出\n        :param cmd: linux命令\n        :return:\n        \"\"\"\n        shell = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n        stdout, stderr = shell.communicate()\n        stdout, stderr = bytes.decode(stdout), bytes.decode(stderr)\n        exit_code = shell.poll()\n        self.logger.info(\"执行cmd命令:{0},结果退出码:{1},执行详情:{2}\".format(cmd, shell.poll(), stdout))\n        if str(exit_code) != \"0\":\n            print(f\"{stderr},退出码:{exit_code}\")\n            sys.exit(1)\n        return stdout\n\n    def check_target_dir(self):\n        try:\n            dirs = os.listdir(self.target_dir)\n            if 'omp_server' not in dirs or 'omp_web' not in dirs:\n                print(\"此目标路径非标准安装路径\")\n                sys.exit(1)\n        except Exception as e:\n            print(f\"路径异常，请检查路径，标准路径如/data/omp/ :{e}\")\n\n    def rollback(self):\n        return\n\n    def run(self):\n        self.rollback()\n\n\nclass PreUpdate(Common):\n    \"\"\"\n    检查前置条件\n    \"\"\"\n\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.target_dir = self.target_dir + \"/\" if self.target_dir[-1] != '/' else self.target_dir\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def check_process(self):\n        \"\"\"\n        检查进程是否存在，存在返回false 不存在返回true\n        \"\"\"\n        cmd_str = f'ps -ef | grep {self.target_dir}|grep -v grep|wc -l'\n        stdout = self.sys_cmd(cmd_str)\n        if str(stdout).strip() == \"0\":\n            return True\n        return False\n\n    def crontab_stop(self):\n        cmd_str = f'crontab -l | grep -v \"{self.target_dir}scripts/omp all start\" 2>/dev/null | crontab -;'\n        self.sys_cmd(cmd_str)\n\n    def process_stop(self):\n        cmd_str = f'bash {self.target_dir}scripts/omp all stop'\n        self.sys_cmd(cmd_str)\n        # ToDo 需要各个停止脚本完全输出停止时不存在延迟停止进程\n        time.sleep(5)\n        if self.check_process():\n            return\n        print(\"服务未停止成功，请检查服务并尝试手动停止进程\")\n        sys.exit(1)\n\n    def run(self):\n        self.crontab_stop()\n        self.process_stop()\n\n\nclass Mysql(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.target_data = os.path.join(self.target_data, 'mysql')\n        self.target_log = os.path.join(self.target_log, 'mysql')\n        self.base_dir = os.path.join(self.target_dir, 'component/mysql')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        self.sys_cmd(\n            \"mv {0} {3}/mysqlbase  && mv {1} {3}/mysqldata   && mv {2} {3}/mysqllog\".format(\n                self.base_dir,\n                self.target_data,\n                self.target_log,\n                self.target_backup\n            ))\n        self.sys_cmd(\n            \"mv {3}/mysqldata {0}  && mv {3}/mysqllogs {1}  && mv {3}/mysqlbase {2}\".format(\n                self.target_data, self.target_log, self.base_dir, self.backup_dir))\n\n\nclass OmpWeb(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'omp_web')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        self.sys_cmd(\n            \"mv {0} {1}/omp_web_base\".format(\n                self.base_dir,\n                self.target_backup\n            ))\n        self.sys_cmd(\"mv {0}/omp_web_base {1}\".format(self.backup_dir, self.base_dir))\n\n\nclass Tengine(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/tengine')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        self.sys_cmd(\"mv -f {0}/nginx {1}/sbin\".format(self.backup_dir, self.base_dir))\n\n\nclass Redis(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/redis')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        self.sys_cmd(f\"mv -f {self.backup_dir}/redis_base/bin_bak/redis* {self.base_dir}/bin\")\n\n\nclass OmpServer(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'omp_server')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        self.sys_cmd(\n            \"mv {0} {1}/omp_server_base\".format(\n                self.base_dir,\n                self.target_backup\n            ))\n        self.sys_cmd(\"mv {0}/omp_server_base {1}\".format(self.backup_dir, self.base_dir))\n        print(f\"1. 请手动备份 mv {self.target_dir}/scripts {self.target_backup}/omp_scripts_bak\")\n        print(f\"2. 请手动移动 mv {os.path.dirname(self.backup_dir)}/omp_scripts_bak {self.target_dir}/scripts\")\n        print(f\"3. 请手动执行 bash {self.target_dir}/scripts/omp all restart\")\n\n\nclass PostUpdate(Common):\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def rollback(self):\n        tmp_dir = f'{PROJECT_FOLDER}/crontab.txt'\n        cmd_str = f'crontab -l > {tmp_dir} && echo \"*/5 * * * * bash ' \\\n                  f'{self.target_dir}/scripts/omp &>/dev/null\" >> {tmp_dir}'\n        self.sys_cmd(cmd_str)\n        self.sys_cmd(f'crontab {tmp_dir}')\n\n\nif __name__ == '__main__':\n    if len(sys.argv) < 2:\n        print(\"参数异常，请提供正确的安装路径\")\n        sys.exit(1)\n    t_dir = sys.argv[1]\n    position = 0\n    if len(sys.argv) >= 3:\n        try:\n            position = int(sys.argv[2])\n        except Exception as e:\n            print(f\"请输入正确下标:{e}\")\n    exec_cls = [PreUpdate, Mysql, Redis, Tengine, OmpWeb, OmpServer, PostUpdate]\n    exec_cls = exec_cls[position:]\n    obj_ls = [cls(t_dir) for cls in exec_cls]\n    [obj.run() for obj in obj_ls]\n"
  },
  {
    "path": "scripts/source/omp_salt_agent",
    "content": "#!/bin/bash\n# Author: jon.liu@yunzhihui.com\n# Date: 2021-05-10\n\n# 加载环境变量\ntest -f ~/.bash_profile && . ~/.bash_profile 2>/dev/null\ntest -f /etc/profile && . /etc/profile 2>/dev/null\ntest -f ~/.bashrc && . ~/.bashrc 2>/dev/null\n\n# 解决java服务启动后页面环境乱码问题\nexport LC_ALL=\"en_US.UTF-8\"\n\n# 如果是手动安装的agent，那么需要判断UNIQUE_INSTALL_DIR_FLAG这个是否已经被替换\n# 正常UNIQUE_INSTALL_DIR_FLAG应该为产品安装目录，对标/data/app\ntest -f UNIQUE_INSTALL_DIR_FLAG/bash_profile && . UNIQUE_INSTALL_DIR_FLAG/bash_profile\n\nCURRENT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\n\nOMP_DIR=$(cd ${CURRENT_DIR}/.. && pwd)\nOMP_PYTHON_BIN_DIR=${OMP_DIR}/env/bin\nSALT_MINION_PATH=${OMP_DIR}/env/bin/salt-minion\nPYTHON3=${OMP_DIR}/env/bin/python3\n\n######### 执行脚本用户判断\nuser_id=$(id -u)\nif [ ${user_id} -eq 0 ]; then\n  runUser=RUNUSER\nfi\n######### END\n\n# 解决openssl错误\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${OMP_DIR}/env/lib/\n\nSALT_AGENT_LOG_LEVEL=error\nSALT_CONFIG_DIR=${OMP_DIR}/conf\nSALT_AGENT_LOG_FILE=${OMP_DIR}/logs/salt-minion.log\nSALT_AGENT_PID_FILE=${OMP_DIR}/pid/salt-minion.pid\n\n# 执行命令的拼接\nSTART_COMMAND=\"${PYTHON3} ${SALT_MINION_PATH} \\\n    -c ${SALT_CONFIG_DIR} \\\n    --pid-file ${SALT_AGENT_PID_FILE} \\\n    --log-file=${SALT_AGENT_LOG_FILE} \\\n    --log-file-level=${SALT_AGENT_LOG_LEVEL} \\\n    -d\"\n\nfunction check_key_file() {\n  MINION_KEY_FILE=\"${OMP_DIR}/data/etc/salt/pki/minion/minion_master.pub\"\n  if [ -f $MINION_KEY_FILE ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nfunction clean_and_restart() {\n  echo \"agent clean and restart\"\n  agent_stop\n  sleep 1\n  rm -rf ${OMP_DIR}/data/*\n  echo \"\" >${SALT_AGENT_LOG_FILE}\n  if [ ${user_id} -eq 0 ]; then\n    su $runUser -c \"${START_COMMAND}\"\n  else\n    ${START_COMMAND}\n  fi\n  sleep 5\n}\n\nfunction recheck_agent() {\n  sleep 20\n  num=1\n  while (($num < 10)); do\n    let \"num++\"\n    check_key_file\n    if [ $? -eq 0 ]; then\n      cat ${SALT_AGENT_LOG_FILE} | grep 'ValueError: RSA key format is not supported' >/dev/null\n      if [ $? -eq 0 ]; then\n        clean_and_restart\n        continue\n      fi\n      return 0\n    fi\n    clean_and_restart\n    sleep 20\n  done\n  return 1\n}\n\nfunction check_master_port_access() {\n  master_ip_flag=$(cat ${OMP_DIR}/conf/minion | grep 'master:' | tr \"master:\" \" \")\n  master_ip=${master_ip_flag// /}\n  master_port_flag=$(cat ${OMP_DIR}/conf/minion | grep 'master_port:' | tr \"master_port:\" \" \")\n  master_port=${master_port_flag// /}\n  </dev/tcp/${master_ip}/${master_port}\n  return $?\n}\n\nfunction agent_start() {\n  check_master_port_access\n  if [ $? -ne 0 ]; then\n    echo \"Can not connect master, please check master_service or firewalld\"\n    exit 1\n  fi\n  agent_status\n  if [ $? -eq 0 ]; then\n    exit 0\n  fi\n  if [ ${user_id} -eq 0 ]; then\n    chown -R ${runUser}.${runUser} ${OMP_DIR}\n    su $runUser -c \"${START_COMMAND}\"\n  else\n    ${START_COMMAND}\n  fi\n  sleep 10\n  agent_status\n  if [ \"$1\" == \"init\" ]; then\n    recheck_agent\n    if [ $? -ne 0 ]; then\n      echo \"init agent failed, please check manual.\"\n      exit 1\n    fi\n    echo \"INIT_OMP_SALT_AGENT_SUCCESS\"\n  fi\n}\n\n#使用pid判断进程的方式\n#function agent_status() {\n#  if [ ! -f ${SALT_AGENT_PID_FILE} ]; then\n#    echo \"not running\"\n#    return 1\n#  fi\n#  kill -0 $(cat ${SALT_AGENT_PID_FILE}) >/dev/null 2>&1\n#  if [[ $? -eq 0 ]]; then\n#    echo \"running\"\n#    return 0\n#  else\n#    echo \"not running\"\n#    return 1\n#  fi\n#}\n\n# 使用进程判断状态的方式\nfunction agent_status() {\n  ps -ef | grep \"${OMP_DIR}/env/bin/salt-minion\" | grep -v grep >/dev/null 2>&1\n  if [[ $? -eq 0 ]]; then\n    echo \"running\"\n    return 0\n  else\n    echo \"not running\"\n    return 1\n  fi\n}\n\nfunction agent_stop() {\n  ps -ef | grep \"${OMP_DIR}/env/bin/salt-minion\" | grep -v grep | awk '{print $2}' | xargs kill -9\n  sleep 1\n  agent_status\n}\n\nfunction agent_alive() {\n  agent_status\n  if [[ $? -eq 0 ]]; then\n    exit 0\n  else\n    agent_start\n  fi\n}\n\nfunction agent_init() {\n  # 部署初始化时删除data数据，安装报错\n  rm -rf ${OMP_DIR}/data/*\n  agent_start \"init\"\n  crontab -l 2>/dev/null | grep ${THIS_SCRIPT} >/dev/null\n  if [[ $? -eq 0 ]]; then\n    exit 0\n  else\n    crontab -l 2>/dev/null >/tmp/omp_agent_cron\n    grep ${THIS_SCRIPT} /tmp/omp_agent_cron\n    if [[ $? -eq 0 ]]; then\n      exit 0\n    else\n      echo \"*/5 * * * * bash ${THIS_SCRIPT} alive &>/dev/null\" >>/tmp/omp_agent_cron\n      crontab /tmp/omp_agent_cron && rm -f /tmp/omp_agent_cron\n    fi\n  fi\n}\n\nfunction help() {\n  echo \"bash omp_salt_agent [start|stop|status|restart|alive|init]\"\n}\n\nfunction main() {\n  case $1 in\n  start)\n    agent_start\n    ;;\n  status)\n    agent_status\n    ;;\n  stop)\n    agent_stop\n    ;;\n  restart)\n    agent_stop\n    agent_start\n    ;;\n  alive)\n    agent_alive\n    ;;\n  init)\n    agent_init\n    ;;\n  *)\n    help\n    ;;\n  esac\n}\n\nmain $@\n"
  },
  {
    "path": "scripts/source/omp_upgrade.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nimport yaml\nimport time\nimport shutil\nimport sys\nimport logging\nimport re\nfrom abc import abstractmethod, ABCMeta\n\nCURRENT_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\n# 升级包路径\nPROJECT_FOLDER = os.path.dirname(os.path.dirname(CURRENT_FILE_PATH))\n\n\nclass Logging:\n\n    @staticmethod\n    def log_new(app_name, path_log):\n        logger = logging.getLogger(app_name)\n        formatter = logging.Formatter('[%(asctime)s-%(levelname)s]: %(message)s')\n        logger.setLevel(level=logging.INFO)\n        file_handler = logging.FileHandler(path_log)\n        file_handler.setLevel(logging.INFO)\n        file_handler.setFormatter(formatter)\n        logger.addHandler(file_handler)\n        return logger\n\n\n# 流程 检查状态 关闭进程前准备 关闭进程 备份 升级 升级整体恢复\nclass Common(metaclass=ABCMeta):\n\n    def __init__(self, target_dir):\n        \"\"\"\n        target_dir 目标跟路径\n        base_dir 目标安装路径\n        backup_dir 升级备份路径\n        \"\"\"\n        self.base_dir = None\n        self.bin = None\n        self.target_dir = target_dir\n        self.check_target_dir()\n        self.target_data = os.path.join(self.target_dir, \"data\")\n        self.target_log = os.path.join(self.target_dir, \"logs\")\n        self.omp_conf = os.path.join(self.target_dir, \"config/omp.yaml\")\n        self.backup_dir = os.path.join(PROJECT_FOLDER, 'backup', self.get_class_name())\n        if os.path.exists(self.backup_dir):\n            print(f\"请确保该路径:{self.backup_dir}为空\")\n            sys.exit(1)\n        _name = f\"omp_upgrade_{self.get_class_name()}\"\n        self.logger = Logging.log_new(_name,\n                                      os.path.join(PROJECT_FOLDER, f\"logs/{_name}.log\"))\n\n    @abstractmethod\n    def get_class_name(self):\n        raise NotImplementedError(\"Must override get_class_name\")\n\n    def sys_cmd(self, cmd):\n        \"\"\"\n        shell脚本输出\n        :param cmd: linux命令\n        :return:\n        \"\"\"\n        shell = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n        stdout, stderr = shell.communicate()\n        stdout, stderr = bytes.decode(stdout), bytes.decode(stderr)\n        exit_code = shell.poll()\n        self.logger.info(\"执行cmd命令:{0},结果退出码:{1},执行详情:{2}\".format(cmd, shell.poll(), stdout))\n        if str(exit_code) != \"0\":\n            print(f\"{stderr},退出码:{exit_code}\")\n            sys.exit(1)\n        return stdout\n\n    def get_config_dic(self, conf_dir=None):\n        \"\"\"\n        获取配置文件详细信息\n        :return:\n        \"\"\"\n        conf_dir = conf_dir if conf_dir else self.omp_conf\n        with open(conf_dir, \"r\", encoding=\"utf8\") as fp:\n            return yaml.load(fp, Loader=yaml.FullLoader)\n\n    @staticmethod\n    def replace_placeholder(file_path, data):\n        \"\"\"\n        替换占位符\n        :param file_path: 文件路径\n        :param data: 占位符映射关系\n        :type data: dict\n        :return:\n        \"\"\"\n        if not os.path.exists(file_path):\n            print(f\"无法找到文件{file_path}\")\n            sys.exit(1)\n        with open(file_path, \"r\", encoding=\"utf8\") as fp:\n            content = fp.read()\n        for key, value in data.items():\n            content = content.replace(key, str(value))\n        with open(file_path, \"w\", encoding=\"utf8\") as fp:\n            fp.write(content)\n\n    def get_run_user(self):\n        \"\"\"\n        获取程序运行用户\n        :return:\n        \"\"\"\n        global_user = self.get_config_dic().get(\"global_user\")\n        if global_user != \"root\":\n            return global_user\n        default_user = \"omp\"\n        self.sys_cmd(f\"id {default_user} || useradd -s /bin/bash {default_user}\")\n        return default_user\n\n    def check_target_dir(self):\n        try:\n            dirs = os.listdir(self.target_dir)\n            if 'omp_server' not in dirs or 'omp_web' not in dirs:\n                print(\"此目标路径非标准安装路径\")\n                sys.exit(1)\n        except Exception as e:\n            print(f\"路径异常，请检查路径，标准路径如/data/omp/ :{e}\")\n\n    def run(self):\n        print(f\"开始升级{self.get_class_name()}，请不要手动中断\\n\")\n        self.backup()\n        self.install()\n\n    def pre_backup(self):\n        return\n\n    def backup(self):\n        return\n\n    def install(self):\n        return\n\n\nclass PreUpdate(Common):\n    \"\"\"\n    检查前置条件\n    \"\"\"\n\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.target_dir = self.target_dir + \"/\" if self.target_dir[-1] != '/' else self.target_dir\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def check_process(self):\n        \"\"\"\n        检查进程是否存在，存在返回false 不存在返回true\n        \"\"\"\n        cmd_str = f'ps -ef | grep {self.target_dir}|grep -v grep|wc -l'\n        stdout = self.sys_cmd(cmd_str)\n        if str(stdout).strip() == \"0\":\n            return True\n        return False\n\n    def crontab_stop(self):\n        cmd_str = f'crontab -l | grep -v \"{self.target_dir}scripts/omp all start\" 2>/dev/null | crontab -;'\n        self.sys_cmd(cmd_str)\n\n    def process_stop(self):\n        cmd_str = f'bash {self.target_dir}scripts/omp all stop'\n        self.sys_cmd(cmd_str)\n        # ToDo 需要各个停止脚本完全输出停止时不存在延迟停止进程\n        time.sleep(5)\n        if self.check_process():\n            return\n        time.sleep(5)\n        if self.check_process():\n            return\n        # 强制杀死进程\n        kill_cmd = f\"ps -ef |grep {self.target_dir}|grep -v grep|grep -v omp_upgrade|awk '{{print $2}}'|xargs kill -9\"\n        self.sys_cmd(kill_cmd)\n        # print(\"服务未停止成功，请检查服务并尝试手动停止进程\")\n        # sys.exit(1)\n\n    def run(self):\n        self.crontab_stop()\n        self.process_stop()\n\n\nclass PostUpdate(Common):\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def crontab_start(self):\n        tmp_dir = f'{PROJECT_FOLDER}/crontab.txt'\n        cmd_str = f'crontab -l > {tmp_dir} && echo \"*/5 * * * * bash ' \\\n                  f'{self.target_dir}/scripts/omp all start &>/dev/null\" >> {tmp_dir}'\n        self.sys_cmd(cmd_str)\n        self.sys_cmd(f'crontab {tmp_dir}')\n\n    def compare_conf(self):\n        new_omp = self.get_config_dic(f'{PROJECT_FOLDER}/config/omp.yaml')\n        old_omp = self.get_config_dic()\n        for key, values in new_omp.items():\n            if key not in old_omp.keys():\n                old_omp[key] = values\n        old_omp['basic_order'] = new_omp.get('basic_order')\n        old_omp['test_product_list'] = new_omp.get('test_product_list')\n        old_omp['mysql']['port'] = new_omp['mysql']['port']\n        old_omp['redis']['port'] = new_omp['redis']['port']\n        with open(self.omp_conf, \"w\", encoding=\"utf8\") as fp:\n            yaml.dump(old_omp, fp)\n\n    def run(self):\n        self.crontab_start()\n        self.compare_conf()\n\n\nclass Mysql(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.target_data = os.path.join(self.target_data, \"mysql\")\n        self.target_log = os.path.join(self.target_log, \"mysql\")\n        self.mysql_config = self.get_config_dic().get(\"mysql\")\n        self._dic = {\n            \"CW_MYSQL_USERNAME\": self.mysql_config.get(\"username\"),\n            \"CW_MYSQL_PASSWORD\": self.mysql_config.get(\"password\"),\n            \"CW_MYSQL_PORT\": self.mysql_config.get(\"port\"),\n            \"CW_MYSQL_RUN_USER\": self.get_run_user(),\n            \"CW_MYSQL_DATA_DIR\": self.target_data,\n            \"CW_MYSQL_ERROR_LOG_DIR\": self.target_log,\n            \"CW_MYSQL_BASE_DIR\": os.path.join(self.target_dir, \"component/mysql\")\n        }\n        self.bin = os.path.join(self.target_dir, \"component/mysql/bin\")\n        self.base_dir = os.path.join(self.target_dir, \"component/mysql\")\n        self.python_django_dir = os.path.join(self.target_dir,\n                                              \"component/env/lib/python3.8/site-packages/django/db/backends/base\")\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def install(self):\n        \"\"\"\n        安装 mysql 逻辑\n        :return:\n        \"\"\"\n        # 修改pymysql源码\n        self.sys_cmd(\"mv {1}/features.py {0}\".format(self.python_django_dir, CURRENT_FILE_PATH))\n        self.sys_cmd(\"mv {0}/component/mysql {1}\".format(PROJECT_FOLDER, os.path.dirname(self.base_dir)))\n        # 创建日志目录\n        if not os.path.exists(self._dic[\"CW_MYSQL_ERROR_LOG_DIR\"]):\n            self.sys_cmd(f\"mkdir -p {self._dic['CW_MYSQL_ERROR_LOG_DIR']}\")\n        # 复制数据到目标目录\n        shutil.copytree(\n            os.path.join(self.base_dir, \"data\"),\n            os.path.join(self.target_data)\n        )\n        # my.cnf\n        self.replace_placeholder(\n            os.path.join(self.base_dir, \"my.cnf\"), self._dic)\n        # scripts/mysql\n        _mysql_path = os.path.join(self.base_dir, \"scripts/mysql\")\n        self.replace_placeholder(_mysql_path, self._dic)\n        # 启动服务\n        out = self.sys_cmd(f\"bash {_mysql_path} start\")\n        if \"mysql  [running]\" not in out:\n            print(f\"mysql启动失败: {out}\")\n            sys.exit(1)\n        time.sleep(30)\n        # 确保mysql启动成功并可用\n        _mysql_cli = os.path.join(self.bin, \"mysql\")\n        _mysql_cli = f\"{_mysql_cli} -S {os.path.join(self._dic['CW_MYSQL_DATA_DIR'], 'mysql.sock')} -uroot\"\n        try_times = 0\n        while try_times < 10:\n            out = self.sys_cmd(f\"{_mysql_cli} -e 'SHOW DATABASES;'\")\n            if \"information_schema\" in out:\n                break\n            try_times += 1\n            time.sleep(10)\n        else:\n            print(\"mysql启动失败\")\n            sys.exit(1)\n        # 创建数据库\n        create = \"create database omp default charset utf8 collate utf8_general_ci;\"\n        self.sys_cmd(f\"{_mysql_cli} -e '{create}'\")\n        _u = self._dic[\"CW_MYSQL_USERNAME\"]\n        _p = self._dic[\"CW_MYSQL_PASSWORD\"]\n        self.sys_cmd(\n            f\"\"\" {_mysql_cli} -e 'grant all privileges on `omp`.* to \"{_u}\"@\"%\" \n            identified by \"{_p}\" with grant option;' \"\"\")\n        flush = \"flush privileges;\"\n        self.sys_cmd(f\"{_mysql_cli} -e '{flush}'\")\n        self.sys_cmd(\n            \"{0}/mysql  -S{3}/mysql.sock -u{1} -p{2} -Domp< {4}/omp.sql\".format(\n                self.bin,\n                self._dic.get(\n                    \"CW_MYSQL_USERNAME\",\n                    \"common\"), self._dic.get(\n                    \"CW_MYSQL_PASSWORD\", \"Common@123\"),\n                self._dic.get(\n                    \"CW_MYSQL_DATA_DIR\",\n                    \"/data/omp/data/mysql\"),\n                self.backup_dir))\n        # print(self.sys_cmd(\"{0}/mysql --version\".format(self.bin)))\n\n    def pre_backup(self):\n        \"\"\"\n        进程关闭前进行的操作\n        创建备份路径。备份数据等。\n        样例：$MySQLDump --single-transaction -P3307\n        - ucommon - h'127.0.0.1'  -pCommon@123\n        -a --default-character-set=utf8 --skip-comments omp\n        \"\"\"\n        self.sys_cmd(f'mkdir -p {self.backup_dir}')\n        self.sys_cmd(\"{0}/mysqldump --single-transaction -P{1} \"\n                     \"-u{2} -h'127.0.0.1'  -p{3} -a \"\n                     \"--default-character-set=utf8 --skip-comments omp 2>/dev/null > {4}/omp.sql\".format(\n            self.bin, self._dic.get(\"CW_MYSQL_PORT\", \"3307\"),\n            self._dic.get(\"CW_MYSQL_USERNAME\", \"common\"),\n            self._dic.get(\"CW_MYSQL_PASSWORD\", \"Common@123\"),\n            self.backup_dir\n        ))\n\n    def backup(self):\n        \"\"\"\n        \"\n        \"\"\"\n        self.sys_cmd(\n            \"mv {0} {3}/mysqldata && mv {1} {3}/mysqllogs && mv {2} {3}/mysqlbase\".format(\n                self.target_data, self.target_log, self.base_dir, self.backup_dir))\n\n\nclass Grafana(Common):\n    def __init__(self, target_dir):\n        \"\"\"\n        param: target_dir 目标项目路径\n        PROJECT_FOLDER 升级包跟路径\n        base_dir 目标安装路径\n        backup_dir 升级备份路径\n        \"\"\"\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/grafana')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def update_grafana(self):\n        \"\"\"\n        更新当前服务需要更改的配置\n        :return:\n        \"\"\"\n        omp_grafana_log_path = os.path.join(self.target_log, \"grafana\")\n\n        # 修改 conf/defaults.ini\n        cdi_file = os.path.join(self.base_dir, 'conf', 'defaults.ini')\n        cw_grafana_port = self.get_config_dic(). \\\n            get('monitor_port', {}).get('grafana', '19014')\n        cdi_placeholder_script = {'${CW-HTTP-PORT}': cw_grafana_port,\n                                  '${OMP_GRAFANA_LOG_PATH}': omp_grafana_log_path}\n\n        self.replace_placeholder(cdi_file, cdi_placeholder_script)\n\n        # 修改 scripts/grafana\n        sa_placeholder_script = {'${OMP_GRAFANA_LOG_PATH}': omp_grafana_log_path}\n        if not os.path.exists(omp_grafana_log_path):\n            os.makedirs(omp_grafana_log_path)\n        sa_file = os.path.join(self.base_dir, 'scripts', 'grafana')\n        self.replace_placeholder(sa_file, sa_placeholder_script)\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"mv {self.base_dir} {self.backup_dir}/grafanabase\")\n\n    def install(self):\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/component/grafana  {self.target_dir}/component\")\n        self.sys_cmd(f\"mkdir -p {self.base_dir}/data\")\n        self.sys_cmd(f\"cp -a {self.backup_dir}/grafanabase/data/* {self.base_dir}/data/\")\n        self.update_grafana()\n\n\nclass OmpWeb(Common):\n    \"\"\"\n    omp_web升级\n    \"\"\"\n\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'omp_web')\n        self.python_django_dir = os.path.join(\n            self.target_dir,\n            \"/component/env/lib/python3.8/site-packages/django/db/backends/base\")\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"mv {self.base_dir} {self.backup_dir}/omp_web_base\")\n\n    def install(self):\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/omp_web  {self.target_dir}\")\n\n\nclass Tengine(Common):\n    \"\"\"\n    tengine升级\n    \"\"\"\n\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/tengine')\n\n    @staticmethod\n    def replace_str(re_pattern, new_str, file_name):\n        \"\"\"\n        # 字符跨行替换，支持正则\n        # sed -i \"s#^user .*##g\" ${CW_INSTALL_APP_DIR}/app/conf/app.conf\n        \"\"\"\n        content = ''\n        with open(file_name, 'r') as fp:\n            conf_server = fp.readlines()\n            for i in conf_server:\n                conf_server = re.sub(re_pattern, new_str, i)\n                content = content + conf_server\n        with open(file_name, 'w') as fp:\n            fp.write(content)\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"cp -r {self.base_dir} {self.backup_dir}/tengine_base\")\n        self.sys_cmd(f\"mv {self.base_dir}/sbin/nginx  {self.backup_dir}\")\n\n    def install(self):\n        conf_dir = os.path.join(f\"{self.base_dir}/conf/nginx.conf\")\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/component/tengine/sbin/nginx {self.base_dir}/sbin\")\n        self.replace_str(\"worker_processes  10;\",\n                         \"\",\n                         conf_dir)\n        self.replace_str(\"worker_rlimit_nofile 102400;\",\n                         \"worker_processes  10;\\nworker_rlimit_nofile 102400;\",\n                         conf_dir)\n        self.replace_str(\"limit_req_zone\",\n                         \"#limit_req_zone\",\n                         conf_dir)\n\n\nclass OmpServer(Common):\n    \"\"\"\n    omp_server升级\n    \"\"\"\n\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'omp_server')\n        self.bin = os.path.join(self.base_dir, \"manage.py\")\n        self.python_django_dir = os.path.join(\n            PROJECT_FOLDER,\n            \"component/env/bin/python3.8\")\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"mv {self.base_dir} {self.backup_dir}/omp_server_base\")\n\n    def explain_one(self, dirs, ignore=None):\n        out_ls = self.sys_cmd(f'find {dirs} -name \\\"*\\\"').split()\n        compare_set = set()\n        dirs = dirs if dirs.endswith(\"/\") else f'{dirs}/'\n        for i in out_ls[1:]:\n            if not i.startswith(dirs):\n                continue\n            if len(i.split(dirs)) < 2:\n                continue\n            if i.split(dirs)[1].split(\"/\")[0] in ignore:\n                continue\n            compare_set.add(i.split(dirs)[1])\n        return compare_set\n\n    def compare_dir(self, source_dir, target_dir, is_exec=True):\n        \"\"\"\n        文件比对模块\n        需要注意文件格式 带/都带 不带斜杠都不带\n        \"\"\"\n        # 计算出旧版本的文件目录与目标版本目录的不同\n        ignore = [\"back_end_verified\", \"front_end_verified\", \"verified\"]\n        force_override = [\"_modules\", \"grafana_dashboard_json\",\n                          \"omp_salt_agent.tar.gz\", \"omp_monitor_agent-0.5.tar.gz\"\n                          ]\n        ignore.extend(force_override)\n        need_change = self.explain_one(source_dir, ignore) - self.explain_one(target_dir, ignore)\n        # 排序\n        dir_ls = []\n        file_ls = []\n        for i in need_change:\n            if os.path.isdir(os.path.join(source_dir, i)):\n                dir_ls.append(i)\n            file_ls.append(i)\n        # 执行迁移\n        if is_exec:\n            for d in dir_ls:\n                self.sys_cmd(f\"mkdir -p {os.path.join(target_dir, d)}\")\n            for f in file_ls:\n                self.sys_cmd(f\"cp -a {os.path.join(source_dir, f)} {os.path.join(target_dir, f)}\")\n        # 部分路径强制覆盖\n        for force in force_override:\n            f\"cp -a {os.path.join(source_dir, force)} {os.path.join(target_dir, force)}\"\n\n    def install(self):\n        # 启动redis\n        self.sys_cmd(f\"bash {os.path.join(self.target_dir, 'scripts/omp')} redis start\")\n        time.sleep(1)\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/config/private_key.pem {self.target_dir}/config/\")\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/omp_server  {self.target_dir}\")\n        # 对比文件\n        self.compare_dir(os.path.join(PROJECT_FOLDER, \"package_hub\"),\n                         os.path.join(self.target_dir, \"package_hub\"))\n\n        self.sys_cmd(f\"{self.python_django_dir} {self.bin} migrate\")\n        # print(f\"1. 请手动执行 {self.python_django_dir} {self.bin} migrations\\n\")\n        print(f\"1. 请手动备份 mv {self.target_dir}/scripts {os.path.dirname(self.backup_dir)}/omp_scripts_bak\")\n        print(f\"2. 请手动移动 \\cp -a {PROJECT_FOLDER}/scripts {self.target_dir}/scripts\")\n        print(f\"3. 请手动执行 bash {self.target_dir}/scripts/omp all restart\")\n        # print(f\"1.7.1版本之前的版本升级需要执行，普通用户安装需要执行，其余忽略。,\"\n        #      f\"jh含纳管版本需更新覆盖omp_salt_agent.tar.gz并界面重装主机\")\n        print(\"4. 执行前请确认上述步骤已完成 请手动执行 export LD_LIBRARY_PATH=:{0} && {1} {2}\".format(\n            os.path.join(self.target_dir, 'component/env/lib/'),\n            os.path.join(self.target_dir, 'component/env/bin/python3.8'),\n            os.path.join(self.target_dir, 'scripts/source/update_data.py')\n        ))\n\n\nclass Python(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/env')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"mv {self.base_dir} {self.backup_dir}/python_base\")\n\n    def install(self):\n        self.sys_cmd(f\"cp -ra {PROJECT_FOLDER}/component/env  {os.path.dirname(self.base_dir)}\")\n\n\nclass Redis(Common):\n    def __init__(self, target_dir):\n        super().__init__(target_dir)\n        self.base_dir = os.path.join(self.target_dir, 'component/redis')\n\n    def get_class_name(self):\n        return self.__class__.__name__\n\n    def backup(self):\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}\")\n        self.sys_cmd(f\"cp -r {self.base_dir} {self.backup_dir}/redis_base\")\n        self.sys_cmd(f\"mkdir -p {self.backup_dir}/redis_base/bin_bak\")\n        self.sys_cmd(f\"mv {self.base_dir}/bin/redis* {self.backup_dir}/redis_base/bin_bak\")\n\n    def install(self):\n        self.sys_cmd(f\"cp -a {PROJECT_FOLDER}/component/redis/bin/redis* {self.base_dir}/bin\")\n\n\nif __name__ == '__main__':\n    if len(sys.argv) < 2:\n        print(\"参数异常，请提供正确的安装路径\")\n        sys.exit(1)\n    t_dir = sys.argv[1]\n    position = 0\n    if len(sys.argv) >= 3:\n        try:\n            position = int(sys.argv[2])\n        except Exception as e:\n            print(f\"请输入正确下标:{e}\")\n    exec_cls = [PreUpdate, Mysql, Redis, Grafana, Tengine, OmpWeb, OmpServer, Python, PostUpdate]\n    exec_cls = exec_cls[position:]\n    print(\"开始升级前置操作，请不要手动中断\")\n    obj_ls = [cls(t_dir) for cls in exec_cls]\n    [obj.pre_backup() for obj in obj_ls]\n    [obj.run() for obj in obj_ls]\n    print(\"请执行上述手动命令后，查询omp状态无异常后，则升级完成\")\n"
  },
  {
    "path": "scripts/source/repair_dirty_data.py",
    "content": "# -*- coding:utf-8 -*-\nimport os\nimport sys\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import DetailInstallHistory, Host, Service\n\nif __name__ == '__main__':\n    detail_objs = DetailInstallHistory.objects.all()\n    for obj in detail_objs:\n        run_user = Host.objects.filter(ip=obj.service.ip).first().username\n        if run_user != 'root':\n            obj.install_detail_args['run_user'] = run_user\n            for i in obj.install_detail_args.get('install_args', []):\n                if i.get('key', '') == 'run_user':\n                    i['default'] = run_user\n            obj.save()\n        else:\n            continue\n    service_obj = Service.split_objects.all()\n    for ser in service_obj:\n        if ser.service.app_name == \"hadoop\":\n            ser.service_status = Service.SERVICE_STATUS_NORMAL\n            ser.save()\n"
  },
  {
    "path": "scripts/source/salt",
    "content": "#!/bin/bash\n\n# salt的控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nAPP_NAME=\"salt\"\nPYTHON3=\"${PROJECT_FOLDER}/component/env/bin/python3\"\nSALT_CONFIG_DIR=\"${PROJECT_FOLDER}/config/salt\"\nSALT_PID_FILE=\"${PROJECT_FOLDER}/logs/salt-master.pid\"\nSALT_LOG_FILE=\"${PROJECT_FOLDER}/logs/salt-master.log\"\n\nstart() {\n  real_status\n  if [ $? -eq 0 ];then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    ${PYTHON3} ${PROJECT_FOLDER}/component/env/bin/salt-master \\\n      -c ${SALT_CONFIG_DIR} \\\n      --pid-file ${SALT_PID_FILE} \\\n      --log-file=${SALT_LOG_FILE} \\\n      --log-file-level=error \\\n      -d\n    echo \"${APP_NAME} [running]\"\n  fi\n}\n\nstop() {\n  real_status\n  if [ $? -eq 0 ]; then\n    ps -ef | grep \"${PROJECT_FOLDER}/component/env/bin/salt-master\" | grep -v grep | awk '{print $2}' | xargs kill -9\n  fi\n  sleep 3\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 1\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 0\n  fi\n}\n\nreal_status() {\n  salt_master_num=$(ps -ef | grep ${PROJECT_FOLDER}/component/env/bin/salt-master | grep -v grep | wc -l)\n  if [ $salt_master_num -gt 12 ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nstatus() {\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 1\n  fi\n}\n\ncase $1 in\nstart) start ;;\nstop) stop ;;\nrestart)\n  stop\n  start\n  ;;\nstatus)\n  status\n  ;;\n*)\n  echo \"usage: $0 [start|stop|restart|status]\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/source/salt_agent_manager",
    "content": "#!/bin/bash\n\n# salt管理脚本\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nPYTHON3=\"${PROJECT_FOLDER}/component/env/bin/python3\"\nSALT=\"${PROJECT_FOLDER}/component/env/bin/salt\"\nSALT_KEY=\"${PROJECT_FOLDER}/component/env/bin/salt-key\"\nSALT_CONFIG_DIR=\"${PROJECT_FOLDER}/config/salt\"\n\n# 列出当前salt管理的key\nfunction list_salt_keys() {\n  $PYTHON3 $SALT_KEY -L -c $SALT_CONFIG_DIR\n}\n\nfunction delete_all_keys() {\n  $PYTHON3 $SALT_KEY -D -c $SALT_CONFIG_DIR\n}\n\nfunction delete_one_key() {\n  $PYTHON3 $SALT_KEY -d $1 -c $SALT_CONFIG_DIR\n}\n\nfunction cmd() {\n  # $1为ip地址 $2为要执行的命令\n  $PYTHON3 $SALT \"$1\" cmd.run \"$2\" -c $SALT_CONFIG_DIR\n}\n\nfunction ping() {\n  $PYTHON3 $SALT \"$1\" test.ping -c $SALT_CONFIG_DIR\n}\n\nfunction echo_help() {\n  echo \"bash salt_agent_manager [ping|cmd|delete_one_key|delete_all_keys|list_salt_keys]\"\n}\n\nif [[ $# -eq 0 ]]; then\n  echo_help\nelse\n  case $1 in\n  ping)\n    shift\n    ping \"$@\"\n    ;;\n  cmd)\n    shift\n    cmd \"$@\"\n    ;;\n  delete_one_key)\n    shift\n    delete_one_key \"$@\"\n    ;;\n  delete_all_keys)\n    shift\n    delete_all_keys \"$@\"\n    ;;\n  list_salt_keys)\n    shift\n    list_salt_keys \"$@\"\n    ;;\n  *)\n    echo_help\n    ;;\n  esac\nfi\n"
  },
  {
    "path": "scripts/source/scan_tar_file.py",
    "content": "# -*- coding:utf-8 -*-\nimport os\nimport sys\nimport time\nfrom datetime import datetime\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom utils.plugin.public_utils import local_cmd\nfrom app_store.tmp_exec_back_task import back_end_verified_init\nfrom app_store.tmp_exec_back_task import RedisLock\nfrom utils.parse_config import (\n    OMP_REDIS_PORT, OMP_REDIS_PASSWORD, OMP_REDIS_HOST\n)\nfrom db_models.models import UploadPackageHistory\n\n\ndef log_print(message, level=\"info\"):\n    msg_str = f\"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} \" \\\n              f\"{level.upper()} \" \\\n              f\"{message}\"\n    print(msg_str)\n\n\ndef check_upload(uuid):\n    valid_uuids = UploadPackageHistory.objects.filter(\n        operation_uuid=uuid,\n        package_parent__isnull=True,\n    ).values_list(\"package_status\", flat=True)\n    upload_obj = UploadPackageHistory.objects.filter(\n        operation_uuid=uuid,\n        package_parent__isnull=True,\n    ).first()\n    if 5 in valid_uuids:\n        for i in range(6):\n            time.sleep(5)\n            print(f\"等待发布第{i}次\")\n            upload_obj.refresh_from_db()\n            if upload_obj.package_status != 5:\n                break\n    return UploadPackageHistory.objects.filter(\n        operation_uuid=uuid,\n        package_parent__isnull=True,\n    ).values_list(\"package_name\", \"package_status\", \"error_msg\")\n\n\nclass ScanFile:\n    def __init__(self):\n        self._scan_lock_key = \"back_end_verified\"\n        self._move_lock_key = \"mv_back_end_verified\"\n        self.redis = RedisLock(\n            host=OMP_REDIS_HOST, port=OMP_REDIS_PORT,\n            password=OMP_REDIS_PASSWORD)\n        self.move = True\n        self.count = 0\n\n    def valid_package(self):\n        try:\n            while self.move:\n                lock, redis_key = self.redis.get_lock(self._move_lock_key)\n                if not lock:\n                    redis_key.lpush(self._move_lock_key, \"moving\")\n                    redis_key.expire(self._move_lock_key, 1200)\n                    self.move = False\n                log_print(\"有安装包正在上传至back_end_verified路径\")\n                time.sleep(5)\n\n            back_verified = os.path.join(\n                PROJECT_DIR, \"package_hub/back_end_verified\"\n            )\n            tmp_verified = os.path.join(\n                PROJECT_DIR, \"package_hub/tmp_end_verified\"\n            )\n            service_name = os.listdir(tmp_verified)\n            exec_name = [\n                os.path.join(tmp_verified, p) for p in service_name\n                if os.path.isfile(os.path.join(tmp_verified, p)) and (p.endswith('.tar') or p.endswith('.tar.gz'))\n            ]\n            if not exec_name:\n                log_print(\"无需要扫描的安装包，或安装包已被上一个任务获取送出。\")\n                sys.exit(0)\n            while self.redis.get_lock()[0]:\n                log_print(f\"后台有扫描任务正在执行,等待次数{self.count}。\")\n                log_print(f\"等待扫描安装包列表：{' '.join(exec_name)}\")\n                time.sleep(5)\n                self.count += 1\n                if self.count > 100:\n                    log_print(\"扫描超时，或队列积压严重，请重试。\")\n                    sys.exit(1)\n            _cmd_str = f'mv {\" \".join(exec_name)} {back_verified}'\n            _out, _err, _code = local_cmd(_cmd_str)\n            if _code:\n                log_print(f\"执行移动文件:{_cmd_str}发生错误:{_out}\")\n                sys.exit(1)\n            uuid, exec_name = back_end_verified_init(\n                operation_user=\"admin\"\n            )\n            log_print(\"后台安装包扫描提交至omp\")\n            result = True\n            while result:\n                valid_uuids = UploadPackageHistory.objects.filter(\n                    operation_uuid=uuid,\n                    package_parent__isnull=True,\n                ).exclude(\n                    package_status__in=[2, 5])\n                if len(exec_name) != valid_uuids.count():\n                    log_print(\"后台扫描中\")\n                    time.sleep(5)\n                # elif 4 in valid_uuids.values_list(\"package_status\", flat=True):\n                #    log_print(\"后台扫描校验失败\")\n                #    result = False\n                else:\n                    status = True\n                    result_ls = []\n                    package_status = {\n                        0: \"成功\",\n                        1: \"失败\",\n                        2: \"解析中\",\n                        3: \"发布成功\",\n                        4: \"发布失败\",\n                        5: \"发布中\",\n                    }\n                    valid_uuids = check_upload(uuid)\n                    for value in valid_uuids:\n                        if value[1] != 3:\n                            status = False\n                        result_ls.append(f\"安装包{value[0]},扫描状态:{package_status.get(value[1], '')},扫描信息:{value[2]}\")\n                    log_print(\"应用商店扫描完成\")\n                    log_print(\"\\n\".join(result_ls))\n                    if status:\n                        sys.exit(0)\n                    else:\n                        sys.exit(1)\n        except Exception as e:\n            log_print(f\"后台扫描失败:{e}\")\n            sys.exit(1)\n        finally:\n            self.redis.rdcon.delete(self._move_lock_key)\n\n\nif __name__ == \"__main__\":\n    scan = ScanFile()\n    scan.valid_package()\n"
  },
  {
    "path": "scripts/source/scan_tools.py",
    "content": "# -*- coding:utf-8 -*-\nimport os\nimport sys\n\nimport django\n\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\n\nfrom tool.find_tools import find_tools_package\n\n\nif __name__ == '__main__':\n    result = find_tools_package()\n    if result:\n        print(\"扫描小工具完成！\")\n        sys.exit(0)\n    print(\"扫描小工具完成,部分工具包校验失败，详细请查看logs/debug.log！\")\n    sys.exit(1)\n"
  },
  {
    "path": "scripts/source/service_manager.py",
    "content": "# -*- coding:utf-8 -*-\nimport os\nimport sys\nimport time\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom services.tasks import exec_action\nfrom db_models.models import Service, ApplicationHub\nfrom concurrent.futures import (\n    ThreadPoolExecutor, as_completed\n)\nfrom utils.parse_config import BASIC_ORDER\nimport logging\nfrom utils.plugin.salt_client import SaltClient\n\nlogger = logging.getLogger('server')\n\nTHREAD_POOL_MAX_WORKERS = 20\n\n\ndef check_result(future_list):\n    \"\"\"\n    查看线程结果\n    \"\"\"\n    for future in as_completed(future_list):\n        ip, message = future.result()\n        print(f\"{ip} {message}\")\n    time.sleep(5)\n\n\ndef order(service_obj, actions):\n    \"\"\"\n    执行顺序排序\n    \"\"\"\n    basic_lists = []\n    for m in range(10):\n        if m not in BASIC_ORDER:\n            break\n        basic_list = [\n            item for item in service_obj\n            if item.service.app_name in BASIC_ORDER[m]\n        ]\n        if len(basic_list) != 0:\n            basic_lists.append(basic_list)\n    self_service = [\n        item for item in service_obj\n        if item.service.app_type == ApplicationHub.APP_TYPE_SERVICE\n    ]\n    if len(self_service) != 0:\n        basic_lists.append(self_service)\n    if actions == \"stop\":\n        basic_lists.reverse()\n    if actions == \"restart\":\n        basic_copy = basic_lists[:]\n        basic_lists.reverse()\n        basic_lists.extend(basic_copy)\n    return basic_lists\n\n\ndef service_status(service_objs):\n    \"\"\"\n    查询全局状态\n    \"\"\"\n    salt_obj = SaltClient()\n    ips = {}\n    for obj in service_objs:\n        exec_cmd = obj.service_controllers.get(\n            \"start\", \"\").replace(\" start\", \" status\")\n        if not ips.get(obj.ip):\n            ips[obj.ip] = exec_cmd\n        else:\n            ips[obj.ip] = ips.get(obj.ip) + f\" && {exec_cmd}\"\n\n    for ip, exe_action in ips.items():\n        is_success, info = salt_obj.cmd(ip, exe_action, 600)\n        print(f\"{ip}:{is_success}\\n{info}\")\n\n\ndef service_actions(actions, ip=None, service_name=None):\n    \"\"\"\n    执行服务启停，支持ip筛选\n    状态为删除中，安装中，升级中，会滚中状态不被允许执行服务起停操作\n    \"\"\"\n    choice = {\n        \"start\": [\"1\", Service.SERVICE_STATUS_STARTING],\n        \"stop\": [\"2\", Service.SERVICE_STATUS_STOPPING],\n        \"restart\": [\"3\", Service.SERVICE_STATUS_RESTARTING]\n    }\n    action = choice.get(actions)\n    old_actions = actions\n    actions = \"start\" if actions in [\"status\", \"restart\"] else actions\n    service_obj = Service.objects.filter(service_controllers__has_key=actions).exclude(\n        service_status__in=[Service.SERVICE_STATUS_INSTALLING,\n                            Service.SERVICE_STATUS_UPGRADE,\n                            Service.SERVICE_STATUS_ROLLBACK,\n                            Service.SERVICE_STATUS_DELETING\n                            ]\n    ).select_related(\"service\")\n    if not action:\n        service_status(service_obj)\n        return\n    if ip:\n        service_obj = service_obj.filter(ip=ip)\n    if service_name:\n        service_obj = service_obj.filter(service__app_name=service_name)\n    service_obj.update(service_status=action[1])\n    service_ls = order(service_obj, old_actions)\n    for index, service_ids in enumerate(service_ls):\n        # 重启重写\n        if old_actions == \"restart\":\n            if index < len(service_ls) / 2:\n                action[0] = \"2\"\n            else:\n                action[0] = \"1\"\n        with ThreadPoolExecutor(THREAD_POOL_MAX_WORKERS) as executor:\n            future_list = []\n            for i in service_ids:\n                # print(i.get(\"service_instance_name\"))\n                future_obj = executor.submit(\n                    exec_action, action[0],\n                    i.id, \"admin\", need_sleep=False\n                )\n                future_list.append(future_obj)\n            check_result(future_list)\n\n\nif __name__ == '__main__':\n    try:\n        actions = sys.argv[1:]\n        if actions[0] not in [\"start\", \"stop\", \"restart\", \"status\"]:\n            print(\"请输入正确的(start,stop,restart,status)参数\")\n            sys.exit(1)\n        service_actions(*actions)\n    except Exception as err:\n        print(f\"请输入参数(start,stop,restart,status):{err}\")\n"
  },
  {
    "path": "scripts/source/tengine",
    "content": "#!/bin/bash\n\n# tengine的控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nAPP_NAME=\"tengine\"\n\nstart() {\n  real_status\n  if [ $? -eq 0 ];then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    $PROJECT_FOLDER/component/tengine/sbin/nginx -p $PROJECT_FOLDER/component/tengine >/dev/null  2>&1\n    echo \"${APP_NAME} [running]\"\n  fi\n}\n\nstop() {\n  real_status\n  if [ $? -eq 1 ]; then\n    echo \"${APP_NAME} [not running]\"\n    return 0\n  else\n    $PROJECT_FOLDER/component/tengine/sbin/nginx -p $PROJECT_FOLDER/component/tengine -s stop\n    sleep 3\n    real_status\n    if [ $? -eq 0 ]; then\n      echo \"${APP_NAME} [running]\"\n      return 1\n    else\n      echo \"${APP_NAME} [not running]\"\n      return 0\n    fi\n  fi\n}\n\nreal_status() {\n  tengine_status=$(ps -ef | grep nginx | grep ${PROJECT_FOLDER}/component/tengine | grep -v grep)\n  if [ -n \"$tengine_status\" ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nstatus() {\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 1\n  fi\n}\n\ncase $1 in\nstart) start ;;\nstop) stop ;;\nrestart)\n  stop\n  start\n  ;;\nstatus)\n  status\n  ;;\n*)\n  echo \"usage: $0 [start|stop|restart|status]\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/source/uninstall_app_store.py",
    "content": "import os\nimport sys\nimport time\n\nimport django\nimport subprocess\nimport argparse\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nPACKAGE_DIR = os.path.join(PROJECT_DIR, \"package_hub\")\nPYTHON_PATH = os.path.join(PROJECT_DIR, 'component/env/bin/python3')\nSALT_KEY_PATH = os.path.join(PROJECT_DIR, \"component/env/bin/salt-key\")\nSALT_CONFIG_PATH = os.path.join(PROJECT_DIR, \"config/salt\")\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\nMAX_NUM = 8\n\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nimport logging\nfrom db_models.models import (ApplicationHub, ProductHub, Service, UpgradeDetail)\n\n\nclass UninstallServices(object):\n    def __init__(self, app_name=None, product=None, version=None):\n        self.app_name = app_name\n        self.version = version\n        self.product = product\n        self._app_obj = None\n        self.product_all = False\n        self.del_dir = set()\n\n    def check_database(self):\n        \"\"\"\n        支持服务开头匹配,服务版本开头匹配，删除产品版本必须完全匹配，且只支持一个一个产品卸载\n        \"\"\"\n        if not self.app_name and not self.product:\n            print(\"请输入卸载产品名或服务名称\")\n            sys.exit(1)\n\n        # 服务存在，产品不存在，当产品为基础组建时则认为正常\n        if self.app_name and not self.product:\n            app_obj = ApplicationHub.objects.filter(app_name__startswith=self.app_name)\n            if app_obj and 1 not in app_obj.values_list(\"app_type\", flat=True):\n                self._app_obj = app_obj\n            else:\n                print(\"存在服务为产品，请输入服务名对应的产品名\")\n                sys.exit(1)\n        # 产品存在 服务不存在\n        if not self.app_name and self.product:\n            self._app_obj = ApplicationHub.objects.filter(product__pro_name=self.product)\n            self.product_all = True\n        # 产品服务均存在\n        if self.app_name and self.product:\n            app_obj = ApplicationHub.objects.filter(\n                app_name__startswith=self.app_name, product__pro_name=self.product)\n            if not app_obj:\n                print(f\"{self.product}产品下无此服务\")\n                sys.exit(1)\n            self._app_obj = app_obj\n        # 版本筛选\n        if self.version:\n            if self._app_obj and self.app_name:\n                self._app_obj = self._app_obj.filter(app_version__startswith=self.version)\n            if self._app_obj and not self.app_name:\n                self._app_obj = self._app_obj.filter(product__pro_version=self.version)\n        # 检查是否存在已安装的应用\n        have_app = False\n        serivce_instance_name = []\n        # error_status = UpgradeDetail.objects.filter(upgrade_state__in=[0, 1, 2]).values_list(\"target_app\", flat=True)\n        for obj in self._app_obj:\n            if obj.service_set.count() != 0:\n                serivce_instance_name.append(obj.service_set.values_list('service_instance_name', flat=True))\n                have_app = True\n            # if obj.id in error_status:\n            #    print(\"存在升级中使用的升级包外键，不可删除\")\n            #    exit(1)\n        if serivce_instance_name:\n            list_str = \"\"\n            for _ in serivce_instance_name:\n                list_str = list_str + \",\".join(_) + \",\"\n            print(f\"请删除已安装服务{list_str}再删除应用商店\")\n        return have_app\n\n    def sys_cmd(self, cmd, ignore_exception=True):\n        \"\"\"\n        shell脚本输出\n        :param cmd: linux命令\n        :param ignore_exception: 默认不抛出异常\n        :return:\n        \"\"\"\n        shell = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)\n        stdout, stderr = shell.communicate()\n        stdout, stderr = bytes.decode(stdout), bytes.decode(stderr)\n        if ignore_exception:\n            print(stderr)\n            return stdout\n        if not ignore_exception:\n            if shell.poll() != 0:\n                print(\"执行cmd命令失败，执行cmd命令:{0},结果退出码:{1},执行详情:{2}\".format(cmd, shell.poll(), stderr))\n                sys.exit(shell.poll())\n            else:\n                print(stdout)\n        else:\n            return stdout\n\n    def delete_database(self):\n        try:\n            for obj in self._app_obj:\n                upload_obj = obj.app_package\n                if self.product_all:\n                    self.del_dir.add(os.path.join(PACKAGE_DIR, upload_obj.package_path))\n                    pro_obj = ProductHub.objects.filter(pro_name=self.product, pro_version=self.version)\n                    if len(pro_obj) == 1:\n                        pro_ser_obj = ApplicationHub.objects.filter(product=pro_obj.first())\n                        pro_ser_obj.delete()\n                    else:\n                        print(\"要删除的版本可能存在风险\")\n                        sys.exit(1)\n                    pro_obj.delete()\n                    break\n                else:\n                    self.del_dir.add(os.path.join(PACKAGE_DIR, upload_obj.package_path, upload_obj.package_name))\n                    obj.delete()\n            if self._app_obj and self.product and not self.app_name:\n                pro_obj = ProductHub.objects.filter(pro_name=self.product, pro_version=self.version).first()\n                if pro_obj:\n                    self.del_dir.add(os.path.join(PACKAGE_DIR, \"verified\", f\"{pro_obj.pro_name}-{pro_obj.pro_version}\"))\n                    pro_obj.delete()\n\n        except Exception as e:\n            print(f\"数据库异常:{e}\")\n            sys.exit(1)\n\n    def delete_file(self):\n        need_dir = \" \".join(self.del_dir)\n        if need_dir and len(need_dir) > 28:\n            self.sys_cmd(f\"/bin/rm -rf {need_dir}\", ignore_exception=False)\n\n\ndef parameters():\n    \"\"\"\n    传递参数\n    :return: 脚本接收到的参数\n    \"\"\"\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--app_name\", \"-app_name\",\n                        help=\"开头匹配的服务名称\")\n    parser.add_argument(\"--product\", \"-product\", help=\"完全匹配的产品名\")\n    parser.add_argument(\"--version\", \"-version\", help=\"开头匹配的服务名\")\n    param = parser.parse_args()\n    return param\n\n\nif __name__ == '__main__':\n    para = parameters()\n    uninstall_obj = UninstallServices(\n        app_name=para.app_name,\n        product=para.product,\n        version=para.version\n    )\n    result = uninstall_obj.check_database()\n    if result:\n        sys.exit(1)\n    uninstall_obj.delete_database()\n    time.sleep(5)\n    uninstall_obj.delete_file()\n    print(\"删除完成\")\n"
  },
  {
    "path": "scripts/source/uninstall_services.py",
    "content": "# -*- coding:utf-8 -*-\n# Project: uninstall_services\n# Author:Times.niu@yunzhihui.com\n# Create time: 2022/1/7 2:37 下午\n\nimport os\nimport sys\nimport django\nimport subprocess\nimport time\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nPYTHON_PATH = os.path.join(PROJECT_DIR, 'component/env/bin/python3')\nSALT_KEY_PATH = os.path.join(PROJECT_DIR, \"component/env/bin/salt-key\")\nSALT_CONFIG_PATH = os.path.join(PROJECT_DIR, \"config/salt\")\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\nMAX_NUM = 8\n\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nimport logging\nfrom db_models.models import (Service, ApplicationHub, Host, HostOperateLog,\n                              PreInstallHistory, DetailInstallHistory,\n                              PostInstallHistory, MainInstallHistory, Alert,\n                              ExecutionRecord, UpgradeHistory, RollbackDetail,\n                              RollbackHistory, UpgradeDetail, DeploymentPlan)\nfrom utils.parse_config import BASIC_ORDER\nfrom services.tasks import exec_action as uninstall_exec_action\nfrom utils.plugin.salt_client import SaltClient\nfrom hosts.tasks import UninstallHosts\n\nlogger = logging.getLogger(\"server\")\n\n\nclass UninstallServices(object):\n    def __init__(self, env_id):\n        self.env_id = env_id\n        self.salt_obj = SaltClient()\n        self.is_success = True\n        self.all_host = Host.objects.filter(env_id=self.env_id)\n        self.service_num = Service.objects.filter(env_id=self.env_id).count()\n\n    @staticmethod\n    def cmd(command):\n        \"\"\"执行本地shell命令\"\"\"\n        p = subprocess.Popen(\n            command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True\n        )\n        stdout, stderr = p.communicate()\n        _out, _err, _code = stdout.decode(\n            \"utf8\"), stderr.decode(\"utf8\"), p.returncode\n        return _out, _err, _code\n\n    def get_all_services(self):\n        \"\"\"通过环境名找到所有的服务\"\"\"\n        services = Service.objects.filter(env_id=self.env_id)\n        if not services:\n            return []\n        return services\n\n    def get_uninstall_order(self, service_list):\n        \"\"\"卸载服务排序（与安装顺序相反）\"\"\"\n        uninstall_list = list()\n\n        # 过滤出自研服务\n        self_service = [\n            item for item in service_list\n            if item.service.app_type == ApplicationHub.APP_TYPE_SERVICE\n        ]\n        # level不为0 自研服务\n        uninstall_list.append(\n            [\n                item for item in self_service\n                if str(item.service.extend_fields.get('level')) != \"0\"\n            ]\n        )\n        # level为0 自研服务\n        uninstall_list.append(\n            [\n                item for item in self_service\n                if str(item.service.extend_fields.get('level')) == \"0\"\n            ]\n        )\n        # 基础组件\n        basic_lists = list()\n        for m in range(10):\n            if m not in BASIC_ORDER:\n                break\n            basic_list = [\n                item for item in service_list\n                if item.service.app_name in BASIC_ORDER[m]\n            ]\n            basic_lists.append(basic_list)\n        basic_lists.reverse()\n        uninstall_list.extend(basic_lists)\n        return uninstall_list\n\n    def uninstall_service(self, item_list):\n        \"\"\"调用卸载函数执行卸载\"\"\"\n        need_split = [\"hadoop\"]\n        for service_obj in item_list:\n            if service_obj and service_obj.service.app_name in need_split:\n                delete_objs = Service.objects.filter(ip=service_obj.ip, service__app_name=\"hadoop\")\n                status = service_obj.service_status\n                delete_objs.update(service_status=Service.SERVICE_STATUS_DELETING)\n                if status != Service.SERVICE_STATUS_DELETING \\\n                        and delete_objs.first().id == service_obj.id:\n                    del_file = True\n                else:\n                    del_file = False\n            else:\n                del_file = True\n            uninstall_exec_action.delay(\n                action=\"4\", instance=service_obj.id, operation_user=\"command_line\", del_file=del_file)\n\n    def uninstall_all_services(self, uninstall_list):\n        \"\"\"卸载所有的服务\"\"\"\n        for item_list in uninstall_list:\n            if not item_list:\n                continue\n            self.uninstall_service(item_list)\n\n    def clean_db(self):\n        \"\"\"清理数据库\"\"\"\n        HostOperateLog.objects.all().delete()\n        self.all_host.delete()\n        PreInstallHistory.objects.all().delete()\n        DetailInstallHistory.objects.all().delete()\n        PostInstallHistory.objects.all().delete()\n        MainInstallHistory.objects.all().delete()\n        Service.all_objects.all().delete()\n        # TODO Alert.objects.filter(env_id=self.env_id).delete()\n        Alert.objects.all().delete()\n        ExecutionRecord.objects.all().delete()\n        RollbackDetail.objects.all().delete()\n        RollbackHistory.objects.all().delete()\n        UpgradeDetail.objects.all().delete()\n        UpgradeHistory.objects.all().delete()\n        DeploymentPlan.objects.all().delete()\n\n    def run(self):\n        \"\"\"卸载的总控制函数\"\"\"\n        service_list = self.get_all_services()\n        uninstall_list = self.get_uninstall_order(service_list=service_list)\n        self.uninstall_all_services(uninstall_list=uninstall_list)\n        for i in range(10):\n            if Service.objects.all().count() != 0:\n                time.sleep(int(self.service_num))\n                print(f\"等待服务删除第{i}次\")\n            else:\n                break\n        uninstall_host_obj = UninstallHosts(self.all_host)\n        self.is_success = uninstall_host_obj.delete_all_omp_agent()\n        self.clean_db()\n        if not self.is_success:\n            raise Exception(\"本次卸载失败，请按照上述打印信息手动进行卸载\")\n\n\nif __name__ == '__main__':\n    env_id = 1\n    UninstallServices(env_id).run()\n"
  },
  {
    "path": "scripts/source/update_conf.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: update_conf\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-15 14:18\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n更新配置文件\n\"\"\"\n\nimport os\nimport sys\nimport yaml\nimport socket\nfrom ruamel.yaml import YAML\nimport shutil\n\nCURRENT_FILE_PATH = os.path.dirname(os.path.abspath(__file__))\nPROJECT_FOLDER = os.path.dirname(os.path.dirname(CURRENT_FILE_PATH))\n\nconfig_path = os.path.join(PROJECT_FOLDER, \"config/omp.yaml\")\nPROJECT_DATA_PATH = os.path.join(PROJECT_FOLDER, \"data\")\nPROJECT_LOG_PATH = os.path.join(PROJECT_FOLDER, \"logs\")\n\nsys.path.append(os.path.join(PROJECT_FOLDER, \"omp_server\"))\nimport django\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom utils.parse_config import MONITOR_PORT, PROMETHEUS_AUTH, GRAFANA_AUTH\n\n\ndef get_config_dic():\n    \"\"\"\n    获取配置文件详细信息\n    :return:\n    \"\"\"\n    with open(config_path, \"r\", encoding=\"utf8\") as fp:\n        return yaml.load(fp, Loader=yaml.FullLoader)\n\n\ndef update_local_ip_run_user(local_ip, run_user):\n    \"\"\"\n    更新用户为当前操作用户\n    :param local_ip:\n    :param run_user:\n    :return:\n    \"\"\"\n    with open(config_path, \"r\", encoding=\"utf8\") as fp:\n        content = fp.read()\n    my_yaml = YAML()\n    code = my_yaml.load(content)\n    code[\"global_user\"] = run_user\n    code[\"local_ip\"] = local_ip\n    with open(config_path, \"w\", encoding=\"utf8\") as fp:\n        my_yaml.dump(code, fp)\n\n\nmaster_config_content = f\"\"\"\ninterface: 0.0.0.0\npublish_port: PUBLISH_PORT\nret_port: RET_PORT\nuser: RUN_USER\nenable_ssh_minions: False\npresence_events: True\nauto_accept: True\ntimeout: TIMEOUT\nroot_dir: {os.path.join(PROJECT_FOLDER, 'data/salt')}\nconf_file: {os.path.join(PROJECT_FOLDER, 'config/salt/master')}\nfile_roots:\n  base:\n    - {os.path.join(PROJECT_FOLDER, 'package_hub')}\nfile_recv: True\nfile_recv_max_size: 524288\nreactor:\n  - 'salt/auth':\n    - salt://reactor/auth.sls\n  - 'salt/minion/*/start':\n    - salt://reactor/start.sls\n  - 'salt/presence/present':\n    - salt://reactor/stop.sls\nrunner_dirs:\n  - {os.path.join(PROJECT_FOLDER, 'package_hub/runners')}\n\"\"\"\n\nuwsgi_content = f\"\"\"\n[uwsgi]\nsocket = SOCKET\nchdir = {os.path.join(PROJECT_FOLDER, 'omp_server')}\nwsgi-file = {os.path.join(PROJECT_FOLDER, 'omp_server/omp_server/wsgi.py')}\nmodule = omp_server.wsgi:application\nmaster = true\nprocesses = PROCESSES\nthreads = THREADS\nchmod-socket = 664\nbuffer-size = 65536\nvacuum = true\ndaemonize = {os.path.join(PROJECT_FOLDER, 'logs/uwsgi.log')}\npidfile = {os.path.join(PROJECT_FOLDER, 'logs/omp_uwsgi.pid')}\nhome = {os.path.join(PROJECT_FOLDER, 'component/env')}\nenable-threads = true\npreload=true\nlazy-apps=true\n\"\"\"\n\ntengine_nginx_conf = \"\"\"\n# user RUN_USER RUN_USER;\n\nerror_log %s;\npid %s;\n\nworker_processes  10;\nworker_rlimit_nofile 102400;\n\nevents {\n    use epoll;\n    worker_connections 102400;\n}\n\nhttp {\n    include mime.types;\n    default_type application/octet-stream;\n\n    #access_log on;\n    log_format main '$remote_addr - $remote_user [$time_local] \"$request\" '\n    '$status $body_bytes_sent \"$http_referer\" '\n    '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n    error_page 404 /404.html;\n\n    server_names_hash_bucket_size 128;\n    client_header_buffer_size 32k;\n    large_client_header_buffers 4 32k;\n    client_max_body_size 4000m;\n    sendfile on;\n    tcp_nopush on;\n    keepalive_timeout 30;\n    underscores_in_headers on;\n\n    # limit_req_zone $binary_remote_addr zone=one:3m rate=1r/s;\n    # imit_req_zone $binary_remote_addr $uri zone=two:3m rate=1r/s;\n\n    gzip on;\n    gzip_min_length 1k;\n    gzip_buffers 4 16k;\n    gzip_http_version 1.0;\n    gzip_comp_level 2;\n    gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/javascript;\n    gzip_vary on;\n\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_redirect off;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $remote_addr;\n    proxy_temp_path %s;\n    proxy_cache_path %s levels=1:2 keys_zone=cache_one:128m inactive=30m max_size=256m;\n    proxy_read_timeout 1200;\n    proxy_hide_header X-Powered-By;\n    proxy_hide_header Server;\n    server_tokens off;\n    autoindex off;\n\n    log_format access '$remote_addr $remote_user [$time_local] \"$request\" $status '\n    'Upstream: $upstream_addr '\n    'ups_resp_time: $upstream_response_time '\n    'request_time: $request_time';\n\n    include pools/*.conf;\n    include vhost/*.conf;\n    include jkb/*.conf;\n}\n\"\"\" % (\n    os.path.join(PROJECT_FOLDER, \"logs/tengine-error.log\"),\n    os.path.join(PROJECT_FOLDER, \"logs/tengine.pid\"),\n    os.path.join(PROJECT_FOLDER, \"component/tengine/temp\"),\n    os.path.join(PROJECT_FOLDER, \"component/tengine/cache\")\n)\n\ntengine_vhost_content = \"\"\"\nserver {\n    listen ACCESS_PORT;\n    server_name LOCAL_IP;\n    location /download-inspection/ {\n        alias %s/data/inspection_file/;\n    }\n    location /download-backup/ {\n        alias %s/data/backup/;\n    }\n    location /tool/ {\n        alias %s/package_hub/tool/;\n    }\n    location /custom_scripts/ {\n        alias %s/package_hub/custom_scripts/;\n    }\n    location /download/ {\n        alias %s/tmp/;\n    }\n    location /api/ {\n        uwsgi_connect_timeout 600;\n        uwsgi_send_timeout 600;\n        uwsgi_read_timeout 600;\n        uwsgi_pass SOCKET;\n        include %s;\n    }\n    location /proxy/ {\n        uwsgi_pass SOCKET;\n        include %s;\n    }\n    location / {\n        root %s;\n        index index.html;\n        try_files $uri $uri/ /index.html;\n    }\n}\n\"\"\" % (\n    PROJECT_FOLDER,\n    PROJECT_FOLDER,\n    PROJECT_FOLDER,\n    PROJECT_FOLDER,\n    PROJECT_FOLDER,\n    os.path.join(PROJECT_FOLDER, \"component/tengine/conf/uwsgi_params\"),\n    os.path.join(PROJECT_FOLDER, \"component/tengine/conf/uwsgi_params\"),\n    os.path.join(PROJECT_FOLDER, \"omp_web/dist\"),\n)\n\n\ndef check_port_access(port):\n    \"\"\"\n    检查端口联通性\n    :param port:\n    :return:\n    \"\"\"\n    try:\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.settimeout(0.5)\n        result = sock.connect_ex((\"127.0.0.1\", int(port)))\n        sock.close()\n        if not result:\n            return True\n        return False\n    except Exception as e:\n        print(f\"{port} Check Error {str(e)}\")\n        return False\n\n\ndef update_salt_master():\n    \"\"\"\n    更新salt-master的配置文件\n    :return:\n    \"\"\"\n    master_file_path = os.path.join(PROJECT_FOLDER, \"config/salt/master\")\n    settings = get_config_dic()\n    publish_port = settings.get(\"salt_master\", {}).get(\"publish_port\")\n    if not publish_port:\n        publish_port = 4505\n    ret_port = settings.get(\"salt_master\", {}).get(\"ret_port\")\n    if not ret_port:\n        ret_port = 4506\n    timeout = settings.get(\"salt_master\", {}).get(\"timeout\")\n    if not timeout:\n        timeout = 30\n    run_user = settings.get(\"global_user\", \"commonuser\")\n    if not run_user:\n        run_user = \"commonuser\"\n    content = master_config_content.replace(\n        \"PUBLISH_PORT\", str(publish_port),\n    ).replace(\n        \"RET_PORT\", str(ret_port)\n    ).replace(\n        \"RUN_USER\", str(run_user)\n    ).replace(\n        \"TIMEOUT\", str(timeout)\n    ).lstrip()\n    if not os.path.exists(os.path.dirname(master_file_path)):\n        os.makedirs(os.path.dirname(master_file_path))\n    with open(master_file_path, \"w\", encoding=\"utf8\") as fp:\n        fp.write(content)\n\n\ndef update_uwsgi():\n    \"\"\"\n    更新uwsgi的配置文件\n    :return:\n    \"\"\"\n    uwsgi_file_path = os.path.join(PROJECT_FOLDER, \"config/uwsgi.ini\")\n    settings = get_config_dic()\n    uwsgi_socket = settings.get(\"uwsgi\", {}).get(\"socket\")\n    if not uwsgi_socket:\n        uwsgi_socket = \"127.0.0.1:19003\"\n    processes = settings.get(\"uwsgi\", {}).get(\"processes\")\n    if not processes:\n        processes = 4\n    threads = settings.get(\"uwsgi\", {}).get(\"threads\")\n    if not threads:\n        threads = 2\n    content = uwsgi_content.replace(\n        \"SOCKET\", str(uwsgi_socket),\n    ).replace(\n        \"PROCESSES\", str(processes)\n    ).replace(\n        \"THREADS\", str(threads)\n    ).lstrip()\n    with open(uwsgi_file_path, \"w\", encoding=\"utf8\") as fp:\n        fp.write(content)\n\n\ndef update_nginx():\n    \"\"\"\n    更新tengine的配置文件\n    :return:\n    \"\"\"\n    nginx_conf_path = os.path.join(\n        PROJECT_FOLDER, \"component/tengine/conf/nginx.conf\")\n    settings = get_config_dic()\n    # run_user = settings.get(\"tengine\", {}).get(\"run_user\")\n    run_user = settings.get(\"global_user\", \"commonuser\")\n    if not run_user:\n        # run_user = settings.get(\"global_user\", \"cloudwise\")\n        run_user = \"commonuser\"\n    content = tengine_nginx_conf.replace(\"RUN_USER\", run_user)\n    with open(nginx_conf_path, \"w\", encoding=\"utf8\") as fp:\n        fp.write(content)\n    vhost_path = os.path.join(\n        PROJECT_FOLDER, \"component/tengine/conf/vhost/omp.conf\")\n    access_port = settings.get(\"tengine\", {}).get(\"access_port\")\n    if not access_port:\n        access_port = \"18888\"\n    local_ip = settings.get(\"local_ip\")\n    tengine_socket = settings.get(\"uwsgi\", {}).get(\"socket\")\n    if not tengine_socket:\n        tengine_socket = \"127.0.0.1:8888\"\n    content = tengine_vhost_content.replace(\n        \"ACCESS_PORT\", str(access_port)\n    ).replace(\n        \"LOCAL_IP\", str(local_ip)\n    ).replace(\n        \"SOCKET\", str(tengine_socket)\n    ).lstrip()\n    with open(vhost_path, \"w\", encoding=\"utf8\") as fp:\n        fp.write(content)\n\n\ndef check_port_is_used():\n    \"\"\"\n    检查端口是否被使用\n    :return:\n    \"\"\"\n    settings = get_config_dic()\n    port_dic = {\n        \"salt_publish_port\": settings.get(\"salt_master\", {}).get(\"publish_port\"),\n        \"salt_ret_port\": settings.get(\"salt_master\", {}).get(\"ret_port\"),\n        \"runserver_port\": settings.get(\"tengine\", {}).get(\"runserver_port\"),\n        \"tengine_port\": settings.get(\"tengine\", {}).get(\"access_port\"),\n    }\n    exit_flag = False\n    for key, value in port_dic.items():\n        if check_port_access(value):\n            print(f\"{key} is already in used, please check!\")\n            exit_flag = True\n    if exit_flag is True:\n        print(\"Port Is Already Used!\")\n        sys.exit(1)\n\n\ndef replace_placeholder(path, placeholder_list):\n    \"\"\"配置文件占位符替换\n    参数: path 要替换的文件路径, 占位符字典列表 [{\"key\":\"value\"}]\n    \"\"\"\n    try:\n        if not os.path.isfile(path):\n            print(\"No such file {}\".format(path))\n            sys.exit(1)\n\n        if not os.path.isfile(f'{path}.template'):\n            shutil.copyfile(path, f'{path}.template')\n        os.remove(path)\n        shutil.copyfile(f'{path}.template', path)\n        with open(path, \"r\") as f:\n            data = f.read()\n            for item in placeholder_list:\n                for k, v in item.items():\n                    placeholder = \"${{{}}}\".format(k)\n                    data = data.replace(placeholder, str(v))\n        with open(path, \"w\") as f:\n            f.write(data)\n        return True\n    except Exception as e:\n        print(f\"Updata Conf Failed, Check Error {str(e)}\")\n        return False\n\n\ndef update_prometheus():\n    \"\"\"\n    更新uwsgi的配置文件\n    :return:\n    \"\"\"\n    prometheus_path = os.path.join(\n        PROJECT_FOLDER, \"component/prometheus\")\n\n    \"\"\"\n    更新当前服务需要更改的配置\n    :return:\n    \"\"\"\n\n    # 修改 conf/prometheus.yml\n    MONITOR_PORT.get(\"prometheus\", '19011')\n    CW_PROMETHEUS_PORT = MONITOR_PORT.get(\"prometheus\")\n    CW_ALERTMANAGER_PORT = MONITOR_PORT.get(\"alertmanager\")\n    PROMETHEUS_USERNAME = PROMETHEUS_AUTH.get(\"username\", \"omp\")\n    PROMETHEUS_CIPHERTEXT_PASSWORD = PROMETHEUS_AUTH.get(\"ciphertext_password\")\n    PROMETHEUS_PLAINTEXT_PASSWORD = PROMETHEUS_AUTH.get(\"plaintext_password\")\n    prometheus_yml_file = os.path.join(\n        prometheus_path, 'conf', 'prometheus.yml')\n    prometheus_web_yml_file = os.path.join(\n        prometheus_path, 'conf', 'web.yml')\n    # 如果没有占位符的话，则不更新\n    with open(prometheus_yml_file, \"r\") as fp:\n        content = fp.read()\n        if \"CW_ALERTMANAGER_PORT\" not in content:\n            return\n    omp_prometheus_data_path = os.path.join(PROJECT_DATA_PATH, \"prometheus\")\n    omp_prometheus_log_path = os.path.join(PROJECT_LOG_PATH, \"prometheus\")\n\n    cpy_placeholder_script = [\n        {'CW_PROMETHEUS_PORT': CW_PROMETHEUS_PORT},\n        {'CW_ALERTMANAGER_PORT': CW_ALERTMANAGER_PORT},\n        {'CW_ALERTMANAGER_USERNAME': PROMETHEUS_USERNAME},\n        {'CW_ALERTMANAGER_PASSWORD': PROMETHEUS_PLAINTEXT_PASSWORD},\n    ]\n\n    replace_placeholder(prometheus_yml_file, cpy_placeholder_script)\n\n    # 修改 scripts/prometheus\n    sp_placeholder_script = [\n        {'OMP_PROMETHEUS_DATA_PATH': omp_prometheus_data_path},\n        {'OMP_PROMETHEUS_LOG_PATH': omp_prometheus_log_path},\n        {'CW_PROMETHEUS_PORT': CW_PROMETHEUS_PORT}\n    ]\n    if not os.path.exists(omp_prometheus_data_path):\n        os.makedirs(omp_prometheus_data_path)\n    if not os.path.exists(omp_prometheus_log_path):\n        os.makedirs(omp_prometheus_log_path)\n    sl_file = os.path.join(prometheus_path, 'scripts', 'prometheus')\n    replace_placeholder(sl_file, sp_placeholder_script)\n\n    # 修改 conf/web.yml\n    cwy_placeholder_script = [\n        {'PROMETHEUS_USERNAME': PROMETHEUS_USERNAME},\n        {'PROMETHEUS_CIPHERTEXT_PASSWORD': PROMETHEUS_CIPHERTEXT_PASSWORD}\n    ]\n    replace_placeholder(prometheus_web_yml_file, cwy_placeholder_script)\n\n\ndef update_grafana():\n    \"\"\"\n    更新当前服务需要更改的配置\n    :return:\n    \"\"\"\n    grafana_path = os.path.join(PROJECT_FOLDER, \"component/grafana\")\n    omp_grafana_log_path = os.path.join(PROJECT_LOG_PATH, \"grafana\")\n\n    # 修改 conf/defaults.ini\n    cdi_file = os.path.join(grafana_path, 'conf', 'defaults.ini')\n    CW_GRAFANA_PORT = MONITOR_PORT.get(\"grafana\", '19014')\n    CW_GRAFANA_ADMIN_USER = GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"username\", \"admin\")\n    CW_GRAFANA_ADMIN_PASSWORD = GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"plaintext_password\", \"Yunweiguanli@OMP_123\")\n\n    cdi_placeholder_script = [\n        {'CW-HTTP-PORT': CW_GRAFANA_PORT},\n        {'OMP_GRAFANA_LOG_PATH': omp_grafana_log_path},\n        {'CW_GRAFANA_ADMIN_USER': CW_GRAFANA_ADMIN_USER},\n        {'CW_GRAFANA_ADMIN_PASSWORD': CW_GRAFANA_ADMIN_PASSWORD},\n    ]\n    replace_placeholder(cdi_file, cdi_placeholder_script)\n\n    # 修改 scripts/grafana\n    sa_placeholder_script = [\n        {'OMP_GRAFANA_LOG_PATH': omp_grafana_log_path},\n    ]\n    if not os.path.exists(omp_grafana_log_path):\n        os.makedirs(omp_grafana_log_path)\n    sa_file = os.path.join(grafana_path, 'scripts', 'grafana')\n    replace_placeholder(sa_file, sa_placeholder_script)\n\n\ndef update_alertmanager():\n    \"\"\"\n    更新当前服务需要更改的配置\n    :return:\n    \"\"\"\n    alertmanager_path = os.path.join(PROJECT_FOLDER, \"component/alertmanager\")\n    omp_alertmanager_log_path = os.path.join(PROJECT_LOG_PATH, \"alertmanager\")\n    # 修改 conf/alertmanager.yml\n    EMAIL_SEND = MONITOR_PORT.get('test', '123456789@qq.com')\n    SMTP_SMARTHOST = MONITOR_PORT.get('test', '1smtp.qq.com:465')\n    EMAIL_SEND_USER = MONITOR_PORT.get('test', '123456789@qq.com')\n    EMAIL_SEND_PASSWORD = MONITOR_PORT.get('test', 'xxxxxxxx')\n    SMTP_HELLO = MONITOR_PORT.get('test', 'qq.com')\n    EMAIL_ADDRESS = MONITOR_PORT.get('test', '987654321@qq.com')\n    RECEIVER = MONITOR_PORT.get('test', 'commonuser')\n    EMAIL_SEND_INTERVAL = MONITOR_PORT.get('test', '30m')\n    WEBHOOK_URL = MONITOR_PORT.get(\n        'test', 'http://127.0.0.1:19001/api/promemonitor/receiveAlert/')\n    PROMETHEUS_USERNAME = PROMETHEUS_AUTH.get(\"username\", \"omp\")\n    PROMETHEUS_CIPHERTEXT_PASSWORD = PROMETHEUS_AUTH.get(\"ciphertext_password\")\n    alertmanager_web_yml_file = os.path.join(\n        alertmanager_path, 'conf', 'web.yml')\n\n    alertmanager_yml_file = os.path.join(\n        alertmanager_path, 'conf', 'alertmanager.yml')\n\n    cay_placeholder_script = [\n        {'EMAIL_SEND': EMAIL_SEND},\n        {'SMTP_SMARTHOST': SMTP_SMARTHOST},\n        {'EMAIL_SEND_USER': EMAIL_SEND_USER},\n        {'EMAIL_SEND_PASSWORD': EMAIL_SEND_PASSWORD},\n        {'SMTP_HELLO': SMTP_HELLO},\n        {'EMAIL_ADDRESS': EMAIL_ADDRESS},\n        {'RECEIVER': RECEIVER},\n        {'EMAIL_SEND_INTERVAL': EMAIL_SEND_INTERVAL},\n        {'WEBHOOK_URL': WEBHOOK_URL},\n        {'ALERTMANAGER_PATH': alertmanager_path}\n    ]\n    replace_placeholder(alertmanager_yml_file, cay_placeholder_script)\n\n    # 修改 scripts/alertmanager\n    CW_ALERTMANAGER_PORT = MONITOR_PORT.get('alertmanager', '19013')\n    sa_placeholder_script = [\n        {'OMP_ALERTMANAGER_LOG_PATH': omp_alertmanager_log_path},\n        {'CW_ALERTMANAGER_PORT': CW_ALERTMANAGER_PORT}\n    ]\n    if not os.path.exists(omp_alertmanager_log_path):\n        os.makedirs(omp_alertmanager_log_path)\n    sa_file = os.path.join(alertmanager_path, 'scripts', 'alertmanager')\n    replace_placeholder(sa_file, sa_placeholder_script)\n    # 修改 conf/web.yml\n    cwy_placeholder_script = [\n        {'PROMETHEUS_USERNAME': PROMETHEUS_USERNAME},\n        {'PROMETHEUS_CIPHERTEXT_PASSWORD': PROMETHEUS_CIPHERTEXT_PASSWORD}\n    ]\n    replace_placeholder(alertmanager_web_yml_file, cwy_placeholder_script)\n\n\ndef update_loki():\n    \"\"\"\n    更新当前服务需要更改的配置\n    :return:\n    \"\"\"\n    loki_path = os.path.join(PROJECT_FOLDER, \"component/loki\")\n    omp_loki_log_path = os.path.join(PROJECT_LOG_PATH, \"loki\")\n    omp_loki_data_path = os.path.join(PROJECT_DATA_PATH, \"loki\")\n    loki_retention_period = MONITOR_PORT.get('test', '24h')\n\n    # 修改 conf/loki.yml\n    CW_LOKI_PORT = MONITOR_PORT.get('loki', 19012)\n    loki_yml_file = os.path.join(loki_path, 'conf', 'loki.yml')\n\n    cly_placeholder_script = [\n        {'CW_LOKI_PORT': CW_LOKI_PORT},\n        {'OMP_LOKI_DATA_PATH': omp_loki_data_path},\n        {'LOKI_RETENTION_PERIOD': loki_retention_period}\n    ]\n    if not os.path.exists(omp_loki_data_path):\n        os.makedirs(omp_loki_data_path)\n    replace_placeholder(loki_yml_file, cly_placeholder_script)\n\n    # 修改 scripts/loki\n    sa_placeholder_script = [\n        {'OMP_LOKI_LOG_PATH': omp_loki_log_path},\n        {'LOKI_RETENTION_PERIOD': loki_retention_period}\n    ]\n    if not os.path.exists(omp_loki_log_path):\n        os.makedirs(omp_loki_log_path)\n    sl_file = os.path.join(loki_path, 'scripts', 'loki')\n    replace_placeholder(sl_file, sa_placeholder_script)\n\n\ndef main(local_ip, run_user):\n    \"\"\"\n    更新配置文件主流程\n    :param local_ip: 本机的ip地址\n    :param run_user: 运行用户\n    :return:\n    \"\"\"\n    print(\"Start Update Conf!\")\n    update_local_ip_run_user(local_ip, run_user)\n    # check_port_is_used()\n    update_salt_master()\n    update_uwsgi()\n    update_nginx()\n    update_prometheus()\n    update_grafana()\n    update_alertmanager()\n    update_loki()\n\n    print(\"Finish Update Conf!\")\n\n\nif __name__ == '__main__':\n    local_ip = sys.argv[1]\n    run_user = sys.argv[2]\n    main(local_ip, run_user)\n"
  },
  {
    "path": "scripts/source/update_data.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: update_data\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-09-18 10:36\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\nimport os\nimport sys\nimport hashlib\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nPYTHON_PATH = os.path.join(PROJECT_DIR, \"component/env/bin/python3\")\nMANAGE_PATH = os.path.join(PROJECT_DIR, \"omp_server/manage.py\")\nsys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import UserProfile\nfrom db_models.models import MonitorUrl\nfrom utils.parse_config import MONITOR_PORT, CLEAR_DB\nfrom db_models.models import Env, AlertRule, \\\n    Rule, Service, \\\n    BackupHistory, BackupCustom, \\\n    DetailInstallHistory, Host, SelfHealingSetting\nfrom utils.plugin.crontab_utils import change_task\n\n\ndef create_default_user():\n    \"\"\"\n    创建基础用户\n    :return:\n    \"\"\"\n    TO_CREATE_USER_LIST = [\n        {\"username\": \"admin\", \"password\": \"Yunweiguanli@OMP_123\", \"email\": \"omp@cloudwise.com\", \"role\": \"SuperUser\"},\n        {\"username\": \"omp\", \"password\": \"Yunweiguanli@OMP_123\", \"email\": \"omp@cloudwise.com\", \"role\": \"ReadOnlyUser\"}\n    ]\n    username = password = email = role = \"\"\n    for user_dict in TO_CREATE_USER_LIST:\n        username = user_dict.get(\"username\")\n        password = user_dict.get(\"password\")\n        email = user_dict.get(\"email\")\n        role = user_dict.get(\"role\")\n        if UserProfile.objects.filter(username=username).count() != 0:\n            continue\n        UserProfile.objects.create_user(\n            username=username,\n            password=password,\n            email=email,\n            role=role\n        )\n\n\ndef create_default_monitor_url():\n    \"\"\"\n    配置监控地址初始入库\n    :return:\n    \"\"\"\n    if MonitorUrl.objects.all().count() != 0:\n        return\n    monitor_list = []\n    local_ip = \"127.0.0.1:\"\n    monitor_list.append(\n        MonitorUrl(id=\"1\", name=\"prometheus\", monitor_url=local_ip + str(\n            MONITOR_PORT.get(\"prometheus\", \"19011\"))))\n    monitor_list.append(\n        MonitorUrl(id=\"2\", name=\"alertmanager\", monitor_url=local_ip + str(\n            MONITOR_PORT.get(\"alertmanager\", \"19013\"))))\n    monitor_list.append(MonitorUrl(\n        id=\"3\", name=\"grafana\",\n        monitor_url=local_ip + str(MONITOR_PORT.get(\"grafana\", \"19014\"))))\n    MonitorUrl.objects.bulk_create(monitor_list)\n\n\ndef create_default_env():\n    \"\"\"\n    创建默认环境\n    :return:\n    \"\"\"\n    env_name = \"default\"\n    if Env.objects.filter(name=env_name).count() != 0:\n        return\n    Env(name=env_name).save()\n\n\ndef get_hash_value(expr, severity):\n    data = expr + severity\n    hash_data = hashlib.md5(data.encode(encoding='UTF-8')).hexdigest()\n    return hash_data\n\n\ndef create_threshold():\n    \"\"\"\n    为告警添加默认的告警阈值规则\n    :return:\n    - alert: exporter 异常\n    annotations:\n      consignee: omp@cloudwise.com\n      description: 主机 {{ $labels.instance }} 中的 {{ $labels.app }}_exporter 已经down掉超过一分钟.\n      summary: exporter status(instance {{ $labels.instance }})\n    expr: exporter_status{env=\"default\"} == 0\n    for: 1m\n    labels:\n      severity: critical\n    \"\"\"\n    builtins_rules = [\n        {\n            \"alert\": \"实例宕机\",\n            \"description\": '实例 {{ $labels.instance }} '\n                           'monitor_agent进程丢失或主机发生宕机已超过1分钟',\n            \"expr\": 'sum(up{job=\"nodeExporter\", env=\"default\"}) by (instance)',\n            \"summary\": \"-\",\n            \"compare_str\": \"<\",\n            \"threshold_value\": 1,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"critical\"\n            },\n            \"name\": \"实例宕机\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2,\n\n        },\n        {\n            \"alert\": \"主机 CPU 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} CPU 使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 90%',\n            \"expr\": '(100 - sum(avg without (cpu)(irate('\n                    'node_cpu_seconds_total{mode=\"idle\", env=\"default\"}['\n                    '2m])))by (instance) * 100)',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 90,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"critical\"\n            },\n            \"name\": \"CPU使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n\n        },\n        {\n            \"alert\": \"主机 CPU 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} CPU 使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 80%',\n            \"expr\": '(100 - sum(avg without (cpu)(irate('\n                    'node_cpu_seconds_total{mode=\"idle\", env=\"default\"}['\n                    '2m])))by (instance) * 100)',\n            \"compare_str\": \">=\",\n            \"summary\": \"-\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"warning\"\n            },\n            \"name\": \"CPU使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"主机 内存 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} 内存使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 90%',\n            \"expr\": '(1 - (node_memory_MemAvailable_bytes{env=\"default\"} / (node_memory_MemTotal_bytes{env=\"default\"})))* 100',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 90,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"critical\"\n            },\n            \"name\": \"内存使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"主机 内存 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} 内存使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 80%',\n            \"expr\": '(1 - (node_memory_MemAvailable_bytes{env=\"default\"} / (node_memory_MemTotal_bytes{env=\"default\"})))* 100',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"warning\"\n            },\n            \"name\": \"内存使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"主机 根分区磁盘 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} 根分区使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 90%',\n            \"expr\": 'max((node_filesystem_size_bytes{env=\"default\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"default\",mountpoint=\"/\"})*100/('\n                    'node_filesystem_avail_bytes{env=\"default\",'\n                    'mountpoint=\"/\"}+(node_filesystem_size_bytes{'\n                    'env=\"default\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"default\",mountpoint=\"/\"})))by(instance)',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 90,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"critical\"\n            },\n            \"name\": \"根分区使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"主机 根分区磁盘 使用率过高\",\n            \"description\": '主机 {{ $labels.instance }} 根分区使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 90%',\n            \"expr\": 'max((node_filesystem_size_bytes{env=\"default\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"default\",mountpoint=\"/\"})*100/('\n                    'node_filesystem_avail_bytes{env=\"default\",'\n                    'mountpoint=\"/\"}+(node_filesystem_size_bytes{'\n                    'env=\"default\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"default\",mountpoint=\"/\"})))by(instance)',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"job\": \"nodeExporter\",\n                \"severity\": \"warning\"\n            },\n            \"name\": \"根分区使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"kafka消费组堆积数过多\",\n            \"description\": 'Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多 {{ '\n                           'humanize $value}} 大于阈值 4000',\n            \"expr\": 'sum(kafka_consumergroup_lag{env=\"default\"}) by ('\n                    'consumergroup,instance,job,env)',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 4000,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"job\": \"kafkaExporter\",\n                \"severity\": \"warning\"\n            },\n            \"name\": \"消费组堆积消息\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"kafka\",\n            \"forbidden\": 1\n        },\n        {\n            \"alert\": \"kafka消费组堆积数过多\",\n            \"description\": 'Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多 大于 {{ '\n                           'humanize $value}} 大于阈值 5000',\n            \"expr\": 'sum(kafka_consumergroup_lag{env=\"default\"}) by ('\n                    'consumergroup,instance,job,env)',\n            \"summary\": \"-\",\n            \"compare_str\": \">=\",\n            \"threshold_value\": 5000,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"job\": \"kafkaExporter\",\n                \"severity\": \"critical\"\n            },\n            \"name\": \"消费组堆积消息\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"kafka\",\n            \"forbidden\": 1\n        },\n        {\n            \"alert\": \"exporter 异常\",\n            \"description\": '主机 {{ $labels.instance }} 中的 {{ $labels.app }}_exporter 已经down掉超过一分钟',\n            \"expr\": 'exporter_status{env=\"default\"}',\n            \"summary\": \"-\",\n            \"compare_str\": \"==\",\n            \"threshold_value\": 0,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"severity\": \"critical\"\n            },\n            \"name\": \"exporter异常\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"node\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"服务存活状态\",\n            \"description\": '主机 {{ $labels.instance }} 中的 服务 {{ $labels.app }} been down for more than a minute.',\n            \"expr\": 'probe_success{env=\"default\"}',\n            \"summary\": \"-\",\n            \"compare_str\": \"==\",\n            \"threshold_value\": 0,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"severity\": \"critical\"\n            },\n            \"name\": \"服务状态\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"service\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"jvm 文件句柄使用率过高\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} jvm 文件句柄使用率是{{ $value | '\n                           'humanize }}%， 大于阈值 80%',\n            \"expr\": '(process_files_open_files)*100/process_files_max_files',\n            \"summary\": \"-\",\n            \"compare_str\": \">\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"severity\": \"critical\"\n            },\n            \"name\": \"jvm 文件句柄使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"service\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"产品http请求 5XX 错误\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,请求方法是：{{ $labels.method }}，请求uri是 {{ $labels.uri }}发生http请求 status: {{ $labels.status }} 错误',\n            \"expr\": 'http_server_requests_seconds_count{status=~\\\"5..\\\"}',\n            \"summary\": \"-\",\n            \"compare_str\": \">\",\n            \"threshold_value\": 0,\n            \"for_time\": \"60s\",\n            \"severity\": \"critical\",\n            \"labels\": {\n                \"severity\": \"critical\"\n            },\n            \"name\": \"产品http请求 5XX 错误\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"service\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"heap使用率过高，可能导致oom\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,heap内存使用率为 {{ $value | '\n                           'humanize }}%, 大于阈值 80%',\n            \"expr\": 'sum(jvm_memory_used_bytes{area=~\"heap\"}) by (env, instance, job)/sum(jvm_memory_max_bytes{area=~\"heap\"}) by (env, instance, job) * 100',\n            \"summary\": \"-\",\n            \"compare_str\": \">\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"severity\": \"warning\"\n            },\n            \"name\": \"heap使用率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"service\",\n            \"forbidden\": 2\n        },\n        {\n            \"alert\": \"gc后老年代所占内存比例过高，可能导致oom\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,gc后老年代所占内存比例为 {{ $value | '\n                           'humanize }}%, 大于阈值 80%',\n            \"expr\": 'sum(jvm_gc_live_data_size_bytes) by (env, instance, job)/sum(jvm_gc_max_data_size_bytes) by (env, instance, job) * 100',\n            \"summary\": \"-\",\n            \"compare_str\": \">\",\n            \"threshold_value\": 80,\n            \"for_time\": \"60s\",\n            \"severity\": \"warning\",\n            \"labels\": {\n                \"severity\": \"warning\"\n            },\n            \"name\": \"gc后老年代所占内存率\",\n            \"quota_type\": 0,\n            \"status\": 1,\n            \"service\": \"service\",\n            \"forbidden\": 2\n        },\n    ]\n    rule = [\n        {\n            \"name\": \"实例宕机\",\n            \"description\": '实例 {{ $labels.instance }} '\n                           'monitor_agent进程丢失或主机发生宕机已超过1分钟',\n            \"expr\": 'sum(up{job=\"nodeExporter\", env=\"$env$\"}) by (instance)',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"exporter异常\",\n            \"description\": '主机 {{ $labels.instance }} 中的 {{ $labels.app }}_exporter 已经down掉超过一分钟',\n            \"expr\": 'exporter_status{env=\"$env$\"}',\n            \"service\": \"service\",\n        },\n        {\n            \"name\": \"服务状态\",\n            \"description\": '主机 {{ $labels.instance }} 中的 服务 {{ $labels.app }} been down for more than a minute.',\n            \"expr\": 'probe_success{env=\"$env$\"}',\n            \"service\": \"service\",\n        },\n        {\n            \"name\": \"CPU使用率\",\n            \"description\": '主机 {{ $labels.instance }} CPU 使用率为 {{ $value | '\n                           'humanize }}%, $compare_str$ 阈值 $threshold_value$%',\n            \"expr\": '(100 - sum(avg without (cpu)(irate('\n                    'node_cpu_seconds_total{mode=\"idle\", env=\"$env$\"}['\n                    '2m])))by (instance) * 100)',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"内存使用率\",\n            \"description\": '主机 {{ $labels.instance }} 内存使用率为 {{ $value | '\n                           'humanize }}%, $compare_str$阈值 $threshold_value$%',\n            \"expr\": '(1 - (node_memory_MemAvailable_bytes{env=\"$env$\"} / (node_memory_MemTotal_bytes{env=\"$env$\"})))* 100',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"根分区使用率\",\n            \"description\": '主机 {{ $labels.instance }} 根分区使用率为 {{ $value | '\n                           'humanize }}%, $compare_str$阈值 $threshold_value$%',\n            \"expr\": 'max((node_filesystem_size_bytes{env=\"$env$\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"$env$\",mountpoint=\"/\"})*100/('\n                    'node_filesystem_avail_bytes{env=\"$env$\",'\n                    'mountpoint=\"/\"}+(node_filesystem_size_bytes{'\n                    'env=\"$env$\",'\n                    'mountpoint=\"/\"}-node_filesystem_free_bytes{'\n                    'env=\"$env$\",mountpoint=\"/\"})))by(instance)',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"消费组堆积消息\",\n            \"description\": 'Kafka 消费组{{ $labels.consumergroup }}消息堆积数过多  {{ '\n                           'humanize $value}} $compare_str$阈值 $threshold_value$',\n            \"expr\": 'sum(kafka_consumergroup_lag{env=\"$env$\"}) by ('\n                    'consumergroup,instance,job,env)',\n            \"service\": \"kafka\",\n        },\n        {\n            \"name\": \"数据分区使用率\",\n            \"description\": '主机 {{ $labels.instance }} 数据分区使用率为 {{ $value | humanize }}%, $compare_str$阈值 $threshold_value$%',\n            \"expr\": 'max((node_filesystem_size_bytes{env=\"$env$\",mountpoint=\"$data_dir$\"}-node_filesystem_free_bytes{env=\"$env$\",mountpoint=\"$data_dir$\"})*100/(node_filesystem_avail_bytes{env=\"$env$\",mountpoint=\"$data_dir$\"}+(node_filesystem_size_bytes{env=\"$env$\",mountpoint=\"$data_dir$\"}-node_filesystem_free_bytes{env=\"$env$\",mountpoint=\"$data_dir$\"}))) by (instance,env)',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"jvm 文件句柄使用率\",\n            \"description\": '主机 {{ $labels.instance }} jvm 文件句柄使用率为 {{ $value | humanize }}%, $compare_str$阈值 $threshold_value$%',\n            \"expr\": '(process_files_open_files)*100/process_files_max_files',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"产品http请求 5XX 错误\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,请求方法是：{{ $labels.method }}，请求uri是 {{ $labels.uri }}发生http请求 status: {{ $labels.status }} 错误',\n            \"expr\": 'http_server_requests_seconds_count{status=~\\\"5..\\\"}',\n            \"service\": \"node\",\n        },\n        {\n            \"name\": \"heap 使用率过高，可能导致oom\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,heap内存使用率为 {{ $value | '\n                           'humanize }}%, $compare_str$阈值 $threshold_value$',\n            \"expr\": 'sum(jvm_memory_used_bytes{area=~\"heap\"}) by (env, instance, job)/sum(jvm_memory_max_bytes{area=~\"heap\"}) by (env, instance, job) * 100',\n            \"service\": \"service\",\n        },\n        {\n            \"name\": \"gc后老年代所占内存比例过高，可能导致oom\",\n            \"description\": '主机 {{ $labels.instance }}中的服务 {{ $labels.instance_name }} ,gc后老年代所占内存比例为 {{ $value | '\n                           'humanize }}%, $compare_str$阈值 $threshold_value$',\n            \"expr\": 'sum(jvm_gc_live_data_size_bytes) by (env, instance, job)/sum(jvm_gc_max_data_size_bytes) by (env, instance, job) * 100',\n            \"service\": \"service\",\n        },\n    ]\n    try:\n        for info in builtins_rules:\n            hash_value = get_hash_value(info.get(\"expr\"), info.get(\"severity\"))\n            alert = AlertRule.objects.filter(expr=info.get(\"expr\"), severity=info.get(\"severity\"),\n                                             service=info.get(\"service\"))\n            info.update(hash_data=hash_value)\n            if alert:\n                alert.update(**info)\n            else:\n                AlertRule(**info).save()\n    except Exception as e:\n        print(f\"初始化规则数据失败{e}\")\n    for rule_info in rule:\n        if Rule.objects.filter(name=rule_info.get(\"name\")).exists():\n            continue\n        Rule(**rule_info).save()\n\n\ndef create_self_healing_setting():\n    \"\"\"添加默认自愈策略\"\"\"\n    # self_obj = SelfHealingSetting.objects.all().first()\n    # if not self_obj.repair_instance:\n    #     self_obj.repair_instance = [\"all\"]\n    #     self_obj.save()\n\n\nclass ClearDb:\n\n    def alert(self):\n        return\n\n    def health(self):\n        return\n\n    def __call__(self, *args, **kwargs):\n        for k, v in CLEAR_DB.items():\n            obj = getattr(self, k)\n            if not obj:\n                print(\"改函数未定义\")\n        # ToDo 暂时无其他逻辑以后补充\n        data = {\n            \"is_on\": True,\n            'task_func': 'services.tasks.clear_db',\n            'task_name': 'self_clear_cron_db',\n            'crontab_detail': dict(\n                day_of_month='*', day_of_week='*',\n                hour=\"00\", minute=\"00\",\n                month_of_year='*')\n        }\n        change_task(1, data)\n\n\ndef create_back_settings():\n    init_db = [\n        {\"field_k\": \"db_name\",\n         \"field_v\": \"test_db\",\n         \"notes\": \"数据库名称,适用mysql,postgre\"},\n        {\"field_k\": \"no_pass\",\n         \"field_v\": \"true\",\n         \"notes\": \"无需认证,非必填,适用mysql\"},\n        {\n            \"field_k\": \"need_push\",\n            \"field_v\": \"true\",\n            \"notes\": \"异地备份,非必填,适用全部\"\n        },\n        {\n            \"field_k\": \"need_app\",\n            \"field_v\": \"true\",\n            \"notes\": \"安装路径备份,非必填,适用pg\"\n        }\n    ]\n\n    his_obj = BackupHistory.objects.all()\n\n    for i in his_obj:\n        if \",\" in i.content:\n            i.delete()\n\n    for v in init_db:\n        BackupCustom.objects.get_or_create(**v)\n\n\ndef repair_dirty_data():\n    detail_objs = DetailInstallHistory.objects.all()\n    for obj in detail_objs:\n        run_user = Host.objects.filter(ip=obj.service.ip).first().username\n        if run_user != 'root':\n            obj.install_detail_args['run_user'] = run_user\n            for i in obj.install_detail_args.get('install_args', []):\n                if i.get('key', '') == 'run_user':\n                    i['default'] = run_user\n            obj.save()\n        else:\n            continue\n    service_obj = Service.split_objects.all()\n    for ser in service_obj:\n        if ser.service.app_name == \"hadoop\":\n            ser.service_status = Service.SERVICE_STATUS_NORMAL\n            ser.save()\n\n\ndef main():\n    \"\"\"\n    基础数据创建流程\n    :return:\n    \"\"\"\n    # 创建默认用户\n    create_default_user()\n    # 创建监控配置项\n    create_default_monitor_url()\n    # 创建默认环境\n    create_default_env()\n    # 添加默认告警阈值规则\n    create_threshold()\n    # 添加默认自愈策略\n    create_self_healing_setting()\n    # 创建清理任务\n    ClearDb()()\n    # 添加告警策略\n    create_back_settings()\n    # 升级是清洗以前问题数据\n    repair_dirty_data()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/source/update_grafana.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: update_grafana\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-10 16:58\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\ngrafana数据更新及初始化操作\ngrafana默认的用户名密码均为admin，不做更改\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport time\n\nimport django\nimport requests\nfrom ruamel import yaml\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nGRAFANA_DASHBOARD_JSON = os.path.join(\n    PROJECT_DIR, \"package_hub/grafana_dashboard_json\")\nAGREE = \"http\"\nsys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\nfrom utils.parse_config import PROMETHEUS_AUTH, GRAFANA_AUTH\nfrom utils.plugin.synch_grafana import synch_grafana_info\n\n\nclass Grafana(object):\n    \"\"\" grafana 安装时使用 \"\"\"\n\n    def __init__(\n            self,\n            ip=\"127.0.0.1\", port=19014,\n            loki_ip=\"127.0.0.1\", loki_port=19012,\n            prometheus_ip=\"127.0.0.1\", prometheus_port=19011\n    ):\n        self.ip = ip\n        self.port = port\n        self.loki_ip = loki_ip\n        self.loki_port = loki_port\n        self.prometheus_ip = prometheus_ip\n        self.prometheus_port = prometheus_port\n        self.login_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/proxy/v1/grafana/login\"\n        self.login_key = \\\n            f\"{AGREE}://{self.ip}:{self.port}/proxy/v1/grafana/api/auth/keys\"\n        self.create_user_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/api/admin/users\"\n        self.data_source_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/api/datasources\"\n        self.dashboard_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/api/dashboards/db\"\n        self.dashboard_id_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/api/dashboards/uid/XrwAXz_Mz\"\n        self.profile_url = \\\n            f\"{AGREE}://{self.ip}:{self.port}/api/user/preferences\"\n        self.content_type = {'Content-Type': 'application/json'}\n        self.basic_auth = (GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"username\", \"admin\"),\n                           GRAFANA_AUTH.get(\"grafana_admin_auth\").get(\"plaintext_password\", \"Yunweiguanli@OMP_123\"))\n        self.omp_auth = (GRAFANA_AUTH.get(\"grafana_viewer_auth\").get(\"username\", \"omp\"),\n                         GRAFANA_AUTH.get(\"grafana_viewer_auth\").get(\"plaintext_password\", \"Common@123\"))\n\n    def post(self, url, data, auth):\n        \"\"\"\n        封装post方法\n        :param url: 请求url\n        :param data: 请求数据\n        :param auth: 权限信息\n        :return:\n        \"\"\"\n        try:\n            res = requests.post(\n                url=url,\n                data=json.dumps(data),\n                headers=self.content_type,\n                auth=auth\n            )\n            return True, json.loads(res.text)\n        except requests.ConnectionError:\n            print(\"Grafana can not connected - POST!\")\n            return False, \"ConnectionError\"\n\n    def put(self, url, data, auth):\n        \"\"\"\n        封装put方法\n        :param url: 请求url\n        :param data: 请求数据\n        :param auth: 权限信息\n        :return:\n        \"\"\"\n        try:\n            res = requests.put(\n                url=url,\n                data=json.dumps(data),\n                headers=self.content_type,\n                auth=auth\n            )\n            return True, json.loads(res.text)\n        except requests.ConnectionError:\n            print(\"Grafana can not connected - PUT!\")\n            return False, \"ConnectionError\"\n\n    def get(self, url, params, auth):\n        \"\"\"\n        封装get方法\n        :param url: 请求url\n        :param params: 请求参数\n        :param auth: 权限信息\n        :return:\n        \"\"\"\n        try:\n            res = requests.get(\n                url=url,\n                params=params,\n                headers=self.content_type,\n                auth=auth\n            )\n            return True, json.loads(res.text)\n        except requests.ConnectionError:\n            print(\"Grafana can not connected - GET!\")\n            return False, \"ConnectionError\"\n\n    def create_omp_user(self):\n        \"\"\"\n        创建omp使用用户\n        :return:\n        \"\"\"\n        user_dict = {\n            \"name\": self.omp_auth[0],\n            \"email\": f\"{self.omp_auth[0]}@cloudwise.com\",\n            \"login\": self.omp_auth[0],\n            \"password\": self.omp_auth[1],\n            \"OrgId\": 1\n        }\n        create_flag, create_message = self.post(\n            url=self.create_user_url,\n            data=user_dict,\n            auth=self.basic_auth\n        )\n        if not create_flag:\n            return create_flag, create_message\n        if create_message.get(\"message\") == \"User created\" or \\\n                \"already exists\" in create_message.get(\"message\"):\n            return True, \"success\"\n        return False, f\"Create omp user failed with error: {create_message}\"\n\n    def create_data_source(self):\n        \"\"\"\n        创建数据源\n        :return:\n        \"\"\"\n        prometheus_content = {\n            \"name\": \"Prometheus\",\n            \"type\": \"prometheus\",\n            \"url\": f\"{AGREE}://{self.prometheus_ip}:{self.prometheus_port}\",\n            \"access\": \"proxy\",\n            \"basicAuth\": True,\n            \"basicAuthUser\": \"omp\",\n            \"secureJsonData\": {\n                \"basicAuthPassword\": PROMETHEUS_AUTH.get(\"plaintext_password\")\n            }\n        }\n        loki_content = {\n            \"name\": \"Loki\",\n            \"type\": \"loki\",\n            \"url\": f\"{AGREE}://{self.loki_ip}:{self.loki_port}\",\n            \"access\": \"proxy\",\n            \"basicAuth\": False\n        }\n        # 创建prometheus数据源\n        pro_flag, pro_msg = self.post(\n            url=self.data_source_url,\n            data=prometheus_content,\n            auth=self.basic_auth\n        )\n        if not pro_flag:\n            return pro_flag, pro_msg\n        # 创建loki数据源\n        loki_flag, loki_msg = self.post(\n            url=self.data_source_url,\n            data=loki_content,\n            auth=self.basic_auth\n        )\n        if not loki_flag:\n            return loki_flag, loki_msg\n        return True, \"success\"\n\n    def update_one_dashboard(self, dashboard_content):\n        \"\"\"\n        更新dashboard面板\n        :param dashboard_content: 字符串json数据\n        :return:\n        \"\"\"\n        if not dashboard_content or not json.loads(dashboard_content):\n            return False, \"content can not be null!\"\n        dashboard_json = json.loads(dashboard_content)\n        if 'id' not in dashboard_json.get('dashboard').keys() or \\\n                'uid' not in dashboard_json.get('dashboard').keys():\n            return False, \"dashboard content error\"\n        dashboard_json[\"dashboard\"][\"id\"] = \"null\"\n        flag, res = self.post(\n            url=self.dashboard_url,\n            data=dashboard_json,\n            auth=self.basic_auth\n        )\n        if not flag:\n            return flag, res\n        return True, \"success\"\n\n    def update_dashboard(self):\n        \"\"\"\n        读取grafana面板信息并更新\n        :return:\n        \"\"\"\n        for item in os.listdir(GRAFANA_DASHBOARD_JSON):\n            if not item.endswith(\".json\"):\n                continue\n            file_path = os.path.join(GRAFANA_DASHBOARD_JSON, item)\n            with open(file_path, \"r\") as fp_obj:\n                _flag, _msg = self.update_one_dashboard(fp_obj.read())\n            if not _flag:\n                return _flag, _msg\n\n    def update_user_profile(self):\n        \"\"\"\n        更新用户设置\n        :return:\n        \"\"\"\n        flag, res = self.get(\n            url=self.dashboard_id_url,\n            params=None,\n            auth=self.basic_auth\n        )\n        home_dashboard_id = res.get(\"dashboard\", {}).get(\"id\")\n        if not home_dashboard_id:\n            return False, \"can not get home dashboard id\"\n        profile_setting = {\n            \"theme\": \"light\",\n            \"homeDashboardId\": int(home_dashboard_id),\n            \"timezone\": \"browser\"\n        }\n        self.put(\n            url=self.profile_url,\n            data=profile_setting,\n            auth=self.basic_auth\n        )\n        self.put(\n            url=self.profile_url,\n            data=profile_setting,\n            auth=self.omp_auth\n        )\n        return True, \"success\"\n\n    def grafana_auth(self):\n        \"\"\"\n         请求grafana获取api_keys\n        \"\"\"\n        # name 必须是唯一值\n        data = {\"name\": \"jonA\", \"role\": \"Admin\", \"secondsToLive\": None}\n        basic_auth = (\"admin\", \"admin\")\n\n        res = self.post(\n            url=self.login_key,\n            data=data,\n            auth=basic_auth\n        )\n        api_key = None\n        if res[0]:\n            api_key = res[1].get(\"key\")\n        if api_key:\n            config_path = os.path.join(PROJECT_DIR, \"config/omp.yaml\")\n            with open(config_path, \"r\", encoding=\"utf8\") as fp:\n                content = fp.read()\n            my_yaml = yaml.YAML()\n            code = my_yaml.load(content)\n            code[\"grafana_api_key\"] = api_key\n            with open(config_path, \"w\", encoding=\"utf8\") as fp:\n                my_yaml.dump(code, fp)\n\n    def run(self):\n        \"\"\"\n        更新入口\n        :return:\n        \"\"\"\n        self.create_omp_user()\n        self.create_data_source()\n        self.update_dashboard()\n        self.update_user_profile()\n        self.grafana_auth()\n\n\nif __name__ == '__main__':\n    if len(sys.argv[1:]) != 1:\n        print(\"Please use python update_grafana.py local_ip\")\n        exit(1)\n    local_ip = sys.argv[1:][0]\n\n    config_file_path = os.path.join(PROJECT_DIR, \"config/omp.yaml\")\n    with open(config_file_path, \"r\") as fp:\n        CONFIG_DIC = yaml.load(fp, Loader=yaml.SafeLoader)\n    g_port = CONFIG_DIC.get(\"monitor_port\", {}).get(\"grafana\")\n    p_port = CONFIG_DIC.get(\"monitor_port\", {}).get(\"prometheus\")\n    l_port = CONFIG_DIC.get(\"monitor_port\", {}).get(\"loki\")\n    obj = Grafana(\n        ip=local_ip, port=g_port,\n        loki_ip=local_ip, loki_port=l_port,\n        prometheus_ip=local_ip, prometheus_port=p_port\n    )\n    obj.run()\n    # 更新grafana的面板数据\n    time.sleep(30)\n    synch_grafana_info()\n"
  },
  {
    "path": "scripts/source/update_monitor_agent.py",
    "content": "# -*- coding: utf-8 -*-\n# Project: update_monitor_agent\n# Author: jon.liu@yunzhihui.com\n# Create time: 2021-10-09 18:39\n# IDE: PyCharm\n# Version: 1.0\n# Introduction:\n\n\"\"\"\n更新监控Agent使用的方法\n\"\"\"\n\nimport os\nimport sys\nimport shutil\n\nimport django\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, \"omp_server\"))\nPACKAGE_HUB = os.path.join(PROJECT_DIR, \"package_hub\")\nUPDATE_HOME = os.environ.get(\"OMP_UPDATE_HOME_PATH\", \"\")\nif not UPDATE_HOME:\n    UPDATE_HOME = os.path.join(os.path.dirname(PROJECT_DIR), \"omp_update\")\n\n# 加载Django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom db_models.models import Host\nfrom utils.plugin.public_utils import get_file_md5\nfrom utils.plugin.monitor_agent import MonitorAgentManager\n\n\ndef make_backups(pack_md5):\n    \"\"\"\n    备份监控Agent文件\n    :param pack_md5: md5值\n    :return:\n    \"\"\"\n    old_pack_path = \"\"\n    for item in os.listdir(PACKAGE_HUB):\n        if item.startswith(\"omp_monitor_agent\") and item.endswith(\"tar.gz\"):\n            old_pack_path = os.path.join(PACKAGE_HUB, item)\n    if not old_pack_path:\n        return\n    backup_path = os.path.join(UPDATE_HOME, pack_md5)\n    if not os.path.exists(backup_path):\n        os.makedirs(backup_path)\n    shutil.copyfile(\n        old_pack_path,\n        os.path.join(backup_path, os.path.basename(old_pack_path))\n    )\n    os.remove(old_pack_path)\n\n\ndef update(pack_path, pack_md5):\n    \"\"\"\n    更新监控Agent\n    :param pack_path: 新包的路径\n    :param pack_md5: 新包的md5值\n    :return:\n    \"\"\"\n    # step1: 备份原文件\n    make_backups(pack_md5=pack_md5)\n    # step2: 复制新文件到目标路径\n    shutil.copyfile(\n        pack_path,\n        os.path.join(PACKAGE_HUB, os.path.basename(pack_path))\n    )\n    # step3: 查询所有主机，更新监控Agent\n    host_obj_lst = Host.objects.all()\n    if not host_obj_lst.exists():\n        return\n    # 利用封装好的方法先卸载，再安装\n    agent_manager = MonitorAgentManager(host_objs=list(host_obj_lst))\n    agent_manager.uninstall()\n    install_flag, install_message = agent_manager.install()\n    print(\n        f\"Update new omp_monitor_agent: \"\n        f\"install_flag: {install_flag}; install_message: {install_message}\")\n\n\ndef main(pack_path=None, pack_md5=None):\n    \"\"\"\n    更新主流程方法\n    :param pack_path: 安装包路径\n    :param pack_md5: 安装包md5值\n    :return:\n    \"\"\"\n    update(pack_path, pack_md5)\n    # TODO 如果有服务的配置可能需要有额外的更新操作！！\n\n\nif __name__ == '__main__':\n    sys_args = sys.argv[1:]\n    if len(sys_args) != 1:\n        print(\"Please use: python update_monitor_agent.py package_path\")\n    package_path = sys_args[0]\n    if not os.path.exists(package_path):\n        print(\"{0} Package Not Exist!\".format(package_path))\n        exit(1)\n    flag, package_md5 = get_file_md5(package_path)\n    main(pack_path=package_path, pack_md5=package_md5)\n"
  },
  {
    "path": "scripts/source/upgrade_service.py",
    "content": "# -*- coding:utf-8 -*-\nimport json\nimport os\nimport re\nimport sys\nimport time\nfrom datetime import datetime\nimport random\n\nimport django\nfrom django.db import transaction\n\nCURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\nPROJECT_DIR = os.path.dirname(os.path.dirname(CURRENT_DIR))\nsys.path.append(os.path.join(PROJECT_DIR, 'omp_server'))\n# 加载django环境\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"omp_server.settings\")\ndjango.setup()\n\nfrom app_store.new_install_utils import RedisDB\nfrom app_store.tmp_exec_back_task import back_end_verified_init\nfrom db_models.models import UserProfile, UploadPackageHistory, Service, \\\n    ApplicationHub, UpgradeHistory, Env, UpgradeDetail, RollbackHistory, \\\n    RollbackDetail\nfrom db_models.mixins import UpgradeStateChoices, RollbackStateChoices\nfrom service_upgrade.tasks import get_service_order, upgrade_service, \\\n    rollback_service\nfrom utils.plugin.public_utils import local_cmd\n\n\ndef log_print(message, level=\"info\"):\n    msg_str = f\"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]} \" \\\n              f\"{level.upper()} \" \\\n              f\"{message}\"\n    print(msg_str)\n\n\nclass BaseOperation:\n    services_level_key = \"service_levels\"\n    _lock_key = \"operation_lock\"\n\n    def __init__(self, service_package, ip=None):\n        self.redis = RedisDB()\n        self.service_package = service_package\n        self.ip = ip\n\n    @property\n    def service_level(self):\n        if hasattr(self, \"_level\"):\n            return getattr(self, \"_level\")\n        service_level = int(self.app.extend_fields.get(\"level\", 0))\n        services_layer = get_service_order()\n        level = services_layer.get(self.app_name, None)\n        if level is None:\n            level = service_level + max(services_layer.values()) + 1\n        setattr(self, \"_level\", level)\n        return level\n\n    @property\n    def get_services_level(self):\n        state, levels = self.redis.get(self.services_level_key)\n        if state:\n            return levels\n        return set()\n\n    def set_levels(self, levels):\n        self.redis.set(self.services_level_key, levels)\n\n    def incr(self):\n        level_key = f\"service_level:{self.service_level}\"\n        self.redis.conn.incr(level_key)\n\n    def decr(self):\n        level_key = f\"service_level:{self.service_level}\"\n        self.redis.conn.decr(level_key)\n\n    def set_app_name(self):\n        self.redis.set(self.app_name, True)\n\n    def delete_app_name(self):\n        self.redis.delete_keys(self.app_name)\n\n    def get_app_name(self):\n        state, data = self.redis.get(self.app_name)\n        if state:\n            return data\n        return False\n\n    def can_operation(self):\n        levels = self.get_services_level\n        if self.service_level in levels:\n            levels.remove(self.service_level)\n        can_operation = True\n        for i in levels:\n            service_level_key = f\"service_level:{i}\"\n            state, service_data = self.redis.get(service_level_key)\n            if not state:\n                continue\n            return False, levels\n        levels.add(self.service_level)\n        return can_operation, levels\n\n    def _wait(self):\n        # 非队列形式，后续优化 & 同时同一服务正在升级或回滚\n        while True:\n            with self.redis.conn.lock(self._lock_key):\n                can_operation, levels = self.can_operation()\n                if not can_operation:\n                    log_print(\"存在不同优先级服务在升级/回滚, 稍等片刻...\")\n                    continue\n                operating = self.get_app_name()\n                if operating:\n                    continue\n                self.set_levels(levels)\n                self.incr()\n                break\n\n    def handle(self):\n        pass\n\n    def __call__(self, *args, **kwargs):\n        regular = re.compile(r\"([a-zA-Z0-9]+)-.*\\.tar\\.gz\")\n        service_info = regular.findall(self.service_package)\n        if not service_info:\n            log_print(\"服务包包名格式错误！\", \"error\")\n            return False\n        self.app_name = service_info[0]\n        self.services = Service.objects.filter(service__app_name=self.app_name)\n        if self.ip:\n            self.services = self.services.filter(ip=self.ip)\n        if not self.services.exists():\n            log_print(\"环境未安装该服务，请先安装！\", \"error\")\n            return False\n        return self.handle()\n\n\nclass Upgrade(BaseOperation):\n    _valid_lock_key = \"valid_package\"\n\n    def valid_package(self):\n        # 校验服务包会删目录，需要锁,并且保证back_end_verified_path目录下无其他包\n        with self.redis.conn.lock(self._valid_lock_key):\n            frond_package_path = os.path.join(\n                PROJECT_DIR,\n                \"package_hub/front_end_verified\",\n                self.service_package\n            )\n            back_end_verified_path = os.path.join(\n                PROJECT_DIR, \"package_hub/back_end_verified\"\n            )\n            _cmd_str = f'mv {frond_package_path} {back_end_verified_path}'\n            _out, _err, _code = local_cmd(_cmd_str)\n            if _code:\n                log_print(f\"执行移动文件:{_cmd_str}发生错误:{_out}\")\n            back_end_verified_init(\n                operation_user=UserProfile.objects.first().username\n            )\n            package = UploadPackageHistory.objects.filter(\n                package_name=self.service_package\n            ).last()\n            if not package:\n                log_print(f\"未找到服务包：{self.service_package}!\", \"error\")\n                return None\n            start = time.time()\n            while True:\n                if time.time() - start > 60 * 10:\n                    log_print(f\"校验服务包超时！\", \"error\")\n                    break\n                package.refresh_from_db()\n                if package.package_status in [\n                    package.PACKAGE_STATUS_FAILED,\n                    package.PACKAGE_STATUS_PUBLISH_FAILED\n                ]:\n                    log_print(\n                        f\"校验服务包失败，失败详情:{package.error_msg}\", \"error\")\n                    return None\n                if package.package_status == \\\n                        package.PACKAGE_STATUS_PUBLISH_SUCCESS:\n                    log_print(\"校验服务包通过！\")\n                    return ApplicationHub.objects.filter(\n                        app_package=package).first()\n                log_print(\"校验服务包中...\")\n                time.sleep(random.uniform(1, 2))\n\n    def upgrade(self, app):\n        # 重新升级，原有升级记录不动\n        with transaction.atomic():\n            history = UpgradeHistory.objects.create(\n                env=Env.objects.first(),\n                operator=UserProfile.objects.first()\n            )\n            details = []\n            for service in self.services:\n                Service.update_dependence(\n                    service.service_dependence,\n                    json.loads(app.app_dependence or '[]')\n                )\n                details.append(\n                    UpgradeDetail(\n                        history=history,\n                        service_id=service.id,\n                        union_server=f\"{service.ip}-{self.app_name}\",\n                        target_app=app,\n                        current_app=service.service\n                    )\n                )\n            UpgradeDetail.objects.bulk_create(details)\n        upgrade_service(history.id)\n        return history\n\n    def handle(self):\n        app = self.valid_package()\n        if not app:\n            # 无app情况下流水线不凋回滚\n            return True\n        self.app = app\n        self.services = self.services.exclude(service=app)\n        if not self.services.exists():\n            log_print(\"该服务包已被升级！\")\n            # 流水线不凋回滚\n            return True\n        self._wait()\n        try:\n            history = self.upgrade(app)\n        except Exception as e:\n            log_print(f\"服务依赖校验失败:{str(e)}\")\n            # 流水线不凋回滚\n            return True\n        self.decr()\n        history.refresh_from_db()\n        if history.upgrade_state != UpgradeStateChoices.UPGRADE_SUCCESS:\n            return False\n        log_print(\"操作成功，即将退出！\")\n        return True\n\n\nclass Rollback(BaseOperation):\n\n    def roll_back(self, upgrade_details):\n        with transaction.atomic():\n            history = RollbackHistory.objects.create(\n                env=Env.objects.first(),\n                operator=UserProfile.objects.first()\n            )\n            RollbackDetail.objects.bulk_create(\n                [\n                    RollbackDetail(\n                        history=history,\n                        upgrade_id=upgrade_detail.id\n                    )\n                    for upgrade_detail in upgrade_details\n                ]\n            )\n        rollback_service(history.id)\n        return history\n\n    def handle(self):\n        app = ApplicationHub.objects.filter(\n            app_package__package_name=self.service_package\n        ).first()\n        if not app:\n            log_print(\"未找到该服务包相关信息，不可回滚！\", \"error\")\n            return False\n        self.app = app\n        details = UpgradeDetail.objects.filter(\n            target_app=app,\n            upgrade_state__in=[\n                UpgradeStateChoices.UPGRADE_SUCCESS,\n                UpgradeStateChoices.UPGRADE_FAIL\n            ],\n            has_rollback=False\n        )\n        if self.ip:\n            details = details.filter(service__ip=self.ip)\n        if not details.exists():\n            log_print(\"该服务包无可回滚记录，不可回滚！\", \"error\")\n            return False\n        can_roll_back_details = []\n        for detail in details:\n            if not UpgradeDetail.objects.filter(\n                service=detail.service,\n                id__gt=detail.id,\n                has_rollback=False\n            ).exists():\n                can_roll_back_details.append(detail)\n        if not can_roll_back_details:\n            log_print(\"该服务包无可回滚记录，不可回滚！\", \"error\")\n            return False\n        self._wait()\n        history = self.roll_back(can_roll_back_details)\n        self.decr()\n        history.refresh_from_db()\n        if history.rollback_state != RollbackStateChoices.ROLLBACK_SUCCESS:\n            return False\n        log_print(\"操作成功，即将退出！\")\n        return True\n\n\nif __name__ == '__main__':\n    # env 暂时不考虑\n    sys_args = sys.argv[1:]\n    if len(sys_args) < 2 or len(sys_args) > 3:\n        print(\"执行命令出错，请执行 \"\n              \"bash cmd_manager [upgrade|rollback] \"\n              \"[服务安装包(如: mysql-xxxxx.tar.gz)] \"\n              \"[服务所在ip(不填默认升级该服务所有实例)]\")\n        exit(1)\n    operation = sys_args[0]\n    if operation not in [\"rollback\", \"upgrade\"]:\n        print(\"执行命令出错，请执行 \"\n              \"bash cmd_manager [upgrade|rollback] \"\n              \"[服务安装包(如: mysql-xxxxx.tar.gz)] \"\n              \"[服务所在ip(不填默认升级该服务所有实例)]\")\n        exit(1)\n    result = eval(operation.capitalize())(*sys_args[1:])()\n    if result:\n        exit(0)\n    log_print(\"操作失败，即将退出！\", \"error\")\n    exit(1)\n"
  },
  {
    "path": "scripts/source/uwsgi",
    "content": "#!/bin/bash\n\n# uwsgi的控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nAPP_NAME=\"uwsgi\"\n\nstart() {\n  real_status\n  if [ $? -eq 0 ];then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    $PROJECT_FOLDER/component/env/bin/uwsgi --ini $PROJECT_FOLDER/config/uwsgi.ini >/dev/null  2>&1\n    echo \"${APP_NAME} [running]\"\n  fi\n}\n\nstop() {\n  real_status\n  if [ $? -eq 1 ]; then\n    echo \"${APP_NAME} [not running]\"\n    return 0\n  else\n    ps -ef | grep \"${PROJECT_FOLDER}/component/env/bin/uwsgi\" | grep -v grep | awk '{print $2}' | xargs kill -9\n    sleep 3\n    real_status\n    if [ $? -eq 0 ]; then\n      echo \"${APP_NAME} [running]\"\n      return 1\n    else\n      echo \"${APP_NAME} [not running]\"\n      return 0\n    fi\n  fi\n}\n\nreal_status() {\n  uwsgi_status=$(ps -ef | grep ${PROJECT_FOLDER}/component/env/bin/uwsgi | grep -v grep)\n  if [ -n \"$uwsgi_status\" ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nstatus() {\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 1\n  fi\n}\n\ncase $1 in\nstart) start ;;\nstop) stop ;;\nrestart)\n  stop\n  start\n  ;;\nstatus)\n  status\n  ;;\n*)\n  echo \"usage: $0 [start|stop|restart|status]\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/source/worker",
    "content": "#!/bin/bash\n\n# celery worker的控制脚本\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nTHIS_SCRIPT=\"${CURRENT_DIR}/$(basename $0)\"\nPROJECT_FOLDER=\"$(dirname $(dirname ${CURRENT_DIR}))\"\n\n# 解决python的ssl依赖问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nAPP_NAME=\"worker\"\n\nstart() {\n  real_status\n  if [ $? -eq 0 ];then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    cd $PROJECT_FOLDER/omp_server\n    nohup $PROJECT_FOLDER/component/env/bin/python3 $PROJECT_FOLDER/component/env/bin/celery \\\n    -A omp_server worker -c 10 --loglevel=INFO -f $PROJECT_FOLDER/logs/celery.log >$PROJECT_FOLDER/logs/celery_worker.log  2>&1 &\n    echo \"${APP_NAME} [running]\"\n  fi\n}\n\nstop() {\n  real_status\n  if [ $? -eq 1 ]; then\n    echo \"${APP_NAME} [not running]\"\n    return 0\n  else\n    ps -ef | grep \"${PROJECT_FOLDER}/component/env/bin/celery\" |grep -v scheduler | grep -v grep | awk '{print $2}' | xargs kill -9\n    sleep 3\n    real_status\n    if [ $? -eq 0 ]; then\n      echo \"${APP_NAME} [running]\"\n      return 1\n    else\n      echo \"${APP_NAME} [not running]\"\n      return 0\n    fi\n  fi\n}\n\nreal_status() {\n  worker_status=$(ps -ef | grep ${PROJECT_FOLDER}/component/env/bin/celery | grep -v scheduler | grep -v grep)\n  if [ -n \"$worker_status\" ]; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nstatus() {\n  real_status\n  if [ $? -eq 0 ]; then\n    echo \"${APP_NAME} [running]\"\n    return 0\n  else\n    echo \"${APP_NAME} [not running]\"\n    return 1\n  fi\n}\n\ncase $1 in\nstart) start ;;\nstop) stop ;;\nrestart)\n  stop\n  start\n  ;;\nstatus)\n  status\n  ;;\n*)\n  echo \"usage: $0 [start|stop|restart|status]\"\n  ;;\nesac\n"
  },
  {
    "path": "scripts/uninstall.sh",
    "content": "#!/bin/bash\n\n# 项目卸载\n\nCURRENT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nPROJECT_FOLDER=\"$(dirname ${CURRENT_DIR})\"\n\n# 解决openssl依赖的问题\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${PROJECT_FOLDER}/component/env/lib/\n\nOMP_SCRIPT=\"${PROJECT_FOLDER}/scripts/omp\"\n\n\n# 清除omp server端保活定时任务\nfunction clear_omp_keep_alive() {\n  crontab -l|grep -v \"omp all start\" 2>/dev/null | crontab -\n}\n\n\nfunction uninstall() {\n  ack=\"N\"\n  while [[ \"$ack\" == \"N\" ]] || [[ \"$ack\" == \"n\" ]]; do\n    read -p \"继续卸载请输入 Y, 中断卸载请输入N > \" ack\n  done\n  if [[ \"$ack\" -ne \"Y\" ]]; then\n   echo \"卸载程序已中断，即将退出！\"\n   exit 1\n  fi\n  clear_omp_keep_alive\n  bash $OMP_SCRIPT all stop\n  sleep 5\n  /bin/rm -rf ${PROJECT_FOLDER}\n}\n\nuninstall"
  }
]