├── .github └── workflows │ └── lsif.yml ├── .gitignore ├── LICENSE ├── README.md ├── Taskfile.yml ├── conf └── app.ini ├── go.mod ├── go.sum ├── internal ├── db │ ├── application.go │ ├── collector.go │ ├── errors │ │ ├── application.go │ │ ├── collector.go │ │ └── webhook.go │ ├── models.go │ └── webhook.go ├── form │ └── form.go ├── route │ ├── api │ │ └── v1 │ │ │ ├── api.go │ │ │ └── webhook.go │ ├── application.go │ ├── collector.go │ ├── config.go │ ├── dashboard.go │ ├── hook.go │ └── webhook.go ├── setting │ └── setting.go ├── templateutil │ └── template.go ├── tool │ ├── string.go │ └── tool.go └── webhook │ └── github.go ├── orbiter.go ├── public ├── assets │ ├── AdminLTE-2.3.2 │ │ ├── css │ │ │ ├── AdminLTE.min.css │ │ │ └── skins │ │ │ │ └── skin-blue.min.css │ │ └── js │ │ │ └── app.min.js │ ├── bootstrap-3.3.5 │ │ ├── css │ │ │ └── bootstrap.min.css │ │ └── js │ │ │ └── bootstrap.min.js │ └── font-awesome-4.5.0 │ │ ├── css │ │ └── font-awesome.min.css │ │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── img │ ├── orbiter-brand.png │ └── orbiter.png ├── js │ └── jquery-1.12.0.min.js └── plugins │ └── highlight-9.1.0 │ ├── github.css │ └── highlight.pack.js └── templates ├── application ├── edit.tmpl ├── list.tmpl └── new.tmpl ├── base ├── alert.tmpl ├── footer.tmpl └── head.tmpl ├── collector ├── edit.tmpl ├── list.tmpl └── new.tmpl ├── config.tmpl ├── dashboard.tmpl ├── status ├── 404.tmpl └── 500.tmpl └── webhook ├── list.tmpl └── view.tmpl /.github/workflows/lsif.yml: -------------------------------------------------------------------------------- 1 | name: LSIF 2 | on: 3 | push: 4 | paths: 5 | - '**.go' 6 | - 'go.mod' 7 | - '.github/workflows/lsif.yml' 8 | env: 9 | GOPROXY: "https://proxy.golang.org" 10 | 11 | jobs: 12 | lsif-go: 13 | if: github.repository == 'unknwon/orbiter' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Generate LSIF data 18 | uses: sourcegraph/lsif-go-action@master 19 | - name: Upload LSIF data to sourcegraph.com 20 | continue-on-error: true 21 | uses: docker://sourcegraph/src-cli:latest 22 | with: 23 | args: lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | orbiter 2 | custom 3 | orbiter.sublime-project 4 | orbiter.sublime-workspace 5 | /.idea 6 | .task 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](public/img/orbiter-brand.png) 2 | 3 | Orbiter is a tool for collecting and redistributing webhooks over the network. 4 | 5 | ## NOTICES 6 | 7 | - **This project comes with absolutely NO warranty for break changes**, 8 | - ... but you can file bugs, feature requests and usage questions **if you have to**. 9 | 10 | 11 | ## Features 12 | 13 | - [x] Receive and save webhook by time, type (GitHub, etc) and tags (repository, owner, etc). 14 | - [x] Redistribute webhook by APIs whenever and whatever it gets requested. 15 | - [ ] Nice admin dashboard to config and do statistics. 16 | 17 | ## WTF? 18 | 19 | Oh well, it is just a tool to collect webhook history when you don't have time to process them at the moment. 20 | 21 | Or, maybe you want to study the history later to understand a basic user action flow. 22 | 23 | And nothing else, sorry! 24 | 25 | ## Installation 26 | 27 | First of all, you need to install MySQL (WHAT?!). 28 | 29 | Then, install Orbiter: 30 | 31 | ```sh 32 | $ go get unknwon.dev/orbiter 33 | ``` 34 | 35 | ### Configuration 36 | 37 | The default configuration is located at `conf/app.ini`, please create another file called `custom/app.ini` to adjust your own values. 38 | 39 | Finally, execute `./orbiter`. 40 | 41 | The server should start listening on `0.0.0.0:8085` by default, visit http://localhost:8085. 42 | 43 | ## Quick start 44 | 45 | ### Config new collector 46 | 47 | Collector defines what the type (currently only support GitHub) and secret token should be. 48 | 49 | **Secret token is auto-generated by Orbiter, you can ask to regenerate as many times as you prefer**. 50 | 51 | Normally, one collector is for an individual repository, but that's up to you. 52 | 53 | After creation, setup the webhook with secret token in corresponding hosting site. 54 | 55 | For example, in GitHub: 56 | 57 | - `Payload URL`: `https://orbiter.unknwon.io/hook?secret=mysecretweapon` 58 | - `Content type`: `application/json` 59 | - `Secret`: **REMAIN EMPTY** 60 | - Events: **ORBITER DOES NOT CARE** 61 | 62 | ### View recent history 63 | 64 | You can go to `/webhooks` to view recent receive history of webhooks, and their payloads. 65 | 66 | Important tags such as **repository owner**, **repository name**, **event type** and **sender** are extracted for payload automatically and can be used as query conditions for applications. 67 | 68 | ### Add new application 69 | 70 | Application is basically nothing but contains a **access token** which is needed for calling APIs. 71 | 72 | ## APIs 73 | 74 | **All parameters and access token is passed by URL query parameters.** 75 | 76 | ### List webhooks 77 | 78 | ``` 79 | GET /api/v1/webhooks 80 | ``` 81 | 82 | ##### Parameters 83 | 84 | Following parameters are all optional, and combine with condition **AND**. 85 | 86 | |Name|Description| 87 | |----|-----------| 88 | |`collector_id`|List webhooks that were received by certain collector| 89 | |`owner`|List webhooks that belongs to certain repository owner| 90 | |`repo_name`|List webhooks that belongs to certain repository| 91 | |`event_type`|List webhooks that have certain event type| 92 | |`sender`|List webhooks that were triggered by someone| 93 | |`after`|List webhooks that received after certain time point, this value is Unix nanoseconds| 94 | |`limit`|Maximum number of webhooks to response| 95 | 96 | ##### Example 97 | 98 | ``` 99 | GET /api/v1/webhooks?token=mysecretdefense&event_type=issue_comment&after=1454740438165946316&limit=10 100 | ``` 101 | 102 | ## Acknowledgments 103 | 104 | - [AdminLTE](https://almsaeedstudio.com/): Open source Bootstrap 3 admin template 105 | - [Font Awesome](http://fontawesome.io/): The iconic font and CSS toolkit 106 | 107 | ## Open-source, not open-contribution 108 | 109 | _Quote from [benbjohnson/litestream](https://github.com/benbjohnson/litestream#open-source-not-open-contribution):_ 110 | 111 | > I am grateful for community involvement, bug reports, & feature requests. I do not wish to come off as anything but welcoming, however, I've made the decision to keep this project closed to contributions for my own mental health and long term viability of the project. 112 | 113 | ## License 114 | 115 | This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. 116 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | vars: 4 | NAME: orbiter 5 | BUILD_TIME: 6 | sh: date -u '+%Y-%m-%d %I:%M:%S %Z' 7 | BUILD_COMMIT: 8 | sh: git rev-parse HEAD 9 | 10 | tasks: 11 | default: 12 | cmds: 13 | - task: web 14 | 15 | web: 16 | desc: Build the binary and start the web server 17 | deps: [build] 18 | cmds: 19 | - ./{{.NAME}} 20 | 21 | build: 22 | desc: Build binary 23 | cmds: 24 | - go build -ldflags '-X unknwon.dev/orbiter/internal/setting.Version={{.BUILD_COMMIT}}' -v -trimpath -o {{.NAME}} 25 | sources: 26 | - ./*.go 27 | - internal/**/*.go 28 | 29 | clean: 30 | desc: Clean up meta and packed files 31 | cmds: 32 | - go clean 33 | -------------------------------------------------------------------------------- /conf/app.ini: -------------------------------------------------------------------------------- 1 | HTTP_PORT = 8085 2 | 3 | [basic_auth] 4 | USER = 5 | PASSWORD = 6 | 7 | [database] 8 | HOST = 127.0.0.1:3306 9 | NAME = orbiter 10 | USER = root 11 | PASSWORD = -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module unknwon.dev/orbiter 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/dustin/go-humanize v1.0.0 7 | github.com/flamego/auth v0.0.0-20210831041357-158bdcabc1f1 8 | github.com/flamego/binding v0.0.0-20210920080236-58abb70d39fd 9 | github.com/flamego/flamego v0.0.0-20210913073131-adc6656c34a1 10 | github.com/flamego/template v0.0.0-20210914144450-be182d441b73 11 | github.com/flamego/validator v0.0.0-20210920053956-d830f2bfdff6 12 | github.com/go-sql-driver/mysql v1.6.0 13 | github.com/jinzhu/gorm v1.9.16 14 | github.com/satori/go.uuid v1.2.0 15 | gopkg.in/ini.v1 v1.62.0 16 | unknwon.dev/clog/v2 v2.2.0 17 | ) 18 | 19 | require ( 20 | github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect 21 | github.com/fatih/color v1.10.0 // indirect 22 | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect 23 | github.com/jinzhu/inflection v1.0.0 // indirect 24 | github.com/leodido/go-urn v1.2.1 // indirect 25 | github.com/lib/pq v1.2.0 // indirect 26 | github.com/mattn/go-colorable v0.1.8 // indirect 27 | github.com/mattn/go-isatty v0.0.12 // indirect 28 | github.com/pkg/errors v0.9.1 // indirect 29 | github.com/smartystreets/assertions v1.0.1 // indirect 30 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect 31 | golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 // indirect 32 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect 33 | golang.org/x/text v0.3.7 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 2 | github.com/alecthomas/participle/v2 v2.0.0-alpha5/go.mod h1:Z1zPLDbcGsVsBYsThKXY00i84575bN/nMczzIrU4rWU= 3 | github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= 4 | github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= 5 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E= 6 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 7 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= 13 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 14 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 15 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 16 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 17 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 18 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 19 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 20 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 21 | github.com/flamego/auth v0.0.0-20210831041357-158bdcabc1f1 h1:9+ob2Sbamnu6dt0k/KhRz9oUKSPgqlK/Q7DLAwINNwA= 22 | github.com/flamego/auth v0.0.0-20210831041357-158bdcabc1f1/go.mod h1:9Nb7VMlVwDN+k1fDlNiSHiP8St0061JOs8kK5RQzdMw= 23 | github.com/flamego/binding v0.0.0-20210920080236-58abb70d39fd h1:6Bsx5ucgfkTgMT/52Rfg8HP1BEJUoKB2qE5J2ffgad0= 24 | github.com/flamego/binding v0.0.0-20210920080236-58abb70d39fd/go.mod h1:HMhaH0EKvYQWAQWE17jzhoIy6XNuvxmCwe0Avwh0QEg= 25 | github.com/flamego/flamego v0.0.0-20210515130931-267d0ace579b/go.mod h1:DkwGPPSOX6S/4th2btOuP3HQX8zL59GZbxxOt6aFF0A= 26 | github.com/flamego/flamego v0.0.0-20210525090435-cdb552aa0e32/go.mod h1:DkwGPPSOX6S/4th2btOuP3HQX8zL59GZbxxOt6aFF0A= 27 | github.com/flamego/flamego v0.0.0-20210827083107-5b1cde1297a4/go.mod h1:DkwGPPSOX6S/4th2btOuP3HQX8zL59GZbxxOt6aFF0A= 28 | github.com/flamego/flamego v0.0.0-20210913073131-adc6656c34a1 h1:+WQYuogBWs50/qWau4AaqCTJzGyrx6du7bTyLPoVAtU= 29 | github.com/flamego/flamego v0.0.0-20210913073131-adc6656c34a1/go.mod h1:apiAxIxeHujHFX4Yr0BHmQFJuZUM07XztdQSChbeuPw= 30 | github.com/flamego/template v0.0.0-20210914144450-be182d441b73 h1:Zlafkjv8W1QqlfNqH8kKmelAt8X/CPkUuqo6otv4F0o= 31 | github.com/flamego/template v0.0.0-20210914144450-be182d441b73/go.mod h1:9bdmujHm26rSXXYmDQ3pfzUoKuV0R3BahgmSsSbvqUU= 32 | github.com/flamego/validator v0.0.0-20210920053956-d830f2bfdff6 h1:z/UoIv4wSNKhYQb1ITKfjrGfgtqhk4JdVMyDUT9yXu0= 33 | github.com/flamego/validator v0.0.0-20210920053956-d830f2bfdff6/go.mod h1:POYn0/5iW4sdamdPAYPrzqN6DFC4YaczY0gYY+Pyx5E= 34 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 35 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 36 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 37 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 38 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 39 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 40 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 41 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 42 | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= 43 | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 44 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 45 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 46 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 47 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 48 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 49 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 50 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 51 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 52 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 54 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 55 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 60 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 61 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 62 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 63 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 64 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 65 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 66 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 67 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 68 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 69 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 70 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 71 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 72 | github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= 73 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 74 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 75 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 76 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 79 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 80 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 81 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 82 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 83 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 84 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 85 | github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= 86 | github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 87 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= 88 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 89 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 90 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 91 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 93 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 94 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 95 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 96 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 97 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 98 | golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8= 99 | golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 100 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 103 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 104 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 105 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 106 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 107 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw= 117 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 119 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 120 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 121 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 122 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 123 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 124 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 125 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 126 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 127 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 128 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 129 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 130 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 131 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 132 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 133 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 134 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 135 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 136 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 137 | unknwon.dev/clog/v2 v2.2.0 h1:jkPdsxux0MC04BT/9NHbT75z4prK92SH10VBNmIpVCc= 138 | unknwon.dev/clog/v2 v2.2.0/go.mod h1:zvUlyibDHI4mykYdWyWje2G9nF/nBzfDOqRo2my4mWc= 139 | -------------------------------------------------------------------------------- /internal/db/application.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package db 16 | 17 | import ( 18 | "fmt" 19 | "time" 20 | 21 | "unknwon.dev/orbiter/internal/db/errors" 22 | "unknwon.dev/orbiter/internal/tool" 23 | ) 24 | 25 | // Application represents a consumer application that calls APIs. 26 | type Application struct { 27 | ID int64 28 | Name string `sql:"unique"` 29 | Token string `sql:"unique"` 30 | Created int64 31 | } 32 | 33 | func (app *Application) CreatedTime() time.Time { 34 | return time.Unix(0, app.Created) 35 | } 36 | 37 | func NewApplication(name string) (*Application, error) { 38 | if !x.Where("name = ?", name).First(new(Application)).RecordNotFound() { 39 | return nil, errors.ApplicationExists{name} 40 | } 41 | 42 | app := &Application{ 43 | Name: name, 44 | Token: tool.NewSecretToken(), 45 | Created: time.Now().UTC().UnixNano(), 46 | } 47 | if err := x.Create(app).Error; err != nil { 48 | return nil, fmt.Errorf("Create: %v", err) 49 | } 50 | return app, nil 51 | } 52 | 53 | func GetApplicationByID(id int64) (*Application, error) { 54 | app := new(Application) 55 | err := x.First(app, id).Error 56 | if IsRecordNotFound(err) { 57 | return nil, errors.ApplicationNotFound{id, "", ""} 58 | } else if err != nil { 59 | return nil, err 60 | } 61 | return app, nil 62 | } 63 | 64 | func GetApplicationByToken(token string) (*Application, error) { 65 | app := new(Application) 66 | err := x.Where("token = ?", token).First(app).Error 67 | if IsRecordNotFound(err) { 68 | return nil, errors.ApplicationNotFound{0, "", token} 69 | } else if err != nil { 70 | return nil, err 71 | } 72 | return app, nil 73 | } 74 | 75 | func ListApplications() ([]*Application, error) { 76 | apps := make([]*Application, 0, 5) 77 | return apps, x.Order("id asc").Find(&apps).Error 78 | } 79 | 80 | func RegenerateApplicationToken(id int64) error { 81 | return x.First(new(Application), id).Update("token", tool.NewSecretToken()).Error 82 | } 83 | 84 | func UpdateApplication(app *Application) error { 85 | if !x.Where("name = ? AND id != ?", app.Name, app.ID).First(new(Application)).RecordNotFound() { 86 | return errors.ApplicationExists{app.Name} 87 | } 88 | return x.Save(app).Error 89 | } 90 | 91 | func DeleteApplicationByID(id int64) error { 92 | return x.Delete(new(Application), id).Error 93 | } 94 | -------------------------------------------------------------------------------- /internal/db/collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package db 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "time" 21 | 22 | "unknwon.dev/orbiter/internal/db/errors" 23 | "unknwon.dev/orbiter/internal/tool" 24 | ) 25 | 26 | type CollectType int 27 | 28 | const ( 29 | CollectTypeGitHub CollectType = iota + 1 30 | ) 31 | 32 | func (t CollectType) String() string { 33 | switch t { 34 | case CollectTypeGitHub: 35 | return "GitHub" 36 | } 37 | return strconv.Itoa(int(t)) 38 | } 39 | 40 | // Collector represents a type of webhook collection to be stored. 41 | type Collector struct { 42 | ID int64 43 | Name string `sql:"unique"` 44 | Type CollectType 45 | Secret string `sql:"unique"` 46 | Created int64 47 | } 48 | 49 | func (c *Collector) CreatedTime() time.Time { 50 | return time.Unix(0, c.Created) 51 | } 52 | 53 | func NewCollector(name string, tp CollectType) (*Collector, error) { 54 | if !x.Where("name = ?", name).First(new(Collector)).RecordNotFound() { 55 | return nil, errors.CollectorExists{name} 56 | } 57 | 58 | collector := &Collector{ 59 | Name: name, 60 | Type: tp, 61 | Secret: tool.NewSecretToken(), 62 | Created: time.Now().UTC().UnixNano(), 63 | } 64 | if err := x.Create(collector).Error; err != nil { 65 | return nil, fmt.Errorf("Create: %v", err) 66 | } 67 | return collector, nil 68 | } 69 | 70 | func GetCollectorByID(id int64) (*Collector, error) { 71 | collector := new(Collector) 72 | err := x.First(collector, id).Error 73 | if IsRecordNotFound(err) { 74 | return nil, errors.CollectorNotFound{id, "", ""} 75 | } else if err != nil { 76 | return nil, err 77 | } 78 | return collector, nil 79 | } 80 | 81 | func GetCollectorBySecret(secret string) (*Collector, error) { 82 | collector := new(Collector) 83 | err := x.Where("secret = ?", secret).First(collector).Error 84 | if IsRecordNotFound(err) { 85 | return nil, errors.CollectorNotFound{0, "", secret} 86 | } else if err != nil { 87 | return nil, err 88 | } 89 | return collector, nil 90 | } 91 | 92 | func ListCollectors() ([]*Collector, error) { 93 | collectors := make([]*Collector, 0, 5) 94 | return collectors, x.Order("id asc").Find(&collectors).Error 95 | } 96 | 97 | func RegenerateCollectorSecret(id int64) error { 98 | return x.First(new(Collector), id).Update("secret", tool.NewSecretToken()).Error 99 | } 100 | 101 | func UpdateCollector(collector *Collector) error { 102 | if !x.Where("name = ? AND id != ?", collector.Name, collector.ID).First(new(Collector)).RecordNotFound() { 103 | return errors.CollectorExists{collector.Name} 104 | } 105 | return x.Save(collector).Error 106 | } 107 | 108 | // TODO: delete webhook history 109 | func DeleteCollectorByID(id int64) error { 110 | return x.Delete(new(Collector), id).Error 111 | } 112 | -------------------------------------------------------------------------------- /internal/db/errors/application.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type ApplicationExists struct { 22 | Name string 23 | } 24 | 25 | func IsApplicationExists(err error) bool { 26 | _, ok := err.(ApplicationExists) 27 | return ok 28 | } 29 | 30 | func (err ApplicationExists) Error() string { 31 | return fmt.Sprintf("Application already exists: [name: %s]", err.Name) 32 | } 33 | 34 | type ApplicationNotFound struct { 35 | ID int64 36 | Name string 37 | Token string 38 | } 39 | 40 | func IsApplicationNotFound(err error) bool { 41 | _, ok := err.(ApplicationNotFound) 42 | return ok 43 | } 44 | 45 | func (err ApplicationNotFound) Error() string { 46 | return fmt.Sprintf("Application not found: [id: %d, name: %s, token: %s]", err.ID, err.Name, err.Token) 47 | } 48 | -------------------------------------------------------------------------------- /internal/db/errors/collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type CollectorExists struct { 22 | Name string 23 | } 24 | 25 | func IsCollectorExists(err error) bool { 26 | _, ok := err.(CollectorExists) 27 | return ok 28 | } 29 | 30 | func (err CollectorExists) Error() string { 31 | return fmt.Sprintf("Collector already exists: [name: %s]", err.Name) 32 | } 33 | 34 | type CollectorNotFound struct { 35 | ID int64 36 | Name string 37 | Secret string 38 | } 39 | 40 | func IsCollectorNotFound(err error) bool { 41 | _, ok := err.(CollectorNotFound) 42 | return ok 43 | } 44 | 45 | func (err CollectorNotFound) Error() string { 46 | return fmt.Sprintf("Collector not found: [id: %d, name: %s, secret: %s]", err.ID, err.Name, err.Secret) 47 | } 48 | -------------------------------------------------------------------------------- /internal/db/errors/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type WebhookNotFound struct { 22 | ID int64 23 | } 24 | 25 | func IsWebhookNotFound(err error) bool { 26 | _, ok := err.(WebhookNotFound) 27 | return ok 28 | } 29 | 30 | func (err WebhookNotFound) Error() string { 31 | return fmt.Sprintf("Webhook not found: [id: %d]", err.ID) 32 | } 33 | -------------------------------------------------------------------------------- /internal/db/models.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package db 16 | 17 | import ( 18 | "fmt" 19 | 20 | _ "github.com/go-sql-driver/mysql" 21 | "github.com/jinzhu/gorm" 22 | log "unknwon.dev/clog/v2" 23 | 24 | "unknwon.dev/orbiter/internal/setting" 25 | ) 26 | 27 | type Engine struct { 28 | *gorm.DB 29 | } 30 | 31 | var x *Engine 32 | 33 | func init() { 34 | db, err := gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true", 35 | setting.Database.User, setting.Database.Password, setting.Database.Host, setting.Database.Name)) 36 | if err != nil { 37 | log.Fatal("Fail to open database: %v", err) 38 | } 39 | x = &Engine{db} 40 | 41 | x.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(new(Collector), new(Webhook), new(Application)) 42 | } 43 | 44 | func IsRecordNotFound(err error) bool { 45 | return err == gorm.ErrRecordNotFound 46 | } 47 | -------------------------------------------------------------------------------- /internal/db/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package db 16 | 17 | import ( 18 | "time" 19 | 20 | log "unknwon.dev/clog/v2" 21 | 22 | "unknwon.dev/orbiter/internal/db/errors" 23 | ) 24 | 25 | // Webhook represents a history record of webhook. 26 | type Webhook struct { 27 | ID int64 28 | CollectorID int64 `sql:"index"` 29 | Owner string 30 | RepoName string 31 | EventType string 32 | Sender string 33 | Payload string `sql:"type:text"` 34 | Created int64 35 | } 36 | 37 | func (w *Webhook) CreatedTime() time.Time { 38 | return time.Unix(0, w.Created) 39 | } 40 | 41 | func NewWebhook(webhook *Webhook) error { 42 | webhook.Created = time.Now().UTC().UnixNano() 43 | return x.Create(webhook).Error 44 | } 45 | 46 | func GetWebhookByID(id int64) (*Webhook, error) { 47 | webhook := new(Webhook) 48 | err := x.First(webhook, id).Error 49 | if IsRecordNotFound(err) { 50 | return nil, errors.WebhookNotFound{id} 51 | } else if err != nil { 52 | return nil, err 53 | } 54 | return webhook, nil 55 | } 56 | 57 | type QueryWebhookOptions struct { 58 | CollectorID int64 59 | Owner string 60 | RepoName string 61 | EventType string 62 | Sender string 63 | After int64 64 | Limit int64 65 | Order string 66 | } 67 | 68 | func QueryWebhooks(opts QueryWebhookOptions) ([]*Webhook, error) { 69 | db := x.Where("created > ?", opts.After) 70 | if opts.CollectorID > 0 { 71 | db = db.Where("collector_id = ?", opts.CollectorID) 72 | } 73 | if len(opts.Owner) > 0 { 74 | db = db.Where("owner = ?", opts.Owner) 75 | } 76 | if len(opts.RepoName) > 0 { 77 | db = db.Where("repo_name = ?", opts.RepoName) 78 | } 79 | if len(opts.EventType) > 0 { 80 | db = db.Where("event_type = ?", opts.EventType) 81 | } 82 | if len(opts.Sender) > 0 { 83 | db = db.Where("sender = ?", opts.Sender) 84 | } 85 | if opts.Limit > 0 { 86 | db = db.Limit(opts.Limit) 87 | } 88 | if len(opts.Order) > 0 { 89 | db = db.Order(opts.Order) 90 | } 91 | 92 | webhooks := make([]*Webhook, 0, 10) 93 | return webhooks, db.Find(&webhooks).Error 94 | } 95 | 96 | func CountWebhook() int64 { 97 | var count int64 98 | if err := x.Model(new(Webhook)).Count(&count).Error; err != nil { 99 | log.Error("CountWebhook: %v", err) 100 | } 101 | return count 102 | } 103 | -------------------------------------------------------------------------------- /internal/form/form.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package form 16 | 17 | import ( 18 | "reflect" 19 | 20 | "github.com/flamego/binding" 21 | "github.com/flamego/template" 22 | "github.com/flamego/validator" 23 | ) 24 | 25 | // AssignForm assign form values back to the template data. 26 | func AssignForm(form interface{}, data map[string]interface{}) { 27 | typ := reflect.TypeOf(form) 28 | val := reflect.ValueOf(form) 29 | 30 | if typ.Kind() == reflect.Ptr { 31 | typ = typ.Elem() 32 | val = val.Elem() 33 | } 34 | 35 | for i := 0; i < typ.NumField(); i++ { 36 | field := typ.Field(i) 37 | 38 | fieldName := field.Tag.Get("form") 39 | // Allow ignored fields in the struct 40 | if fieldName == "-" { 41 | continue 42 | } else if len(fieldName) == 0 { 43 | fieldName = field.Name 44 | } 45 | 46 | data[fieldName] = val.Field(i).Interface() 47 | } 48 | } 49 | 50 | func Validate(errs binding.Errors, data template.Data, f interface{}) { 51 | AssignForm(f, data) 52 | 53 | var validationErrs validator.ValidationErrors 54 | for _, err := range errs { 55 | if err.Category == binding.ErrorCategoryValidation { 56 | validationErrs = err.Err.(validator.ValidationErrors) 57 | break 58 | } 59 | } 60 | if len(validationErrs) == 0 { 61 | return 62 | } 63 | 64 | typ := reflect.TypeOf(f) 65 | val := reflect.ValueOf(f) 66 | 67 | if typ.Kind() == reflect.Ptr { 68 | typ = typ.Elem() 69 | val = val.Elem() 70 | } 71 | 72 | for i := 0; i < typ.NumField(); i++ { 73 | field := typ.Field(i) 74 | 75 | fieldName := field.Tag.Get("form") 76 | // Allow ignored fields in the struct 77 | if fieldName == "-" { 78 | continue 79 | } 80 | 81 | if validationErrs[0].StructField() == field.Name { 82 | data["Err_"+field.Name] = true 83 | 84 | name := field.Tag.Get("name") 85 | 86 | switch validationErrs[0].Tag() { 87 | case "max": 88 | data["Error"] = name + " cannot contain more than " + validationErrs[0].Param() + " characters." 89 | // case binding.ERR_REQUIRED: 90 | // data["ErrorMsg"] = name + " cannot be empty." 91 | // case binding.ERR_ALPHA_DASH: 92 | // data["ErrorMsg"] = name + " must be valid alpha or numeric or dash(-_) characters." 93 | // case binding.ERR_ALPHA_DASH_DOT: 94 | // data["ErrorMsg"] = name + " must be valid alpha or numeric or dash(-_) or dot characters." 95 | // case binding.ERR_SIZE: 96 | // data["ErrorMsg"] = name + " must be size " + GetSize(field) 97 | // case binding.ERR_MIN_SIZE: 98 | // data["ErrorMsg"] = name + " must contain at least " + GetMinSize(field) + " characters." 99 | // case binding.ERR_EMAIL: 100 | // data["ErrorMsg"] = name + " is not a valid email address." 101 | // case binding.ERR_URL: 102 | // data["ErrorMsg"] = name + " is not a valid URL." 103 | // case binding.ERR_INCLUDE: 104 | // data["ErrorMsg"] = name + " must contain substring '" + GetInclude(field) + "'." 105 | default: 106 | data["Error"] = validationErrs[0].Error() 107 | } 108 | break 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /internal/route/api/v1/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/flamego/flamego" 21 | 22 | "unknwon.dev/orbiter/internal/db" 23 | "unknwon.dev/orbiter/internal/db/errors" 24 | ) 25 | 26 | type Context struct { 27 | flamego.Context 28 | App *db.Application 29 | } 30 | 31 | func Contexter() flamego.Handler { 32 | return func(c flamego.Context) { 33 | app, err := db.GetApplicationByToken(c.Query("token")) 34 | if err != nil { 35 | if errors.IsApplicationExists(err) { 36 | c.ResponseWriter().WriteHeader(http.StatusForbidden) 37 | } else { 38 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 39 | } 40 | return 41 | } 42 | 43 | ctx := &Context{ 44 | Context: c, 45 | App: app, 46 | } 47 | c.Map(ctx) 48 | } 49 | } 50 | 51 | func RegisterRoutes(f *flamego.Flame) { 52 | f.Group("/v1", func() { 53 | f.Get("/webhooks", ListWebhooks) 54 | }, Contexter()) 55 | } 56 | -------------------------------------------------------------------------------- /internal/route/api/v1/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package v1 16 | 17 | import ( 18 | "encoding/json" 19 | "net/http" 20 | 21 | "unknwon.dev/orbiter/internal/db" 22 | ) 23 | 24 | func ListWebhooks(c *Context) { 25 | webhooks, err := db.QueryWebhooks(db.QueryWebhookOptions{ 26 | CollectorID: c.QueryInt64("collector_id"), 27 | Owner: c.Query("owner"), 28 | RepoName: c.Query("repo_name"), 29 | EventType: c.Query("event_type"), 30 | Sender: c.Query("sender"), 31 | After: c.QueryInt64("after"), 32 | Limit: c.QueryInt64("limit"), 33 | }) 34 | if err != nil { 35 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 36 | return 37 | } 38 | 39 | c.ResponseWriter().Header().Set("Content-Type", "application/json; charset=utf-8") 40 | c.ResponseWriter().WriteHeader(http.StatusOK) 41 | _ = json.NewEncoder(c.ResponseWriter()).Encode(webhooks) 42 | } 43 | -------------------------------------------------------------------------------- /internal/route/application.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/flamego/binding" 22 | "github.com/flamego/flamego" 23 | "github.com/flamego/template" 24 | 25 | "unknwon.dev/orbiter/internal/db" 26 | "unknwon.dev/orbiter/internal/db/errors" 27 | "unknwon.dev/orbiter/internal/form" 28 | ) 29 | 30 | // GET /applications 31 | func Applications(w http.ResponseWriter, t template.Template, data template.Data) { 32 | data["Title"] = "Applications" 33 | data["PageIsApplication"] = true 34 | 35 | apps, err := db.ListApplications() 36 | if err != nil { 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | data["Applications"] = apps 41 | 42 | t.HTML(http.StatusOK, "application/list") 43 | } 44 | 45 | // GET /applications/new 46 | func NewApplication(t template.Template, data template.Data) { 47 | data["Title"] = "New Application" 48 | data["PageIsApplication"] = true 49 | t.HTML(http.StatusOK, "application/new") 50 | } 51 | 52 | type NewApplicationForm struct { 53 | Name string `form:"name" validate:"required,max=30" name:"Application name"` 54 | } 55 | 56 | // POST /applications/new 57 | func NewApplicationPost(c flamego.Context, t template.Template, data template.Data, errs binding.Errors, f NewApplicationForm) { 58 | data["Title"] = "New Application" 59 | data["PageIsApplication"] = true 60 | 61 | if len(errs) > 0 { 62 | form.Validate(errs, data, f) 63 | t.HTML(http.StatusOK, "application/new") 64 | return 65 | } 66 | 67 | app, err := db.NewApplication(f.Name) 68 | if err != nil { 69 | if errors.IsApplicationExists(err) { 70 | form.AssignForm(f, data) 71 | data["Error"] = "Application name has been used." 72 | data["Err_Name"] = true 73 | t.HTML(http.StatusOK, "application/new") 74 | } else { 75 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 76 | } 77 | return 78 | } 79 | 80 | c.Redirect(fmt.Sprintf("/applications/%d", app.ID)) 81 | } 82 | 83 | func parseApplicationByID(c flamego.Context) *db.Application { 84 | app, err := db.GetApplicationByID(c.ParamInt64("id")) 85 | if err != nil { 86 | if errors.IsApplicationNotFound(err) { 87 | http.NotFound(c.ResponseWriter(), c.Request().Request) 88 | } else { 89 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 90 | } 91 | return nil 92 | } 93 | return app 94 | } 95 | 96 | // GET /applications/{id} 97 | func EditApplication(c flamego.Context, t template.Template, data template.Data) { 98 | data["Title"] = "Edit Application" 99 | data["PageIsApplication"] = true 100 | 101 | data["Application"] = parseApplicationByID(c) 102 | if c.ResponseWriter().Written() { 103 | return 104 | } 105 | 106 | t.HTML(http.StatusOK, "application/edit") 107 | } 108 | 109 | // POST /applications/{id} 110 | func EditApplicationPost(c flamego.Context, t template.Template, data template.Data, f NewApplicationForm) { 111 | data["Title"] = "Edit Application" 112 | data["PageIsApplication"] = true 113 | 114 | app := parseApplicationByID(c) 115 | if c.ResponseWriter().Written() { 116 | return 117 | } 118 | data["Application"] = app 119 | 120 | app.Name = f.Name 121 | if err := db.UpdateApplication(app); err != nil { 122 | if errors.IsApplicationExists(err) { 123 | data["Error"] = "Application name has been used." 124 | data["Err_Name"] = true 125 | t.HTML(http.StatusOK, "application/edit") 126 | } else { 127 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 128 | } 129 | return 130 | } 131 | 132 | c.Redirect(fmt.Sprintf("/applications/%d", app.ID)) 133 | } 134 | 135 | // POST /applications/{id}/regenerate_token 136 | func RegenerateApplicationSecret(c flamego.Context) { 137 | id := c.ParamInt64("id") 138 | if err := db.RegenerateApplicationToken(id); err != nil { 139 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 140 | return 141 | } 142 | 143 | c.Redirect(fmt.Sprintf("/applications/%d", id)) 144 | } 145 | 146 | // POST /applications/{id}/delete 147 | func DeleteApplication(c flamego.Context) { 148 | if err := db.DeleteApplicationByID(c.ParamInt64("id")); err != nil { 149 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 150 | return 151 | } 152 | 153 | c.Redirect("/applications") 154 | } 155 | -------------------------------------------------------------------------------- /internal/route/collector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/flamego/binding" 22 | "github.com/flamego/flamego" 23 | "github.com/flamego/template" 24 | 25 | "unknwon.dev/orbiter/internal/db" 26 | "unknwon.dev/orbiter/internal/db/errors" 27 | "unknwon.dev/orbiter/internal/form" 28 | ) 29 | 30 | // GET /collectors 31 | func Collectors(w http.ResponseWriter, t template.Template, data template.Data) { 32 | data["Title"] = "Collectors" 33 | data["PageIsCollector"] = true 34 | 35 | collectors, err := db.ListCollectors() 36 | if err != nil { 37 | http.Error(w, err.Error(), http.StatusInternalServerError) 38 | return 39 | } 40 | data["Collectors"] = collectors 41 | 42 | t.HTML(http.StatusOK, "collector/list") 43 | } 44 | 45 | // GET /collectors/new 46 | func NewCollector(t template.Template, data template.Data) { 47 | data["Title"] = "New Collector" 48 | data["PageIsCollector"] = true 49 | t.HTML(http.StatusOK, "collector/new") 50 | } 51 | 52 | type NewCollectorForm struct { 53 | Name string `form:"name" validate:"required,max=30" name:"Collector name"` 54 | } 55 | 56 | // POST /collectors/new 57 | func NewCollectorPost(c flamego.Context, t template.Template, data template.Data, errs binding.Errors, f NewCollectorForm) { 58 | data["Title"] = "New Collector" 59 | data["PageIsCollector"] = true 60 | 61 | if len(errs) > 0 { 62 | form.Validate(errs, data, f) 63 | t.HTML(http.StatusOK, "collector/new") 64 | return 65 | } 66 | 67 | collector, err := db.NewCollector(f.Name, db.CollectTypeGitHub) 68 | if err != nil { 69 | if errors.IsCollectorExists(err) { 70 | form.AssignForm(f, data) 71 | data["Error"] = "Collector name has been used." 72 | data["Err_Name"] = true 73 | t.HTML(http.StatusOK, "collector/new") 74 | } else { 75 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 76 | } 77 | return 78 | } 79 | 80 | c.Redirect(fmt.Sprintf("/collectors/%d", collector.ID)) 81 | } 82 | 83 | func parseCollectorByID(c flamego.Context) *db.Collector { 84 | collector, err := db.GetCollectorByID(c.ParamInt64("id")) 85 | if err != nil { 86 | if errors.IsCollectorNotFound(err) { 87 | http.NotFound(c.ResponseWriter(), c.Request().Request) 88 | } else { 89 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 90 | } 91 | return nil 92 | } 93 | return collector 94 | } 95 | 96 | // GET /collectors/{id} 97 | func EditCollector(c flamego.Context, t template.Template, data template.Data) { 98 | data["Title"] = "Edit Collector" 99 | data["PageIsCollector"] = true 100 | 101 | data["Collector"] = parseCollectorByID(c) 102 | if c.ResponseWriter().Written() { 103 | return 104 | } 105 | 106 | t.HTML(http.StatusOK, "collector/edit") 107 | } 108 | 109 | // POST /collectors/{id} 110 | func EditCollectorPost(c flamego.Context, t template.Template, data template.Data, f NewCollectorForm) { 111 | data["Title"] = "Edit Collector" 112 | data["PageIsCollector"] = true 113 | 114 | collector := parseCollectorByID(c) 115 | if c.ResponseWriter().Written() { 116 | return 117 | } 118 | data["Collector"] = collector 119 | 120 | collector.Name = f.Name 121 | if err := db.UpdateCollector(collector); err != nil { 122 | if errors.IsCollectorExists(err) { 123 | data["Error"] = "Collector name has been used." 124 | data["Err_Name"] = true 125 | t.HTML(http.StatusOK, "collector/edit") 126 | } else { 127 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 128 | } 129 | return 130 | } 131 | 132 | c.Redirect(fmt.Sprintf("/collectors/%d", collector.ID)) 133 | } 134 | 135 | // POST /collectors/{id}/regenerate_token 136 | func RegenerateCollectorSecret(c flamego.Context) { 137 | id := c.ParamInt64("id") 138 | if err := db.RegenerateCollectorSecret(id); err != nil { 139 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 140 | return 141 | } 142 | 143 | c.Redirect(fmt.Sprintf("/collectors/%d", id)) 144 | } 145 | 146 | // POST /collectors/{id}/delete 147 | func DeleteCollector(c flamego.Context) { 148 | if err := db.DeleteCollectorByID(c.ParamInt64("id")); err != nil { 149 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 150 | return 151 | } 152 | 153 | c.Redirect("/collectors") 154 | } 155 | -------------------------------------------------------------------------------- /internal/route/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/flamego/template" 21 | ) 22 | 23 | // GET /config 24 | func Config(t template.Template, data template.Data) { 25 | data["Title"] = "Configuration" 26 | data["PageIsConfig"] = true 27 | t.HTML(http.StatusOK, "config") 28 | } 29 | -------------------------------------------------------------------------------- /internal/route/dashboard.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/flamego/template" 21 | 22 | "unknwon.dev/orbiter/internal/db" 23 | ) 24 | 25 | // GET / 26 | func Dashboard(t template.Template, data template.Data) { 27 | data["Title"] = "Dashboard" 28 | data["PageIsDashboard"] = true 29 | data["NumWebhooks"] = db.CountWebhook() 30 | t.HTML(http.StatusOK, "dashboard") 31 | } 32 | -------------------------------------------------------------------------------- /internal/route/hook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/flamego/flamego" 21 | 22 | "unknwon.dev/orbiter/internal/db" 23 | "unknwon.dev/orbiter/internal/db/errors" 24 | "unknwon.dev/orbiter/internal/tool" 25 | "unknwon.dev/orbiter/internal/webhook" 26 | ) 27 | 28 | // POST /hook 29 | func Hook(c flamego.Context) { 30 | collector, err := db.GetCollectorBySecret(c.Query("secret")) 31 | if err != nil { 32 | if errors.IsCollectorNotFound(err) { 33 | c.ResponseWriter().WriteHeader(http.StatusForbidden) 34 | } else { 35 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 36 | } 37 | return 38 | } 39 | 40 | payload, err := c.Request().Body().Bytes() 41 | if err != nil { 42 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 43 | return 44 | } 45 | 46 | // NOTE: Currently only support GitHub 47 | event, err := webhook.ParseGitHubEvent(payload) 48 | if err != nil { 49 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 50 | return 51 | } 52 | 53 | if err = db.NewWebhook(&db.Webhook{ 54 | CollectorID: collector.ID, 55 | Owner: tool.FirstNonEmptyString(event.Repository.Owner.Login, event.Repository.Owner.Name), 56 | RepoName: event.Repository.Name, 57 | EventType: c.Request().Header.Get("X-GitHub-Event"), 58 | Sender: event.Sender.Login, 59 | Payload: string(payload), 60 | }); err != nil { 61 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 62 | return 63 | } 64 | 65 | c.ResponseWriter().WriteHeader(http.StatusAccepted) 66 | } 67 | -------------------------------------------------------------------------------- /internal/route/webhook.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "net/http" 21 | 22 | "github.com/flamego/flamego" 23 | "github.com/flamego/template" 24 | 25 | "unknwon.dev/orbiter/internal/db" 26 | "unknwon.dev/orbiter/internal/db/errors" 27 | ) 28 | 29 | // GET /webhooks 30 | func Webhooks(w http.ResponseWriter, t template.Template, data template.Data) { 31 | data["Title"] = "Webhooks" 32 | data["PageIsWebhook"] = true 33 | 34 | webhooks, err := db.QueryWebhooks( 35 | db.QueryWebhookOptions{ 36 | Limit: 50, 37 | Order: "created desc", 38 | }, 39 | ) 40 | if err != nil { 41 | http.Error(w, err.Error(), http.StatusInternalServerError) 42 | return 43 | } 44 | data["Webhooks"] = webhooks 45 | 46 | t.HTML(http.StatusOK, "webhook/list") 47 | } 48 | 49 | // GET /webhooks/{id} 50 | func ViewWebhook(c flamego.Context, t template.Template, data template.Data) { 51 | data["Title"] = "View Webhook" 52 | data["PageIsWebhook"] = true 53 | data["RequireHighlightJS"] = true 54 | 55 | webhook, err := db.GetWebhookByID(c.ParamInt64("id")) 56 | if err != nil { 57 | if errors.IsWebhookNotFound(err) { 58 | http.NotFound(c.ResponseWriter(), c.Request().Request) 59 | } else { 60 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 61 | } 62 | return 63 | } 64 | data["Webhook"] = webhook 65 | 66 | // Prettify JSON in case it is not. 67 | var buf bytes.Buffer 68 | err = json.Indent(&buf, []byte(webhook.Payload), "", " ") 69 | if err != nil { 70 | http.Error(c.ResponseWriter(), err.Error(), http.StatusInternalServerError) 71 | return 72 | } 73 | webhook.Payload = buf.String() 74 | 75 | t.HTML(http.StatusOK, "webhook/view") 76 | } 77 | -------------------------------------------------------------------------------- /internal/setting/setting.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package setting 16 | 17 | import ( 18 | "gopkg.in/ini.v1" 19 | log "unknwon.dev/clog/v2" 20 | 21 | "unknwon.dev/orbiter/internal/tool" 22 | ) 23 | 24 | var ( 25 | Version = "dev" 26 | 27 | HTTPPort int 28 | 29 | BasicAuth struct { 30 | User string 31 | Password string 32 | } 33 | 34 | Database struct { 35 | Host string 36 | Name string 37 | User string 38 | Password string 39 | } 40 | 41 | Cfg *ini.File 42 | ) 43 | 44 | func init() { 45 | var err error 46 | Cfg, err = ini.Load("conf/app.ini") 47 | if err != nil { 48 | log.Fatal("Fail to load configuration: %v", err) 49 | } 50 | if tool.IsFile("custom/app.ini") { 51 | if err = Cfg.Append("custom/app.ini"); err != nil { 52 | log.Fatal("Fail to load custom configuration: %v", err) 53 | } 54 | } 55 | Cfg.NameMapper = ini.SnackCase 56 | 57 | HTTPPort = Cfg.Section("").Key("HTTP_PORT").MustInt(8085) 58 | if err = Cfg.Section("basic_auth").MapTo(&BasicAuth); err != nil { 59 | log.Fatal("Fail to map section 'basic_auth': %v", err) 60 | } else if err = Cfg.Section("database").MapTo(&Database); err != nil { 61 | log.Fatal("Fail to map section 'database': %v", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/templateutil/template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package templateutil 16 | 17 | import ( 18 | "html/template" 19 | "time" 20 | 21 | "github.com/dustin/go-humanize" 22 | 23 | "unknwon.dev/orbiter/internal/setting" 24 | ) 25 | 26 | func NewFuncMap() []template.FuncMap { 27 | return []template.FuncMap{map[string]interface{}{ 28 | "Version": func() string { 29 | return setting.Version 30 | }, 31 | "Year": func() int { 32 | return time.Now().Year() 33 | }, 34 | "DateFmtShort": func(t time.Time) string { 35 | return t.Format("Jan 02, 2006") 36 | }, 37 | "DateFmtLong": func(t time.Time) string { 38 | return t.Format(time.RFC1123Z) 39 | }, 40 | "TimeFmtShort": func(t time.Time) string { 41 | return t.Format("15:04:05") 42 | }, 43 | "NumCommas": humanize.Comma, 44 | }} 45 | } 46 | -------------------------------------------------------------------------------- /internal/tool/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package tool 16 | 17 | // FirstNonEmptyString returns the value of first string 18 | // that is not empty in the list. 19 | // If all strings are empty, it returns an empty result. 20 | func FirstNonEmptyString(strs ...string) string { 21 | for i := range strs { 22 | if len(strs[i]) > 0 { 23 | return strs[i] 24 | } 25 | } 26 | return "" 27 | } 28 | -------------------------------------------------------------------------------- /internal/tool/tool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package tool 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "os" 21 | 22 | "github.com/satori/go.uuid" 23 | ) 24 | 25 | // EncodeSHA1 encodes string to SHA1 hex value. 26 | func EncodeSHA1(str string) string { 27 | h := sha1.New() 28 | h.Write([]byte(str)) 29 | return hex.EncodeToString(h.Sum(nil)) 30 | } 31 | 32 | // NewSecretToken generates and returns a random secret token based on SHA1. 33 | func NewSecretToken() string { 34 | return EncodeSHA1(uuid.NewV4().String()) 35 | } 36 | 37 | // IsFile returns true if given path exists as a file (i.e. not a directory). 38 | func IsFile(path string) bool { 39 | f, e := os.Stat(path) 40 | if e != nil { 41 | return false 42 | } 43 | return !f.IsDir() 44 | } 45 | -------------------------------------------------------------------------------- /internal/webhook/github.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package webhook 16 | 17 | import ( 18 | "encoding/json" 19 | ) 20 | 21 | type GitHubUser struct { 22 | // Sometimes we have 'login', other times we have 'name'. 23 | Login string `json:"login"` 24 | Name string `json:"name"` 25 | } 26 | 27 | type GitHubRepository struct { 28 | Name string `json:"name"` 29 | Owner *GitHubUser `json:"owner"` 30 | } 31 | 32 | type GitHubEvent struct { 33 | Repository *GitHubRepository `json:"repository"` 34 | Sender *GitHubUser `json:"sender"` 35 | } 36 | 37 | func ParseGitHubEvent(payload []byte) (*GitHubEvent, error) { 38 | event := new(GitHubEvent) 39 | return event, json.Unmarshal(payload, event) 40 | } 41 | -------------------------------------------------------------------------------- /orbiter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Unknwon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Orbiter is a tool for collecting and redistributing webhooks over the network. 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "net/http" 21 | 22 | "github.com/flamego/auth" 23 | "github.com/flamego/binding" 24 | "github.com/flamego/flamego" 25 | "github.com/flamego/template" 26 | log "unknwon.dev/clog/v2" 27 | 28 | "unknwon.dev/orbiter/internal/route" 29 | apiv1 "unknwon.dev/orbiter/internal/route/api/v1" 30 | "unknwon.dev/orbiter/internal/setting" 31 | "unknwon.dev/orbiter/internal/templateutil" 32 | ) 33 | 34 | func main() { 35 | if err := log.NewConsole(); err != nil { 36 | panic("error init logger: " + err.Error()) 37 | } 38 | defer log.Stop() 39 | 40 | log.Info("Orbiter: %s", setting.Version) 41 | 42 | f := flamego.Classic() 43 | f.Use(template.Templater( 44 | template.Options{ 45 | FuncMaps: templateutil.NewFuncMap(), 46 | }, 47 | )) 48 | 49 | f.Group("", 50 | func() { 51 | f.Get("/", route.Dashboard) 52 | 53 | f.Group("/collectors", func() { 54 | f.Get("", route.Collectors) 55 | f.Combo("/new"). 56 | Get(route.NewCollector). 57 | Post(binding.Form(route.NewCollectorForm{}), route.NewCollectorPost) 58 | f.Group("/{id}", func() { 59 | f.Combo(""). 60 | Get(route.EditCollector). 61 | Post(binding.Form(route.NewCollectorForm{}), route.EditCollectorPost) 62 | f.Post("/regenerate_token", route.RegenerateCollectorSecret) 63 | f.Post("/delete", route.DeleteCollector) 64 | }) 65 | }) 66 | 67 | f.Group("/applications", func() { 68 | f.Get("", route.Applications) 69 | f.Combo("/new"). 70 | Get(route.NewApplication). 71 | Post(binding.Form(route.NewApplicationForm{}), route.NewApplicationPost) 72 | f.Group("/{id}", func() { 73 | f.Combo(""). 74 | Get(route.EditApplication). 75 | Post(binding.Form(route.NewApplicationForm{}), route.EditApplicationPost) 76 | f.Post("/regenerate_token", route.RegenerateApplicationSecret) 77 | f.Post("/delete", route.DeleteApplication) 78 | }) 79 | }) 80 | 81 | f.Group("/webhooks", func() { 82 | f.Get("", route.Webhooks) 83 | f.Get("/{id}", route.ViewWebhook) 84 | }) 85 | 86 | f.Get("/config", route.Config) 87 | }, 88 | auth.Basic(setting.BasicAuth.User, setting.BasicAuth.Password), 89 | ) 90 | 91 | f.Post("/hook", route.Hook) 92 | 93 | f.Group("/api", func() { 94 | apiv1.RegisterRoutes(f) 95 | }) 96 | 97 | listenAddr := fmt.Sprintf("0.0.0.0:%d", setting.HTTPPort) 98 | log.Info("Listening on http://%s...", listenAddr) 99 | log.Fatal("Failed to start server: %v", http.ListenAndServe(listenAddr, f)) 100 | } 101 | -------------------------------------------------------------------------------- /public/assets/AdminLTE-2.3.2/css/skins/skin-blue.min.css: -------------------------------------------------------------------------------- 1 | .skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} -------------------------------------------------------------------------------- /public/assets/AdminLTE-2.3.2/js/app.min.js: -------------------------------------------------------------------------------- 1 | /*! AdminLTE app.js 2 | * ================ 3 | * Main JS application file for AdminLTE v2. This file 4 | * should be included in all pages. It controls some layout 5 | * options and implements exclusive AdminLTE plugins. 6 | * 7 | * @Author Almsaeed Studio 8 | * @Support 9 | * @Email 10 | * @version 2.3.2 11 | * @license MIT 12 | */ 13 | function _init(){"use strict";$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){var a=$(".main-header").outerHeight()+$(".main-footer").outerHeight(),b=$(window).height(),c=$(".sidebar").height();if($("body").hasClass("fixed"))$(".content-wrapper, .right-side").css("min-height",b-$(".main-footer").outerHeight());else{var d;b>=c?($(".content-wrapper, .right-side").css("min-height",b-a),d=b-a):($(".content-wrapper, .right-side").css("min-height",c),d=c);var e=$($.AdminLTE.options.controlSidebarOptions.selector);"undefined"!=typeof e&&e.height()>d&&$(".content-wrapper, .right-side").css("min-height",e.height())}},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimscroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu={activate:function(a){var b=$.AdminLTE.options.screenSizes;$(document).on("click",a,function(a){a.preventDefault(),$(window).width()>b.sm-1?$("body").hasClass("sidebar-collapse")?$("body").removeClass("sidebar-collapse").trigger("expanded.pushMenu"):$("body").addClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").hasClass("sidebar-open")?$("body").removeClass("sidebar-open").removeClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").addClass("sidebar-open").trigger("expanded.pushMenu")}),$(".content-wrapper").click(function(){$(window).width()<=b.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")}),($.AdminLTE.options.sidebarExpandOnHover||$("body").hasClass("fixed")&&$("body").hasClass("sidebar-mini"))&&this.expandOnHover()},expandOnHover:function(){var a=this,b=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>b&&a.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>b&&a.collapse()})},expand:function(){$("body").removeClass("sidebar-collapse").addClass("sidebar-expanded-on-hover")},collapse:function(){$("body").hasClass("sidebar-expanded-on-hover")&&$("body").removeClass("sidebar-expanded-on-hover").addClass("sidebar-collapse")}},$.AdminLTE.tree=function(a){var b=this,c=$.AdminLTE.options.animationSpeed;$(a).on("click","li a",function(a){var d=$(this),e=d.next();if(e.is(".treeview-menu")&&e.is(":visible")&&!$("body").hasClass("sidebar-collapse"))e.slideUp(c,function(){e.removeClass("menu-open")}),e.parent("li").removeClass("active");else if(e.is(".treeview-menu")&&!e.is(":visible")){var f=d.parents("ul").first(),g=f.find("ul:visible").slideUp(c);g.removeClass("menu-open");var h=d.parent("li");e.slideDown(c,function(){e.addClass("menu-open"),f.find("li.active").removeClass("active"),h.addClass("active"),b.layout.fix()})}e.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var a=this,b=$.AdminLTE.options.controlSidebarOptions,c=$(b.selector),d=$(b.toggleBtnSelector);d.on("click",function(d){d.preventDefault(),c.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?a.close(c,b.slide):a.open(c,b.slide)});var e=$(".control-sidebar-bg");a._fix(e),$("body").hasClass("fixed")?a._fixForFixed(c):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");c.hasClass("collapsed-box")?(a.children(":first").removeClass(b.icons.open).addClass(b.icons.collapse),d.slideDown(b.animationSpeed,function(){c.removeClass("collapsed-box")})):(a.children(":first").removeClass(b.icons.collapse).addClass(b.icons.open),d.slideUp(b.animationSpeed,function(){c.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp(this.animationSpeed)}}}if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",animationSpeed:500,sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,sidebarExpandOnHover:!1,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!0,enableControlSidebar:!0,controlSidebarOptions:{toggleBtnSelector:"[data-toggle='control-sidebar']",selector:".control-sidebar",slide:!0},enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa-minus",open:"fa-plus",remove:"fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){"use strict";$("body").removeClass("hold-transition"),"undefined"!=typeof AdminLTEOptions&&$.extend(!0,$.AdminLTE.options,AdminLTEOptions);var a=$.AdminLTE.options;_init(),$.AdminLTE.layout.activate(),$.AdminLTE.tree(".sidebar"),a.enableControlSidebar&&$.AdminLTE.controlSidebar.activate(),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:a.navbarMenuHeight,alwaysVisible:!1,size:a.navbarMenuSlimscrollWidth}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu.activate(a.sidebarToggleSelector),a.enableBSToppltip&&$("body").tooltip({selector:a.BSTooltipSelector}),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(document).on("click",a.directChat.contactToggleSelector,function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").on("click",function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),function(a){"use strict";a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(a){return a},onLoadDone:function(a){return a}},b),f=a('
');return this.each(function(){if(""===e.source)return void(window.console&&window.console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.on("click",function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){"use strict";a.fn.activateBox=function(){a.AdminLTE.boxWidget.activate(this)},a.fn.toggleBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.collapse,this);a.AdminLTE.boxWidget.collapse(b)},a.fn.removeBox=function(){var b=a(a.AdminLTE.boxWidget.selectors.remove,this);a.AdminLTE.boxWidget.remove(b)}}(jQuery),function(a){"use strict";a.fn.todolist=function(b){var c=a.extend({onCheck:function(a){return a},onUncheck:function(a){return a}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),a("input",b).is(":checked")?c.onCheck.call(b):c.onUncheck.call(b)})})}}(jQuery); -------------------------------------------------------------------------------- /public/assets/bootstrap-3.3.5/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} 5 | -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/assets/font-awesome-4.5.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/assets/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/img/orbiter-brand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/img/orbiter-brand.png -------------------------------------------------------------------------------- /public/img/orbiter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/orbiter/73ce5686f2c09ecdba977b1ef0e03691144f2007/public/img/orbiter.png -------------------------------------------------------------------------------- /public/plugins/highlight-9.1.0/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /templates/application/edit.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Applications 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

{{.Application.Name}}

13 |
14 |
15 |
16 | {{template "base/alert" .}} 17 |
18 | 19 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 | 34 |
35 |
36 |

Regenerate Access Token

37 |
38 |
39 |
Current access token will be invalid after regenerated, make sure to update new token to all your 40 | applications.
41 |
42 | 47 |
48 | 49 |
50 |
51 |

Delete Application

52 |
53 |
54 |
All data related to this application will be deleted permanently, and RESTful API calls for this 55 | application will no longer be accepted.
56 |
57 | 62 |
63 |
64 |
65 |
66 | {{template "base/footer" .}} 67 | -------------------------------------------------------------------------------- /templates/application/list.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Applications 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

Application List

13 |
14 | New Application 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {{range .Applications}} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {{end}} 36 | 37 |
IDNameOp.
{{.ID}}{{.Name}}
38 |
39 |
40 |
41 |
42 |
43 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/application/new.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Applications 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

New Application

13 |
14 |
15 |
16 | {{template "base/alert" .}} 17 |
18 | 19 | 21 |
22 |
23 | 24 | 27 |
28 |
29 |
30 |
31 |
32 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/base/alert.tmpl: -------------------------------------------------------------------------------- 1 | {{if .Error}} 2 |
3 | 4 | {{if .ErrorTitle}}

{{.ErrorTitle}}

{{end}} 5 | {{.Error}} 6 |
7 | {{end}} -------------------------------------------------------------------------------- /templates/base/footer.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 7 | Copyright © {{Year}} @unknwon. All rights 8 | reserved. 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | {{if .RequireHighlightJS}} 17 | 18 | 19 | 22 | {{end}} 23 | 24 | -------------------------------------------------------------------------------- /templates/base/head.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{.Title}} - Orbiter 15 | 16 | 17 | 18 |
19 |
20 | 24 | 25 | 30 |
31 | 32 |
33 | 53 |
54 | 55 |
-------------------------------------------------------------------------------- /templates/collector/edit.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Collectors 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

{{.Collector.Name}}

13 |
14 |
15 |
16 | {{template "base/alert" .}} 17 |
18 | 19 | 21 |
22 |
23 | 24 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 36 |
37 |
38 | 39 |
40 |
41 |

Regenerate Secret Token

42 |
43 |
44 |
Current secret token will be invalid after regenerated, make sure to update new token to all your 45 | applications.
46 |
47 | 52 |
53 | 54 |
55 |
56 |

Delete Collector

57 |
58 |
59 |
All data related to this collector will be deleted permanently, and incoming webhooks for this collector 60 | will no longer be accepted.
61 |
62 | 67 |
68 |
69 |
70 |
71 | {{template "base/footer" .}} 72 | -------------------------------------------------------------------------------- /templates/collector/list.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Collectors 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

Collector List

13 |
14 | New Collector 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{range .Collectors}} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{end}} 38 | 39 |
IDNameOp.
{{.ID}}{{.Name}}
40 |
41 |
42 |
43 |
44 |
45 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/collector/new.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Collectors 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

New Collector

13 |
14 |
15 |
16 | {{template "base/alert" .}} 17 |
18 | 19 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 31 |
32 |
33 |
34 |
35 |
36 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/config.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 | 3 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/dashboard.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Dashboard 5 |

6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 | Webhooks 14 | {{NumCommas .NumWebhooks}} 15 |
16 |
17 |
18 |
19 |
20 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/status/404.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |
4 |

404

5 | 6 |
7 |
8 |

Oops! Page not found.

9 |

10 | We could not find the page you were looking for. 11 | Meanwhile, you may return to dashboard or try using the search form. 12 |

13 |
14 |
15 |
16 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/status/500.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |
4 |

500

5 | 6 |
7 |
8 |

Oops! Something went wrong.

9 |

10 | We will work on fixing that right away. 11 | Meanwhile, you may return to dashboard or try using the search form. 12 |

13 | 14 |
15 |
16 |
17 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/webhook/list.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Webhooks 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

Recent History

13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {{range .Webhooks}} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | {{end}} 42 | 43 |
IDRepositoryEvent TypeCreatedOp.
{{.ID}}{{.RepoName}}{{.EventType}}{{TimeFmtShort .CreatedTime}} 38 | 39 |
44 |
45 |
46 |
47 |
48 |
49 | {{template "base/footer" .}} -------------------------------------------------------------------------------- /templates/webhook/view.tmpl: -------------------------------------------------------------------------------- 1 | {{template "base/head" .}} 2 |
3 |

4 | Webhooks 5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 |

ID: {{.Webhook.ID}}

13 |
14 |
15 |

Payload

16 |
{{.Webhook.Payload}}
17 |
18 |
19 |
20 |
21 |
22 | {{template "base/footer" .}} --------------------------------------------------------------------------------