app/assets/js/components/asg.coffee (235 lines of code) (raw):
{div, span, h4, p, img, a, table, thead, tbody, th, tr, td, small, strong, button, ul, li} = React.DOM
Stage = React.createClass
getInitialState: () -> {
stacks: null
stackNames: []
activeStack: null
}
updateFromServer: () ->
$.ajax({
url: "/#{@props.name}.json"
success: ((result) ->
stackNames = (name for name, _ of result)
@setState({
stacks: result
stackNames: stackNames
})
if (@state.activeStack == null)
@setState({ activeStack: stackNames[0] })
).bind(this)
})
setTimeout(@updateFromServer, 5000)
componentDidMount: () ->
for node in @getDOMNode().parentNode.childNodes when node != @getDOMNode()
@getDOMNode().parentNode.removeChild(node)
@updateFromServer()
render: () ->
(div {}, [
if (@state.stackNames.length > 1)
(div { className: 'container'},
(ul { className: 'nav nav-pills stacks' }, [
(li { className: if (name == @state.activeStack ) then 'active' }, (a {
href: '#'
onClick: @markActive.bind(this, name)
}, name )) for name in @state.stackNames
])
)
if (@state.activeStack) then (stack { name: @state.activeStack, asgs: @state.stacks[@state.activeStack].asgs })
])
markActive: (stackName) ->
@setState({ activeStack: stackName })
Stack = React.createClass
getInitialState: () -> {
twoCol: false
}
componentDidMount: () ->
@deriveCols()
window.addEventListener('resize', @deriveCols)
deriveCols: () ->
@setState({
twoCol: window.innerWidth > 767 and window.innerWidth < 1200
})
render: () ->
asgs = @props.asgs
chunkSize = (numChunks) ->
asgs.length / numChunks
if (@state.twoCol)
(div {}, [
(div { className: 'col-sm-6' }, [(autoScalingGroup { group: asg }) for asg in @props.asgs.slice(0, chunkSize(2))])
(div { className: 'col-sm-6' }, [(autoScalingGroup { group: asg }) for asg in @props.asgs.slice(chunkSize(2))])
])
else
(div {}, [
(div { className: 'col-lg-4' }, [(autoScalingGroup { group: asg }) for asg in @props.asgs.slice(0, chunkSize(3))])
(div { className: 'col-lg-4' }, [(autoScalingGroup { group: asg }) for asg in @props.asgs.slice(chunkSize(3), 2 * chunkSize(3))])
(div { className: 'col-lg-4' }, [(autoScalingGroup { group: asg }) for asg in @props.asgs.slice(2 * chunkSize(3))])
])
stack = React.createFactory(Stack)
AutoScalingGroup = React.createClass
getInitialState: () -> {
app: "",
members: [],
recentActivity: []
}
render: () ->
(div {
className: "panel panel-default asg"
style: {
overflow: 'hidden'
}
}, [
(clusterTitle {
name: @props.group.name
app: @props.group.app
approxMonthlyCost: @props.group.approxMonthlyCost
moreDetailsLink: @props.group.moreDetailsLink
hint: @props.group.hint
})
if @props.group.suspendedActivities?.length > 0
(div { className: 'panel-body alert-info' }, [
(strong {}, "Suspended activities")
": #{@props.group.suspendedActivities.join(',')}"
])
if @props.group.elb && @props.group.elb.active
(a {
href: "https://console.aws.amazon.com/cloudwatch/home?region=eu-west-1#metrics:graph=!D05!E07!ET6!MN4!NS2!PD1!SS3!ST0!VA-PT3H~60~AWS%25252FELB~Average~Latency~LoadBalancerName~P0D~#{@props.group.elb.name}"
}, [
(sparklinePlus {
points: {x: point.time, y: point.average} for point in @props.group.elb.latency
unit: 'ms'
height: 50
additionalLine:
(sparkline {
points: {x: point.time, y: point.sum} for point in @props.group.elb.errorCount
stroke: 'red'
})
})
])
(clusterMembers {
members: @props.group.members
elb: @props.group.elb
asg: @props.group.name
})
(a {
href: "https://console.aws.amazon.com/cloudwatch/home?region=eu-west-1#metrics:graph=!D03!E06!ET7!MN5!NS2!PD1!SS4!ST0!VA-PT3H~60~AWS%252FEC2~AutoScalingGroupName~Maximum~CPUUtilization~#{@props.group.name}~P0D"
}, [
(sparklinePlus {
points: {x: point.time, y: point.maximum} for point in @props.group.cpu
unit: '%'
height: 50
})
])
(recentActivity {
asgName: @props.group.name
activities: @props.group.recentActivity
})
])
autoScalingGroup = React.createFactory(AutoScalingGroup)
ClusterTitle = React.createClass
render: () ->
(div { className: "panel-heading" }, [
(h4 { className: "panel-title" }, [
(div { className: "pull-right" }, [
(span { className: "asg-hint asg-hint-" + (@props.hint || "-none") }, [ @props.hint || "" ]),
(button {
id: @props.name + "-copy"
'data-clipboard-text': @props.name
title: "Copy ASG name to clipboard"
}, [
(img {
src: "/assets/images/ios7-copy-outline.png"
height: "16px"
})
])
])
if @props.moreDetailsLink? then (a { href: @props.moreDetailsLink }, @props.app) else @props.app
(small {}, " (~$#{d3.format(',.0f')(@props.approxMonthlyCost)}/month)")
])
])
componentDidMount: () ->
new ZeroClipboard(document.getElementById(@props.name + "-copy"))
clusterTitle = React.createFactory(ClusterTitle)
ClusterMembers = React.createClass
render: () ->
hasELB = @props.elb?
hasASG = @props.asg?
(table { className: "table table-condensed" }, [
(thead {}, [
(tr {}, [
(th {}, ["Instance"])
if(hasASG)
(th {}, ["AutoScaling"])
else
(th {}, ["Instance State"])
if (hasELB)
(th {}, ["ELB"])
else
(th {}, ["Uptime"])
(th {}, ["Version"])
])
])
(tbody {}, [
@props.members.map((m) -> (clusterMember {
member: m
key: m.id
hasELB: hasELB
hasASG: hasASG
url: m.url
}))
])
])
clusterMembers = React.createFactory(ClusterMembers)
RecentActivity = React.createClass
toggle: () ->
@setState({ collapsed: !@state.collapsed })
getInitialState: () -> {
collapsed: true
}
render: () ->
if (@props.activities.length == 0) then (div {})
else
(div {
id: @props.asgName + "-activity"
className: "panel-footer"
}, [
(a { onClick: @toggle }, [
(small {}, "Recent activity")
])
if (!@state.collapsed)
(div {}, [
(small {}, [
@props.activities.map((a) ->
(scalingActivity {
age: a.age
cause: a.cause
})
)
])
])
])
recentActivity = React.createFactory(RecentActivity)
ScalingActivity = React.createClass
render: () ->
(React.DOM.p {}, [
(strong {}, [@props.age])
@props.cause
])
scalingActivity = React.createFactory(ScalingActivity)
ClusterMember = React.createClass
render: () ->
hasELB = @props.hasELB
hasASG = @props.hasASG
(tr { className: @props.member.goodorbad }, [
(td {}, [
(a { href: "/instance/#{@props.member.id}" }, [@props.member.truncatedId])
])
if (hasASG)
(td {}, @props.member.lifecycleState )
else
(td {}, @props.member.instance.state )
if (hasELB) then (td { title: @props.member.description }, @props.member.state )
(td {}, @props.member.uptime)
(td {}, @props.member.version)
])
clusterMember = React.createFactory(ClusterMember)
window.Stage = Stage