├── .babelrc ├── .dockerignore ├── .gitignore ├── Dockerfile ├── Procfile ├── README.md ├── assets ├── javascripts │ └── application.js └── stylesheets │ └── application.scss ├── config └── config.exs ├── dashboards ├── elixir.html.eex ├── error.html.eex ├── jobs.html.eex ├── layout.html.eex ├── rotator.html.eex ├── sample.html.eex └── sample1080.html.eex ├── elixir_buildpack.config ├── jobs ├── buzzwords.exs ├── convergence.exs ├── random.exs ├── reddit.exs ├── stats.exs ├── text.exs ├── travis.exs ├── twitter.exs └── users.exs ├── lib ├── convergence.ex ├── demo.ex ├── reddit.ex └── travis.ex ├── mix.exs ├── mix.lock ├── package.json ├── public └── assets │ └── favicon.ico ├── webpack.config.js └── widgets ├── clock ├── clock.js └── clock.scss ├── graph ├── graph.js └── graph.scss ├── list ├── list.js └── list.scss ├── meter ├── meter.js └── meter.scss ├── number ├── number.js └── number.scss ├── text ├── text.js └── text.scss ├── time_took ├── time_took.js └── time_took.scss └── travis ├── travis.js └── travis.scss /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | deps 3 | _build 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | 7 | # Numerous always-ignore extensions 8 | *.diff 9 | *.err 10 | *.orig 11 | *.log 12 | *.rej 13 | *.swo 14 | *.swp 15 | *.vi 16 | *~ 17 | *.sass-cache 18 | 19 | # OS or Editor files/dirs 20 | .DS_Store 21 | .cache 22 | .project 23 | .settings 24 | .tmproj 25 | nbproject 26 | Thumbs.db 27 | 28 | # NPM packages 29 | node_modules/ 30 | 31 | # Compiled Assets 32 | public/* 33 | !public/assets 34 | public/assets/* 35 | !public/assets/favicon.* 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM zorbash/kitto 2 | 3 | ADD . /dashboard 4 | WORKDIR /dashboard 5 | 6 | ENV MIX_ENV prod 7 | 8 | RUN mix deps.get 9 | RUN npm install 10 | RUN npm run build 11 | RUN mix compile 12 | 13 | CMD mix kitto.server 14 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: mix kitto.server 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | This demo app is deployed at http://kitto.io 4 | 5 | The [heroku](https://github.com/kittoframework/demo/tree/heroku) branch of this repo is 6 | also automatically deployed to heroku at: https://kitto.herokuapp.com 7 | 8 | Example dashboards: 9 | 10 | * http://kitto.io/dashboards/sample 11 | 12 | ![sample-dashboard](http://i.imgur.com/6iG6t7l.png) 13 | 14 | * http://kitto.io/dashboards/elixir 15 | 16 | ![elixir-dashboard](http://i.imgur.com/70KYNTw.png) 17 | 18 | * http://kitto.io/dashboards/jobs 19 | 20 | ![jobs-dashboard](http://i.imgur.com/DhNxq03.png) 21 | 22 | To start your Dashboard: 23 | 24 | * Install dependencies with `mix deps.get` 25 | * Install Node.js dependencies with `npm install` 26 | * Start a development server with `mix kitto.server` 27 | 28 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 29 | 30 | Ready to run in production? Please [check our deployment guides](https://github.com/kittoframework/kitto#deployment). 31 | 32 | ## Requirements 33 | 34 | For platform requirements see: [here](https://github.com/kittoframework/kitto#requirements). 35 | 36 | Some of the jobs in the demo make authenticated requests and have to be 37 | configured in order to work. 38 | 39 | ### Travis 40 | 41 | ```elixir 42 | # File: config/config.exs 43 | config :kitto, travis_token: "add-your-token" 44 | ``` 45 | 46 | ### Twitter 47 | 48 | For available configuration options of the `extwitter` dependency, 49 | consult the 50 | [readme](https://github.com/parroty/extwitter/tree/v0.7.0#configuration). 51 | 52 | ```elixir 53 | # File: config/config.exs 54 | config :extwitter, :oauth, 55 | consumer_key: "your-consumer-key", 56 | consumer_secret: "your-consumer-secret", 57 | access_token: "your-access-token", 58 | access_token_secret: "your-access-token-secret" 59 | ``` 60 | 61 | ## Learn more 62 | 63 | * Official website: http://kitto.io 64 | * Source: https://github.com/kittoframework/kitto 65 | -------------------------------------------------------------------------------- /assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | import '../stylesheets/application.scss'; 2 | 3 | import $ from 'jquery'; 4 | import {Kitto} from 'kitto'; 5 | 6 | window.jQuery = window.$ = $; 7 | 8 | Kitto.start(); 9 | -------------------------------------------------------------------------------- /assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "~jquery.gridster.css"; 2 | @import '~font-awesome/css/font-awesome.css'; 3 | 4 | // ---------------------------------------------------------------------------- 5 | // Sass declarations 6 | // ---------------------------------------------------------------------------- 7 | $background-color: #222; 8 | $text-color: #fff; 9 | 10 | $background-warning-color-1: #e82711; 11 | $background-warning-color-2: #9b2d23; 12 | $text-warning-color: #fff; 13 | 14 | $background-danger-color-1: #eeae32; 15 | $background-danger-color-2: #ff9618; 16 | $text-danger-color: #fff; 17 | 18 | @-webkit-keyframes status-warning-background { 19 | 0% { background-color: $background-warning-color-1; } 20 | 50% { background-color: $background-warning-color-2; } 21 | 100% { background-color: $background-warning-color-1; } 22 | } 23 | @-webkit-keyframes status-danger-background { 24 | 0% { background-color: $background-danger-color-1; } 25 | 50% { background-color: $background-danger-color-2; } 26 | 100% { background-color: $background-danger-color-1; } 27 | } 28 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 29 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 30 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 31 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 32 | } 33 | 34 | // ---------------------------------------------------------------------------- 35 | // Base styles 36 | // ---------------------------------------------------------------------------- 37 | html { 38 | font-size: 100%; 39 | -webkit-text-size-adjust: 100%; 40 | -ms-text-size-adjust: 100%; 41 | } 42 | 43 | body { 44 | margin: 0; 45 | background-color: $background-color; 46 | font-size: 20px; 47 | color: $text-color; 48 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 49 | } 50 | 51 | b, strong { 52 | font-weight: bold; 53 | } 54 | 55 | a { 56 | text-decoration: none; 57 | color: inherit; 58 | } 59 | 60 | img { 61 | border: 0; 62 | -ms-interpolation-mode: bicubic; 63 | vertical-align: middle; 64 | } 65 | 66 | img, object { 67 | max-width: 100%; 68 | } 69 | 70 | iframe { 71 | max-width: 100%; 72 | } 73 | 74 | table { 75 | border-collapse: collapse; 76 | border-spacing: 0; 77 | width: 100%; 78 | } 79 | 80 | td { 81 | vertical-align: middle; 82 | } 83 | 84 | ul, ol { 85 | padding: 0; 86 | margin: 0; 87 | } 88 | 89 | h1, h2, h3, h4, h5, p { 90 | padding: 0; 91 | margin: 0; 92 | } 93 | h1 { 94 | margin-bottom: 12px; 95 | text-align: center; 96 | font-size: 30px; 97 | font-weight: 400; 98 | } 99 | h2 { 100 | text-transform: uppercase; 101 | font-size: 76px; 102 | font-weight: 700; 103 | color: $text-color; 104 | } 105 | h3 { 106 | font-size: 25px; 107 | font-weight: 600; 108 | color: $text-color; 109 | } 110 | 111 | // ---------------------------------------------------------------------------- 112 | // Base widget styles 113 | // ---------------------------------------------------------------------------- 114 | .gridster { 115 | margin: 0px auto; 116 | } 117 | 118 | .icon-background { 119 | width: 100%!important; 120 | height: 100%; 121 | position: absolute; 122 | left: 0; 123 | top: 0; 124 | opacity: 0.1; 125 | font-size: 275px; 126 | text-align: center; 127 | margin-top: 82px; 128 | } 129 | 130 | .list-nostyle { 131 | list-style: none; 132 | } 133 | 134 | .gridster ul { 135 | list-style: none; 136 | } 137 | 138 | .gs-w { 139 | width: 100%; 140 | display: table; 141 | cursor: pointer; 142 | } 143 | 144 | .widget { 145 | display: table-cell; 146 | padding: 25px 12px; 147 | box-sizing: border-box; 148 | text-align: center; 149 | width: 100%; 150 | vertical-align: middle; 151 | } 152 | 153 | .widget.status-warning { 154 | background-color: $background-warning-color-1; 155 | @include animation(status-warning-background, 2s, ease, infinite); 156 | 157 | .icon-warning-sign { 158 | display: inline-block; 159 | } 160 | 161 | .title, .more-info { 162 | color: $text-warning-color; 163 | } 164 | } 165 | 166 | .widget.status-danger { 167 | color: $text-danger-color; 168 | background-color: $background-danger-color-1; 169 | @include animation(status-danger-background, 2s, ease, infinite); 170 | 171 | .icon-warning-sign { 172 | display: inline-block; 173 | } 174 | 175 | .title, .more-info { 176 | color: $text-danger-color; 177 | } 178 | } 179 | 180 | .more-info { 181 | font-size: 15px; 182 | position: absolute; 183 | bottom: 32px; 184 | left: 0; 185 | right: 0; 186 | } 187 | 188 | .updated-at { 189 | font-size: 15px; 190 | position: absolute; 191 | bottom: 12px; 192 | left: 0; 193 | right: 0; 194 | } 195 | 196 | #save-gridster { 197 | display: none; 198 | position: fixed; 199 | top: 0; 200 | margin: 0px auto; 201 | left: 50%; 202 | z-index: 1000; 203 | background: black; 204 | width: 190px; 205 | text-align: center; 206 | border: 1px solid white; 207 | border-top: 0px; 208 | margin-left: -95px; 209 | padding: 15px; 210 | } 211 | 212 | #save-gridster:hover { 213 | padding-top: 25px; 214 | } 215 | 216 | #saving-instructions { 217 | display: none; 218 | padding: 10px; 219 | width: 500px; 220 | height: 122px; 221 | z-index: 1000; 222 | background: white; 223 | top: 100px; 224 | color: black; 225 | font-size: 15px; 226 | padding-bottom: 4px; 227 | 228 | textarea { 229 | white-space: nowrap; 230 | width: 494px; 231 | height: 80px; 232 | } 233 | } 234 | 235 | #lean_overlay { 236 | position: fixed; 237 | z-index:100; 238 | top: 0px; 239 | left: 0px; 240 | height:100%; 241 | width:100%; 242 | background: #000; 243 | display: none; 244 | } 245 | 246 | #container { 247 | padding-top: 5px; 248 | } 249 | 250 | .widget-list.widget-reddit { 251 | background-color: #ff4500; 252 | } 253 | 254 | .widget-list.widget-job { 255 | vertical-align: top; 256 | } 257 | 258 | 259 | // ---------------------------------------------------------------------------- 260 | // Clearfix 261 | // ---------------------------------------------------------------------------- 262 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 263 | .clearfix:after { clear: both; } 264 | .clearfix { zoom: 1; } 265 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | use Mix.Config 7 | 8 | config :kitto, root: Path.dirname(__DIR__), 9 | travis_token: System.get_env("TRAVIS_TOKEN"), 10 | google_analytics_id: System.get_env("GOOGLE_ANALYTICS_ID") 11 | 12 | config :extwitter, :oauth, 13 | consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), 14 | consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"), 15 | access_token: System.get_env("TWITTER_ACCESS_TOKEN"), 16 | access_token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET") 17 | 18 | #config :kitto, :watch_assets?, false 19 | 20 | # Use port: {:system, "PORT"} to have port port configurable via env variable 21 | # Example: `PORT=4444 mix kitto.server` will start the server on port 4444 22 | 23 | # Configures Elixir's Logger 24 | config :logger, :console, 25 | format: "$time $metadata[$level] $message\n", 26 | metadata: [:request_id] 27 | -------------------------------------------------------------------------------- /dashboards/elixir.html.eex: -------------------------------------------------------------------------------- 1 |
2 | 54 |
55 | -------------------------------------------------------------------------------- /dashboards/error.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= code %> - <%= message %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 38 | 39 |
40 |

<%= code %>

41 |

<%= message %>

42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /dashboards/jobs.html.eex: -------------------------------------------------------------------------------- 1 |
2 | 71 |
72 | -------------------------------------------------------------------------------- /dashboards/layout.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dashboard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | Fork me on GitHub 18 | 19 | 20 |
21 | <%= @template %> 22 |
23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /dashboards/rotator.html.eex: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | <%= for dashboard <- @dashboards do %> 8 | <%= Kitto.View.render_template(dashboard) %> 9 | <% end %> 10 |
11 | -------------------------------------------------------------------------------- /dashboards/sample.html.eex: -------------------------------------------------------------------------------- 1 |
2 | 58 |
59 | -------------------------------------------------------------------------------- /dashboards/sample1080.html.eex: -------------------------------------------------------------------------------- 1 |
2 | 127 |
128 | -------------------------------------------------------------------------------- /elixir_buildpack.config: -------------------------------------------------------------------------------- 1 | elixir_version=1.3.0 2 | -------------------------------------------------------------------------------- /jobs/buzzwords.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :buzzwords, every: :second do 4 | random = fn -> :rand.uniform * 100 |> Float.round end 5 | 6 | list = ~w[synergy startup catalyst docker microservice container elixir react] 7 | |> Enum.map(fn (w) -> %{label: w, value: random.()} end) 8 | |> Enum.shuffle 9 | 10 | broadcast! %{items: list} 11 | end 12 | -------------------------------------------------------------------------------- /jobs/convergence.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | {:ok, convergence} = Kitto.Jobs.Convergence.new 4 | points = &(&1 |> Kitto.Jobs.Convergence.points) 5 | 6 | job :convergence, every: {2, :seconds} do 7 | broadcast! %{points: convergence |> points.()} 8 | end 9 | -------------------------------------------------------------------------------- /jobs/random.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :random, every: :second do 4 | broadcast! %{value: :rand.uniform * 100 |> Float.round} 5 | end 6 | -------------------------------------------------------------------------------- /jobs/reddit.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :reddit, every: {2, :seconds} do 4 | subreddit = "elixir" 5 | max_posts = 5 6 | 7 | headers = [%{label: "posts", value: "score"}] 8 | items = Demo.Reddit.r(subreddit)["data"]["children"] 9 | |> Enum.take(max_posts) 10 | |> Enum.map(fn (%{"data" => post}) -> 11 | %{label: post["title"], value: post["score"]} 12 | end) 13 | 14 | broadcast! %{items: headers ++ items} 15 | end 16 | -------------------------------------------------------------------------------- /jobs/stats.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :job_failures, every: :second do 4 | stats = Kitto.StatsServer.stats 5 | 6 | failures = stats 7 | |> Enum.map(fn ({name, m}) -> %{label: name, value: m[:failures]} end) 8 | |> Enum.sort(fn (a, b) -> a[:value] > b[:value] end) 9 | |> Enum.take(15) 10 | 11 | broadcast! %{items: failures} 12 | end 13 | 14 | job :job_avg_time, every: {500, :milliseconds} do 15 | stats = Kitto.StatsServer.stats 16 | 17 | metrics = stats 18 | |> Enum.map(fn ({name, m}) -> 19 | %{label: name, value: m[:avg_time_took] |> Float.round(3)} 20 | end) 21 | |> Enum.sort(fn (a, b) -> a[:value] > b[:value] end) 22 | |> Enum.take(15) 23 | 24 | broadcast! :job_avg_time_took, %{items: metrics} 25 | end 26 | 27 | job :jobs_running, every: {200, :milliseconds} do 28 | stats = Kitto.StatsServer.stats 29 | |> Enum.filter(fn ({_name, m}) -> 30 | (m[:times_completed] + m[:failures]) < m[:times_triggered] 31 | end) 32 | |> length 33 | 34 | broadcast! %{value: stats} 35 | end 36 | 37 | job :footprint, every: :second do 38 | mem = :erlang.memory[:processes_used] / 1024 / 1024 |> Float.round(3) 39 | 40 | broadcast! :processes_count, %{value: length(:erlang.processes)} 41 | broadcast! :memory_usage, %{value: mem} 42 | end 43 | 44 | job :uptime, every: :second do 45 | hours = ((:erlang.statistics(:wall_clock) |> elem(0)) / 1000 / 60.0 / 60.0) 46 | |> Float.round(3) 47 | 48 | broadcast! %{value: hours} 49 | end 50 | -------------------------------------------------------------------------------- /jobs/text.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :text, every: {4, :seconds} do 4 | phrases = ["This is your shiny new dashboard", "Built on the Kitto Framework"] 5 | 6 | broadcast! %{text: (phrases |> Enum.shuffle |> Enum.take(1))} 7 | end 8 | -------------------------------------------------------------------------------- /jobs/travis.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :travis, every: {4, :seconds} do 4 | repo_names = ~w[elixir-lang/elixir elixir-lang/plug elixir-lang/ex_doc 5 | elixir-ecto/ecto phoenixframework/phoenix hexpm/hex kittoframework/kitto 6 | rrrene/credo edgurgel/httpoison bitwalker/distillery rails/rails] 7 | 8 | statuses = fn (status) -> 9 | case status do 10 | 0 -> "success" 11 | 1 -> "failure" 12 | _ -> "unknown" 13 | end 14 | end 15 | 16 | builds = repo_names |> Enum.map(fn (repo) -> 17 | %{label: repo, value: Demo.Travis.repo(repo)["last_build_status"] |> statuses.()} 18 | end) 19 | 20 | broadcast! %{items: builds} 21 | end 22 | -------------------------------------------------------------------------------- /jobs/twitter.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :twitter, every: {20, :seconds} do 4 | items = ExTwitter.search("elixir-lang", count: 2) 5 | |> Enum.map(fn (t) -> %{label: t.text, value: ""} end) 6 | 7 | broadcast! %{items: items} 8 | end 9 | 10 | job :twitter_linux do 11 | spawn fn -> 12 | for tweet <- ExTwitter.stream_filter(track: "linux") do 13 | broadcast! %{text: tweet.text} 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /jobs/users.exs: -------------------------------------------------------------------------------- 1 | use Kitto.Job.DSL 2 | 3 | job :users, every: :second do 4 | broadcast! %{value: Kitto.Notifier.connections |> Enum.count} 5 | end 6 | -------------------------------------------------------------------------------- /lib/convergence.ex: -------------------------------------------------------------------------------- 1 | defmodule Kitto.Jobs.Convergence do 2 | def new, do: Agent.start(fn -> 0 end) 3 | 4 | def points(pid, n \\ 10), do: points(pid, n, [point(pid)]) 5 | defp points(_, n, acc) when length(acc) == n, do: acc 6 | defp points(pid, n, acc), do: points(pid, n, acc ++ [point(pid)]) 7 | defp point(pid), do: %{x: pid |> next_point, y: random()} 8 | 9 | defp next_point(pid) do 10 | pid |> Agent.get_and_update(fn(n) -> next = n + 1; {next, next} end) 11 | end 12 | 13 | defp random, do: :rand.uniform * 100 |> Float.round 14 | end 15 | -------------------------------------------------------------------------------- /lib/demo.ex: -------------------------------------------------------------------------------- 1 | defmodule Demo do 2 | end 3 | -------------------------------------------------------------------------------- /lib/reddit.ex: -------------------------------------------------------------------------------- 1 | defmodule Demo.Reddit do 2 | @base_url "https://www.reddit.com/" 3 | 4 | def r(topic) do 5 | %{body: body} = (@base_url <> "r/" <> topic <> ".json") |> HTTPoison.get!(%{}) 6 | 7 | body |> Poison.decode! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/travis.ex: -------------------------------------------------------------------------------- 1 | defmodule Demo.Travis do 2 | @base_url "https://api.travis-ci.org/" 3 | @token Application.get_env :kitto, :travis_token 4 | 5 | def repo(r) do 6 | %{body: body} = (@base_url <> "repos/" <> r) |> HTTPoison.get!(%{}, headers) 7 | 8 | body |> Poison.decode! 9 | end 10 | 11 | defp headers do 12 | [{"Authorization", "token #{@token}" }] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Demo.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :demo, 6 | version: "0.0.1", 7 | elixir: "~> 1.3", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application. 14 | # 15 | # Type `mix help compile.app` for more information. 16 | def application do 17 | [applications: [:logger, :kitto, :httpoison]] 18 | end 19 | 20 | # Specifies your project dependencies. 21 | # 22 | # Type `mix help deps` for examples and options. 23 | defp deps do 24 | [{:kitto, "0.5.2"}, 25 | {:httpoison, "~> 0.9", override: true}, 26 | {:poison, "3.0.0", override: true}, 27 | {:extwitter, "~> 0.9"}] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], [], "hexpm"}, 3 | "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, 4 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, 5 | "extwitter": {:hex, :extwitter, "0.9.1", "ce17b050f6139bbdfeafe100d3aa0b133e6f51cbe3f51d4879de0d28dba63d13", [:mix], [{:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []}, 7 | "hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [repo: "hexpm", hex: :certifi, optional: false]}, {:idna, "4.0.0", [repo: "hexpm", hex: :idna, optional: false]}, {:metrics, "1.0.1", [repo: "hexpm", hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [repo: "hexpm", hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [repo: "hexpm", hex: :ssl_verify_fun, optional: false]}], "hexpm"}, 8 | "httpoison": {:hex, :httpoison, "0.11.1", "d06c571274c0e77b6cc50e548db3fd7779f611fbed6681fd60a331f66c143a0b", [:mix], [{:hackney, "~> 1.7.0", [repo: "hexpm", hex: :hackney, optional: false]}], "hexpm"}, 9 | "idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], [], "hexpm"}, 10 | "kitto": {:hex, :kitto, "0.5.2", "f6ef75c60124d6ebb28ad289416b042e7bc0ed64afa56313f102cd963227885f", [:mix], [{:cowboy, "~> 1.0.0", [repo: "hexpm", hex: :cowboy, optional: false]}, {:fs, "~> 2.12.0", [repo: "hexpm", hex: :fs, optional: false]}, {:httpoison, "~> 0.10.0", [repo: "hexpm", hex: :httpoison, optional: false]}, {:plug, "~> 1.3.2", [repo: "hexpm", hex: :plug, optional: false]}, {:poison, "~> 3.0", [repo: "hexpm", hex: :poison, optional: false]}], "hexpm"}, 11 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 12 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"}, 13 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 14 | "oauth": {:git, "https://github.com/tim/erlang-oauth.git", "fca8163e8f7af867015e2413c4b03a4f8f4df0c9", []}, 15 | "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm"}, 16 | "plug": {:hex, :plug, "1.3.4", "b4ef3a383f991bfa594552ded44934f2a9853407899d47ecc0481777fb1906f6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [repo: "hexpm", hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [repo: "hexpm", hex: :mime, optional: false]}], "hexpm"}, 17 | "poison": {:hex, :poison, "3.0.0", "625ebd64d33ae2e65201c2c14d6c85c27cc8b68f2d0dd37828fde9c6920dd131", [:mix], []}, 18 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, 19 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.1", 4 | "author": "Dimitris Zorbas", 5 | "description": "Dashboard Application", 6 | "scripts": { 7 | "start": "./node_modules/.bin/webpack-dev-server --progress --colors", 8 | "heroku-postbuild": "npm run build", 9 | "build": "./node_modules/.bin/webpack --config webpack.config.js" 10 | }, 11 | "main": "index.js", 12 | "license": "MIT", 13 | "keywords": [ 14 | "webpack" 15 | ], 16 | "engines": { 17 | "npm": "3.10.9", 18 | "node": "4.3.1" 19 | }, 20 | "devDependencies": { 21 | "webpack-dev-server": "^1.14.1" 22 | }, 23 | "dependencies": { 24 | "kitto": "file:deps/kitto", 25 | "webpack": "^1.12.13", 26 | "webpack-merge": "^0.15.0", 27 | "compression-webpack-plugin": "0.3.1", 28 | "babel-core": "^6.5.2", 29 | "babel-loader": "^6.2.2", 30 | "babel-preset-es2015": "6.18.0", 31 | "babel-preset-react": "^6.5.0", 32 | "babel-preset-react-hmre": "^1.1.0", 33 | "sass-loader": "^3.2.0", 34 | "url-loader": "^0.5.5", 35 | "file-loader": "^0.8.1", 36 | "style-loader": "^0.13.0", 37 | "script-loader": "^0.7.0", 38 | "imports-loader": "0.6.5", 39 | "expose-loader": "0.7.1", 40 | "css-loader": "^0.9.1", 41 | "node-sass": "^3.4.2", 42 | "react": "^0.14.7", 43 | "react-dom": "^0.14.7", 44 | "glob": "7.1.0", 45 | "font-awesome": "^4.3.0", 46 | "d3": "3.5.17", 47 | "gridster": "0.5.6", 48 | "imports": "^1.0.0", 49 | "rickshaw": "1.6.0", 50 | "jquery": "^3.0.0", 51 | "jquery-knob": "1.2.11", 52 | "fscreen": "^1.0.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittoframework/demo/cd42644e4129a6da2b727f84c97c19c6b589fbec/public/assets/favicon.ico -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const path = require('path'); 3 | const merge = require('webpack-merge'); 4 | const webpack = require('webpack'); 5 | 6 | const TARGET = process.env.npm_lifecycle_event; 7 | const PATHS = { 8 | app: path.join(__dirname, 'assets/javascripts/application.js'), 9 | widgets: glob.sync('./widgets/**/*.js'), 10 | build: path.join(__dirname, 'public/assets'), 11 | gridster: path.join(__dirname, 'node_modules/gridster/dist'), 12 | d3: path.join(__dirname, 'node_modules/d3/d3.min.js'), 13 | rickshaw: path.join(__dirname, 'node_modules/rickshaw/rickshaw.js') 14 | }; 15 | 16 | process.env.BABEL_ENV = TARGET; 17 | 18 | const common = { 19 | entry: { 20 | application: PATHS.app, 21 | widgets: PATHS.widgets 22 | }, 23 | resolve: { 24 | extensions: ['', '.js', '.jsx', 'css', 'scss'], 25 | modulesDirectories: ['node_modules', PATHS.gridster], 26 | alias: { 27 | d3: PATHS.d3 28 | } 29 | }, 30 | output: { 31 | path: PATHS.build, 32 | publicPath: '/assets/', 33 | filename: '[name].js' 34 | }, 35 | module: { 36 | loaders: [ 37 | { test: /\.css$/, loaders: ['style', 'css'] }, 38 | { test: /\.scss$/, loaders: ['style', 'css', 'sass'] }, 39 | { test: /\.jsx?$/, loaders: ['babel?cacheDirectory'] }, 40 | { 41 | test: /\.(svg|png|jpe?g|gif)(\?\S*)?$/, 42 | loader: 'url?limit=1000&name=images/[name].[ext]' 43 | }, 44 | { 45 | test: /\.(eot|woff|woff2|ttf)(\?\S*)?$/, 46 | loader: 'url?limit=1000&name=fonts/[name].[ext]' 47 | }, 48 | { 49 | test: require.resolve('jquery-knob'), 50 | loader: "imports?require=>false,define=>false,this=>window" 51 | }, 52 | { 53 | test: PATHS.d3, 54 | loader: "script" 55 | }, 56 | { 57 | test: require.resolve('rickshaw'), 58 | loader: "script" 59 | } 60 | ] 61 | } 62 | }; 63 | 64 | // Development Environment 65 | if (TARGET === 'start' || !TARGET) { 66 | module.exports = merge(common, { 67 | devtool: 'eval-source-map', 68 | devServer: { 69 | contentBase: PATHS.build, 70 | 71 | historyApiFallback: true, 72 | hot: true, 73 | inline: true, 74 | progress: true, 75 | 76 | // display only errors to reduce the amount of output 77 | stats: 'errors-only', 78 | 79 | // Binding address of webpack-dev-server 80 | // Read more: https://github.com/kittoframework/kitto/wiki/Customize-Asset-Watcher 81 | host: process.env.KITTO_ASSETS_HOST, 82 | port: process.env.KITTO_ASSETS_PORT 83 | }, 84 | plugins: [new webpack.HotModuleReplacementPlugin()] 85 | }); 86 | } 87 | 88 | // Production Environment 89 | if (TARGET === 'build') { 90 | var CompressionPlugin = require("compression-webpack-plugin"); 91 | 92 | module.exports = merge(common, { 93 | plugins: [ 94 | new webpack.optimize.UglifyJsPlugin({ 95 | compress: { 96 | warnings: false, 97 | keep_fnames: true 98 | }, 99 | mangle: { 100 | keep_fnames: true 101 | } 102 | }), 103 | new CompressionPlugin({ 104 | asset: '[path].gz[query]', 105 | algorithm: 'gzip', 106 | test: /\.js$|\.html$/, 107 | verbose: true 108 | }) 109 | ] 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /widgets/clock/clock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Widget} from 'kitto'; 3 | 4 | import './clock.scss'; 5 | 6 | class Clock extends Widget { 7 | constructor(props) { 8 | super(props); 9 | this.state = Clock.dateTime() 10 | setInterval(this.update.bind(this), 500); 11 | } 12 | update() { this.setState(Clock.dateTime()); } 13 | render() { 14 | return ( 15 |
16 |

{this.state.date}

17 |

{this.state.time}

18 |
19 | ); 20 | } 21 | static formatTime(i) { return i < 10 ? "0" + i : i; } 22 | static dateTime() { 23 | var today = new Date(), 24 | h = today.getHours(), 25 | m = today.getMinutes(), 26 | s = today.getSeconds(), 27 | m = Clock.formatTime(m), 28 | s = Clock.formatTime(s); 29 | 30 | return { 31 | time: (h + ":" + m + ":" + s), 32 | date: today.toDateString(), 33 | } 34 | } 35 | }; 36 | 37 | Widget.mount(Clock); 38 | export default Clock; 39 | -------------------------------------------------------------------------------- /widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-clock styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-clock { 10 | background-color: $background-color; 11 | 12 | .time { 13 | font-size: 55px; 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /widgets/graph/graph.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | import 'd3'; 4 | import 'rickshaw'; 5 | import {Kitto, Widget} from 'kitto'; 6 | 7 | import './graph.scss'; 8 | 9 | class Graph extends Widget { 10 | static get defaultProps() { 11 | return { graphType: 'area' }; 12 | } 13 | 14 | componentDidMount() { 15 | this.$node = $(ReactDOM.findDOMNode(this)); 16 | this.current = 0; 17 | this.renderGraph(); 18 | } 19 | renderGraph() { 20 | let container = this.$node.parent(); 21 | let $gridster = $('.gridster'); 22 | let config = Kitto.config(); 23 | let widget_base_dimensions = config.widget_base_dimensions; 24 | let width = (widget_base_dimensions[0] * 25 | container.data('sizex')) + 5 * 2 * (container.data('sizex') - 1); 26 | let height = (widget_base_dimensions[1] * container.data('sizey')); 27 | 28 | this.graph = new Rickshaw.Graph({ 29 | element: this.$node[0], 30 | width: width, 31 | height: height, 32 | renderer: this.props.graphType, 33 | series: [{color: '#fff', data: [{ x: 0, y: 0 }]}] 34 | }); 35 | 36 | new Rickshaw.Graph.Axis.Time({ graph: this.graph }); 37 | new Rickshaw.Graph.Axis.Y({ graph: this.graph, 38 | tickFormat: Rickshaw.Fixtures.Number.formatKMBT }); 39 | this.graph.render(); 40 | } 41 | componentWillUpdate(_props, state) { 42 | this.graph.series[0].data = state.points; 43 | this.current = state.points[state.points.length -1].y; 44 | this.graph.render(); 45 | } 46 | currentValue() { 47 | return this.prettyNumber(this.prepend(this.current)); 48 | } 49 | render() { 50 | return ( 51 |
52 |

{this.props.title}

53 |

{this.currentValue()}

54 |

{this.props.moreinfo}

55 |
56 | ); 57 | } 58 | }; 59 | 60 | Widget.mount(Graph); 61 | export default Graph; 62 | -------------------------------------------------------------------------------- /widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 20px; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 20px; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 20px; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /widgets/list/list.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Widget, Helpers} from 'kitto'; 3 | 4 | import './list.scss'; 5 | 6 | class ListItem extends React.Component { 7 | render() { 8 | return ( 9 |
  • 10 | 11 | {Helpers.truncate(this.props.label, this.props.labelLength || 80)} 12 | 13 | 14 | {Helpers.truncate(this.props.value, this.props.valueLength)} 15 | 16 |
  • 17 | ); 18 | } 19 | } 20 | 21 | export class List extends Widget { 22 | renderItems(items) { 23 | return items.map((item, i) => { 24 | return ; 29 | }); 30 | } 31 | renderList(items) { 32 | return this.props.unordered ? :
      {items}
    ; 33 | } 34 | render() { 35 | return ( 36 |
    37 |

    {this.props.title}

    38 |

    {this.props.text}

    39 | 42 |

    {this.props.moreinfo}

    43 |

    {this.updatedAt(this.state.updated_at)}

    44 |
    45 | ); 46 | } 47 | }; 48 | 49 | Widget.mount(List); 50 | export default List; 51 | -------------------------------------------------------------------------------- /widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #12b0c5; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $label-color: rgba(255, 255, 255, 0.7); 9 | $moreinfo-color: rgba(255, 255, 255, 0.7); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-list styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-list { 15 | 16 | background-color: $background-color; 17 | 18 | .title { 19 | color: $title-color; 20 | } 21 | 22 | ol, ul { 23 | margin: 0 15px; 24 | text-align: left; 25 | color: $label-color; 26 | } 27 | 28 | ol { 29 | list-style-position: inside; 30 | } 31 | 32 | li { 33 | margin-bottom: 5px; 34 | } 35 | 36 | .list-nostyle { 37 | list-style: none; 38 | } 39 | 40 | .label { 41 | color: $label-color; 42 | } 43 | 44 | .value { 45 | float: right; 46 | margin-left: 12px; 47 | font-weight: 600; 48 | color: $value-color; 49 | } 50 | 51 | .updated-at { 52 | color: rgba(0, 0, 0, 0.3); 53 | } 54 | 55 | .more-info { 56 | color: $moreinfo-color; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /widgets/meter/meter.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import React from 'react'; 3 | import Knob from 'jquery-knob'; 4 | import {Widget} from 'kitto'; 5 | 6 | import './meter.scss'; 7 | 8 | class Meter extends Widget { 9 | componentDidMount() { 10 | this.state = { value: 0 }; 11 | this.$node = $(ReactDOM.findDOMNode(this)); 12 | this.$meter = this.$node.find('.meter'); 13 | this.$meter.attr('data-bgcolor', this.$meter.css('background-color')); 14 | this.$meter.attr('data-fgcolor', this.$meter.css('color')); 15 | this.$meter.knob(); 16 | } 17 | componentDidUpdate() { 18 | this.$meter.val(this.state.value); 19 | this.$meter.trigger('change'); 20 | } 21 | render() { 22 | return ( 23 |
    24 |

    {this.props.title}

    25 | 33 |

    {this.updatedAt(this.state.updated_at)}

    34 |
    35 | ); 36 | } 37 | }; 38 | 39 | Widget.mount(Meter); 40 | export default Meter; 41 | -------------------------------------------------------------------------------- /widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #9c4274; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | 9 | $meter-background: darken($background-color, 15%); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: #fff; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | .updated-at { 32 | color: rgba(0, 0, 0, 0.3); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /widgets/number/number.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Widget} from 'kitto'; 3 | 4 | import './number.scss'; 5 | 6 | class Number extends Widget { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { value: 0 }; 11 | this.lastValue = 0; 12 | } 13 | componentWillUpdate(_props, lastState) { 14 | this.lastValue = this.state.value; 15 | } 16 | decorateValue(value) { 17 | let number = this.shortenedNumber(this.state.value); 18 | 19 | return this.append(this.prepend(number, this.props.prefix), this.props.suffix); 20 | } 21 | arrow() { 22 | if (this.state.value > this.lastValue) { 23 | return (); 24 | } else { 25 | return (); 26 | } 27 | } 28 | difference() { 29 | if (this.lastValue && this.lastValue !== 0) { 30 | let normalized = (this.state.value - this.lastValue) / this.lastValue * 100; 31 | return `${Math.abs(Math.round(normalized))}%` 32 | } else { 33 | return ''; 34 | } 35 | } 36 | changeRate() { 37 | if (this.props.changerate == "off") { return; } 38 | 39 | return ( 40 |

    41 | {this.arrow()}{this.difference()} 42 |

    43 | ); 44 | } 45 | render() { 46 | return ( 47 |
    48 |

    {this.props.title}

    49 |

    {this.decorateValue(this.state.value)}

    50 |

    {this.props.moreinfo}

    51 | {this.changeRate()} 52 |

    {this.updatedAt(this.state.updated_at)}

    53 |
    54 | ); 55 | } 56 | }; 57 | 58 | Widget.mount(Number); 59 | export default Number; 60 | -------------------------------------------------------------------------------- /widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 30px; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | .updated-at { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /widgets/text/text.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Widget} from 'kitto'; 3 | 4 | import './text.scss'; 5 | 6 | class Text extends Widget { 7 | status() { 8 | if (!this.state.status) { return ""; } 9 | 10 | return`status-${this.state.status}`; 11 | } 12 | 13 | render() { 14 | return ( 15 |
    16 |

    {this.state.title || this.props.title}

    17 |

    {this.state.text || this.props.text}

    18 |

    {this.state.moreinfo || this.props.moreinfo}

    19 |

    {this.updatedAt(this.state.updated_at)}

    20 |
    21 | ); 22 | } 23 | }; 24 | 25 | Widget.mount(Text); 26 | export default Text; 27 | -------------------------------------------------------------------------------- /widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | .updated-at { 25 | color: rgba(255, 255, 255, 0.7); 26 | } 27 | 28 | 29 | &.large h3 { 30 | font-size: 65px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /widgets/time_took/time_took.js: -------------------------------------------------------------------------------- 1 | import './time_took.scss'; 2 | -------------------------------------------------------------------------------- /widgets/time_took/time_took.scss: -------------------------------------------------------------------------------- 1 | .widget-list { 2 | &.time-took { 3 | background-color: #9c4274; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /widgets/travis/travis.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Widget} from 'kitto'; 3 | 4 | import './travis.scss'; 5 | 6 | class ListItem extends React.Component { 7 | render() { 8 | return ( 9 |
  • 10 | {this.props.label} 11 | 12 |
  • 13 | ); 14 | } 15 | } 16 | 17 | Widget.mount(class Travis extends Widget { 18 | renderItems(items) { 19 | return items.map((item, i) => { 20 | return ; 21 | }); 22 | } 23 | renderList(items) { 24 | return this.props.unordered ? :
      {items}
    ; 25 | } 26 | render() { 27 | return ( 28 |
    29 |

    {this.props.title}

    30 |

    {this.props.text}

    31 |
      32 | {this.renderList(this.renderItems(this.state.items || []))} 33 |
    34 |

    {this.updatedAt(this.state.updated_at)}

    35 |
    36 | ); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /widgets/travis/travis.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #f1f1f1; 5 | $value-color: #fff; 6 | 7 | $title-color: #666; 8 | $label-color: #555; 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-list styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-travis { 14 | vertical-align: top; 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | ol, ul { 22 | margin: 0 15px; 23 | text-align: left; 24 | color: $label-color; 25 | } 26 | 27 | ol { 28 | list-style-position: inside; 29 | } 30 | 31 | li { 32 | margin-bottom: 5px; 33 | } 34 | 35 | .list-nostyle { 36 | list-style: none; 37 | } 38 | 39 | .label { 40 | color: $label-color; 41 | font-size: 13px; 42 | } 43 | 44 | .value { 45 | float: right; 46 | margin-left: 12px; 47 | font-weight: 600; 48 | color: $value-color; 49 | font-size: 20px; 50 | } 51 | 52 | .success { 53 | @extend .value; 54 | color: green; 55 | } 56 | 57 | .failure{ 58 | @extend .value; 59 | color: red; 60 | } 61 | 62 | .unknown{ 63 | @extend .value; 64 | color: gray; 65 | } 66 | 67 | .updated-at { 68 | color: rgba(0, 0, 0, 0.3); 69 | } 70 | } 71 | --------------------------------------------------------------------------------