br-nanite 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +201 -0
- data/README.rdoc +356 -0
- data/Rakefile +74 -0
- data/TODO +24 -0
- data/bin/nanite-admin +59 -0
- data/bin/nanite-agent +30 -0
- data/bin/nanite-mapper +22 -0
- data/lib/nanite.rb +58 -0
- data/lib/nanite/actor.rb +64 -0
- data/lib/nanite/actor_registry.rb +25 -0
- data/lib/nanite/admin.rb +147 -0
- data/lib/nanite/agent.rb +163 -0
- data/lib/nanite/amqp.rb +47 -0
- data/lib/nanite/cluster.rb +110 -0
- data/lib/nanite/config.rb +80 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +12 -0
- data/lib/nanite/dispatcher.rb +59 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +50 -0
- data/lib/nanite/log.rb +23 -0
- data/lib/nanite/mapper.rb +214 -0
- data/lib/nanite/packets.rb +192 -0
- data/lib/nanite/reaper.rb +30 -0
- data/lib/nanite/serializer.rb +40 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +51 -0
- metadata +104 -0
data/Rakefile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require "spec/rake/spectask"
|
4
|
+
begin; require 'rubygems'; rescue LoadError; end
|
5
|
+
begin
|
6
|
+
require 'hanna/rdoctask'
|
7
|
+
rescue LoadError
|
8
|
+
require 'rake/rdoctask'
|
9
|
+
end
|
10
|
+
require 'rake/clean'
|
11
|
+
|
12
|
+
GEM = "br-nanite"
|
13
|
+
VER = "0.3.0"
|
14
|
+
AUTHOR = "Ezra Zygmuntowicz"
|
15
|
+
EMAIL = "ezra@engineyard.com"
|
16
|
+
HOMEPAGE = "http://github.com/ezmobius/nanite"
|
17
|
+
SUMMARY = "self assembling fabric of ruby daemons"
|
18
|
+
|
19
|
+
Dir.glob('tasks/*.rake').each { |r| Rake.application.add_import r }
|
20
|
+
|
21
|
+
spec = Gem::Specification.new do |s|
|
22
|
+
s.name = GEM
|
23
|
+
s.version = ::VER
|
24
|
+
s.platform = Gem::Platform::RUBY
|
25
|
+
s.has_rdoc = true
|
26
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE", 'TODO']
|
27
|
+
s.summary = SUMMARY
|
28
|
+
s.description = s.summary
|
29
|
+
s.author = AUTHOR
|
30
|
+
s.email = EMAIL
|
31
|
+
s.homepage = HOMEPAGE
|
32
|
+
|
33
|
+
s.bindir = "bin"
|
34
|
+
s.executables = %w( nanite-agent nanite-mapper nanite-admin )
|
35
|
+
|
36
|
+
s.add_dependency "extlib"
|
37
|
+
s.add_dependency('amqp', '>= 0.6.0')
|
38
|
+
|
39
|
+
s.require_path = 'lib'
|
40
|
+
s.files = %w(LICENSE README.rdoc Rakefile TODO) + Dir.glob("{lib,bin,specs}/**/*")
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
44
|
+
pkg.gem_spec = spec
|
45
|
+
end
|
46
|
+
|
47
|
+
task :install => [:package] do
|
48
|
+
sh %{sudo gem install pkg/#{GEM}-#{VER}}
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Run unit specs"
|
52
|
+
Spec::Rake::SpecTask.new do |t|
|
53
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
54
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Generate RDoc documentation'
|
58
|
+
Rake::RDocTask.new do |rd|
|
59
|
+
rd.title = spec.name
|
60
|
+
rd.rdoc_dir = 'rdoc'
|
61
|
+
rd.main = "README.rdoc"
|
62
|
+
rd.rdoc_files.include("lib/**/*.rb", *spec.extra_rdoc_files)
|
63
|
+
end
|
64
|
+
CLOBBER.include(:clobber_rdoc)
|
65
|
+
|
66
|
+
desc 'Generate and open documentation'
|
67
|
+
task :docs => :rdoc do
|
68
|
+
case RUBY_PLATFORM
|
69
|
+
when /darwin/ ; sh 'open rdoc/index.html'
|
70
|
+
when /mswin|mingw/ ; sh 'start rdoc\index.html'
|
71
|
+
else
|
72
|
+
sh 'firefox rdoc/index.html'
|
73
|
+
end
|
74
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
TODO:
|
2
|
+
|
3
|
+
- The examples/crew.rb file is pointing towards a hard coded user dir. Needs to be
|
4
|
+
documented as part of a working example.
|
5
|
+
- examples/async_rack_front/async_rack_front.ru needs to be documented and verified working.
|
6
|
+
|
7
|
+
Ian:
|
8
|
+
- Sync Mapper/Agent#start with nanite-mapper/agent
|
9
|
+
- Update docs for Agent#start and Mapper#start
|
10
|
+
- Update docs in nanite-agent and nanite-mapper
|
11
|
+
- Ensure file transfer works
|
12
|
+
- Check secure stuff still works
|
13
|
+
- Check custom status_proc works
|
14
|
+
- Check documentation, only document public methods
|
15
|
+
- ensure the removal of threaded_actors option doesn't cause shit to block
|
16
|
+
|
17
|
+
- Look into using EM deferables for actors dispatch.
|
18
|
+
- Integration specs that spawn a small cluster of nanites
|
19
|
+
- Rename Ping to Status
|
20
|
+
- request/push should take *args for payload?
|
21
|
+
|
22
|
+
Maybe:
|
23
|
+
- Make mapper queue durable and Results respect :persistent flag on the request
|
24
|
+
- Add a global result received callback
|
data/bin/nanite-admin
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# To work without being installed as a gem:
|
4
|
+
libdir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$:.unshift libdir unless $:.include? libdir
|
6
|
+
|
7
|
+
require 'nanite'
|
8
|
+
require 'nanite/admin'
|
9
|
+
require 'eventmachine'
|
10
|
+
require 'thin'
|
11
|
+
# IMPORTANT!
|
12
|
+
# You need raggi's patched async version of Thin at the moment to use
|
13
|
+
# the nanite-admin tool.
|
14
|
+
#
|
15
|
+
# raggi's Git repo contains a branch called 'async_for_rack' which contains the
|
16
|
+
# version of Thin you want to install. raggi has apparently removed the 'master'
|
17
|
+
# branch of his Git repo so you may see a warning like that shown below.
|
18
|
+
#
|
19
|
+
# git clone git://github.com/raggi/thin.git thin-raggi-async
|
20
|
+
# ...
|
21
|
+
# warning: remote HEAD refers to nonexistent ref, unable to checkout. <<== IGNORE THIS
|
22
|
+
#
|
23
|
+
# cd thin-raggi-async/
|
24
|
+
# git checkout --track -b async_for_rack origin/async_for_rack
|
25
|
+
# warning: You appear to be on a branch yet to be born. <<== IGNORE THIS
|
26
|
+
# warning: Forcing checkout of origin/async_for_rack. <<== IGNORE THIS
|
27
|
+
# Branch async_for_rack set up to track remote branch refs/remotes/origin/async_for_rack.
|
28
|
+
# Switched to a new branch "async_for_rack"
|
29
|
+
|
30
|
+
# run : 'rake install' to build and install the Thin gem
|
31
|
+
# cd <NANITE>
|
32
|
+
# ./bin/nanite-admin
|
33
|
+
|
34
|
+
# When you need to update this Thin install you should be able to do a 'git pull' on the
|
35
|
+
# "async_for_rack" branch.
|
36
|
+
|
37
|
+
require File.dirname(__FILE__) + '/../lib/nanite'
|
38
|
+
require 'yaml'
|
39
|
+
require "optparse"
|
40
|
+
|
41
|
+
include Nanite::CommonConfig
|
42
|
+
|
43
|
+
options = {}
|
44
|
+
|
45
|
+
opts = OptionParser.new do |opts|
|
46
|
+
opts.banner = "Usage: nanite-admin [-flags] [argument]"
|
47
|
+
opts.define_head "Nanite Admin: a basic control interface for your nanite cluster."
|
48
|
+
opts.separator '*'*80
|
49
|
+
|
50
|
+
setup_mapper_options(opts, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.parse!
|
54
|
+
|
55
|
+
EM.run do
|
56
|
+
Nanite.start_mapper(options)
|
57
|
+
puts "starting nanite-admin"
|
58
|
+
Rack::Handler::Thin.run(Nanite::Admin.new(Nanite.mapper), :Port => 4000)
|
59
|
+
end
|
data/bin/nanite-agent
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../lib/nanite'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
include Nanite::CommonConfig
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
opts = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: nanite-agent [-flag] [argument]"
|
12
|
+
opts.define_head "Nanite Agent: ruby process that acts upon messages passed to it by a mapper."
|
13
|
+
opts.separator '*'*80
|
14
|
+
|
15
|
+
setup_common_options(opts, options, 'agent')
|
16
|
+
|
17
|
+
opts.on("-n", "--nanite NANITE_ROOT", "Specify the root of your nanite agent project.") do |nanite|
|
18
|
+
options[:root] = nanite
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("--ping-time PINGTIME", "Specify how often the agents contacts the mapper") do |ping|
|
22
|
+
options[:ping_time] = ping
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.parse!
|
27
|
+
|
28
|
+
EM.run do
|
29
|
+
Nanite.start_agent(options)
|
30
|
+
end
|
data/bin/nanite-mapper
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../lib/nanite'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
include Nanite::CommonConfig
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
opts = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: nanite-mapper [-flags] [argument]"
|
12
|
+
opts.define_head "Nanite Mapper: clustered head unit for self assembling cluster of ruby processes."
|
13
|
+
opts.separator '*'*80
|
14
|
+
|
15
|
+
setup_mapper_options(opts, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.parse!
|
19
|
+
|
20
|
+
EM.run do
|
21
|
+
Nanite.start_mapper(options)
|
22
|
+
end
|
data/lib/nanite.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'amqp'
|
3
|
+
require 'mq'
|
4
|
+
require 'json'
|
5
|
+
require 'logger'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
$:.unshift File.dirname(__FILE__)
|
9
|
+
require 'nanite/amqp'
|
10
|
+
require 'nanite/util'
|
11
|
+
require 'nanite/config'
|
12
|
+
require 'nanite/packets'
|
13
|
+
require 'nanite/identity'
|
14
|
+
require 'nanite/console'
|
15
|
+
require 'nanite/daemonize'
|
16
|
+
require 'nanite/job'
|
17
|
+
require 'nanite/mapper'
|
18
|
+
require 'nanite/actor'
|
19
|
+
require 'nanite/actor_registry'
|
20
|
+
require 'nanite/streaming'
|
21
|
+
require 'nanite/dispatcher'
|
22
|
+
require 'nanite/agent'
|
23
|
+
require 'nanite/cluster'
|
24
|
+
require 'nanite/reaper'
|
25
|
+
require 'nanite/serializer'
|
26
|
+
require 'nanite/log'
|
27
|
+
|
28
|
+
module Nanite
|
29
|
+
VERSION = '0.3.0' unless defined?(Nanite::VERSION)
|
30
|
+
|
31
|
+
class MapperNotRunning < StandardError; end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_reader :mapper, :agent
|
35
|
+
|
36
|
+
def start_agent(options = {})
|
37
|
+
@agent = Nanite::Agent.start(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def start_mapper(options = {})
|
41
|
+
@mapper = Nanite::Mapper.start(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(*args, &blk)
|
45
|
+
ensure_mapper
|
46
|
+
@mapper.request(*args, &blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
def push(*args)
|
50
|
+
ensure_mapper
|
51
|
+
@mapper.push(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ensure_mapper
|
55
|
+
raise MapperNotRunning.new('A mapper needs to be started via Nanite.start_mapper') unless @mapper
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/nanite/actor.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Nanite
|
2
|
+
# This mixin provides Nanite actor functionality.
|
3
|
+
#
|
4
|
+
# To use it simply include it your class containing the functionality to be exposed:
|
5
|
+
#
|
6
|
+
# class Foo
|
7
|
+
# include Nanite::Actor
|
8
|
+
# expose :bar
|
9
|
+
#
|
10
|
+
# def bar(payload)
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# end
|
15
|
+
module Actor
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval do
|
19
|
+
include Nanite::Actor::InstanceMethods
|
20
|
+
extend Nanite::Actor::ClassMethods
|
21
|
+
end # base.class_eval
|
22
|
+
end # self.included
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def default_prefix
|
26
|
+
to_s.to_const_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def expose(*meths)
|
30
|
+
@exposed ||= []
|
31
|
+
meths.each do |meth|
|
32
|
+
@exposed << meth unless @exposed.include?(meth)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def provides_for(prefix)
|
37
|
+
return [] unless @exposed
|
38
|
+
@exposed.map {|meth| "/#{prefix}/#{meth}".squeeze('/')}
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_exception(proc = nil, &blk)
|
42
|
+
raise 'No callback provided for on_exception' unless proc || blk
|
43
|
+
if Nanite::Actor == self
|
44
|
+
raise 'Method name callbacks cannot be used on the Nanite::Actor superclass' if Symbol === proc || String === proc
|
45
|
+
@superclass_exception_callback = proc || blk
|
46
|
+
else
|
47
|
+
@instance_exception_callback = proc || blk
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def superclass_exception_callback
|
52
|
+
@superclass_exception_callback
|
53
|
+
end
|
54
|
+
|
55
|
+
def instance_exception_callback
|
56
|
+
@instance_exception_callback
|
57
|
+
end
|
58
|
+
end # ClassMethods
|
59
|
+
|
60
|
+
module InstanceMethods
|
61
|
+
end # InstanceMethods
|
62
|
+
|
63
|
+
end # Actor
|
64
|
+
end # Nanite
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nanite
|
2
|
+
class ActorRegistry
|
3
|
+
attr_reader :actors, :log
|
4
|
+
|
5
|
+
def initialize(log)
|
6
|
+
@log = log
|
7
|
+
@actors = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def register(actor, prefix)
|
11
|
+
raise ArgumentError, "#{actor.inspect} is not a Nanite::Actor subclass instance" unless Nanite::Actor === actor
|
12
|
+
log.info("Registering #{actor.inspect} with prefix #{prefix.inspect}")
|
13
|
+
prefix ||= actor.class.default_prefix
|
14
|
+
actors[prefix.to_s] = actor
|
15
|
+
end
|
16
|
+
|
17
|
+
def services
|
18
|
+
actors.map {|prefix, actor| actor.class.provides_for(prefix) }.flatten.uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
def actor_for(prefix)
|
22
|
+
actor = actors[prefix]
|
23
|
+
end
|
24
|
+
end # ActorRegistry
|
25
|
+
end # Nanite
|
data/lib/nanite/admin.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Nanite
|
4
|
+
# This is a Rack app for nanite-admin. You need to have an async capable
|
5
|
+
# version of Thin installed for this to work. See bin/nanite-admin for install
|
6
|
+
# instructions.
|
7
|
+
class Admin
|
8
|
+
def initialize(mapper)
|
9
|
+
@mapper = mapper
|
10
|
+
end
|
11
|
+
|
12
|
+
AsyncResponse = [-1, {}, []].freeze
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
req = Rack::Request.new(env)
|
16
|
+
if cmd = req.params['command']
|
17
|
+
@command = cmd
|
18
|
+
@selection = req.params['type'] if req.params['type']
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
case @selection
|
22
|
+
when 'least_loaded', 'random', 'all', 'rr'
|
23
|
+
options[:selector] = @selection
|
24
|
+
else
|
25
|
+
options[:target] = @selection
|
26
|
+
end
|
27
|
+
|
28
|
+
@mapper.request(cmd, req.params['payload'], options) do |response|
|
29
|
+
env['async.callback'].call [200, {'Content-Type' => 'text/html'}, [layout(ul(response))]]
|
30
|
+
end
|
31
|
+
AsyncResponse
|
32
|
+
else
|
33
|
+
[200, {'Content-Type' => 'text/html'}, layout]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def services
|
38
|
+
buf = "<select name='command'>"
|
39
|
+
@mapper.cluster.nanites.map{|n,s| s[:services] }.flatten.each do |srv|
|
40
|
+
buf << "<option value='#{srv}' #{@command == srv ? 'selected="true"' : ''}>#{srv}</option>"
|
41
|
+
end
|
42
|
+
buf << "</select>"
|
43
|
+
buf
|
44
|
+
end
|
45
|
+
|
46
|
+
def ul(hash)
|
47
|
+
buf = "<ul>"
|
48
|
+
hash.each do |k,v|
|
49
|
+
buf << "<li><div class=\"nanite\">#{k}:</div><div class=\"response\">#{v.inspect}</div></li>"
|
50
|
+
end
|
51
|
+
buf << "</ul>"
|
52
|
+
buf
|
53
|
+
end
|
54
|
+
|
55
|
+
def layout(content=nil)
|
56
|
+
%Q{
|
57
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
58
|
+
<html xmlns='http://www.w3.org/1999/xhtml'>
|
59
|
+
<head>
|
60
|
+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
61
|
+
<meta content='en' http-equiv='Content-Language' />
|
62
|
+
<meta content='Engineyard' name='author' />
|
63
|
+
<title>Nanite Control Tower</title>
|
64
|
+
|
65
|
+
<!-- Google AJAX Libraries API -->
|
66
|
+
<script src="http://www.google.com/jsapi"></script>
|
67
|
+
<script type="text/javascript">
|
68
|
+
google.load("jquery", "1");
|
69
|
+
</script>
|
70
|
+
|
71
|
+
<script type="text/javascript">
|
72
|
+
$(document).ready(function(){
|
73
|
+
|
74
|
+
// set the focus to the payload field
|
75
|
+
$("#payload").focus();
|
76
|
+
|
77
|
+
});
|
78
|
+
</script>
|
79
|
+
|
80
|
+
<style>
|
81
|
+
body {margin: 0; font-family: verdana; background-color: #fcfcfc;}
|
82
|
+
ul {margin: 0; padding: 0; margin-left: 10px}
|
83
|
+
li {list-style-type: none; margin-bottom: 6px}
|
84
|
+
li .nanite {font-weight: bold; font-size: 12px}
|
85
|
+
li .response {padding: 8px}
|
86
|
+
h1, h2, h3 {margin-top: none; padding: none; margin-left: 40px;}
|
87
|
+
h1 {font-size: 22px; margin-top: 40px; margin-bottom: 30px; border-bottom: 1px solid #ddd; padding-bottom: 6px;
|
88
|
+
margin-right: 40px}
|
89
|
+
h2 {font-size: 16px;}
|
90
|
+
h3 {margin-left: 0; font-size: 14px}
|
91
|
+
.section {border: 1px solid #ccc; background-color: #fefefe; padding: 10px; margin: 20px 40px; padding: 20px;
|
92
|
+
font-size: 14px}
|
93
|
+
#footer {text-align: center; color: #AAA; font-size: 12px}
|
94
|
+
</style>
|
95
|
+
|
96
|
+
</head>
|
97
|
+
|
98
|
+
<body>
|
99
|
+
<div id="header">
|
100
|
+
<h1>Nanite Control Tower</h1>
|
101
|
+
</div>
|
102
|
+
|
103
|
+
<h2>#{@mapper.options[:vhost]}</h2>
|
104
|
+
<div class="section">
|
105
|
+
<form method="post" action="/">
|
106
|
+
<input type="hidden" value="POST" name="_method"/>
|
107
|
+
|
108
|
+
<label>Send</label>
|
109
|
+
<select name="type">
|
110
|
+
<option #{@selection == 'least_loaded' ? 'selected="true"' : ''} value="least_loaded">the least loaded nanite</option>
|
111
|
+
<option #{@selection == 'random' ? 'selected="true"' : ''} value="random">a random nanite</option>
|
112
|
+
<option #{@selection == 'all' ? 'selected="true"' : ''} value="all">all nanites</option>
|
113
|
+
<option #{@selection == 'rr' ? 'selected="true"' : ''} value="rr">a nanite chosen by round robin</option>
|
114
|
+
#{@mapper.cluster.nanites.map {|k,v| "<option #{@selection == k ? 'selected="true"' : ''} value='#{k}'>#{k}</option>" }.join}
|
115
|
+
</select>
|
116
|
+
|
117
|
+
<label>providing service</label>
|
118
|
+
#{services}
|
119
|
+
|
120
|
+
<label>the payload</label>
|
121
|
+
<input type="text" class="text" name="payload" id="payload"/>
|
122
|
+
|
123
|
+
<input type="submit" class="submit" value="Go!" name="submit"/>
|
124
|
+
</form>
|
125
|
+
|
126
|
+
#{"<h3>Responses</h3>" if content}
|
127
|
+
#{content}
|
128
|
+
</div>
|
129
|
+
|
130
|
+
<h2>Running nanites</h2>
|
131
|
+
<div class="section">
|
132
|
+
#{"No nanites online." if @mapper.cluster.nanites.size == 0}
|
133
|
+
<ul>
|
134
|
+
#{@mapper.cluster.nanites.map {|k,v| "<li>identity : #{k}<br />load : #{v[:status]}<br />services : #{v[:services].inspect}</li>" }.join}
|
135
|
+
</ul>
|
136
|
+
</div>
|
137
|
+
<div id="footer">
|
138
|
+
Nanite #{Nanite::VERSION}
|
139
|
+
<br />
|
140
|
+
© 2009 a bunch of random geeks
|
141
|
+
</div>
|
142
|
+
</body>
|
143
|
+
</html>
|
144
|
+
}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|