{table, div, th, tr, td, thead, tbody, section, h3, time} = React.DOM {map, each, unique, is-type, join} = require 'prelude-ls' compact_fields = <[ user action entities ]> extended_fields = <[ timestamp user acting_as action entities privilege ]> FieldsMixin = { fields: -> if @props.compact compact_fields else extended_fields } AuditTableHeader = React.createClass { mixins: [ FieldsMixin ] display-name: \AuditTableHeader render: -> thead {}, (tr {}, (@fields() |> map -> th key: it, it.replace \_, ' ')) } Timestamp = React.createClass { display-name: \Timestamp render: -> ts = moment(@props.time) time date-time: ts.format!, title: ts.calendar!, [ ts.from-now! ] } wrap-array = -> if is-type \Array it it else [it] export AuditEntry = React.createClass { mixins: [ FieldsMixin ] display-name: \AuditEntry transform-field: (key, value) -> switch key | \entities => [ if @props.resource? then ResourceLink data: that if @props.role? then RoleLink id: that ] | <[ user acting_as ]> => RoleLink {id: value} if value? | \timestamp => Timestamp {time: value} if value? | _ => value render: -> tr class-name: @props.action, @fields() |> map ~> td key: it, ...wrap-array(@transform-field(it, @props[it])) } # compare events by id to prevent duplicates # reverse order to push newest items on top new-event-set = -> evts = new SortedSet comparator: (a, b) -> a && b && (b.id - a.id) evts.contains-like = (item) -> existing = @find-iterator(item).value! @priv.comparator(existing, item) == 0 if existing? evts export AuditTable = React.createClass { display-name: \AuditTable get-initial-state: -> events: new-event-set! render: -> compact = @props.compact section class-name: \audit, [ h3 {}, @props.caption table class-name: \audit-table, [ AuditTableHeader(key: \thead, compact: compact), tbody key: \tbody, @state.events.map -> new AuditEntry it with key: it.id, compact: compact ] ] component-did-mount: -> @props.src |> wrap-array |> each @add-source component-will-unmount: -> @sources |> each -> console.log "closing event source ", it it.close! add-event: ({data}) -> event = JSON.parse data if event.action == "check" && event.privilege == "read" && event.allowed # pass true else unless @state.events.contains-like event # console.log event @state.events.insert event @force-update! add-source: (url) -> console.log "opening eventsource to #url" evt-src = new EventSource url console.log evt-src evt-src.onmessage = @add-event evt-src.onerror = (a, b, c, d) -> console.log a, b, c, d @[]sources.push evt-src } export GlobalAudit = React.createClass { display-name: \GlobalAudit render: -> AuditTable src: '/api/audit/all', caption: 'All recent audit events' } url-of-role = (role) -> "/api/audit/roles/#{encodeURIComponent role}" url-of-resource = (resource) -> "/api/audit/resources/#{encodeURIComponent resource}" export AuditBox = React.createClass { display-name: \AuditBox render: -> roles = @props.roles || [] resources = @props.resources || [] role-srcs = roles |> map url-of-role res-srcs = resources |> map url-of-resource things = (roles ++ resources) |> unique |> join ', ' AuditTable { src: role-srcs ++ res-srcs caption: "Recent Activity" } }