diff --git a/packages/server/certd-client b/packages/server/certd-client
deleted file mode 160000
index 8c7b3853b..000000000
--- a/packages/server/certd-client
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 8c7b3853be27df392dff765d39c6c53de09418bd
diff --git a/packages/server/certd-server b/packages/server/certd-server
deleted file mode 160000
index a5681f154..000000000
--- a/packages/server/certd-server
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a5681f154468fc5c4aac7e4a0ce090cfffc00e9e
diff --git a/packages/ui/certd-client/.browserslistrc b/packages/ui/certd-client/.browserslistrc
new file mode 100644
index 000000000..214388fe4
--- /dev/null
+++ b/packages/ui/certd-client/.browserslistrc
@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead
diff --git a/packages/ui/certd-client/.env b/packages/ui/certd-client/.env
new file mode 100644
index 000000000..4933b20fc
--- /dev/null
+++ b/packages/ui/certd-client/.env
@@ -0,0 +1,3 @@
+VITE_APP_API=/api
+#登录与权限关闭
+VITE_APP_PM_ENABLED=true
diff --git a/packages/ui/certd-client/.env.debug b/packages/ui/certd-client/.env.debug
new file mode 100644
index 000000000..6a532d749
--- /dev/null
+++ b/packages/ui/certd-client/.env.debug
@@ -0,0 +1,2 @@
+#登录与权限开启
+VITE_APP_PM_ENABLED=false
diff --git a/packages/ui/certd-client/.env.debugpm b/packages/ui/certd-client/.env.debugpm
new file mode 100644
index 000000000..c3b69a80f
--- /dev/null
+++ b/packages/ui/certd-client/.env.debugpm
@@ -0,0 +1,2 @@
+#登录与权限开启
+VITE_APP_PM_ENABLED=true
diff --git a/packages/ui/certd-client/.env.pm b/packages/ui/certd-client/.env.pm
new file mode 100644
index 000000000..c3b69a80f
--- /dev/null
+++ b/packages/ui/certd-client/.env.pm
@@ -0,0 +1,2 @@
+#登录与权限开启
+VITE_APP_PM_ENABLED=true
diff --git a/packages/ui/certd-client/.env.production b/packages/ui/certd-client/.env.production
new file mode 100644
index 000000000..bc3dbcb73
--- /dev/null
+++ b/packages/ui/certd-client/.env.production
@@ -0,0 +1,3 @@
+VITE_APP_API=http://www.docmirror.cn:7001/api
+#登录与权限开启
+VITE_APP_PM_ENABLED=true
diff --git a/packages/ui/certd-client/.eslintignore b/packages/ui/certd-client/.eslintignore
new file mode 100644
index 000000000..eb79dd5fc
--- /dev/null
+++ b/packages/ui/certd-client/.eslintignore
@@ -0,0 +1,2 @@
+node_modules
+.idea
diff --git a/packages/ui/certd-client/.eslintrc.js b/packages/ui/certd-client/.eslintrc.js
new file mode 100644
index 000000000..2dcd3abe2
--- /dev/null
+++ b/packages/ui/certd-client/.eslintrc.js
@@ -0,0 +1,76 @@
+module.exports = {
+ root: true,
+ env: {
+ browser: true,
+ node: true,
+ es6: true
+ },
+ parser: "vue-eslint-parser",
+ parserOptions: {
+ parser: "@typescript-eslint/parser",
+ ecmaVersion: 2020,
+ sourceType: "module",
+ jsxPragma: "React",
+ ecmaFeatures: {
+ jsx: true,
+ tsx: true
+ }
+ },
+ extends: [
+ "plugin:vue/vue3-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:prettier/recommended",
+ "prettier"
+ ],
+ rules: {
+ //"max-len": [0, 200, 2, { ignoreUrls: true }],
+ "@typescript-eslint/ban-ts-ignore": "off",
+ "@typescript-eslint/ban-ts-comment": "off",
+ "@typescript-eslint/ban-types": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off"
+ // "@typescript-eslint/no-unused-vars": [
+ // "error",
+ // {
+ // argsIgnorePattern: "^h$",
+ // varsIgnorePattern: "^h$",
+ // },
+ // ],
+ // "no-unused-vars": [
+ // "error",
+ // {
+ // argsIgnorePattern: "^h$",
+ // varsIgnorePattern: "^h$",
+ // },
+ // ],
+ // "vue/custom-event-name-casing": "off",
+ // "no-use-before-define": "off",
+ // "space-before-function-paren": "off",
+
+ // "vue/attributes-order": "off",
+ // "vue/one-component-per-file": "off",
+ // "vue/html-closing-bracket-newline": "off",
+ // "vue/max-attributes-per-line": "off",
+ // "vue/multiline-html-element-content-newline": "off",
+ // "vue/singleline-html-element-content-newline": "off",
+ // "vue/attribute-hyphenation": "off",
+ // "vue/require-default-prop": "off",
+ // "vue/html-self-closing": [
+ // "error",
+ // {
+ // html: {
+ // void: "always",
+ // normal: "never",
+ // component: "always",
+ // },
+ // svg: "always",
+ // math: "always",
+ // },
+ // ],
+ }
+};
diff --git a/packages/ui/certd-client/.gitignore b/packages/ui/certd-client/.gitignore
new file mode 100644
index 000000000..980be95df
--- /dev/null
+++ b/packages/ui/certd-client/.gitignore
@@ -0,0 +1,11 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
+/stats.html
+yarn.lock
+.idea
+/.idea/
+yarn-error.log
+vite-profile.cpuprofile
diff --git a/packages/ui/certd-client/.npmignore b/packages/ui/certd-client/.npmignore
new file mode 100644
index 000000000..b6c09bbd8
--- /dev/null
+++ b/packages/ui/certd-client/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+/stats.html
diff --git a/packages/ui/certd-client/.prettierrc b/packages/ui/certd-client/.prettierrc
new file mode 100644
index 000000000..61e99f7c5
--- /dev/null
+++ b/packages/ui/certd-client/.prettierrc
@@ -0,0 +1,5 @@
+{
+
+ "trailingComma": "none",
+ "printWidth": 220
+}
diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md
new file mode 100644
index 000000000..ccaa88d14
--- /dev/null
+++ b/packages/ui/certd-client/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.10.5](https://github.com/fast-crud/fast-crud/compare/v0.10.4...v0.10.5) (2021-07-01)
+
+
+### Performance Improvements
+
+* fs-admin 与crud demo ([4e6b20f](https://github.com/fast-crud/fast-crud/commit/4e6b20fe19434460853841f371b9fd5f16e5e2d3))
+* fs-admin纳入子模块 ([2940d30](https://github.com/fast-crud/fast-crud/commit/2940d30f419bf4bde1e8e791f1fbdb9184818285))
diff --git a/packages/ui/certd-client/LICENSE b/packages/ui/certd-client/LICENSE
new file mode 100644
index 000000000..0ad25db4b
--- /dev/null
+++ b/packages/ui/certd-client/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/packages/ui/certd-client/README.md b/packages/ui/certd-client/README.md
new file mode 100644
index 000000000..600173994
--- /dev/null
+++ b/packages/ui/certd-client/README.md
@@ -0,0 +1,27 @@
+# fs-admin-antdv
+
+基于vue3、antdv 的 admin管理后台脚手架
+更多信息请参考: [fast-crud](https://github.com/fast-crud/fast-crud)
+# server端
+
+## fs-server-js
+https://github.com/fast-crud/fs-server-js
+
+
+# 感谢
+
+### 依赖
+* [vue](https://github.com/vuejs/vue-next)
+* [vue-router](https://github.com/vuejs/vue-router-next)
+* [antdv 2x](https://github.com/vueComponent/ant-design-vue)
+* [vitejs](https://github.com/vitejs/vite)
+* [pinia](https://github.com/posva/pinia)
+* [purge-icons](https://github.com/antfu/purge-icons)
+
+### 参考如下项目
+* [d2-admin](https://github.com/d2-projects/d2-admin)
+* [antdv-pro](https://github.com/vueComponent/ant-design-vue-pro)
+* [vben-admin](https://github.com/anncwb/vue-vben-admin)
+
+感谢这些优秀的项目
+
diff --git a/packages/ui/certd-client/build/modify-vars.ts b/packages/ui/certd-client/build/modify-vars.ts
new file mode 100644
index 000000000..c6cf20a37
--- /dev/null
+++ b/packages/ui/certd-client/build/modify-vars.ts
@@ -0,0 +1,11 @@
+// import { getThemeVariables } from "ant-design-vue/dist/theme";
+// import path from "path";
+// const resolve = path.resolve;
+export function generateModifyVars(dark = false) {
+ //const modifyVars = getThemeVariables({ dark });
+ // const vars = `${resolve("src/style/theme/index.less")}`;
+ return {
+ //...modifyVars
+ // hack: `true; @import (reference) "${vars}";`
+ };
+}
diff --git a/packages/ui/certd-client/build/theme-colors.ts b/packages/ui/certd-client/build/theme-colors.ts
new file mode 100644
index 000000000..41396ef06
--- /dev/null
+++ b/packages/ui/certd-client/build/theme-colors.ts
@@ -0,0 +1,72 @@
+import { generate } from "@ant-design/colors";
+
+export const primaryColor = "#1890ff";
+
+export const darkMode = "light";
+
+type Fn = (...arg: any) => any;
+
+export interface GenerateColorsParams {
+ mixLighten: Fn;
+ mixDarken: Fn;
+ tinycolor: any;
+ color?: string;
+}
+
+export function generateAntColors(color: string) {
+ return generate(color, {
+ theme: "default"
+ });
+}
+
+export function getThemeColors(color?: string) {
+ const tc = color || primaryColor;
+ const colors = generateAntColors(tc);
+ const primary = colors[5];
+ const modeColors = generateAntColors(primary);
+
+ return [...colors, ...modeColors];
+}
+
+export function generateColors({ color = primaryColor, mixLighten, mixDarken, tinycolor }: GenerateColorsParams) {
+ const arr = new Array(19).fill(0);
+ const lightens = arr.map((_t, i) => {
+ return mixLighten(color, i / 5);
+ });
+
+ const darkens = arr.map((_t, i) => {
+ return mixDarken(color, i / 5);
+ });
+
+ const alphaColors = arr.map((_t, i) => {
+ return tinycolor(color)
+ .setAlpha(i / 20)
+ .toRgbString();
+ });
+
+ const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, "").replace(/0\./g, "."));
+
+ const tinycolorLightens = arr
+ .map((_t, i) => {
+ return tinycolor(color)
+ .lighten(i * 5)
+ .toHexString();
+ })
+ .filter((item) => item !== "#ffffff");
+
+ const tinycolorDarkens = arr
+ .map((_t, i) => {
+ return tinycolor(color)
+ .darken(i * 5)
+ .toHexString();
+ })
+ .filter((item) => item !== "#000000");
+ return [
+ ...lightens,
+ ...darkens,
+ ...alphaColors,
+ ...shortAlphaColors,
+ ...tinycolorDarkens,
+ ...tinycolorLightens
+ ].filter((item) => !item.includes("-"));
+}
diff --git a/packages/ui/certd-client/build/theme-plugin.ts b/packages/ui/certd-client/build/theme-plugin.ts
new file mode 100644
index 000000000..d223642b4
--- /dev/null
+++ b/packages/ui/certd-client/build/theme-plugin.ts
@@ -0,0 +1,68 @@
+/**
+ * Vite plugin for website theme color switching
+ * https://github.com/anncwb/vite-plugin-theme
+ */
+import type { Plugin } from "vite";
+import path from "path";
+import { viteThemePlugin, mixLighten, mixDarken, tinycolor, antdDarkThemePlugin } from "vite-plugin-theme";
+import { getThemeColors, generateColors } from "./theme-colors";
+import { generateModifyVars } from "./modify-vars";
+
+export function configThemePlugin(isBuild: boolean): Plugin[] {
+ const colors = generateColors({
+ mixDarken,
+ mixLighten,
+ tinycolor
+ });
+ const colorVariables = [...getThemeColors(), ...colors];
+ const plugin = [
+ viteThemePlugin({
+ // resolveSelector: (s) => {
+ // s = s.trim();
+ // switch (s) {
+ // case ".ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon":
+ // return ".ant-steps-item-icon > .ant-steps-icon";
+ // case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)":
+ // case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover":
+ // case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active":
+ // return s;
+ // case ".ant-steps-item-icon > .ant-steps-icon":
+ // return s;
+ // }
+ // return `[data-theme] ${s}`;
+ // },
+ resolveSelector: (s) => {
+ s = s.trim();
+ if (s === ".ant-btn:hover,.ant-btn:focus") {
+ // console.log("ssss", s);
+ return ".theme-discard-xxxxxxx";
+ }
+ return s;
+ },
+ colorVariables
+ }),
+ antdDarkThemePlugin({
+ preloadFiles: [
+ path.resolve(process.cwd(), "node_modules/ant-design-vue/dist/antd.less"),
+ path.resolve(process.cwd(), "src/style/theme/index.less")
+ ],
+ filter: (id) => (isBuild ? !id.endsWith("antd.less") : true),
+ // extractCss: false,
+ darkModifyVars: {
+ ...generateModifyVars(true),
+ "text-color": "#c9d1d9",
+ "text-color-base": "#c9d1d9",
+ "component-background": "#151515",
+ // black: '#0e1117',
+ // #8b949e
+ "text-color-secondary": "#8b949e",
+ "border-color-base": "#303030",
+ // 'border-color-split': '#30363d',
+ "item-active-bg": "#111b26",
+ "app-content-background": "rgb(255 255 255 / 4%)"
+ }
+ })
+ ];
+
+ return (plugin as unknown) as Plugin[];
+}
diff --git a/packages/ui/certd-client/index.html b/packages/ui/certd-client/index.html
new file mode 100644
index 000000000..aac3b8bfe
--- /dev/null
+++ b/packages/ui/certd-client/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ antdv-fast-crud
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/certd-client/tailwind.config.js b/packages/ui/certd-client/tailwind.config.js
new file mode 100644
index 000000000..ec39f7e53
--- /dev/null
+++ b/packages/ui/certd-client/tailwind.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ purge: [],
+ darkMode: false, // or 'media' or 'class'
+ theme: {
+ extend: {}
+ },
+ variants: {
+ extend: {}
+ },
+ plugins: []
+};
diff --git a/packages/ui/certd-client/tests/unit/example.spec.ts b/packages/ui/certd-client/tests/unit/example.spec.ts
new file mode 100644
index 000000000..bc9939b65
--- /dev/null
+++ b/packages/ui/certd-client/tests/unit/example.spec.ts
@@ -0,0 +1,13 @@
+import { expect } from 'chai'
+import { shallowMount } from '@vue/test-utils'
+import HelloWorld from '@/components/HelloWorld.vue'
+
+describe('HelloWorld.vue', () => {
+ it('renders props.msg when passed', () => {
+ const msg = 'new message'
+ const wrapper = shallowMount(HelloWorld, {
+ props: { msg }
+ })
+ expect(wrapper.text()).to.include(msg)
+ })
+})
diff --git a/packages/ui/certd-client/tsconfig.json b/packages/ui/certd-client/tsconfig.json
new file mode 100644
index 000000000..bdb44e0fe
--- /dev/null
+++ b/packages/ui/certd-client/tsconfig.json
@@ -0,0 +1,45 @@
+{
+ "compilerOptions": {
+ // 这样就可以对 `this` 上的数据属性进行更严格的推断`
+ "noImplicitAny": false,
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "jsx": "preserve",
+ "importHelpers": true,
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "outDir": "./dist/ts",
+ "types": [
+ "mocha",
+ "chai",
+ "node"
+ ],
+ "paths": {
+ "/@/*": ["src/*"],
+ "/src/*": ["src/*"],
+ "/#/*": ["types/*"]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "src/**/*.vue",
+ "tests/**/*.ts",
+ "tests/**/*.tsx"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/ui/certd-client/vite.config.ts b/packages/ui/certd-client/vite.config.ts
new file mode 100644
index 000000000..de89cd4be
--- /dev/null
+++ b/packages/ui/certd-client/vite.config.ts
@@ -0,0 +1,101 @@
+import vue from "@vitejs/plugin-vue";
+import vueJsx from "@vitejs/plugin-vue-jsx";
+import visualizer from "rollup-plugin-visualizer";
+import viteCompression from "vite-plugin-compression";
+import PurgeIcons from "vite-plugin-purge-icons";
+import * as path from "path";
+// import WindiCSS from "vite-plugin-windicss";
+// import { generateModifyVars } from "./build/modify-vars";
+// import { configThemePlugin } from "./build/theme-plugin";
+// import OptimizationPersist from "vite-plugin-optimize-persist";
+// import PkgConfig from "vite-plugin-package-config";
+// https://vitejs.dev/config/
+// 增加环境变量 _
+process.env.VITE_APP_VERSION = require("./package.json").version;
+process.env.VITE_APP_BUILD_TIME = require("dayjs")().format("YYYY-M-D HH:mm:ss");
+
+export default ({ command, mode }) => {
+ console.log("args", command, mode);
+
+ let devServerFs: any = {};
+ let devAlias: any[] = [];
+ if (mode.startsWith("debug")) {
+ devAlias = [
+ { find: /@fast-crud\/fast-crud\/dist/, replacement: path.resolve("../../fast-crud/src/") },
+ { find: /@fast-crud\/fast-crud$/, replacement: path.resolve("../../fast-crud/src/") },
+ { find: /@fast-crud\/fast-extends\/dist/, replacement: path.resolve("../../fast-extends/src/") },
+ { find: /@fast-crud\/fast-extends$/, replacement: path.resolve("../../fast-extends/src/") },
+ { find: /@fast-crud\/ui-antdv$/, replacement: path.resolve("../../ui/ui-antdv/src/") }
+ ];
+ devServerFs = {
+ // 如果是你自己的项目,这项可以删掉
+ // 这里配置dev启动时读取的项目根目录
+ allow: ["../../"]
+ };
+ console.log("devAlias", devAlias);
+ }
+
+ return {
+ base: "/",
+ plugins: [
+ vueJsx(),
+ vue(),
+ // 压缩build后的代码
+ viteCompression(),
+ PurgeIcons({
+ // iconSource: "local"
+ // remoteDataAPI: "https://gitee.com/fast-crud/collections-json/raw/master/json",
+ // includedCollections: ["ion"]
+ })
+ //主题色替换
+ //...configThemePlugin(true),
+ // viteThemePlugin({
+ // // Match the color to be modified
+ // colorVariables: ["#1890ff", "#40a9ff"]
+ // }),
+ // windicss tailwindcss
+ // WindiCSS()
+ ],
+ esbuild: {
+ // pure: ["console.log", "debugger"],
+ jsxFactory: "h",
+ jsxFragment: "Fragment"
+ },
+ resolve: {
+ alias: [
+ ...devAlias,
+ { find: "/@", replacement: path.resolve("./src") },
+ { find: "/#", replacement: path.resolve("./types") }
+ ],
+ dedupe: ["vue"]
+ },
+ optimizeDeps: {
+ include: ["ant-design-vue"]
+ },
+ build: {
+ rollupOptions: {
+ plugins: [visualizer()]
+ }
+ },
+ css: {
+ preprocessorOptions: {
+ less: {
+ // 修改默认主题颜色,配置less变量
+ // modifyVars: generateModifyVars(),
+ javascriptEnabled: true
+ }
+ }
+ },
+ server: {
+ port: 3002,
+ fs: devServerFs,
+ proxy: {
+ // with options
+ "/api": {
+ //配套后端 https://github.com/fast-crud/fs-server-js
+ target: "http://127.0.0.1:7001"
+ }
+ }
+ }
+ };
+};
diff --git a/packages/ui/certd-client/windi.config.js b/packages/ui/certd-client/windi.config.js
new file mode 100644
index 000000000..6d82c4951
--- /dev/null
+++ b/packages/ui/certd-client/windi.config.js
@@ -0,0 +1,6 @@
+// windi.config.ts
+export default {
+ attributify: {
+ prefix: "w:"
+ }
+};
diff --git a/packages/ui/certd-server/.dockerignore b/packages/ui/certd-server/.dockerignore
new file mode 100644
index 000000000..6eb6e80d1
--- /dev/null
+++ b/packages/ui/certd-server/.dockerignore
@@ -0,0 +1,16 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+!dist/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+.tsbuildinfo
+.tsbuildinfo.*
+/data/db.sqlite
diff --git a/packages/ui/certd-server/.editorconfig b/packages/ui/certd-server/.editorconfig
new file mode 100644
index 000000000..4c7f8a8ef
--- /dev/null
+++ b/packages/ui/certd-server/.editorconfig
@@ -0,0 +1,11 @@
+# 🎨 editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true
\ No newline at end of file
diff --git a/packages/ui/certd-server/.eslintrc.json b/packages/ui/certd-server/.eslintrc.json
new file mode 100644
index 000000000..7828b2c51
--- /dev/null
+++ b/packages/ui/certd-server/.eslintrc.json
@@ -0,0 +1,11 @@
+{
+ "extends": "./node_modules/mwts/",
+ "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
+ "env": {
+ "jest": true
+ },
+ "rules": {
+ "@typescript-eslint/ban-ts-ignore": "off",
+ "@typescript-eslint/ban-ts-comment": "off"
+ }
+}
diff --git a/packages/ui/certd-server/.gitignore b/packages/ui/certd-server/.gitignore
new file mode 100644
index 000000000..41f712631
--- /dev/null
+++ b/packages/ui/certd-server/.gitignore
@@ -0,0 +1,18 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+dist/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+.tsbuildinfo
+.tsbuildinfo.*
+/data/db.sqlite
+/pnpm-lock.yaml
+.serverless
diff --git a/packages/ui/certd-server/.prettierrc.js b/packages/ui/certd-server/.prettierrc.js
new file mode 100644
index 000000000..b964930f3
--- /dev/null
+++ b/packages/ui/certd-server/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('mwts/.prettierrc.json')
+}
diff --git a/packages/ui/certd-server/Dockerfile b/packages/ui/certd-server/Dockerfile
new file mode 100644
index 000000000..521407343
--- /dev/null
+++ b/packages/ui/certd-server/Dockerfile
@@ -0,0 +1,15 @@
+FROM registry.cn-shenzhen.aliyuncs.com/greper/node:15.8.0-alpine
+
+WORKDIR /home
+
+COPY . .
+# 如果各公司有自己的私有源,可以替换registry地址
+#RUN npm install --registry=https://registry.npmmirror.com
+RUN npm install -g cnpm
+RUN cnpm install
+RUN npm run build:preview
+
+# 如果端口更换,这边可以更新一下
+EXPOSE 7001
+
+CMD ["npm", "run", "online:preview"]
diff --git a/packages/ui/certd-server/LICENSE b/packages/ui/certd-server/LICENSE
new file mode 100644
index 000000000..0ad25db4b
--- /dev/null
+++ b/packages/ui/certd-server/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/packages/ui/certd-server/README.md b/packages/ui/certd-server/README.md
new file mode 100644
index 000000000..d0aca2df6
--- /dev/null
+++ b/packages/ui/certd-server/README.md
@@ -0,0 +1,31 @@
+# fast-server-js
+
+base on midway
+
+## QuickStart
+
+
+
+see [midway docs][midway] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+
+
+[midway]: https://midwayjs.org
diff --git a/packages/ui/certd-server/README.zh-CN.md b/packages/ui/certd-server/README.zh-CN.md
new file mode 100644
index 000000000..54b99c0e4
--- /dev/null
+++ b/packages/ui/certd-server/README.zh-CN.md
@@ -0,0 +1,30 @@
+# fast-server-js
+
+## 快速入门
+
+
+
+如需进一步了解,参见 [midway 文档][midway]。
+
+### 本地开发
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### 部署
+
+```bash
+$ npm start
+```
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+
+
+[midway]: https://midwayjs.org
+
diff --git a/packages/ui/certd-server/app.js b/packages/ui/certd-server/app.js
new file mode 100644
index 000000000..7f1d93539
--- /dev/null
+++ b/packages/ui/certd-server/app.js
@@ -0,0 +1,23 @@
+// 获取框架
+const WebFramework = require('@midwayjs/koa').Framework;
+const { Bootstrap } = require('@midwayjs/bootstrap');
+
+const DirectoryFileDetector = require( "@midwayjs/core").DirectoryFileDetector;
+
+const baseDir = process.cwd()
+const pipelineDir = baseDir +"./node_modules/@certd/pipeline"
+const customFileDetector = new DirectoryFileDetector({loadDir:[baseDir,pipelineDir]})
+
+
+module.exports = async () => {
+ // 加载框架并执行
+ await Bootstrap.configure({
+ moduleDetector:customFileDetector
+ }).run();
+ // 获取依赖注入容器
+ const container = Bootstrap.getApplicationContext();
+ // 获取 koa framework
+ const framework = container.get(WebFramework);
+ // 返回 app 对象
+ return framework.getApplication();
+};
diff --git a/packages/ui/certd-server/bootstrap.js b/packages/ui/certd-server/bootstrap.js
new file mode 100644
index 000000000..1b0bbb73a
--- /dev/null
+++ b/packages/ui/certd-server/bootstrap.js
@@ -0,0 +1,15 @@
+const WebFramework = require('@midwayjs/koa').Framework;
+const web = new WebFramework().configure({
+ port: 7001,
+});
+
+const DirectoryFileDetector = require( "@midwayjs/core").DirectoryFileDetector;
+
+const baseDir = process.cwd()
+const pipelineDir = baseDir +"./node_modules/@certd/pipeline"
+const customFileDetector = new DirectoryFileDetector({loadDir:[baseDir,pipelineDir]})
+
+const { Bootstrap } = require('@midwayjs/bootstrap');
+Bootstrap.load(web).configure({
+ moduleDetector:customFileDetector
+}).run();
diff --git a/packages/ui/certd-server/db/migration/v00001__init.sql b/packages/ui/certd-server/db/migration/v00001__init.sql
new file mode 100644
index 000000000..b8f169c35
--- /dev/null
+++ b/packages/ui/certd-server/db/migration/v00001__init.sql
@@ -0,0 +1,77 @@
+-- 表:sys_permission
+CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (2, '权限管理', 'sys:auth', 1, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (3, '用户管理', 'sys:auth:user', 2, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (4, '查看', 'sys:auth:user:view', 3, 100, 1, 1624189112333);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (5, '权限管理', 'sys:auth:per', 2, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (6, '查看', 'sys:auth:per:view', 5, 100, 1, 1624189161317);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (7, '角色管理', 'sys:auth:role', 2, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (8, '查看', 'sys:auth:role:view', 7, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (9, '修改', 'sys:auth:user:edit', 3, 300, 1, 1624189127688);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (10, '删除', 'sys:auth:user:remove', 3, 400, 1, 1624189133184);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (11, '添加', 'sys:auth:user:add', 3, 200, 1, 1624189142576);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (12, '修改', 'sys:auth:role:edit', 7, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (13, '删除', 'sys:auth:role:remove', 7, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (14, '添加', 'sys:auth:role:add', 7, 1, 1, 1);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (15, '修改', 'sys:auth:per:edit', 5, 300, 1, 1624189308837);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (16, '删除', 'sys:auth:per:remove', 5, 400, 1, 1624189256926);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (17, '添加', 'sys:auth:per:add', 5, 200, 1, 1624189283766);
+INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (18,'授权','sys:auth:role:authz',7,100,1,1624335712144);
+
+
+
+-- 表:sys_role
+CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537);
+INSERT INTO sys_role (id, name, create_time, update_time) VALUES (2, '只读角色', 1, 1623749138537);
+
+-- 表:sys_role_permission
+CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id"));
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 2);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 3);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 4);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 5);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 6);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 7);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 8);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 9);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 10);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 11);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 12);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 13);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 14);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 15);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 16);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 17);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 18);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, -1);
+
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 4);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 6);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 8);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 1);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 2);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 3);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 5);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 7);
+INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, -1);
+
+-- 表:sys_user
+CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL);
+INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (2, 'readonly', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456');
+
+-- 表:sys_user_role
+CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id"));
+INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1);
+INSERT INTO sys_user_role (role_id, user_id) VALUES (2, 2);
+
+-- 索引:IDX_223de54d6badbe43a5490450c3
+CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name");
+
+-- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da
+CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username");
+
+
diff --git a/packages/ui/certd-server/db/migration/v00002__for_pre.sql b/packages/ui/certd-server/db/migration/v00002__for_pre.sql
new file mode 100644
index 000000000..708d7aed5
--- /dev/null
+++ b/packages/ui/certd-server/db/migration/v00002__for_pre.sql
@@ -0,0 +1,4 @@
+-- for preview 限制演示环境的数据修改
+update sqlite_sequence set seq = 1000 where name = 'sys_user' ;
+update sqlite_sequence set seq = 1000 where name = 'sys_permission' ;
+update sqlite_sequence set seq = 1000 where name = 'sys_role' ;
diff --git a/packages/ui/certd-server/db/migration/v10000__certd.sql b/packages/ui/certd-server/db/migration/v10000__certd.sql
new file mode 100644
index 000000000..c3867d6b0
--- /dev/null
+++ b/packages/ui/certd-server/db/migration/v10000__certd.sql
@@ -0,0 +1,24 @@
+--
+-- 由SQLiteStudio v3.3.3 产生的文件 周六 7月 3 00:38:02 2021
+--
+-- 文本编码:UTF-8
+--
+
+-- 表:cd_access
+CREATE TABLE "cd_access" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "name" varchar(100) NOT NULL, "type" varchar(100) NOT NULL, "setting" varchar(1024), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
+-- 表:cd_cert
+CREATE TABLE "cd_cert" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "domains" varchar(2048) NOT NULL, "email" varchar(100) NOT NULL, "cert_issuer_id" integer, "challenge_type" varchar(100), "challenge_dns_type" varchar(100),"challenge_access_id" integer, "country" varchar(100), "state" varchar(100), "locality" varchar(100), "organization" varchar(100), "organization_unit" varchar(100), "remark" varchar(100), "last_history_id" integer, "last_success_id" integer, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
+-- 表:cd_cert_apply_history
+CREATE TABLE "cd_cert_apply_history" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "cert_id" integer NOT NULL, "success" boolean, "result" varchar(1024), "cert_crt" varchar(1024), "cert_key" varchar(1024), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
+-- 表:cd_cert_issuer
+CREATE TABLE "cd_cert_issuer" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "type" varchar(20) NOT NULL, "account" varchar(100) NOT NULL, "private_key" varchar(1024), "setting" varchar, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
+-- 表:cd_task
+CREATE TABLE "cd_task" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "name" varchar(100), "type" varchar(100), "setting" varchar(2048), "cert_id" integer NOT NULL, "last_history_id" integer, "last_success_id" integer, "remark" varchar(100), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
+-- 表:cd_task_history
+CREATE TABLE "cd_task_history" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, "task_id" integer NOT NULL, "cert_id" integer NOT NULL, "cert_apply_history_id" integer NOT NULL, "success" boolean, "result" varchar(2048), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP));
+
diff --git a/packages/ui/certd-server/f.yaml b/packages/ui/certd-server/f.yaml
new file mode 100644
index 000000000..a63871126
--- /dev/null
+++ b/packages/ui/certd-server/f.yaml
@@ -0,0 +1,11 @@
+service: certd ## 应用发布到云平台的名字,一般指应用名
+
+provider:
+ name: aliyun ## 发布的云平台,aliyun,tencent 等
+
+deployType:
+ type: koa ## 部署的应用类型
+ version: 3.0.0
+custom:
+ customDomain:
+ domainName: auto
diff --git a/packages/ui/certd-server/jest.config.js b/packages/ui/certd-server/jest.config.js
new file mode 100644
index 000000000..c5bd388a7
--- /dev/null
+++ b/packages/ui/certd-server/jest.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ testPathIgnorePatterns: ['/test/fixtures'],
+ coveragePathIgnorePatterns: ['/test/'],
+};
\ No newline at end of file
diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json
new file mode 100644
index 000000000..bd8a55d53
--- /dev/null
+++ b/packages/ui/certd-server/package.json
@@ -0,0 +1,91 @@
+{
+ "name": "@fast-crud/fs-server-js",
+ "version": "0.2.0",
+ "description": "fast-server base midway",
+ "private": true,
+ "midway-integration": {
+ "lifecycle": {
+ "before:package:cleanup": "npm run build"
+ }
+ },
+ "scripts": {
+ "deploy": "midway-bin deploy",
+ "start": "NODE_ENV=production node ./bootstrap.js",
+ "online": "NODE_ENV=production node ./bootstrap.js",
+ "online:preview": "NODE_ENV=preview node ./bootstrap.js",
+ "dev": "cross-env NODE_ENV=local midway-bin dev --ts --watchFile='../../core/pipeline/src,../../plugins/'",
+ "dev:preview": "cross-env NODE_ENV=preview midway-bin dev --ts",
+ "dev:syncdb": "cross-env NODE_ENV=syncdb midway-bin dev --ts --watchFile='../../core/pipeline/src'",
+ "test": "midway-bin test --ts",
+ "cov": "midway-bin cov --ts",
+ "lint": "mwts check",
+ "lint:fix": "mwts fix",
+ "ci": "npm run cov",
+ "build": "midway-bin build -c",
+ "build:preview": "cross-env NODE_ENV=preview midway-bin build -c",
+ "check": "luckyeye",
+ "mig": "typeorm migration:create -n name"
+ },
+ "dependencies": {
+ "@alicloud/pop-core": "^1.7.12",
+ "@certd/pipeline": "^0.3.0",
+ "@certd/plugin-all": "^0.3.0",
+ "@koa/cors": "^3.4.3",
+ "@midwayjs/bootstrap": "^3.9.1",
+ "@midwayjs/cache": "^3.9.0",
+ "@midwayjs/core": "^3.9.0",
+ "@midwayjs/decorator": "^3.9.0",
+ "@midwayjs/koa": "^3.9.0",
+ "@midwayjs/logger": "^2.17.0",
+ "@midwayjs/typeorm": "^3.9.5",
+ "@midwayjs/validate": "^3.9.0",
+ "@midwayjs/i18n": "^3.9.0",
+ "@types/cache-manager": "^3.4.3",
+ "cache-manager": "^3.6.3",
+ "dayjs": "^1.11.7",
+ "glob": "^7.2.3",
+ "jsonwebtoken": "^8.5.1",
+ "lodash": "^4.17.21",
+ "log4js": "^6.7.1",
+ "md5": "^2.3.0",
+ "midway-flyway-js": "^3.0.0",
+ "node-cron": "^3.0.2",
+ "sqlite3": "^5.1.4",
+ "svg-captcha": "^1.4.0",
+ "typeorm": "^0.3.11"
+ },
+ "devDependencies": {
+ "@midwayjs/cli": "^1.3.21",
+ "@midwayjs/luckyeye": "^1.1.0",
+ "@midwayjs/mock": "^3.9.0",
+ "@midwayjs/mwcc": "^0.8.0",
+ "@types/jest": "^26.0.24",
+ "@types/koa": "2.13.4",
+ "@types/node": "^14.18.35",
+ "cross-env": "^6.0.3",
+ "jest": "^26.6.3",
+ "mwts": "^1.3.0",
+ "ts-jest": "^26.5.6",
+ "ts-node": "^10.9.1",
+ "tsconfig-paths": "^4.1.1",
+ "typescript": "^4.9.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "midway-bin-clean": [
+ ".vscode/.tsbuildinfo",
+ "dist"
+ ],
+ "midway-luckyeye": {
+ "packages": [
+ "midway_v2"
+ ]
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/fast-crud/fast-server-js"
+ },
+ "author": "Greper",
+ "license": "MIT"
+}
diff --git a/packages/ui/certd-server/src/basic/base-controller.ts b/packages/ui/certd-server/src/basic/base-controller.ts
new file mode 100644
index 000000000..38a5d76b2
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/base-controller.ts
@@ -0,0 +1,33 @@
+import { Inject } from '@midwayjs/decorator';
+import { Context } from '@midwayjs/koa';
+import { Constants } from './constants';
+
+export abstract class BaseController {
+ @Inject()
+ ctx: Context;
+
+ /**
+ * 成功返回
+ * @param data 返回数据
+ */
+ ok(data) {
+ const res = {
+ ...Constants.res.success,
+ data: undefined,
+ };
+ if (data) {
+ res.data = data;
+ }
+ return res;
+ }
+ /**
+ * 失败返回
+ * @param message
+ */
+ fail(msg, code) {
+ return {
+ code: code ? code : Constants.res.error.code,
+ msg: msg ? msg : Constants.res.error.code,
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/base-service.ts b/packages/ui/certd-server/src/basic/base-service.ts
new file mode 100644
index 000000000..c6b055c90
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/base-service.ts
@@ -0,0 +1,207 @@
+import { Inject } from '@midwayjs/decorator';
+import { ValidateException } from './exception/validation-exception';
+import * as _ from 'lodash';
+import { Context } from '@midwayjs/koa';
+import { PermissionException } from './exception/permission-exception';
+import { Repository } from 'typeorm';
+
+/**
+ * 服务基类
+ */
+export abstract class BaseService {
+ @Inject()
+ ctx: Context;
+
+ abstract getRepository(): Repository;
+
+ /**
+ * 获得单个ID
+ * @param id ID
+ * @param infoIgnoreProperty 忽略返回属性
+ */
+ async info(id, infoIgnoreProperty?): Promise {
+ if (!id) {
+ throw new ValidateException('id不能为空');
+ }
+ // @ts-ignore
+ const info = await this.getRepository().findOne({ where: { id } });
+ if (info && infoIgnoreProperty) {
+ for (const property of infoIgnoreProperty) {
+ delete info[property];
+ }
+ }
+ return info;
+ }
+
+ /**
+ * 非分页查询
+ * @param option 查询配置
+ */
+ async find(options) {
+ return await this.getRepository().find(options);
+ }
+
+ /**
+ * 删除
+ * @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3
+ */
+ async delete(ids) {
+ if (ids instanceof Array) {
+ await this.getRepository().delete(ids);
+ } else if (typeof ids === 'string') {
+ await this.getRepository().delete(ids.split(','));
+ } else {
+ //ids是一个condition
+ await this.getRepository().delete(ids);
+ }
+ await this.modifyAfter(ids);
+ }
+
+ /**
+ * 新增|修改
+ * @param param 数据
+ */
+ async addOrUpdate(param) {
+ await this.getRepository().save(param);
+ }
+
+ /**
+ * 新增
+ * @param param 数据
+ */
+ async add(param) {
+ const now = new Date().getTime();
+ param.createTime = now;
+ param.updateTime = now;
+ await this.addOrUpdate(param);
+ await this.modifyAfter(param);
+ return {
+ id: param.id,
+ };
+ }
+
+ /**
+ * 修改
+ * @param param 数据
+ */
+ async update(param) {
+ if (!param.id) throw new ValidateException('no id');
+ param.updateTime = new Date().getTime();
+ await this.addOrUpdate(param);
+ await this.modifyAfter(param);
+ }
+
+ /**
+ * 新增|修改|删除 之后的操作
+ * @param data 对应数据
+ */
+ async modifyAfter(data) {}
+
+ /**
+ * 分页查询
+ * @param query 查询条件 bean
+ * @param page
+ * @param order
+ * @param buildQuery
+ */
+ async page(query, page = { offset: 0, limit: 20 }, order, buildQuery) {
+ if (page.offset == null) {
+ page.offset = 0;
+ }
+ if (page.limit == null) {
+ page.limit = 20;
+ }
+ const qb = this.getRepository().createQueryBuilder('main');
+ if (order && order.prop) {
+ qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC');
+ } else {
+ qb.orderBy('id', 'DESC');
+ }
+ qb.offset(page.offset).limit(page.limit);
+ //根据bean query
+ if (query) {
+ let whereSql = '';
+ let index = 0;
+ _.forEach(query, (value, key) => {
+ if (!value) {
+ return;
+ }
+ if (index !== 0) {
+ whereSql += ' and ';
+ }
+ whereSql += ` main.${key} = :${key} `;
+ index++;
+ });
+ if (index > 0) {
+ qb.where(whereSql, query);
+ }
+ }
+ //自定义query
+ if (buildQuery) {
+ buildQuery(qb);
+ }
+ const list = await qb.getMany();
+ const total = await qb.getCount();
+ return {
+ records: list,
+ total,
+ offset: page.offset,
+ limit: page.limit,
+ };
+ }
+
+ /**
+ * 分页查询
+ * @param query 查询条件 bean
+ * @param order
+ * @param buildQuery
+ */
+ async list(query, order, buildQuery) {
+ const qb = this.getRepository().createQueryBuilder('main');
+ if (order && order.prop) {
+ qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC');
+ } else {
+ qb.orderBy('id', 'DESC');
+ }
+ //根据bean query
+ if (query) {
+ let whereSql = '';
+ let index = 0;
+ _.forEach(query, (value, key) => {
+ if (!value) {
+ return;
+ }
+ if (index !== 0) {
+ whereSql += ' and ';
+ }
+ whereSql += ` main.${key} = :${key} `;
+ index++;
+ });
+ if (index > 0) {
+ qb.where(whereSql, query);
+ }
+ }
+ //自定义query
+ if (buildQuery) {
+ buildQuery(qb);
+ }
+ return await qb.getMany();
+ }
+
+ async checkUserId(id = 0, userId, userKey = 'userId') {
+ // @ts-ignore
+ const res = await this.getRepository().findOne({
+ // @ts-ignore
+ select: { [userKey]: true },
+ where: {
+ // @ts-ignore
+ id,
+ },
+ });
+ // @ts-ignore
+ if (!res || res.userId === userId) {
+ return;
+ }
+ throw new PermissionException('权限不足');
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/constants.ts b/packages/ui/certd-server/src/basic/constants.ts
new file mode 100644
index 000000000..992f13af0
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/constants.ts
@@ -0,0 +1,28 @@
+export const Constants = {
+ res: {
+ error: {
+ code: 1,
+ message: 'error',
+ },
+ success: {
+ code: 0,
+ message: 'success',
+ },
+ validation: {
+ code: 10,
+ message: '参数错误',
+ },
+ auth: {
+ code: 401,
+ message: '您还未登录或token已过期',
+ },
+ permission: {
+ code: 402,
+ message: '您没有权限',
+ },
+ preview: {
+ code: 10001,
+ message: '对不起,预览环境不允许修改此数据',
+ },
+ },
+};
diff --git a/packages/ui/certd-server/src/basic/crud-controller.ts b/packages/ui/certd-server/src/basic/crud-controller.ts
new file mode 100644
index 000000000..131cba5c7
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/crud-controller.ts
@@ -0,0 +1,64 @@
+import { ALL, Body, Post, Query } from '@midwayjs/decorator';
+import { BaseController } from './base-controller';
+
+export abstract class CrudController extends BaseController {
+ abstract getService();
+
+ @Post('/page')
+ async page(
+ @Body(ALL)
+ body
+ ) {
+ const pageRet = await this.getService().page(
+ body?.query,
+ body?.page,
+ body?.sort,
+ null
+ );
+ return this.ok(pageRet);
+ }
+
+ @Post('/list')
+ async list(
+ @Body(ALL)
+ body
+ ) {
+ const listRet = await this.getService().list(body, null, null);
+ return this.ok(listRet);
+ }
+
+ @Post('/add')
+ async add(
+ @Body(ALL)
+ bean
+ ) {
+ const id = await this.getService().add(bean);
+ return this.ok(id);
+ }
+
+ @Post('/info')
+ async info(
+ @Query('id')
+ id
+ ) {
+ const bean = await this.getService().info(id);
+ return this.ok(bean);
+ }
+
+ @Post('/update')
+ async update(
+ @Body(ALL)
+ bean
+ ) {
+ await this.getService().update(bean);
+ return this.ok(null);
+ }
+ @Post('/delete')
+ async delete(
+ @Query('id')
+ id
+ ) {
+ await this.getService().delete([id]);
+ return this.ok(null);
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/enum-item.ts b/packages/ui/certd-server/src/basic/enum-item.ts
new file mode 100644
index 000000000..a7e4a4a88
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/enum-item.ts
@@ -0,0 +1,11 @@
+export class EnumItem {
+ value: string;
+ label: string;
+ color: string;
+
+ constructor(value, label, color) {
+ this.value = value;
+ this.label = label;
+ this.color = color;
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/auth-exception.ts b/packages/ui/certd-server/src/basic/exception/auth-exception.ts
new file mode 100644
index 000000000..92e82a4d7
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/auth-exception.ts
@@ -0,0 +1,14 @@
+import { Constants } from '../constants';
+import { BaseException } from './base-exception';
+/**
+ * 授权异常
+ */
+export class AuthException extends BaseException {
+ constructor(message) {
+ super(
+ 'AuthException',
+ Constants.res.auth.code,
+ message ? message : Constants.res.auth.message
+ );
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/base-exception.ts b/packages/ui/certd-server/src/basic/exception/base-exception.ts
new file mode 100644
index 000000000..a4e89604b
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/base-exception.ts
@@ -0,0 +1,11 @@
+/**
+ * 异常基类
+ */
+export class BaseException extends Error {
+ status: number;
+ constructor(name, code, message) {
+ super(message);
+ this.name = name;
+ this.status = code;
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/common-exception.ts b/packages/ui/certd-server/src/basic/exception/common-exception.ts
new file mode 100644
index 000000000..f4afa91e6
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/common-exception.ts
@@ -0,0 +1,14 @@
+import { Constants } from '../constants';
+import { BaseException } from './base-exception';
+/**
+ * 通用异常
+ */
+export class CommonException extends BaseException {
+ constructor(message) {
+ super(
+ 'CommonException',
+ Constants.res.error.code,
+ message ? message : Constants.res.error.message
+ );
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/permission-exception.ts b/packages/ui/certd-server/src/basic/exception/permission-exception.ts
new file mode 100644
index 000000000..edeb2b7e2
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/permission-exception.ts
@@ -0,0 +1,14 @@
+import { Constants } from '../constants';
+import { BaseException } from './base-exception';
+/**
+ * 授权异常
+ */
+export class PermissionException extends BaseException {
+ constructor(message) {
+ super(
+ 'PermissionException',
+ Constants.res.permission.code,
+ message ? message : Constants.res.permission.message
+ );
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/preview-exception.ts b/packages/ui/certd-server/src/basic/exception/preview-exception.ts
new file mode 100644
index 000000000..2abfc48e9
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/preview-exception.ts
@@ -0,0 +1,14 @@
+import { Constants } from '../constants';
+import { BaseException } from './base-exception';
+/**
+ * 预览模式
+ */
+export class PreviewException extends BaseException {
+ constructor(message) {
+ super(
+ 'PreviewException',
+ Constants.res.preview.code,
+ message ? message : Constants.res.preview.message
+ );
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/exception/validation-exception.ts b/packages/ui/certd-server/src/basic/exception/validation-exception.ts
new file mode 100644
index 000000000..d595ff71b
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/exception/validation-exception.ts
@@ -0,0 +1,14 @@
+import { Constants } from '../constants';
+import { BaseException } from './base-exception';
+/**
+ * 校验异常
+ */
+export class ValidateException extends BaseException {
+ constructor(message) {
+ super(
+ 'ValidateException',
+ Constants.res.validation.code,
+ message ? message : Constants.res.validation.message
+ );
+ }
+}
diff --git a/packages/ui/certd-server/src/basic/result.ts b/packages/ui/certd-server/src/basic/result.ts
new file mode 100644
index 000000000..ea1022444
--- /dev/null
+++ b/packages/ui/certd-server/src/basic/result.ts
@@ -0,0 +1,18 @@
+export class Result {
+ code: number;
+ msg: string;
+ data: T;
+ constructor(code, msg, data?) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ }
+
+ static error(code = 1, msg) {
+ return new Result(code, msg, null);
+ }
+
+ static success(msg, data?) {
+ return new Result(0, msg, data);
+ }
+}
diff --git a/packages/ui/certd-server/src/config/config.default.ts b/packages/ui/certd-server/src/config/config.default.ts
new file mode 100644
index 000000000..ed2d93628
--- /dev/null
+++ b/packages/ui/certd-server/src/config/config.default.ts
@@ -0,0 +1,60 @@
+import { join } from 'path';
+import { FlywayHistory } from 'midway-flyway-js/dist/entity';
+
+import { MidwayConfig } from '@midwayjs/core';
+import { UserEntity } from '../modules/authority/entity/user';
+
+export default {
+ // use for cookie sign key, should change to your own and keep security
+ keys: 'certd666',
+ koa: {
+ port: 7001,
+ },
+ cron: {},
+ /**
+ * 演示环境
+ */
+ preview: {
+ enabled: false,
+ },
+
+ /**
+ * 数据库
+ */
+ typeorm: {
+ dataSource: {
+ default: {
+ /**
+ * 单数据库实例
+ */
+ type: 'sqlite',
+ database: join(__dirname, '../../data/db.sqlite'),
+ synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true
+ logging: true,
+
+ // 配置实体模型 或者 entities: '/entity',
+ entities: [
+ '**/modules/*/entity/*.ts',
+ FlywayHistory,
+ UserEntity,
+ ],
+ },
+ },
+ },
+ /**
+ * 自动升级数据库脚本
+ */
+ flyway: {
+ scriptDir: join(__dirname, '../../db/migration'),
+ },
+
+ biz: {
+ jwt: {
+ secret: 'greper-is-666',
+ expire: 7 * 24 * 60, //单位秒
+ },
+ auth: {
+ ignoreUrls: ['/', '/api/login', '/api/register'],
+ },
+ },
+} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/config/config.preview.ts b/packages/ui/certd-server/src/config/config.preview.ts
new file mode 100644
index 000000000..aa4bc6aac
--- /dev/null
+++ b/packages/ui/certd-server/src/config/config.preview.ts
@@ -0,0 +1,10 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+ /**
+ * 演示环境
+ */
+ preview: {
+ enabled: true,
+ }
+} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/config/config.production.ts b/packages/ui/certd-server/src/config/config.production.ts
new file mode 100644
index 000000000..aa4bc6aac
--- /dev/null
+++ b/packages/ui/certd-server/src/config/config.production.ts
@@ -0,0 +1,10 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+ /**
+ * 演示环境
+ */
+ preview: {
+ enabled: true,
+ }
+} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/config/config.syncdb.ts b/packages/ui/certd-server/src/config/config.syncdb.ts
new file mode 100644
index 000000000..478af50b6
--- /dev/null
+++ b/packages/ui/certd-server/src/config/config.syncdb.ts
@@ -0,0 +1,11 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+ typeorm: {
+ dataSource: {
+ default: {
+ synchronize: true, // 如果第一次使用,不存在表,有同步的需求可以写 true
+ },
+ },
+ },
+} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts
new file mode 100644
index 000000000..97970bd52
--- /dev/null
+++ b/packages/ui/certd-server/src/configuration.ts
@@ -0,0 +1,60 @@
+import * as validateComp from '@midwayjs/validate';
+import * as productionConfig from './config/config.production';
+import * as previewConfig from './config/config.preview';
+import * as defaultConfig from './config/config.default';
+import { Configuration, App } from '@midwayjs/decorator';
+import * as koa from '@midwayjs/koa';
+import * as orm from '@midwayjs/typeorm';
+import * as cache from '@midwayjs/cache';
+import cors from '@koa/cors';
+import { join } from 'path';
+import * as flyway from 'midway-flyway-js';
+import { ReportMiddleware } from './middleware/report';
+import { GlobalExceptionMiddleware } from './middleware/global-exception';
+import { PreviewMiddleware } from './middleware/preview';
+import { AuthorityMiddleware } from './middleware/authority';
+import * as pipeline from './plugins/pipeline';
+import * as cron from './plugins/cron';
+@Configuration({
+ imports: [koa, orm, cache, flyway, validateComp,pipeline, cron],
+ importConfigs: [
+ {
+ default: defaultConfig,
+ preview: previewConfig,
+ production: productionConfig,
+ },
+ ],
+})
+export class ContainerConfiguration {}
+@Configuration({
+ conflictCheck: true,
+ importConfigs: [join(__dirname, './config')],
+})
+export class ContainerLifeCycle {
+ @App()
+ app: koa.Application;
+
+ async onReady() {
+ //跨域
+ this.app.use(
+ cors({
+ origin: '*',
+ })
+ );
+ // bodyparser options see https://github.com/koajs/bodyparser
+ //this.app.use(bodyParser());
+ //请求日志打印
+
+ this.app.useMiddleware([
+ ReportMiddleware,
+ //统一异常处理
+ GlobalExceptionMiddleware,
+ //预览模式限制修改id<1000的数据
+ PreviewMiddleware,
+ //授权处理
+ AuthorityMiddleware,
+ ]);
+
+ //加载插件
+ }
+}
diff --git a/packages/ui/certd-server/src/controller/home.ts b/packages/ui/certd-server/src/controller/home.ts
new file mode 100644
index 000000000..6aa5a9a74
--- /dev/null
+++ b/packages/ui/certd-server/src/controller/home.ts
@@ -0,0 +1,10 @@
+import { Controller, Get, Provide } from '@midwayjs/decorator';
+
+@Provide()
+@Controller('/')
+export class HomeController {
+ @Get('/')
+ async home(): Promise {
+ return 'Hello Midwayjs!';
+ }
+}
diff --git a/packages/ui/certd-server/src/middleware/authority.ts b/packages/ui/certd-server/src/middleware/authority.ts
new file mode 100644
index 000000000..431d2b692
--- /dev/null
+++ b/packages/ui/certd-server/src/middleware/authority.ts
@@ -0,0 +1,48 @@
+import { Config, Provide } from '@midwayjs/decorator';
+import {
+ IWebMiddleware,
+ IMidwayKoaContext,
+ IMidwayKoaNext,
+} from '@midwayjs/koa';
+import * as _ from 'lodash';
+import * as jwt from 'jsonwebtoken';
+import { Constants } from '../basic/constants';
+
+/**
+ * 权限校验
+ */
+@Provide()
+export class AuthorityMiddleware implements IWebMiddleware {
+ @Config('biz.jwt.secret')
+ private secret: string;
+ @Config('biz.auth.ignoreUrls')
+ private ignoreUrls: string[];
+
+ resolve() {
+ return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
+ const { url } = ctx;
+ const token = ctx.get('Authorization');
+ // 路由地址为 admin前缀的 需要权限校验
+ // console.log('ctx', ctx);
+ const queryIndex = url.indexOf('?');
+ let uri = url;
+ if (queryIndex >= 0) {
+ uri = url.substring(0, queryIndex);
+ }
+ const yes = this.ignoreUrls.includes(uri);
+ if (yes) {
+ await next();
+ return;
+ }
+
+ try {
+ ctx.user = jwt.verify(token, this.secret);
+ } catch (err) {
+ ctx.status = 401;
+ ctx.body = Constants.res.auth;
+ return;
+ }
+ await next();
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/middleware/global-exception.ts b/packages/ui/certd-server/src/middleware/global-exception.ts
new file mode 100644
index 000000000..b67faf118
--- /dev/null
+++ b/packages/ui/certd-server/src/middleware/global-exception.ts
@@ -0,0 +1,27 @@
+import { Provide } from '@midwayjs/decorator';
+import {
+ IWebMiddleware,
+ IMidwayKoaContext,
+ IMidwayKoaNext,
+} from '@midwayjs/koa';
+import { logger } from '../utils/logger';
+import { Result } from '../basic/result';
+
+@Provide()
+export class GlobalExceptionMiddleware implements IWebMiddleware {
+ resolve() {
+ return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
+ const { url } = ctx;
+ const startTime = Date.now();
+ logger.info('请求开始:', url);
+ try {
+ await next();
+ logger.info('请求完成', url, Date.now() - startTime + 'ms');
+ } catch (err) {
+ logger.error('请求异常:', url, Date.now() - startTime + 'ms', err);
+ ctx.status = 200;
+ ctx.body = Result.error(err.code != null ? err.code : 1, err.message);
+ }
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/middleware/preview.ts b/packages/ui/certd-server/src/middleware/preview.ts
new file mode 100644
index 000000000..c93ecfa7b
--- /dev/null
+++ b/packages/ui/certd-server/src/middleware/preview.ts
@@ -0,0 +1,50 @@
+import { Config, Provide } from '@midwayjs/decorator';
+import {
+ IMidwayKoaContext,
+ IMidwayKoaNext,
+ IWebMiddleware,
+} from '@midwayjs/koa';
+import { PreviewException } from '../basic/exception/preview-exception';
+
+/**
+ * 预览模式
+ */
+@Provide()
+export class PreviewMiddleware implements IWebMiddleware {
+ @Config('preview.enabled')
+ private preview: boolean;
+
+ resolve() {
+ return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
+ if (!this.preview) {
+ await next();
+ return;
+ }
+ let { url, request } = ctx;
+ const body: any = request.body;
+ let id = body.id || request.query.id;
+ const roleId = body.roleId;
+ if (id == null && roleId != null) {
+ id = roleId;
+ }
+ if (id != null && typeof id === 'string') {
+ id = parseInt(id);
+ }
+ if (url.indexOf('?') !== -1) {
+ url = url.substring(0, url.indexOf('?'));
+ }
+ const isModify =
+ url.endsWith('update') ||
+ url.endsWith('delete') ||
+ url.endsWith('authz');
+ const isPreviewId = id < 1000;
+ if (this.preview && isModify && isPreviewId) {
+ throw new PreviewException(
+ '对不起,预览环境不允许修改此数据,如需体验请添加新数据'
+ );
+ }
+ await next();
+ return;
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/middleware/report.ts b/packages/ui/certd-server/src/middleware/report.ts
new file mode 100644
index 000000000..d81eff85d
--- /dev/null
+++ b/packages/ui/certd-server/src/middleware/report.ts
@@ -0,0 +1,28 @@
+import { Provide } from '@midwayjs/decorator';
+import {
+ IWebMiddleware,
+ IMidwayKoaContext,
+ IMidwayKoaNext,
+} from '@midwayjs/koa';
+import { logger } from '../utils/logger';
+
+@Provide()
+export class ReportMiddleware implements IWebMiddleware {
+ resolve() {
+ return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
+ const { url } = ctx;
+ logger.info('请求开始:', url);
+ const startTime = Date.now();
+ await next();
+ if (ctx.status !== 200) {
+ logger.error(
+ '请求失败:',
+ url,
+ ctx.status,
+ Date.now() - startTime + 'ms'
+ );
+ }
+ logger.info('请求完成:', url, ctx.status, Date.now() - startTime + 'ms');
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts
new file mode 100644
index 000000000..6e0f0e176
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts
@@ -0,0 +1,62 @@
+import {
+ ALL,
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { CrudController } from '../../../basic/crud-controller';
+import { PermissionService } from '../service/permission-service';
+
+/**
+ * 权限资源
+ */
+@Provide()
+@Controller('/api/sys/authority/permission')
+export class PermissionController extends CrudController {
+ @Inject()
+ service: PermissionService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(
+ @Body(ALL)
+ body
+ ) {
+ return await super.page(body);
+ }
+
+ @Post('/add')
+ async add(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.add(bean);
+ }
+
+ @Post('/update')
+ async update(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.update(bean);
+ }
+ @Post('/delete')
+ async delete(
+ @Query('id')
+ id
+ ) {
+ return await super.delete(id);
+ }
+
+ @Post('/tree')
+ async tree() {
+ const tree = await this.service.tree({});
+ return this.ok(tree);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts
new file mode 100644
index 000000000..3c3a99199
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts
@@ -0,0 +1,95 @@
+import {
+ ALL,
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { CrudController } from '../../../basic/crud-controller';
+import { RoleService } from '../service/role-service';
+
+/**
+ * 系统用户
+ */
+@Provide()
+@Controller('/api/sys/authority/role')
+export class RoleController extends CrudController {
+ @Inject()
+ service: RoleService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(
+ @Body(ALL)
+ body
+ ) {
+ return await super.page(body);
+ }
+
+ @Post('/list')
+ async list() {
+ const ret = await this.service.find({});
+ return this.ok(ret);
+ }
+
+ @Post('/add')
+ async add(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.add(bean);
+ }
+
+ @Post('/update')
+ async update(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.update(bean);
+ }
+ @Post('/delete')
+ async delete(
+ @Query('id')
+ id
+ ) {
+ return await super.delete(id);
+ }
+
+ @Post('/getPermissionTree')
+ async getPermissionTree(
+ @Query('id')
+ id
+ ) {
+ const ret = await this.service.getPermissionTreeByRoleId(id);
+ return this.ok(ret);
+ }
+
+ @Post('/getPermissionIds')
+ async getPermissionIds(
+ @Query('id')
+ id
+ ) {
+ const ret = await this.service.getPermissionIdsByRoleId(id);
+ return this.ok(ret);
+ }
+
+ /**
+ * 给角色授予权限
+ * @param id
+ */
+ @Post('/authz')
+ async authz(
+ @Body('roleId')
+ roleId,
+ @Body('permissionIds')
+ permissionIds
+ ) {
+ await this.service.authz(roleId, permissionIds);
+ return this.ok(null);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts
new file mode 100644
index 000000000..1b8096240
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts
@@ -0,0 +1,118 @@
+import {
+ Provide,
+ Controller,
+ Post,
+ Inject,
+ Body,
+ Query,
+ ALL,
+} from '@midwayjs/decorator';
+import { UserService } from '../service/user-service';
+import { CrudController } from '../../../basic/crud-controller';
+import { RoleService } from '../service/role-service';
+import { PermissionService } from '../service/permission-service';
+
+/**
+ * 系统用户
+ */
+@Provide()
+@Controller('/api/sys/authority/user')
+export class UserController extends CrudController {
+ @Inject()
+ service: UserService;
+
+ @Inject()
+ roleService: RoleService;
+ @Inject()
+ permissionService: PermissionService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(
+ @Body(ALL)
+ body
+ ) {
+ const ret = await super.page(body);
+
+ const users = ret.data.records;
+
+ //获取roles
+ const userIds = users.map(item => item.id);
+ const userRoles = await this.roleService.getByUserIds(userIds);
+ const userRolesMap = new Map();
+ for (const ur of userRoles) {
+ let roles = userRolesMap.get(ur.userId);
+ if (roles == null) {
+ roles = [];
+ userRolesMap.set(ur.userId, roles);
+ }
+ roles.push(ur.roleId);
+ }
+
+ for (const record of users) {
+ //withRoles
+ record.roles = userRolesMap.get(record.id);
+ //删除密码字段
+ delete record.password;
+ }
+
+ return ret;
+ }
+
+ @Post('/add')
+ async add(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.add(bean);
+ }
+
+ @Post('/update')
+ async update(
+ @Body(ALL)
+ bean
+ ) {
+ return await super.update(bean);
+ }
+ @Post('/delete')
+ async delete(
+ @Query('id')
+ id
+ ) {
+ return await super.delete(id);
+ }
+
+ /**
+ * 当前登录用户的个人信息
+ */
+ @Post('/mine')
+ public async mine() {
+ const id = this.ctx.user.id;
+ const info = await this.service.info(id, ['password']);
+ return this.ok(info);
+ }
+
+ /**
+ * 当前登录用户的权限列表
+ */
+ @Post('/permissions')
+ public async permissions() {
+ const id = this.ctx.user.id;
+ const permissions = await this.service.getUserPermissions(id);
+ return this.ok(permissions);
+ }
+
+ /**
+ * 当前登录用户的权限树形列表
+ */
+ @Post('/permissionTree')
+ public async permissionTree() {
+ const id = this.ctx.user.id;
+ const permissions = await this.service.getUserPermissions(id);
+ const tree = this.permissionService.buildTree(permissions);
+ return this.ok(tree);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/entity/permission.ts b/packages/ui/certd-server/src/modules/authority/entity/permission.ts
new file mode 100644
index 000000000..6fa91e18d
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/entity/permission.ts
@@ -0,0 +1,40 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+/**
+ * 权限
+ */
+@Entity('sys_permission')
+export class PermissionEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+ @Column({ comment: '标题', length: 100 })
+ title: string;
+ /**
+ * 权限代码
+ * 示例:sys:user:read
+ */
+ @Column({ comment: '权限代码', length: 100, nullable: true })
+ permission: string;
+
+ @Column({ name: 'parent_id', comment: '父节点ID', default: -1 })
+ parentId: number;
+
+ @Column({ comment: '排序号' })
+ sort: number;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+
+ // @ManyToMany(type => RoleEntity, res => res.permissions)
+ // roles: RoleEntity[];
+}
diff --git a/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts b/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts
new file mode 100644
index 000000000..85ac0c734
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts
@@ -0,0 +1,12 @@
+import { Entity, PrimaryColumn } from 'typeorm';
+
+/**
+ * 角色权限多对多
+ */
+@Entity('sys_role_permission')
+export class RolePermissionEntity {
+ @PrimaryColumn({ name: 'role_id' })
+ roleId: number;
+ @PrimaryColumn({ name: 'permission_id' })
+ permissionId: number;
+}
diff --git a/packages/ui/certd-server/src/modules/authority/entity/role.ts b/packages/ui/certd-server/src/modules/authority/entity/role.ts
new file mode 100644
index 000000000..2aecfb743
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/entity/role.ts
@@ -0,0 +1,43 @@
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
+
+/**
+ * 角色
+ */
+@Entity('sys_role')
+export class RoleEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+ @Index({ unique: true })
+ @Column({ comment: '角色名称', length: 100 })
+ name: string;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+
+ // @ManyToMany(type => PermissionEntity, res => res.roles)
+ // @JoinTable({
+ // name: 'sys_role_resources',
+ // joinColumn: {
+ // name: 'roleId',
+ // referencedColumnName: 'id',
+ // },
+ // inverseJoinColumn: {
+ // name: 'resourceId',
+ // referencedColumnName: 'id',
+ // },
+ // })
+ // resources: PermissionEntity[];
+
+ // @ManyToMany(type => UserEntity, res => res.roles)
+ // users: UserEntity[];
+}
diff --git a/packages/ui/certd-server/src/modules/authority/entity/user-role.ts b/packages/ui/certd-server/src/modules/authority/entity/user-role.ts
new file mode 100644
index 000000000..533bf6441
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/entity/user-role.ts
@@ -0,0 +1,12 @@
+import { Entity, PrimaryColumn } from 'typeorm';
+
+/**
+ * 用户角色多对多
+ */
+@Entity('sys_user_role')
+export class UserRoleEntity {
+ @PrimaryColumn({ name: 'role_id' })
+ roleId: number;
+ @PrimaryColumn({ name: 'user_id' })
+ userId: number;
+}
diff --git a/packages/ui/certd-server/src/modules/authority/entity/user.ts b/packages/ui/certd-server/src/modules/authority/entity/user.ts
new file mode 100644
index 000000000..598245836
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/entity/user.ts
@@ -0,0 +1,63 @@
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
+
+/**
+ * 系统用户
+ */
+@Entity('sys_user')
+export class UserEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+ @Index({ unique: true })
+ @Column({ comment: '用户名', length: 100 })
+ username: string;
+
+ @Column({ comment: '密码', length: 100 })
+ password: string;
+
+ @Column({ name: 'nick_name', comment: '昵称', length: 100, nullable: true })
+ nickName: string;
+
+ @Column({ comment: '头像', length: 255, nullable: true })
+ avatar: string;
+
+ @Column({ name: 'phone_code', comment: '区号', length: 20, nullable: true })
+ phoneCode: string;
+
+ @Column({ comment: '手机', length: 20, nullable: true })
+ mobile: string;
+
+ @Column({ comment: '邮箱', length: 50, nullable: true })
+ email: string;
+
+ @Column({ comment: '备注', length: 100, nullable: true })
+ remark: string;
+
+ @Column({ comment: '状态 0:禁用 1:启用', default: 1, type: 'int' })
+ status: number;
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+
+ // @ManyToMany(type => RoleEntity, res => res.users)
+ // @JoinTable({
+ // name: 'sys_user_roles',
+ // joinColumn: {
+ // name: 'userId',
+ // referencedColumnName: 'id',
+ // },
+ // inverseJoinColumn: {
+ // name: 'roleId',
+ // referencedColumnName: 'id',
+ // },
+ // })
+ // roles: RoleEntity[];
+}
diff --git a/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts b/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts
new file mode 100644
index 000000000..cf3411ece
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts
@@ -0,0 +1,17 @@
+import { EnumItem } from '../../../basic/enum-item';
+import * as _ from 'lodash';
+class ResourceTypes {
+ MENU = new EnumItem('menu', '菜单', 'blue');
+ BTN = new EnumItem('btn', '按钮', 'green');
+ ROUTE = new EnumItem('route', '路由', 'red');
+
+ names() {
+ const list = [];
+ _.forEach(this, (item, key) => {
+ list.push(item);
+ });
+ return list;
+ }
+}
+
+export const ResourceTypeEnum = new ResourceTypes();
diff --git a/packages/ui/certd-server/src/modules/authority/service/permission-service.ts b/packages/ui/certd-server/src/modules/authority/service/permission-service.ts
new file mode 100644
index 000000000..950a04973
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/service/permission-service.ts
@@ -0,0 +1,52 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { PermissionEntity } from '../entity/permission';
+
+/**
+ * 权限资源
+ */
+@Provide()
+export class PermissionService extends BaseService {
+ @InjectEntityModel(PermissionEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async tree(options: any = {}) {
+ if (options.order == null) {
+ options.order = {
+ sort: 'ASC',
+ };
+ }
+ const list = await this.find(options);
+ return this.buildTree(list);
+ }
+
+ buildTree(list: any) {
+ const idMap = {};
+ const root = [];
+ for (const item of list) {
+ idMap[item.id] = item;
+ if (item.parentId == null || item.parentId <= 0) {
+ root.push(item);
+ }
+ }
+
+ for (const item of list) {
+ if (item.parentId > 0) {
+ const parent = idMap[item.parentId];
+ if (parent) {
+ if (parent.children == null) {
+ parent.children = [];
+ }
+ parent.children.push(item);
+ }
+ }
+ }
+ return root;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts b/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts
new file mode 100644
index 000000000..e2b2b3d4c
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts
@@ -0,0 +1,18 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { RolePermissionEntity } from '../entity/role-permission';
+
+/**
+ * 角色->权限
+ */
+@Provide()
+export class RolePermissionService extends BaseService {
+ @InjectEntityModel(RolePermissionEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/service/role-service.ts b/packages/ui/certd-server/src/modules/authority/service/role-service.ts
new file mode 100644
index 000000000..330bc360a
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/service/role-service.ts
@@ -0,0 +1,101 @@
+import { Inject, Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { In, Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { RoleEntity } from '../entity/role';
+import { UserRoleService } from './user-role-service';
+import { RolePermissionEntity } from '../entity/role-permission';
+import { PermissionService } from './permission-service';
+import * as _ from 'lodash';
+import { RolePermissionService } from './role-permission-service';
+/**
+ * 角色
+ */
+@Provide()
+export class RoleService extends BaseService {
+ @InjectEntityModel(RoleEntity)
+ repository: Repository;
+ @Inject()
+ userRoleService: UserRoleService;
+ @Inject()
+ permissionService: PermissionService;
+ @Inject()
+ rolePermissionService: RolePermissionService;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async getRoleIdsByUserId(id: any) {
+ const userRoles = await this.userRoleService.find({
+ where: { userId: id },
+ });
+ return userRoles.map(item => item.roleId);
+ }
+ async getByUserIds(ids: any) {
+ return await this.userRoleService.find({
+ where: {
+ userId: In(ids),
+ },
+ });
+ }
+
+ async getPermissionByRoleIds(roleIds: any) {
+ return await this.permissionService.repository
+ .createQueryBuilder('permission')
+ .innerJoinAndSelect(
+ RolePermissionEntity,
+ 'rp',
+ 'rp.permissionId = permission.id and rp.roleId in (:...roleIds)',
+ { roleIds }
+ )
+ .getMany();
+ }
+
+ async addRoles(userId: number, roles) {
+ if (roles == null || roles.length === 0) {
+ return;
+ }
+ for (const roleId of roles) {
+ await this.userRoleService.add({
+ userId,
+ roleId,
+ });
+ }
+ }
+
+ async updateRoles(userId, roles) {
+ if (roles == null) {
+ return;
+ }
+ const oldRoleIds = await this.getRoleIdsByUserId(userId);
+ if (_.xor(roles, oldRoleIds).length === 0) {
+ //如果两个数组相等,则不修改
+ return;
+ }
+ //先删除所有
+ await this.userRoleService.delete({ userId });
+ //再添加
+ await this.addRoles(userId, roles);
+ }
+
+ async getPermissionTreeByRoleId(id: any) {
+ const list = await this.getPermissionByRoleIds([id]);
+ return this.permissionService.buildTree(list);
+ }
+
+ async getPermissionIdsByRoleId(id: any) {
+ const list = await this.getPermissionByRoleIds([id]);
+ return list.map(item => item.id);
+ }
+
+ async authz(roleId: any, permissionIds: any) {
+ await this.rolePermissionService.delete({ roleId });
+ for (const permissionId of permissionIds) {
+ await this.rolePermissionService.add({
+ roleId,
+ permissionId,
+ });
+ }
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts b/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts
new file mode 100644
index 000000000..7c528cff4
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts
@@ -0,0 +1,18 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { UserRoleEntity } from '../entity/user-role';
+
+/**
+ * 用户->角色
+ */
+@Provide()
+export class UserRoleService extends BaseService {
+ @InjectEntityModel(UserRoleEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/authority/service/user-service.ts
new file mode 100644
index 000000000..0fc0c5f32
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/authority/service/user-service.ts
@@ -0,0 +1,113 @@
+import { Inject, Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { UserEntity } from '../entity/user';
+import _ from 'lodash';
+import md5 from 'md5';
+import { CommonException } from '../../../basic/exception/common-exception';
+import { BaseService } from '../../../basic/base-service';
+import { logger } from '../../../utils/logger';
+import { RoleService } from './role-service';
+import { PermissionService } from './permission-service';
+import { UserRoleService } from './user-role-service';
+
+/**
+ * 系统用户
+ */
+@Provide()
+export class UserService extends BaseService {
+ @InjectEntityModel(UserEntity)
+ repository: Repository;
+ @Inject()
+ roleService: RoleService;
+ @Inject()
+ permissionService: PermissionService;
+ @Inject()
+ userRoleService: UserRoleService;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ /**
+ * 获得个人信息
+ */
+ async mine() {
+ const info = await this.repository.findOne({
+ where: {
+ id: this.ctx.user.id,
+ },
+ });
+ delete info.password;
+ return info;
+ }
+
+ /**
+ * 新增
+ * @param param
+ */
+ async add(param) {
+ const exists = await this.repository.findOne({
+ where: {
+ username: param.username,
+ },
+ });
+ if (!_.isEmpty(exists)) {
+ throw new CommonException('用户名已经存在');
+ }
+ const password = param.password ?? '123456';
+ param.password = md5(password); // 默认密码 建议未改密码不能登陆
+ await super.add(param);
+ //添加角色
+ if (param.roles && param.roles.length > 0) {
+ await this.roleService.addRoles(param.id, param.roles);
+ }
+ return param.id;
+ }
+
+ /**
+ * 修改
+ * @param param 数据
+ */
+ async update(param) {
+ if (param.id == null) {
+ throw new CommonException('id不能为空');
+ }
+ const userInfo = await this.repository.findOne({
+ where: { id: param.id },
+ });
+ if (!userInfo) {
+ throw new CommonException('用户不存在');
+ }
+
+ delete param.username;
+ if (!_.isEmpty(param.password)) {
+ param.password = md5(param.password);
+ } else {
+ delete param.password;
+ }
+ await super.update(param);
+ await this.roleService.updateRoles(param.id, param.roles);
+ }
+
+ async findOne(param) {
+ return this.repository.findOne({
+ where: param,
+ });
+ }
+
+ checkPassword(rawPassword: any, md5Password: any) {
+ logger.info('md5', md5('123456'));
+ return md5(rawPassword) === md5Password;
+ }
+
+ /**
+ * 获取用户的菜单资源列表
+ * @param id
+ */
+ async getUserPermissions(id: any) {
+ const roleIds = await this.roleService.getRoleIdsByUserId(id);
+
+ return await this.roleService.getPermissionByRoleIds(roleIds);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts b/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts
new file mode 100644
index 000000000..e0d505998
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts
@@ -0,0 +1,55 @@
+import { Rule, RuleType } from '@midwayjs/validate';
+import { ALL, Inject } from '@midwayjs/decorator';
+import { Body } from '@midwayjs/decorator';
+import { Controller, Post, Provide } from '@midwayjs/decorator';
+import { BaseController } from '../../../basic/base-controller';
+import { CodeService } from '../service/code-service';
+export class SmsCodeReq {
+ @Rule(RuleType.number().required())
+ phoneCode: number;
+
+ @Rule(RuleType.string().required())
+ mobile: string;
+
+ @Rule(RuleType.string().required().max(10))
+ randomStr: string;
+
+ @Rule(RuleType.number().required().max(4))
+ imgCode: string;
+}
+
+// const enumsMap = {};
+// glob('src/modules/**/enums/*.ts', {}, (err, matches) => {
+// console.log('matched', matches);
+// for (const filePath of matches) {
+// const module = require('/' + filePath);
+// console.log('modules', module);
+// }
+// });
+
+/**
+ */
+@Provide()
+@Controller('/api/basic')
+export class BasicController extends BaseController {
+ @Inject()
+ codeService: CodeService;
+ @Post('/sendSmsCode')
+ public sendSmsCode(
+ @Body(ALL)
+ body: SmsCodeReq
+ ) {
+ // 设置缓存内容
+ return this.ok(null);
+ }
+
+ @Post('/captcha')
+ public async getCaptcha(
+ @Body()
+ randomStr
+ ) {
+ console.assert(randomStr < 10, 'randomStr 过长');
+ const captcha = await this.codeService.generateCaptcha(randomStr);
+ return this.ok(captcha.data);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts
new file mode 100644
index 000000000..cb306af67
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts
@@ -0,0 +1,57 @@
+import { Inject, Provide } from '@midwayjs/decorator';
+import { CacheManager } from '@midwayjs/cache';
+const svgCaptcha = require('svg-captcha');
+
+// {data: '', text: 'abcd'}
+/**
+ */
+@Provide()
+export class CodeService {
+ @Inject()
+ cache: CacheManager; // 依赖注入CacheManager
+
+ /**
+ */
+ async generateCaptcha(randomStr) {
+ console.assert(randomStr < 10, 'randomStr 过长');
+ const c = svgCaptcha.create();
+ //{data: '', text: 'abcd'}
+ const imgCode = c.text; // = RandomUtil.randomStr(4, true);
+ await this.cache.set('imgCode:' + randomStr, imgCode, {
+ ttl: 2 * 60 * 1000, //过期时间 2分钟
+ });
+ return c;
+ }
+
+ async getCaptchaText(randomStr) {
+ return await this.cache.get('imgCode:' + randomStr);
+ }
+
+ async removeCaptcha(randomStr) {
+ await this.cache.del('imgCode:' + randomStr);
+ }
+
+ async checkCaptcha(randomStr, userCaptcha) {
+ const code = await this.getCaptchaText(randomStr);
+ if (code == null) {
+ throw new Error('验证码已过期');
+ }
+ if (code !== userCaptcha) {
+ throw new Error('验证码不正确');
+ }
+ return true;
+ }
+ /**
+ */
+ async sendSms(phoneCode, mobile, smsCode) {
+ console.assert(phoneCode != null && mobile != null, '手机号不能为空');
+ console.assert(smsCode != null, '验证码不能为空');
+ }
+
+ /**
+ * loginBySmsCode
+ */
+ async loginBySmsCode(user, smsCode) {
+ console.assert(user.mobile != null, '手机号不能为空');
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/login/controller/login-controller.ts b/packages/ui/certd-server/src/modules/login/controller/login-controller.ts
new file mode 100644
index 000000000..efe0212bc
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/login/controller/login-controller.ts
@@ -0,0 +1,30 @@
+import {
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ ALL,
+} from '@midwayjs/decorator';
+import { LoginService } from '../service/login-service';
+import { BaseController } from '../../../basic/base-controller';
+
+/**
+ */
+@Provide()
+@Controller('/api/')
+export class LoginController extends BaseController {
+ @Inject()
+ loginService: LoginService;
+ @Post('/login')
+ public async login(
+ @Body(ALL)
+ user
+ ) {
+ const token = await this.loginService.login(user);
+ return this.ok(token);
+ }
+
+ @Post('/logout')
+ public logout() {}
+}
diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts
new file mode 100644
index 000000000..85ceaee5d
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts
@@ -0,0 +1,52 @@
+import { Config, Inject, Provide } from '@midwayjs/decorator';
+import { UserService } from '../../authority/service/user-service';
+import * as jwt from 'jsonwebtoken';
+import { CommonException } from '../../../basic/exception/common-exception';
+
+/**
+ * 系统用户
+ */
+@Provide()
+export class LoginService {
+ @Inject()
+ userService: UserService;
+ @Config('biz.jwt')
+ private jwt: any;
+
+ /**
+ * login
+ */
+ async login(user) {
+ console.assert(user.username != null, '用户名不能为空');
+ const info = await this.userService.findOne({ username: user.username });
+ if (info == null) {
+ throw new CommonException('用户名或密码错误');
+ }
+ const right = this.userService.checkPassword(user.password, info.password);
+ if (!right) {
+ throw new CommonException('用户名或密码错误');
+ }
+
+ return this.generateToken(info);
+ }
+
+ /**
+ * 生成token
+ * @param user 用户对象
+ */
+ async generateToken(user) {
+ const tokenInfo = {
+ username: user.username,
+ id: user.id,
+ };
+ const expire = this.jwt.expire;
+ const token = jwt.sign(tokenInfo, this.jwt.secret, {
+ expiresIn: expire,
+ });
+
+ return {
+ token,
+ expire,
+ };
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts b/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts
new file mode 100644
index 000000000..263d67090
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts
@@ -0,0 +1,26 @@
+import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { PipelineService } from '../service/pipeline-service';
+import { logger } from '../../../utils/logger';
+
+@Autoload()
+@Scope(ScopeEnum.Singleton)
+export class AutoRegisterCron {
+ @Inject()
+ pipelineService: PipelineService;
+
+ // @Inject()
+ // echoPlugin: EchoPlugin;
+
+ @Init()
+ async init() {
+ logger.info('加载定时trigger开始');
+ await this.pipelineService.onStartup();
+ // logger.info(this.echoPlugin, this.echoPlugin.test);
+ // logger.info('加载定时trigger完成');
+ //
+ // const meta = getClassMetadata(CLASS_KEY, this.echoPlugin);
+ // console.log('meta', meta);
+ // const metas = listPropertyDataFromClass(CLASS_KEY, this.echoPlugin);
+ // console.log('metas', metas);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/controller/access-controller.ts b/packages/ui/certd-server/src/modules/pipeline/controller/access-controller.ts
new file mode 100644
index 000000000..529cdd7d6
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/controller/access-controller.ts
@@ -0,0 +1,80 @@
+import {
+ ALL,
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { CrudController } from '../../../basic/crud-controller';
+import { AccessService } from '../service/access-service';
+
+/**
+ * 授权
+ */
+@Provide()
+@Controller('/api/pi/access')
+export class AccessController extends CrudController {
+ @Inject()
+ service: AccessService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(@Body(ALL) body) {
+ body.query = body.query ?? {};
+ body.query.userId = this.ctx.user.id;
+ return super.page(body);
+ }
+
+ @Post('/list')
+ async list(@Body(ALL) body) {
+ body.userId = this.ctx.user.id;
+ return super.list(body);
+ }
+
+ @Post('/add')
+ async add(@Body(ALL) bean) {
+ bean.userId = this.ctx.user.id;
+ return super.add(bean);
+ }
+
+ @Post('/update')
+ async update(@Body(ALL) bean) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ return super.update(bean);
+ }
+ @Post('/info')
+ async info(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ return super.info(id);
+ }
+
+ @Post('/delete')
+ async delete(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ return super.delete(id);
+ }
+
+ @Post('/define')
+ async define(@Query('type') type) {
+ const provider = this.service.getDefineByType(type);
+ return this.ok(provider);
+ }
+
+ @Post('/accessTypeDict')
+ async getAccessTypeDict() {
+ const list = this.service.getDefineList();
+ const dict = [];
+ for (const item of list) {
+ dict.push({
+ value: item.name,
+ label: item.title,
+ });
+ }
+ return this.ok(dict);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/controller/dns-provider-controller.ts b/packages/ui/certd-server/src/modules/pipeline/controller/dns-provider-controller.ts
new file mode 100644
index 000000000..99f11467b
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/controller/dns-provider-controller.ts
@@ -0,0 +1,40 @@
+import {
+ ALL,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { DnsProviderService } from '../service/dns-provider-service';
+import { BaseController } from '../../../basic/base-controller';
+
+/**
+ * 插件
+ */
+@Provide()
+@Controller('/api/pi/dnsProvider')
+export class DnsProviderController extends BaseController {
+ @Inject()
+ service: DnsProviderService;
+
+ @Post('/list')
+ async list(@Query(ALL) query) {
+ query.userId = this.ctx.user.id;
+ const list = this.service.getList();
+ return this.ok(list);
+ }
+
+ @Post('/dnsProviderTypeDict')
+ async getDnsProviderTypeDict() {
+ const list = this.service.getList();
+ const dict = [];
+ for (const item of list) {
+ dict.push({
+ value: item.name,
+ label: item.title,
+ });
+ }
+ return this.ok(dict);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/controller/history-controller.ts b/packages/ui/certd-server/src/modules/pipeline/controller/history-controller.ts
new file mode 100644
index 000000000..a8410979d
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/controller/history-controller.ts
@@ -0,0 +1,106 @@
+import {
+ ALL,
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { CrudController } from '../../../basic/crud-controller';
+import { PipelineEntity } from '../entity/pipeline';
+import { HistoryService } from '../service/history-service';
+import { HistoryLogService } from '../service/history-log-service';
+import { HistoryEntity } from '../entity/history';
+import { HistoryLogEntity } from '../entity/history-log';
+
+/**
+ * 证书
+ */
+@Provide()
+@Controller('/api/pi/history')
+export class HistoryController extends CrudController {
+ @Inject()
+ service: HistoryService;
+ @Inject()
+ logService: HistoryLogService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(@Body(ALL) body) {
+ body.query.userId = this.ctx.user.id;
+ return super.page(body);
+ }
+
+ @Post('/list')
+ async list(@Body(ALL) body) {
+ body.userId = this.ctx.user.id;
+ if (body.pipelineId == null) {
+ return this.ok([]);
+ }
+ const buildQuery = qb => {
+ qb.limit(10);
+ };
+ const listRet = await this.getService().list(
+ body,
+ { prop: 'id', asc: false },
+ buildQuery
+ );
+ return this.ok(listRet);
+ }
+
+ @Post('/add')
+ async add(@Body(ALL) bean: PipelineEntity) {
+ bean.userId = this.ctx.user.id;
+ return super.add(bean);
+ }
+
+ @Post('/update')
+ async update(@Body(ALL) bean) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ return super.update(bean);
+ }
+
+ @Post('/save')
+ async save(@Body(ALL) bean: HistoryEntity) {
+ bean.userId = this.ctx.user.id;
+ if (bean.id > 0) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ }
+ await this.service.save(bean);
+ return this.ok(bean.id);
+ }
+
+ @Post('/saveLog')
+ async saveLog(@Body(ALL) bean: HistoryLogEntity) {
+ bean.userId = this.ctx.user.id;
+ if (bean.id > 0) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ }
+ await this.logService.save(bean);
+ return this.ok(bean.id);
+ }
+
+ @Post('/delete')
+ async delete(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ return super.delete(id);
+ }
+
+ @Post('/detail')
+ async detail(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ const detail = await this.service.detail(id);
+ return this.ok(detail);
+ }
+
+ @Post('/logs')
+ async logs(@Query('id') id) {
+ await this.logService.checkUserId(id, this.ctx.user.id);
+ const logInfo = await this.logService.info(id);
+ return this.ok(logInfo);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/controller/pipeline-controller.ts b/packages/ui/certd-server/src/modules/pipeline/controller/pipeline-controller.ts
new file mode 100644
index 000000000..c19685439
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/controller/pipeline-controller.ts
@@ -0,0 +1,77 @@
+import {
+ ALL,
+ Body,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { CrudController } from '../../../basic/crud-controller';
+import { PipelineService } from '../service/pipeline-service';
+import { PipelineEntity } from '../entity/pipeline';
+
+/**
+ * 证书
+ */
+@Provide()
+@Controller('/api/pi/pipeline')
+export class PipelineController extends CrudController {
+ @Inject()
+ service: PipelineService;
+
+ getService() {
+ return this.service;
+ }
+
+ @Post('/page')
+ async page(@Body(ALL) body) {
+ body.query.userId = this.ctx.user.id;
+ const buildQuery = qb => {
+ qb.where({});
+ };
+ return super.page({ ...body, buildQuery });
+ }
+
+ @Post('/add')
+ async add(@Body(ALL) bean: PipelineEntity) {
+ bean.userId = this.ctx.user.id;
+ return super.add(bean);
+ }
+
+ @Post('/update')
+ async update(@Body(ALL) bean) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ return super.update(bean);
+ }
+
+ @Post('/save')
+ async save(@Body(ALL) bean: PipelineEntity) {
+ bean.userId = this.ctx.user.id;
+ if (bean.id > 0) {
+ await this.service.checkUserId(bean.id, this.ctx.user.id);
+ }
+ await this.service.save(bean);
+ return this.ok(bean.id);
+ }
+
+ @Post('/delete')
+ async delete(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ return super.delete(id);
+ }
+
+ @Post('/detail')
+ async detail(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ const detail = await this.service.detail(id);
+ return this.ok(detail);
+ }
+
+ @Post('/trigger')
+ async trigger(@Query('id') id) {
+ await this.service.checkUserId(id, this.ctx.user.id);
+ await this.service.trigger(id);
+ return this.ok({});
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/controller/plugin-controller.ts b/packages/ui/certd-server/src/modules/pipeline/controller/plugin-controller.ts
new file mode 100644
index 000000000..4501d2c85
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/controller/plugin-controller.ts
@@ -0,0 +1,27 @@
+import {
+ ALL,
+ Controller,
+ Inject,
+ Post,
+ Provide,
+ Query,
+} from '@midwayjs/decorator';
+import { BaseController } from '../../../basic/base-controller';
+import { PluginService } from '../service/plugin-service';
+
+/**
+ * 插件
+ */
+@Provide()
+@Controller('/api/pi/plugin')
+export class PluginController extends BaseController {
+ @Inject()
+ service: PluginService;
+
+ @Post('/list')
+ async list(@Query(ALL) query) {
+ query.userId = this.ctx.user.id;
+ const list = this.service.getList();
+ return this.ok(list);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/access.ts b/packages/ui/certd-server/src/modules/pipeline/entity/access.ts
new file mode 100644
index 000000000..8855c1ebf
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/access.ts
@@ -0,0 +1,33 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+/**
+ * 授权配置
+ */
+@Entity('cd_access')
+export class AccessEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+ @Column({ name: 'user_id', comment: '用户id' })
+ userId: number;
+ @Column({ comment: '名称', length: 100 })
+ name: string;
+
+ @Column({ comment: '类型', length: 100 })
+ type: string;
+
+ @Column({ name: 'setting', comment: '设置', length: 1024, nullable: true })
+ setting: string;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/history-log.ts b/packages/ui/certd-server/src/modules/pipeline/entity/history-log.ts
new file mode 100644
index 000000000..bdeeeeaf9
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/history-log.ts
@@ -0,0 +1,41 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+@Entity('pi_history_log')
+export class HistoryLogEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({ name: 'user_id', comment: '用户id' })
+ userId: number;
+
+ @Column({ name: 'pipeline_id', comment: '流水线' })
+ pipelineId: number;
+
+ @Column({ name: 'history_id', comment: '历史id' })
+ historyId: number;
+
+ @Column({
+ name: 'node_id',
+ comment: '任务节点id',
+ length: 100,
+ nullable: true,
+ })
+ nodeId: string;
+
+ @Column({ comment: '日志内容', length: 40960, nullable: true })
+ logs: string;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/history.ts b/packages/ui/certd-server/src/modules/pipeline/entity/history.ts
new file mode 100644
index 000000000..ab1425f54
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/history.ts
@@ -0,0 +1,38 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+@Entity('pi_history')
+export class HistoryEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({ name: 'user_id', comment: '用户id' })
+ userId: number;
+
+ @Column({ name: 'pipeline_id', comment: '流水线' })
+ pipelineId: number;
+ @Column({ comment: '运行状态', length: 40960, nullable: true })
+ pipeline: string;
+
+ @Column({ comment: '结果状态', length: 20, nullable: true })
+ status: string;
+
+ @Column({
+ name: 'end_time',
+ comment: '结束时间',
+ nullable: true,
+ })
+ endTime: Date;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/pipeline.ts b/packages/ui/certd-server/src/modules/pipeline/entity/pipeline.ts
new file mode 100644
index 000000000..c9f74d5b7
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/pipeline.ts
@@ -0,0 +1,52 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+@Entity('pi_pipeline')
+export class PipelineEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({ name: 'user_id', comment: '用户id' })
+ userId: number;
+
+ @Column({ name: 'title', comment: '标题' })
+ title: number;
+
+ @Column({ comment: '配置', length: 40960 })
+ content: string;
+
+ @Column({
+ name: 'keep_history_count',
+ comment: '历史记录保持数量',
+ nullable: true,
+ })
+ keepHistoryCount: number;
+
+ @Column({ comment: '备注', length: 100, nullable: true })
+ remark: string;
+
+ @Column({ comment: '状态', length: 100, nullable: true })
+ status: string;
+
+ @Column({ comment: '启用/禁用', nullable: true, default: false })
+ disabled: boolean;
+
+ @Column({
+ name: 'last_history_time',
+ comment: '最后一次执行时间',
+ nullable: true,
+ })
+ lastHistoryTime: number;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/storage.ts b/packages/ui/certd-server/src/modules/pipeline/entity/storage.ts
new file mode 100644
index 000000000..96138c478
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/storage.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+@Entity('pi_storage')
+export class StorageEntity {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({ name: 'user_id', comment: '用户id' })
+ userId: number;
+
+ @Column({ name: 'scope', comment: '范围' })
+ scope: string;
+
+ @Column({ name: 'namespace', comment: '命名空间' })
+ namespace: string;
+
+ @Column({ comment: 'key', length: 100, nullable: true })
+ key: string;
+
+ @Column({ comment: 'value', length: 40960, nullable: true })
+ value: string;
+
+ @Column({
+ name: 'create_time',
+ comment: '创建时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ createTime: Date;
+ @Column({
+ name: 'update_time',
+ comment: '修改时间',
+ default: () => 'CURRENT_TIMESTAMP',
+ })
+ updateTime: Date;
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/vo/history-detail.ts b/packages/ui/certd-server/src/modules/pipeline/entity/vo/history-detail.ts
new file mode 100644
index 000000000..39d6efdbe
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/vo/history-detail.ts
@@ -0,0 +1,12 @@
+import { HistoryEntity } from '../history';
+import { HistoryLogEntity } from '../history-log';
+
+export class HistoryDetail {
+ history: HistoryEntity;
+ log: HistoryLogEntity;
+
+ constructor(history: HistoryEntity, log: HistoryLogEntity) {
+ this.history = history;
+ this.log = log;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/vo/pipeline-detail.ts b/packages/ui/certd-server/src/modules/pipeline/entity/vo/pipeline-detail.ts
new file mode 100644
index 000000000..e7a5f4b32
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/entity/vo/pipeline-detail.ts
@@ -0,0 +1,13 @@
+import { PipelineEntity } from '../pipeline';
+import { HistoryEntity } from '../history';
+import { HistoryLogEntity } from '../history-log';
+
+export class PipelineDetail {
+ pipeline: PipelineEntity;
+ constructor(pipeline: PipelineEntity) {
+ this.pipeline = pipeline;
+ }
+
+ last: HistoryEntity;
+ logs: HistoryLogEntity[];
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/access-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/access-service.ts
new file mode 100644
index 000000000..b911fc295
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/access-service.ts
@@ -0,0 +1,44 @@
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { AccessEntity } from '../entity/access';
+import {
+ accessRegistry,
+ IAccessService,
+} from '@certd/pipeline';
+
+/**
+ * 授权
+ */
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class AccessService
+ extends BaseService
+ implements IAccessService
+{
+ @InjectEntityModel(AccessEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async getById(id: any): Promise {
+ const entity = await this.info(id);
+ // const access = accessRegistry.get(entity.type);
+ const setting = JSON.parse(entity.setting);
+ return {
+ id: entity.id,
+ ...setting,
+ };
+ }
+
+ getDefineList() {
+ return accessRegistry.getDefineList();
+ }
+
+ getDefineByType(type) {
+ return accessRegistry.getDefine(type);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/db-storage.ts b/packages/ui/certd-server/src/modules/pipeline/service/db-storage.ts
new file mode 100644
index 000000000..c95357315
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/db-storage.ts
@@ -0,0 +1,47 @@
+import { IStorage } from '@certd/pipeline/src/core/storage';
+import { StorageService } from './storage-service';
+
+export class DbStorage implements IStorage {
+ /**
+ * 范围: user / pipeline / runtime / task
+ */
+ storageService: StorageService;
+ userId: number;
+ constructor(userId: number, storageService: StorageService) {
+ this.userId = userId;
+ this.storageService = storageService;
+ }
+
+ async get(
+ scope: string,
+ namespace: string,
+ key: string
+ ): Promise {
+ const storageEntity = await this.storageService.get({
+ userId: this.userId,
+ scope: scope,
+ namespace: namespace,
+ key,
+ });
+
+ if (storageEntity != null) {
+ return storageEntity.value;
+ }
+ return null;
+ }
+
+ async set(
+ scope: string,
+ namespace: string,
+ key: string,
+ value: string
+ ): Promise {
+ await this.storageService.set({
+ userId: this.userId,
+ scope: scope,
+ namespace: namespace,
+ key,
+ value,
+ });
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/dns-provider-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/dns-provider-service.ts
new file mode 100644
index 000000000..0473841b8
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/dns-provider-service.ts
@@ -0,0 +1,9 @@
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { dnsProviderRegistry } from '@certd/plugin-cert';
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class DnsProviderService {
+ getList() {
+ return dnsProviderRegistry.getDefineList();
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-log-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-log-service.ts
new file mode 100644
index 000000000..46aaa74b8
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/history-log-service.ts
@@ -0,0 +1,27 @@
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { HistoryLogEntity } from '../entity/history-log';
+
+/**
+ * 证书申请
+ */
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class HistoryLogService extends BaseService {
+ @InjectEntityModel(HistoryLogEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async save(bean: HistoryLogEntity) {
+ if (bean.id > 0) {
+ await this.update(bean);
+ } else {
+ await this.add(bean);
+ }
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts
new file mode 100644
index 000000000..832b9ea86
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts
@@ -0,0 +1,81 @@
+import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { HistoryEntity } from '../entity/history';
+import { PipelineEntity } from '../entity/pipeline';
+import { HistoryDetail } from '../entity/vo/history-detail';
+import { HistoryLogService } from './history-log-service';
+
+/**
+ * 证书申请
+ */
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class HistoryService extends BaseService {
+ @InjectEntityModel(HistoryEntity)
+ repository: Repository;
+ @Inject()
+ logService: HistoryLogService;
+ getRepository() {
+ return this.repository;
+ }
+
+ async save(bean: HistoryEntity) {
+ if (bean.id > 0) {
+ await this.update(bean);
+ } else {
+ await this.add(bean);
+ }
+ }
+
+ async detail(historyId: string) {
+ const entity = await this.info(historyId);
+ const log = await this.logService.info(historyId);
+ return new HistoryDetail(entity, log);
+ }
+
+ async start(pipeline: PipelineEntity) {
+ const bean = {
+ userId: pipeline.userId,
+ pipelineId: pipeline.id,
+ title: pipeline.title,
+ status: 'start',
+ };
+ const { id } = await this.add(bean);
+ //清除大于pipeline.keepHistoryCount的历史记录
+ this.clear(pipeline.id, pipeline.keepHistoryCount);
+ return id;
+ }
+
+ private async clear(pipelineId: number, keepCount = 30) {
+ const count = await this.repository.count({
+ where: {
+ pipelineId,
+ },
+ });
+ if (count <= keepCount) {
+ return;
+ }
+ let shouldDeleteCount = count - keepCount;
+ const deleteCountBatch = 100;
+ while (shouldDeleteCount > 0) {
+ const list = await this.repository.find({
+ select: {
+ id: true,
+ },
+ where: {
+ pipelineId,
+ },
+ order: {
+ id: 'ASC',
+ },
+ skip: 0,
+ take: deleteCountBatch,
+ });
+ await this.repository.remove(list);
+
+ shouldDeleteCount -= deleteCountBatch;
+ }
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
new file mode 100644
index 000000000..b267f93e3
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
@@ -0,0 +1,241 @@
+import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { In, Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { PipelineEntity } from '../entity/pipeline';
+import { PipelineDetail } from '../entity/vo/pipeline-detail';
+import { Executor, Pipeline, RunHistory } from '@certd/pipeline';
+import { AccessService } from './access-service';
+import { DbStorage } from './db-storage';
+import { StorageService } from './storage-service';
+import { Cron } from '../../../plugins/cron/cron';
+import { HistoryService } from './history-service';
+import { HistoryEntity } from '../entity/history';
+import { HistoryLogEntity } from '../entity/history-log';
+import { HistoryLogService } from './history-log-service';
+import { logger } from '../../../utils/logger';
+
+/**
+ * 证书申请
+ */
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class PipelineService extends BaseService {
+ @InjectEntityModel(PipelineEntity)
+ repository: Repository;
+
+ @Inject()
+ accessService: AccessService;
+ @Inject()
+ storageService: StorageService;
+ @Inject()
+ historyService: HistoryService;
+ @Inject()
+ historyLogService: HistoryLogService;
+
+ @Inject()
+ cron: Cron;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async update(entity) {
+ await super.update(entity);
+
+ await this.registerTriggerById(entity.id);
+ }
+
+ private async registerTriggerById(pipelineId) {
+ if (pipelineId == null) {
+ return;
+ }
+ const info = await this.info(pipelineId);
+ if (info && !info.disabled) {
+ const pipeline = JSON.parse(info.content);
+ this.registerTriggers(pipeline);
+ }
+ }
+
+ /**
+ * 获取详情
+ * @param id
+ */
+ async detail(id) {
+ const pipeline = await this.info(id);
+ return new PipelineDetail(pipeline);
+ }
+
+ async save(bean: PipelineEntity) {
+ const pipeline = JSON.parse(bean.content);
+ bean.title = pipeline.title;
+ await this.addOrUpdate(bean);
+ await this.registerTriggerById(bean.id);
+ }
+
+ /**
+ * 应用启动后初始加载记录
+ */
+ async onStartup() {
+ const idEntityList = await this.repository.find({
+ select: {
+ id: true,
+ },
+ where: {
+ disabled: false,
+ },
+ });
+ const ids = idEntityList.map(item => {
+ return item.id;
+ });
+
+ //id 分段
+ const idsSpan = [];
+ let arr = [];
+ for (let i = 0; i < ids.length; i++) {
+ if (i % 20 === 0) {
+ arr = [];
+ idsSpan.push(arr);
+ }
+ arr.push(ids[i]);
+ }
+
+ //分段加载记录
+ for (const idArr of idsSpan) {
+ const list = await this.repository.findBy({
+ id: In(idArr),
+ });
+
+ for (const entity of list) {
+ const pipeline = JSON.parse(entity.content ?? '{}');
+ this.registerTriggers(pipeline);
+ }
+ }
+ logger.info('定时器数量:', this.cron.getList());
+ }
+
+ registerTriggers(pipeline?: Pipeline) {
+ if (pipeline?.triggers == null) {
+ return;
+ }
+ for (const trigger of pipeline.triggers) {
+ this.registerCron(pipeline.id, trigger);
+ }
+ }
+
+ async trigger(id) {
+ this.cron.register({
+ name: `pipeline.${id}.trigger.once`,
+ cron: null,
+ job: async () => {
+ await this.run(id, null);
+ },
+ });
+ logger.info('定时器数量:', this.cron.getList());
+ }
+
+ registerCron(pipelineId, trigger) {
+ let cron = trigger.props?.cron;
+ if (cron == null) {
+ return;
+ }
+ if(cron.startsWith("*")){
+ cron = "0"+ cron.substring(1,cron.length)
+ return
+ }
+ this.cron.register({
+ name: this.buildCronKey(pipelineId, trigger.id),
+ cron: cron,
+ job: async () => {
+ logger.info('定时任务触发:', pipelineId, trigger.id);
+ await this.run(pipelineId, trigger.id);
+ },
+ });
+ }
+
+ async run(id, triggerId) {
+ const entity: PipelineEntity = await this.info(id);
+ const pipeline = JSON.parse(entity.content);
+
+ if (!pipeline.stages || pipeline.stages.length === 0) {
+ return;
+ }
+
+ const triggerType = this.getTriggerType(triggerId, pipeline);
+ if (triggerType == null) {
+ return;
+ }
+
+ const onChanged = async (history: RunHistory) => {
+ //保存执行历史
+ await this.saveHistory(history);
+ };
+
+ const userId = entity.userId;
+ const historyId = await this.historyService.start(entity);
+
+ const executor = new Executor({
+ userId,
+ pipeline,
+ onChanged,
+ accessService: this.accessService,
+ storage: new DbStorage(userId, this.storageService),
+ });
+
+ await executor.run(historyId, triggerType);
+ }
+
+ private getTriggerType(triggerId, pipeline) {
+ let triggerType = 'user';
+ if (triggerId != null) {
+ //如果不是手动触发
+ //查找trigger
+ const found = this.findTrigger(pipeline, triggerId);
+ if (!found) {
+ //如果没有找到triggerId,说明被用户删掉了,这里再删除一次
+ this.cron.remove(this.buildCronKey(pipeline.id, triggerId));
+ triggerType = null;
+ } else {
+ logger.info('timer trigger:' + found.id, found.title, found.cron);
+ triggerType = 'timer';
+ }
+ }
+ return triggerType;
+ }
+
+ private buildCronKey(pipelineId, triggerId) {
+ return `pipeline.${pipelineId}.trigger.${triggerId}`;
+ }
+
+ private findTrigger(pipeline, triggerId) {
+ for (const trigger of pipeline.triggers) {
+ if (trigger.id === triggerId) {
+ return trigger;
+ }
+ }
+ return;
+ }
+
+ private async saveHistory(history: RunHistory) {
+ //修改pipeline状态
+ const pipelineEntity = new PipelineEntity();
+ pipelineEntity.id = parseInt(history.pipeline.id);
+ pipelineEntity.status = history.pipeline.status.status;
+ pipelineEntity.lastHistoryTime = history.pipeline.status.startTime;
+ await this.update(pipelineEntity);
+
+ const entity: HistoryEntity = new HistoryEntity();
+ entity.id = parseInt(history.id);
+ entity.userId = history.pipeline.userId;
+ entity.pipeline = JSON.stringify(history.pipeline);
+ await this.historyService.save(entity);
+
+ const logEntity: HistoryLogEntity = new HistoryLogEntity();
+ logEntity.id = entity.id;
+ logEntity.userId = entity.userId;
+ logEntity.pipelineId = entity.pipelineId;
+ logEntity.historyId = entity.id;
+ logEntity.logs = JSON.stringify(history.logs);
+ await this.historyLogService.addOrUpdate(logEntity);
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/plugin-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/plugin-service.ts
new file mode 100644
index 000000000..c02715450
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/plugin-service.ts
@@ -0,0 +1,15 @@
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { pluginRegistry } from '@certd/pipeline';
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class PluginService {
+ getList() {
+ const collection = pluginRegistry.storage;
+ const list = [];
+ for (const key in collection) {
+ const Plugin = collection[key];
+ list.push({ ...Plugin.define, key });
+ }
+ return list;
+ }
+}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/storage-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/storage-service.ts
new file mode 100644
index 000000000..bcf862964
--- /dev/null
+++ b/packages/ui/certd-server/src/modules/pipeline/service/storage-service.ts
@@ -0,0 +1,56 @@
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { BaseService } from '../../../basic/base-service';
+import { StorageEntity } from '../entity/storage';
+
+/**
+ */
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export class StorageService extends BaseService {
+ @InjectEntityModel(StorageEntity)
+ repository: Repository;
+
+ getRepository() {
+ return this.repository;
+ }
+
+ async get(where: {
+ scope: any;
+ namespace: any;
+ userId: number;
+ key: string;
+ }) {
+ if (where.userId == null) {
+ throw new Error('userId 不能为空');
+ }
+ return await this.repository.findOne({
+ where,
+ });
+ }
+
+ async set(entity: {
+ id?: any;
+ scope: any;
+ namespace: any;
+ userId: number;
+ value: string;
+ key: string;
+ }) {
+ entity.id = null;
+ const query = { ...entity };
+ delete query.value;
+ const ret = await this.get(query);
+ if (ret != null) {
+ entity.id = ret.id;
+ if (ret.userId !== entity.userId) {
+ throw new Error('您没有权限修改此数据');
+ }
+ await this.repository.save(entity);
+ } else {
+ await this.repository.insert(entity);
+ }
+ return;
+ }
+}
diff --git a/packages/ui/certd-server/src/plugins/cron/configuration.ts b/packages/ui/certd-server/src/plugins/cron/configuration.ts
new file mode 100644
index 000000000..b9a6b89ea
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/cron/configuration.ts
@@ -0,0 +1,27 @@
+import { Config, Configuration, Logger } from '@midwayjs/decorator';
+import { ILogger } from '@midwayjs/logger';
+import { IMidwayContainer } from '@midwayjs/core';
+import { Cron } from './cron';
+
+// ... (see below) ...
+@Configuration({
+ namespace: 'cron',
+ //importConfigs: [join(__dirname, './config')],
+})
+export class CronConfiguration {
+ @Config()
+ config;
+ @Logger()
+ logger: ILogger;
+
+ cron: Cron;
+ async onReady(container: IMidwayContainer) {
+ this.logger.info('cron start');
+ this.cron = new Cron({
+ logger: this.logger,
+ ...this.config,
+ });
+ container.registerObject('cron', this.cron);
+ this.logger.info('cron started');
+ }
+}
diff --git a/packages/ui/certd-server/src/plugins/cron/cron.ts b/packages/ui/certd-server/src/plugins/cron/cron.ts
new file mode 100644
index 000000000..e0380707e
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/cron/cron.ts
@@ -0,0 +1,38 @@
+import cron from 'node-cron';
+export type CronTask = {
+ /**
+ * 为空则为单次执行
+ */
+ cron: string;
+ job: () => Promise;
+ name: string;
+};
+export class Cron {
+ logger;
+ constructor(opts) {
+ this.logger = opts.logger;
+ }
+
+ register(task: CronTask) {
+ if (!task.cron) {
+ this.logger.info(`[cron] register once : [${task.name}]`);
+ task.job();
+ return;
+ }
+ this.logger.info(`[cron] register cron : [${task.name}] ,${task.cron}`);
+ cron.schedule(task.cron, task.job, {
+ name: task.name,
+ });
+ }
+
+ remove(taskName: string) {
+ this.logger.info(`[cron] remove : [${taskName}]`);
+ const tasks = cron.getTasks() as Map;
+ tasks.delete(taskName);
+ }
+
+ getList() {
+ const tasks = cron.getTasks();
+ return tasks.size;
+ }
+}
diff --git a/packages/ui/certd-server/src/plugins/cron/index.ts b/packages/ui/certd-server/src/plugins/cron/index.ts
new file mode 100644
index 000000000..69c716a09
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/cron/index.ts
@@ -0,0 +1,5 @@
+// src/index.ts
+export { CronConfiguration as Configuration } from './configuration';
+// export * from './controller/user';
+// export * from './controller/api';
+// export * from './service/user';
diff --git a/packages/ui/certd-server/src/plugins/pipeline/index.ts b/packages/ui/certd-server/src/plugins/pipeline/index.ts
new file mode 100644
index 000000000..388562a88
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/pipeline/index.ts
@@ -0,0 +1,6 @@
+// src/index.ts
+import '@certd/plugin-all';
+export { PipelineConfiguration as Configuration } from '@certd/pipeline';
+// export * from './controller/user';
+// export * from './controller/api';
+// export * from './service/user';
diff --git a/packages/ui/certd-server/src/plugins/pipeline/test/echo-plugin.ts b/packages/ui/certd-server/src/plugins/pipeline/test/echo-plugin.ts
new file mode 100644
index 000000000..7adee962d
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/pipeline/test/echo-plugin.ts
@@ -0,0 +1,27 @@
+import { ILogger } from "@midwayjs/logger";
+import { ITaskPlugin,Autowire, IsTaskPlugin, TaskInput } from "@certd/pipeline";
+
+@IsTaskPlugin({
+ name: "EchoPlugin",
+ title: "测试插件",
+ desc: "test",
+})
+export class EchoPlugin implements ITaskPlugin {
+ @TaskInput({
+ title: "测试属性",
+ component: {
+ name: "text",
+ },
+ })
+ test?: string;
+
+ @Autowire()
+ // @ts-ignore
+ logger: ILogger;
+
+ async onInit(){}
+
+ async execute(): Promise {
+ return Promise.resolve(undefined);
+ }
+}
diff --git a/packages/ui/certd-server/src/utils/logger.ts b/packages/ui/certd-server/src/utils/logger.ts
new file mode 100644
index 000000000..d4fea9ea7
--- /dev/null
+++ b/packages/ui/certd-server/src/utils/logger.ts
@@ -0,0 +1,12 @@
+const log4js = require('log4js');
+const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
+const path = require('path');
+const filename = path.join('/logs/server.log');
+log4js.configure({
+ appenders: {
+ std: { type: 'stdout', level: 'debug' },
+ file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename },
+ },
+ categories: { default: { appenders: ['std'], level } },
+});
+export const logger = log4js.getLogger('fast');
diff --git a/packages/ui/certd-server/src/utils/random.ts b/packages/ui/certd-server/src/utils/random.ts
new file mode 100644
index 000000000..6be6f6e2d
--- /dev/null
+++ b/packages/ui/certd-server/src/utils/random.ts
@@ -0,0 +1,43 @@
+const numbers = '0123456789';
+const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+const specials = '~!@#$%^*()_+-=[]{}|;:,./<>?';
+
+/**
+ * Generate random string
+ * @param {Number} length
+ * @param {Object} options
+ */
+function randomStr(length, options) {
+ length || (length = 8);
+ options || (options = {});
+
+ let chars = '';
+ let result = '';
+
+ if (options === true) {
+ chars = numbers + letters;
+ } else if (typeof options === 'string') {
+ chars = options;
+ } else {
+ if (options.numbers !== false) {
+ chars += typeof options.numbers === 'string' ? options.numbers : numbers;
+ }
+
+ if (options.letters !== false) {
+ chars += typeof options.letters === 'string' ? options.letters : letters;
+ }
+
+ if (options.specials) {
+ chars +=
+ typeof options.specials === 'string' ? options.specials : specials;
+ }
+ }
+
+ while (length > 0) {
+ length--;
+ result += chars[Math.floor(Math.random() * chars.length)];
+ }
+ return result;
+}
+
+export const RandomUtil = { randomStr };
diff --git a/packages/ui/certd-server/test/controller/api.test.ts b/packages/ui/certd-server/test/controller/api.test.ts
new file mode 100644
index 000000000..28dcfd035
--- /dev/null
+++ b/packages/ui/certd-server/test/controller/api.test.ts
@@ -0,0 +1,25 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+import * as assert from 'assert';
+
+describe('test/controller/home.test.ts', () => {
+
+ it('should POST /api/get_user', async () => {
+ // create app
+ const app = await createApp();
+
+ // make request
+ const result = await createHttpRequest(app).post('/api/get_user').query({ uid: 123 });
+
+ // use expect by jest
+ expect(result.status).toBe(200);
+ expect(result.body.message).toBe('OK');
+
+ // or use assert
+ assert.deepStrictEqual(result.status, 200);
+ assert.deepStrictEqual(result.body.data.uid, '123');
+
+ // close app
+ await close(app);
+ });
+});
diff --git a/packages/ui/certd-server/test/controller/home.test.ts b/packages/ui/certd-server/test/controller/home.test.ts
new file mode 100644
index 000000000..2e9578892
--- /dev/null
+++ b/packages/ui/certd-server/test/controller/home.test.ts
@@ -0,0 +1,26 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+import * as assert from 'assert';
+
+describe('test/controller/home.test.ts', () => {
+
+ it('should GET /', async () => {
+ // create app
+ const app = await createApp();
+
+ // make request
+ const result = await createHttpRequest(app).get('/');
+
+ // use expect by jest
+ expect(result.status).toBe(200);
+ expect(result.text).toBe('Hello Midwayjs!');
+
+ // or use assert
+ assert.deepStrictEqual(result.status, 200);
+ assert.deepStrictEqual(result.text, 'Hello Midwayjs!');
+
+ // close app
+ await close(app);
+ });
+
+});
diff --git a/packages/ui/certd-server/tsconfig.json b/packages/ui/certd-server/tsconfig.json
new file mode 100644
index 000000000..d4f44496e
--- /dev/null
+++ b/packages/ui/certd-server/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compileOnSave": true,
+ "compilerOptions": {
+ "target": "ES2018",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "inlineSourceMap":true,
+ "noImplicitThis": true,
+ "noUnusedLocals": true,
+ "stripInternal": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "pretty": true,
+ "declaration": true,
+ "typeRoots": [ "./typings", "./node_modules/@types"],
+ "outDir": "dist"
+ },
+ "exclude": [
+ "dist",
+ "node_modules",
+ "test"
+ ]
+}