diff --git a/app/app/components/section/papertrail/type-editor.js b/app/app/components/section/papertrail/type-editor.js new file mode 100644 index 00000000..622f333b --- /dev/null +++ b/app/app/components/section/papertrail/type-editor.js @@ -0,0 +1,134 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; +import NotifierMixin from '../../../mixins/notifier'; +import TooltipMixin from '../../../mixins/tooltip'; +import SectionMixin from '../../../mixins/section'; + +export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { + sectionService: Ember.inject.service('section'), + isDirty: false, + waiting: false, + authenticated: false, + config: {}, + items: {}, + + didReceiveAttrs() { + let config = {}; + + try { + config = JSON.parse(this.get('meta.config')); + } catch (e) {} + + if (is.empty(config)) { + config = { + APIToken: "", + query: "", + max: 10, + }; + } + + this.set('config', config); + + if (this.get('config.APIToken').length > 0) { + this.send('auth'); + } + }, + + willDestroyElement() { + this.destroyTooltips(); + }, + + actions: { + isDirty() { + return this.get('isDirty'); + }, + + auth() { + // missing data? + this.set('config.APIToken', this.get('config.APIToken').trim()); + + if (is.empty(this.get('config.APIToken'))) { + $("#papertrail-apitoken").addClass("error").focus(); + return; + } + + let page = this.get('page'); + let self = this; + + this.set('waiting', true); + + this.get('sectionService').fetch(page, "auth", this.get('config')) + .then(function(response) { + self.set('authenticated', true); + self.set('items', response); + self.set('waiting', false); + }, function(reason) { //jshint ignore: line + self.set('authenticated', false); + self.set('waiting', false); + + switch (reason.status) { + case 400: + self.showNotification(`Unable to connect to Papertrail`); + break; + case 403: + self.showNotification(`Unable to authenticate`); + break; + default: + self.showNotification(`Something went wrong, try again!`); + } + }); + }, + + onCancel() { + this.attrs.onCancel(); + }, + + onAction(title) { + let self = this; + let page = this.get('page'); + let meta = this.get('meta'); + page.set('title', title); + meta.set('externalSource', true); + + let config = this.get('config'); + let max = 10; + if (is.number(parseInt(config.max))) { + max = parseInt(config.max); + } + + Ember.set(config, 'max', max); + this.set('waiting', true); + + this.get('sectionService').fetch(page, "auth", this.get('config')) + .then(function(response) { + self.set('items', response); + let items = self.get('items'); + + if (items.events.length > max) { + items.events = items.events.slice(0, max); + } + + meta.set('config', JSON.stringify(config)); + meta.set('rawBody', JSON.stringify(items)); + + self.set('waiting', false); + self.attrs.onAction(page, meta); + }, function(reason) { //jshint ignore: line + self.set('authenticated', false); + self.set('waiting', false); + console.log(reason); + self.showNotification(`Something went wrong, try again!`); + }); + } + } +}); diff --git a/app/app/components/section/papertrail/type-renderer.js b/app/app/components/section/papertrail/type-renderer.js new file mode 100644 index 00000000..a5417462 --- /dev/null +++ b/app/app/components/section/papertrail/type-renderer.js @@ -0,0 +1,14 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +import Ember from 'ember'; + +export default Ember.Component.extend({}); \ No newline at end of file diff --git a/app/app/styles/app.scss b/app/app/styles/app.scss index 237930c0..b66df98b 100644 --- a/app/app/styles/app.scss +++ b/app/app/styles/app.scss @@ -37,3 +37,4 @@ @import "section/markdown.scss"; @import "section/table.scss"; @import "section/code.scss"; +@import "section/papertrail.scss"; diff --git a/app/app/styles/section/papertrail.scss b/app/app/styles/section/papertrail.scss new file mode 100644 index 00000000..00dd2f68 --- /dev/null +++ b/app/app/styles/section/papertrail.scss @@ -0,0 +1,16 @@ +.section-papertrail-table { + font-size: 12px; + width: 90% important; + + th { + font-size: 1rem; + } + + td:nth-child(2) { + font-variant: small-caps; + } + + a:hover { + text-decoration: underline; + } +} diff --git a/app/app/templates/components/section/gemini/type-editor.hbs b/app/app/templates/components/section/gemini/type-editor.hbs index f7ee622f..3cfb085d 100644 --- a/app/app/templates/components/section/gemini/type-editor.hbs +++ b/app/app/templates/components/section/gemini/type-editor.hbs @@ -1,5 +1,5 @@ {{#section/base-editor document=document folder=folder page=page busy=waiting tip="Gemini enterprise issue and ticketing software (https://www.countersoft.com)" isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}} - +
@@ -50,5 +50,5 @@
{{/if}}
- -{{/section/base-editor}} \ No newline at end of file + +{{/section/base-editor}} diff --git a/app/app/templates/components/section/papertrail/type-editor.hbs b/app/app/templates/components/section/papertrail/type-editor.hbs new file mode 100644 index 00000000..70e8656c --- /dev/null +++ b/app/app/templates/components/section/papertrail/type-editor.hbs @@ -0,0 +1,46 @@ +{{#section/base-editor document=document folder=folder page=page busy=waiting tip="Papertrail cloud logging service (https://papertrailapp.com)" isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}} + +
+
+ +
+
Papertrail Authentication
+
Provide your Papertrail API token
+
+
+ +
API Token (from your profile)
+ {{focus-input id="papertrail-apitoken" type="password" value=config.APIToken readonly=isReadonly}} +
+
Authenticate
+ +
+
+ + {{#if authenticated}} +
 
+
+
+
+
+
Log Filter
+
Determine which log entries you want to display
+
+
+ +
e.g. bob OR ("some phrase" AND sally)
+ {{input id="papertrail-query" type="text" class="mousetrap" value=config.query}} +
+
+ +
How many log entries do you want?
+ {{input id="papertrail-max" type="number" class="mousetrap" value=config.max}} +
+
+
+
+ {{/if}} + +
+ +{{/section/base-editor}} diff --git a/app/app/templates/components/section/papertrail/type-renderer.hbs b/app/app/templates/components/section/papertrail/type-renderer.hbs new file mode 100644 index 00000000..f2eac9e5 --- /dev/null +++ b/app/app/templates/components/section/papertrail/type-renderer.hbs @@ -0,0 +1 @@ +{{{page.body}}} \ No newline at end of file diff --git a/app/public/sections/papertrail.png b/app/public/sections/papertrail.png new file mode 100644 index 00000000..d383ae12 Binary files /dev/null and b/app/public/sections/papertrail.png differ diff --git a/app/public/sections/papertrail@2x.png b/app/public/sections/papertrail@2x.png new file mode 100644 index 00000000..8e50d707 Binary files /dev/null and b/app/public/sections/papertrail@2x.png differ diff --git a/documize/api/endpoint/router.go b/documize/api/endpoint/router.go index 493979ee..eeff52fb 100644 --- a/documize/api/endpoint/router.go +++ b/documize/api/endpoint/router.go @@ -30,7 +30,7 @@ const ( // AppVersion does what it says // Versioning scheme major.minor where "minor" is optional // e.g. 1, 2, 3, 4.1, 4.2, 5, 6, 7, 7.1, 8, 9, 10, ..... 127, 127.1, 128 - AppVersion = "12.8" + AppVersion = "12.9" ) var port, certFile, keyFile, forcePort2SSL string diff --git a/documize/section/papertrail/model.go b/documize/section/papertrail/model.go new file mode 100644 index 00000000..1311a255 --- /dev/null +++ b/documize/section/papertrail/model.go @@ -0,0 +1,72 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package papertrail + +import "strings" + +// the HTML that is rendered by this section. +const renderTemplate = ` +{{if .HasData}} +

The Papertrail log for query {{.Config.Query}} contains {{.Count}} entries.

+ + + + + + + + + + {{range $item := .Events}} + + + + + + {{end}} + +
DateSeverityMessage
{{ $item.Dated }}{{ $item.Severity }}{{ $item.Message }}
+{{else}} +

There are no Papertrail log entries to see.

+{{end}} +` + +// Papertrail helpers +type papertrailRender struct { + Config papertrailConfig + Events []papertrailEvent + Count int + Authenticated bool + HasData bool +} + +type papertrailSearch struct { + Events []papertrailEvent `json:"events"` +} + +type papertrailEvent struct { + ID string `json:"id"` + Dated string `json:"display_received_at"` + Message string `json:"message"` + Severity string `json:"severity"` +} + +type papertrailConfig struct { + APIToken string `json:"APIToken"` + Query string `json:"query"` + Max int `json:"max"` +} + +func (c *papertrailConfig) Clean() { + c.APIToken = strings.TrimSpace(c.APIToken) + c.Query = strings.TrimSpace(c.Query) +} diff --git a/documize/section/papertrail/papertrail.go b/documize/section/papertrail/papertrail.go new file mode 100644 index 00000000..852aed22 --- /dev/null +++ b/documize/section/papertrail/papertrail.go @@ -0,0 +1,372 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package papertrail + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + "io/ioutil" + "net/http" + "net/url" + + "github.com/documize/community/documize/section/provider" +) + +const me = "papertrail" + +// Provider represents Gemini +type Provider struct { +} + +// Meta describes us. +func (*Provider) Meta() provider.TypeMeta { + section := provider.TypeMeta{} + section.ID = "db0a3a0a-b5d4-4d00-bfac-ee28abba451d" + section.Title = "Papertrail" + section.Description = "Display log entries" + section.ContentType = "papertrail" + + return section +} + +// Render converts Papertrail data into HTML suitable for browser rendering. +func (*Provider) Render(config, data string) string { + + var search papertrailSearch + var events []papertrailEvent + var payload = papertrailRender{} + var c = papertrailConfig{} + + json.Unmarshal([]byte(data), &search) + json.Unmarshal([]byte(config), &c) + + max := len(search.Events) + if c.Max < max { + max = c.Max + } + + events = search.Events[:max] + payload.Count = len(events) + payload.HasData = payload.Count > 0 + + payload.Events = events + payload.Config = c + payload.Authenticated = c.APIToken != "" + + t := template.New("items") + t, _ = t.Parse(renderTemplate) + + buffer := new(bytes.Buffer) + t.Execute(buffer, payload) + + return buffer.String() +} + +// Command handles authentication, workspace listing and items retrieval. +func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + method := query.Get("method") + + if len(method) == 0 { + provider.WriteMessage(w, me, "missing method name") + return + } + + switch method { + case "auth": + auth(w, r) + // case "items": + // items(w, r) + } +} + +// Refresh just sends back data as-is. +func (*Provider) Refresh(config, data string) (newData string) { + + return data + // var c = geminiConfig{} + // err := json.Unmarshal([]byte(config), &c) + // + // if err != nil { + // log.Error("Unable to read Gemini config", err) + // return + // } + // + // c.Clean() + // + // if len(c.URL) == 0 { + // log.Info("Gemini.Refresh received empty URL") + // return + // } + // + // if len(c.Username) == 0 { + // log.Info("Gemini.Refresh received empty username") + // return + // } + // + // if len(c.APIKey) == 0 { + // log.Info("Gemini.Refresh received empty API key") + // return + // } + // + // req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/items/card/%d", c.URL, c.WorkspaceID), nil) + // // req.Header.Set("Content-Type", "application/json") + // + // creds := []byte(fmt.Sprintf("%s:%s", c.Username, c.APIKey)) + // req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds)) + // + // client := &http.Client{} + // res, err := client.Do(req) + // + // if err != nil { + // fmt.Println(err) + // return + // } + // + // if res.StatusCode != http.StatusOK { + // return + // } + // + // defer res.Body.Close() + // var items []geminiItem + // + // dec := json.NewDecoder(res.Body) + // err = dec.Decode(&items) + // + // if err != nil { + // fmt.Println(err) + // return + // } + // + // j, err := json.Marshal(items) + // + // if err != nil { + // log.Error("unable to marshall gemini items", err) + // return + // } + // + // newData = string(j) + // return +} + +func auth(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + if err != nil { + provider.WriteMessage(w, me, "Bad payload") + return + } + + var config = papertrailConfig{} + err = json.Unmarshal(body, &config) + + if err != nil { + provider.WriteMessage(w, me, "Bad config") + return + } + + config.Clean() + + if len(config.APIToken) == 0 { + provider.WriteMessage(w, me, "Missing API token") + return + } + + var search string + if len(config.Query) > 0 { + search = "q=" + url.QueryEscape(config.Query) + } + req, err := http.NewRequest("GET", "https://papertrailapp.com/api/v1/events/search.json?"+search, nil) + req.Header.Set("X-Papertrail-Token", config.APIToken) + + client := &http.Client{} + res, err := client.Do(req) + + if err != nil { + fmt.Println(err) + provider.WriteError(w, me, err) + return + } + + if res.StatusCode != http.StatusOK { + provider.WriteForbidden(w) + return + } + + defer res.Body.Close() + var result interface{} + + dec := json.NewDecoder(res.Body) + err = dec.Decode(&result) + + if err != nil { + fmt.Println(err) + provider.WriteError(w, me, err) + return + } + + provider.WriteJSON(w, result) +} + +// +// func workspace(w http.ResponseWriter, r *http.Request) { +// defer r.Body.Close() +// body, err := ioutil.ReadAll(r.Body) +// +// if err != nil { +// provider.WriteMessage(w, "gemini", "Bad payload") +// return +// } +// +// var config = geminiConfig{} +// err = json.Unmarshal(body, &config) +// +// if err != nil { +// provider.WriteMessage(w, "gemini", "Bad payload") +// return +// } +// +// config.Clean() +// +// if len(config.URL) == 0 { +// provider.WriteMessage(w, "gemini", "Missing URL value") +// return +// } +// +// if len(config.Username) == 0 { +// provider.WriteMessage(w, "gemini", "Missing Username value") +// return +// } +// +// if len(config.APIKey) == 0 { +// provider.WriteMessage(w, "gemini", "Missing APIKey value") +// return +// } +// +// if config.UserID == 0 { +// provider.WriteMessage(w, "gemini", "Missing UserId value") +// return +// } +// +// req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/navigationcards/users/%d", config.URL, config.UserID), nil) +// +// creds := []byte(fmt.Sprintf("%s:%s", config.Username, config.APIKey)) +// req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds)) +// +// client := &http.Client{} +// res, err := client.Do(req) +// +// if err != nil { +// fmt.Println(err) +// provider.WriteError(w, "gemini", err) +// return +// } +// +// if res.StatusCode != http.StatusOK { +// provider.WriteForbidden(w) +// return +// } +// +// defer res.Body.Close() +// var workspace interface{} +// +// dec := json.NewDecoder(res.Body) +// err = dec.Decode(&workspace) +// +// if err != nil { +// fmt.Println(err) +// provider.WriteError(w, "gemini", err) +// return +// } +// +// provider.WriteJSON(w, workspace) +// } +// +// func items(w http.ResponseWriter, r *http.Request) { +// defer r.Body.Close() +// body, err := ioutil.ReadAll(r.Body) +// +// if err != nil { +// provider.WriteMessage(w, "gemini", "Bad payload") +// return +// } +// +// var config = geminiConfig{} +// err = json.Unmarshal(body, &config) +// +// if err != nil { +// provider.WriteMessage(w, "gemini", "Bad payload") +// return +// } +// +// config.Clean() +// +// if len(config.URL) == 0 { +// provider.WriteMessage(w, "gemini", "Missing URL value") +// return +// } +// +// if len(config.Username) == 0 { +// provider.WriteMessage(w, "gemini", "Missing Username value") +// return +// } +// +// if len(config.APIKey) == 0 { +// provider.WriteMessage(w, "gemini", "Missing APIKey value") +// return +// } +// +// creds := []byte(fmt.Sprintf("%s:%s", config.Username, config.APIKey)) +// +// filter, err := json.Marshal(config.Filter) +// if err != nil { +// fmt.Println(err) +// provider.WriteError(w, "gemini", err) +// return +// } +// +// var jsonFilter = []byte(string(filter)) +// req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/items/filtered", config.URL), bytes.NewBuffer(jsonFilter)) +// req.Header.Set("Content-Type", "application/json") +// req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(creds)) +// +// client := &http.Client{} +// res, err := client.Do(req) +// +// if err != nil { +// fmt.Println(err) +// provider.WriteError(w, "gemini", err) +// return +// } +// +// if res.StatusCode != http.StatusOK { +// provider.WriteForbidden(w) +// return +// } +// +// defer res.Body.Close() +// var items interface{} +// +// dec := json.NewDecoder(res.Body) +// err = dec.Decode(&items) +// +// if err != nil { +// fmt.Println(err) +// provider.WriteError(w, "gemini", err) +// return +// } +// +// provider.WriteJSON(w, items) +// } diff --git a/documize/section/register.go b/documize/section/register.go index 23fc99f7..057e937a 100644 --- a/documize/section/register.go +++ b/documize/section/register.go @@ -22,6 +22,7 @@ import ( "github.com/documize/community/documize/section/intercom" "github.com/documize/community/documize/section/mailchimp" "github.com/documize/community/documize/section/markdown" + "github.com/documize/community/documize/section/papertrail" "github.com/documize/community/documize/section/provider" "github.com/documize/community/documize/section/salesforce" "github.com/documize/community/documize/section/stripe" @@ -43,6 +44,7 @@ func Register() { provider.Register("mailchimp", &mailchimp.Provider{}) provider.Register("markdown", &markdown.Provider{}) provider.Register("salesforce", &salesforce.Provider{}) + provider.Register("papertrail", &papertrail.Provider{}) provider.Register("stripe", &stripe.Provider{}) provider.Register("table", &table.Provider{}) provider.Register("trello", &trello.Provider{})