mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 22:03:50 +03:00
Merge pull request #2875 from urbit/m/debug-dashboard
Initial debug dashboard
This commit is contained in:
commit
016fd9101c
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:de3b622f4ce90a9c2ce73fa233ba356e239a64d8aa4820ea7f48663110aa3fdd
|
||||
size 18821765
|
||||
oid sha256:6504214aef3dff2d5e9a477239e9f9cafa0e11855be6b6a2b704e7164bb8cb37
|
||||
size 12901460
|
||||
|
898
pkg/arvo/app/dbug.hoon
Normal file
898
pkg/arvo/app/dbug.hoon
Normal file
@ -0,0 +1,898 @@
|
||||
:: dbug: debug dashboard server
|
||||
::
|
||||
/- spider
|
||||
/+ server, default-agent, verb, dbug
|
||||
::
|
||||
|%
|
||||
+$ state-0 [%0 passcode=(unit @t)]
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
::
|
||||
=| state-0
|
||||
=* state -
|
||||
::
|
||||
%+ verb |
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
do ~(. +> bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
:_ this
|
||||
[%pass /connect %arvo %e %connect [~ /'~debug'] dap.bowl]~
|
||||
::
|
||||
++ on-save !>(state)
|
||||
::
|
||||
++ on-load
|
||||
|= old=vase
|
||||
^- (quip card _this)
|
||||
[~ this(state !<(state-0 old))]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
?. ?=([%http-response *] path)
|
||||
(on-watch:def path)
|
||||
[~ this]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?: ?=(%noun mark)
|
||||
?> (team:title [our src]:bowl)
|
||||
=/ code !<((unit @t) vase)
|
||||
=/ msg=tape
|
||||
?~ code
|
||||
"Removing passcode access for debug interface."
|
||||
"""
|
||||
Enabling passcode access for debug interface. Anyone with this code can
|
||||
view your applications' state, the people you've talked to, etc. Only
|
||||
share with people you trust. To disable, run :dbug ~
|
||||
"""
|
||||
%- (slog leaf+msg ~)
|
||||
[~ this(passcode code)]
|
||||
?. ?=(%handle-http-request mark)
|
||||
(on-poke:def mark vase)
|
||||
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
|
||||
:_ this
|
||||
%+ give-simple-payload:app:server eyre-id
|
||||
%+ authorize-http-request:do inbound-request
|
||||
handle-http-request:do
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
?. ?=([%e %bound *] sign-arvo)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
~? !accepted.sign-arvo
|
||||
[dap.bowl "bind rejected!" binding.sign-arvo]
|
||||
[~ this]
|
||||
::
|
||||
++ on-peek on-peek:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
::
|
||||
:: serving
|
||||
::
|
||||
++ authorize-http-request
|
||||
=, server
|
||||
:: if no passcode configured, only allow host ship to view
|
||||
::
|
||||
?~ passcode require-authorization:app
|
||||
|= $: =inbound-request:eyre
|
||||
handler=$-(inbound-request:eyre simple-payload:http)
|
||||
==
|
||||
?: authenticated.inbound-request
|
||||
(handler inbound-request)
|
||||
:: else, allow randos access,
|
||||
:: on the condition they provide a correct ?passcode= url parameter
|
||||
::
|
||||
=; pass=(unit @t)
|
||||
?: =(passcode pass)
|
||||
(handler inbound-request)
|
||||
(require-authorization:app inbound-request handler)
|
||||
=/ from-url=(unit @t)
|
||||
=- (~(get by -) 'passcode')
|
||||
%- ~(gas by *(map @t @t))
|
||||
args:(parse-request-line url.request.inbound-request)
|
||||
?^ from-url from-url
|
||||
:: try the referer field instead
|
||||
::
|
||||
=/ ref-url=(unit @t)
|
||||
(get-header:http 'referer' header-list.request.inbound-request)
|
||||
?~ ref-url ~
|
||||
?~ (find "passcode={(trip u.passcode)}" (trip u.ref-url)) ~
|
||||
passcode
|
||||
::
|
||||
++ handle-http-request
|
||||
=, server
|
||||
|= =inbound-request:eyre
|
||||
^- simple-payload:http
|
||||
=/ =request-line
|
||||
%- parse-request-line
|
||||
url.request.inbound-request
|
||||
=* req-head header-list.request.inbound-request
|
||||
::TODO handle POST
|
||||
?. ?=(%'GET' method.request.inbound-request)
|
||||
not-found:gen
|
||||
(handle-get-request req-head request-line)
|
||||
::
|
||||
++ handle-get-request
|
||||
=, server
|
||||
|= [headers=header-list:http request-line]
|
||||
^- simple-payload:http
|
||||
=? site ?=([%'~debug' *] site) t.site
|
||||
?~ ext
|
||||
$(ext `%html, site [%index ~]) ::NOTE hack
|
||||
:: if not json, serve static file
|
||||
::
|
||||
?. ?=([~ %json] ext)
|
||||
=/ file=(unit octs)
|
||||
(get-file-at /app/debug site u.ext)
|
||||
?~ file not-found:gen
|
||||
?+ u.ext not-found:gen
|
||||
%html (html-response:gen u.file)
|
||||
%js (js-response:gen u.file)
|
||||
%css (css-response:gen u.file)
|
||||
%png (png-response:gen u.file)
|
||||
==
|
||||
:: get data matching the json and convert it
|
||||
::
|
||||
=; json=(unit json)
|
||||
?~ json not-found:gen
|
||||
%- json-response:gen
|
||||
=, html
|
||||
(as-octt:mimes (en-json u.json))
|
||||
=, enjs:format
|
||||
?+ site ~
|
||||
:: /apps.json: {appname: running?}
|
||||
::
|
||||
[%apps ~]
|
||||
%- some
|
||||
%- pairs
|
||||
%+ turn all:apps
|
||||
|= app=term
|
||||
[app b+(running:apps app)]
|
||||
::
|
||||
:: /app/[appname]...
|
||||
::
|
||||
[%app @ *]
|
||||
=* app i.t.site
|
||||
::TODO ?. (dbugable:apps app) ~
|
||||
=/ rest=^path t.t.site
|
||||
?+ rest ~
|
||||
:: /app/[appname].json: {state: }
|
||||
::
|
||||
~
|
||||
%- some
|
||||
%- pairs
|
||||
:~ :- 'simpleState'
|
||||
%- tank
|
||||
=; head=(unit ^tank)
|
||||
(fall head leaf+"unversioned")
|
||||
:: try to print the state version
|
||||
::
|
||||
=/ version=(unit vase)
|
||||
(slew 2 (state:apps app))
|
||||
?~ version ~
|
||||
?. ?=(%atom -.p.u.version) ~
|
||||
`(sell u.version)
|
||||
::
|
||||
:- 'subscriptions'
|
||||
%- pairs
|
||||
=+ (subscriptions:apps app)
|
||||
|^ ~['in'^(incoming in) 'out'^(outgoing out)]
|
||||
::
|
||||
++ incoming
|
||||
|= =bitt:gall
|
||||
^- json
|
||||
:- %a
|
||||
%+ turn ~(tap by bitt)
|
||||
|= [d=duct [s=^ship p=^path]]
|
||||
%- pairs
|
||||
:~ 'duct'^a+(turn d path)
|
||||
'ship'^(ship s)
|
||||
'path'^(path p)
|
||||
==
|
||||
::
|
||||
++ outgoing
|
||||
|= =boat:gall
|
||||
^- json
|
||||
:- %a
|
||||
%+ turn ~(tap by boat)
|
||||
|= [[w=wire s=^ship t=term] [a=? p=^path]]
|
||||
%- pairs
|
||||
:~ 'wire'^(path w)
|
||||
'ship'^(ship s)
|
||||
'app'^s+t
|
||||
'acked'^b+a
|
||||
'path'^(path p)
|
||||
==
|
||||
--
|
||||
==
|
||||
::
|
||||
:: /app/[appname]/state.json
|
||||
:: /app/[appname]/state/[query].json
|
||||
::
|
||||
[%state ?(~ [@ ~])]
|
||||
%- some
|
||||
=- (pairs 'state'^(tank -) ~)
|
||||
%+ state-at:apps app
|
||||
?~ t.rest ~
|
||||
(slaw %t i.t.rest)
|
||||
==
|
||||
::
|
||||
:: /spider.json
|
||||
::
|
||||
[%spider %threads ~]
|
||||
%- some
|
||||
:: turn flat stack descriptors into object (tree) representing stacks
|
||||
::
|
||||
|^ (tree-to-json build-thread-tree)
|
||||
::
|
||||
+$ tree
|
||||
$~ ~
|
||||
(map tid:spider tree)
|
||||
::
|
||||
++ build-thread-tree
|
||||
%+ roll tree:threads
|
||||
|= [stack=(list tid:spider) =tree]
|
||||
?~ stack tree
|
||||
%+ ~(put by tree) i.stack
|
||||
%_ $
|
||||
stack t.stack
|
||||
tree (~(gut by tree) i.stack ~)
|
||||
==
|
||||
::
|
||||
++ tree-to-json
|
||||
|= =tree
|
||||
o+(~(run by tree) tree-to-json)
|
||||
--
|
||||
::
|
||||
:: /azimuth/status
|
||||
::
|
||||
:: /ames/peer.json
|
||||
::
|
||||
[%ames %peer ~]
|
||||
=/ [known=(list [^ship *]) alien=(list [^ship *])]
|
||||
%+ skid ~(tap by peers:v-ames)
|
||||
|= [^ship kind=?(%alien %known)]
|
||||
?=(%known kind)
|
||||
%- some
|
||||
%- pairs
|
||||
::NOTE would do (cork head ship) but can't get that to compile...
|
||||
:~ 'known'^a+(turn (turn known head) ship)
|
||||
'alien'^a+(turn (turn alien head) ship)
|
||||
==
|
||||
::
|
||||
:: /ames/peer/[shipname].json
|
||||
::
|
||||
[%ames %peer @ ~]
|
||||
=/ who=^ship
|
||||
(rash i.t.t.site fed:ag)
|
||||
%- some
|
||||
=, v-ames
|
||||
(peer-to-json (peer who))
|
||||
::
|
||||
:: /behn/timers.json
|
||||
::
|
||||
[%behn %timers ~]
|
||||
%- some
|
||||
:- %a
|
||||
%+ turn timers:v-behn
|
||||
|= [date=@da =duct]
|
||||
%- pairs
|
||||
:~ 'date'^(time date)
|
||||
'duct'^a+(turn duct path)
|
||||
==
|
||||
::
|
||||
:: /clay/commits.json
|
||||
::
|
||||
[%clay %commits ~]
|
||||
(some commits-json:v-clay)
|
||||
::
|
||||
:: /eyre/bindings.json
|
||||
::
|
||||
[%eyre %bindings ~]
|
||||
%- some
|
||||
:- %a
|
||||
%+ turn bindings:v-eyre
|
||||
=, eyre
|
||||
|= [binding =duct =action]
|
||||
%- pairs
|
||||
:~ 'location'^s+(cat 3 (fall site '*') (spat path))
|
||||
'action'^(render-action:v-eyre action)
|
||||
==
|
||||
::
|
||||
:: /eyre/connections.json
|
||||
::
|
||||
[%eyre %connections ~]
|
||||
%- some
|
||||
:- %a
|
||||
%+ turn ~(tap by connections:v-eyre)
|
||||
|= [=duct outstanding-connection:eyre]
|
||||
%- pairs
|
||||
:~ 'duct'^a+(turn duct path)
|
||||
'action'^(render-action:v-eyre action)
|
||||
::
|
||||
:- 'request'
|
||||
%- pairs
|
||||
=, inbound-request
|
||||
:~ 'authenticated'^b+authenticated
|
||||
'secure'^b+secure
|
||||
'source'^s+(scot %if +.address)
|
||||
:: ?- -.address
|
||||
:: %ipv4 %if
|
||||
:: %ipv6 %is
|
||||
:: ==
|
||||
==
|
||||
::
|
||||
:- 'response'
|
||||
%- pairs
|
||||
:~ 'sent'^(numb bytes-sent)
|
||||
::
|
||||
:- 'header'
|
||||
?~ response-header ~
|
||||
=, u.response-header
|
||||
%- pairs
|
||||
:~ 'status-code'^(numb status-code)
|
||||
::
|
||||
:- 'headers'
|
||||
:- %a
|
||||
%+ turn headers
|
||||
|=([k=@t v=@t] s+:((cury cat 3) k ': ' v))
|
||||
==
|
||||
==
|
||||
==
|
||||
::
|
||||
:: /eyre/authentication.json
|
||||
::
|
||||
[%eyre %authentication ~]
|
||||
%- some
|
||||
:- %a
|
||||
%+ turn
|
||||
%+ sort ~(tap by sessions:auth-state:v-eyre)
|
||||
|= [[@uv a=@da] [@uv b=@da]]
|
||||
(gth a b)
|
||||
|= [cookie=@uv session:eyre]
|
||||
%- pairs
|
||||
:~ 'cookie'^s+(end 3 4 (rsh 3 2 (scot %x (shax cookie))))
|
||||
'expiry'^(time expiry-time)
|
||||
==
|
||||
::
|
||||
:: /eyre/channels.json
|
||||
::
|
||||
[%eyre %channels ~]
|
||||
%- some
|
||||
:- %a
|
||||
=+ channel-state:v-eyre
|
||||
%+ turn ~(tap by session)
|
||||
|= [key=@t channel:eyre]
|
||||
%- pairs
|
||||
:~ 'session'^s+key
|
||||
'connected'^b+!-.state
|
||||
'expiry'^?-(-.state %& (time date.p.state), %| ~)
|
||||
'next-id'^(numb next-id)
|
||||
'unacked'^a+(turn (sort (turn ~(tap in events) head) dor) numb)
|
||||
::
|
||||
:- 'subscriptions'
|
||||
:- %a
|
||||
%+ turn ~(tap by subscriptions)
|
||||
|= [=wire [=^ship app=term =^path *]]
|
||||
%- pairs
|
||||
:~ 'wire'^(^path wire)
|
||||
'ship'^(^ship ship)
|
||||
'app'^s+app
|
||||
'path'^(^path path)
|
||||
==
|
||||
==
|
||||
==
|
||||
::
|
||||
++ get-file-at
|
||||
|= [base=path file=path ext=@ta]
|
||||
^- (unit octs)
|
||||
?. ?=(?(%html %css %js %png) ext)
|
||||
~
|
||||
=/ =path
|
||||
:* (scot %p our.bowl)
|
||||
q.byk.bowl
|
||||
(scot %da now.bowl)
|
||||
(snoc (weld base file) ext)
|
||||
==
|
||||
?. .^(? %cu path) ~
|
||||
%- some
|
||||
%- as-octs:mimes:html
|
||||
.^(@ %cx path)
|
||||
::
|
||||
:: applications
|
||||
::
|
||||
++ apps
|
||||
|%
|
||||
++ all
|
||||
^- (list term)
|
||||
%+ murn
|
||||
(scry (list path) %ct %home /app)
|
||||
|= =path
|
||||
^- (unit term)
|
||||
?. ?=([%app @ %hoon ~] path) ~
|
||||
`i.t.path
|
||||
::
|
||||
++ running
|
||||
|= app=term
|
||||
(scry ? %gu app ~)
|
||||
::
|
||||
++ dbugable
|
||||
|= app=term
|
||||
^- ?
|
||||
!! ::TODO how to check if it supports the /dbug scries?
|
||||
::
|
||||
++ state
|
||||
|= app=term
|
||||
^- vase
|
||||
(scry-dbug vase app /state)
|
||||
::
|
||||
++ state-at
|
||||
|= [app=term what=(unit @t)]
|
||||
^- tank
|
||||
=/ state=vase (state app)
|
||||
?~ what (sell state)
|
||||
=/ result=(each vase tang)
|
||||
%- mule |.
|
||||
%+ slap
|
||||
(slop state !>([bowl=bowl ..zuse]))
|
||||
(ream u.what)
|
||||
?- -.result
|
||||
%& (sell p.result)
|
||||
%| (head p.result)
|
||||
==
|
||||
::
|
||||
++ subscriptions
|
||||
=, gall
|
||||
|= app=term
|
||||
^- [out=boat in=bitt]
|
||||
(scry-dbug ,[boat bitt] app /subscriptions)
|
||||
::
|
||||
++ scry-dbug
|
||||
|* [=mold app=term =path]
|
||||
(scry mold %gx app (snoc `^path`[%dbug path] %noun))
|
||||
::
|
||||
::TODO but why? we can't tell if it's on or not
|
||||
++ poke-verb-toggle
|
||||
|= app=term
|
||||
^- card
|
||||
(poke /verb/[app] app %verb !>(%loud))
|
||||
--
|
||||
::
|
||||
:: threads
|
||||
::
|
||||
++ threads
|
||||
|%
|
||||
::NOTE every (list tid:spider) represents a stack,
|
||||
:: with a unique tid at the end
|
||||
++ tree
|
||||
(scry (list (list tid:spider)) %gx %spider /tree/noun)
|
||||
::
|
||||
++ poke-kill
|
||||
|= =tid:spider
|
||||
^- card
|
||||
(poke /spider/kill/[tid] %spider %spider-stop !>([tid |]))
|
||||
--
|
||||
::
|
||||
:: ames
|
||||
::
|
||||
++ v-ames
|
||||
|%
|
||||
++ peers
|
||||
(scry (map ship ?(%alien %known)) %a %peers ~)
|
||||
::
|
||||
++ peer
|
||||
|= who=ship
|
||||
(scry ship-state:ames %a %peer /(scot %p who))
|
||||
::
|
||||
++ peer-to-json
|
||||
=, ames
|
||||
=, enjs:format
|
||||
|= =ship-state
|
||||
|^ ^- json
|
||||
%+ frond -.ship-state
|
||||
?- -.ship-state
|
||||
%alien (alien +.ship-state)
|
||||
%known (known +.ship-state)
|
||||
==
|
||||
::
|
||||
++ alien
|
||||
|= alien-agenda
|
||||
%- pairs
|
||||
:~ 'messages'^(numb (lent messages))
|
||||
'packets'^(numb ~(wyt in packets))
|
||||
'heeds'^(set-array heeds from-duct)
|
||||
==
|
||||
::
|
||||
:: json for known peer is structured to closely match the peer-state type.
|
||||
:: where an index is specified, the array is generally sorted by those.
|
||||
::
|
||||
:: { life: 123,
|
||||
:: route: { direct: true, lane: 'something' },
|
||||
:: qos: { kind: 'status', last-contact: 123456 }, // ms timestamp
|
||||
:: flows: { forward: [snd, rcv, ...], backward: [snd, rcv, ...] }
|
||||
:: -> snd:
|
||||
:: { bone: 123, // index
|
||||
:: duct: ['/paths', ...]
|
||||
:: current: 123,
|
||||
:: next: 123,
|
||||
:: unsent-messages: [123, ...], // size in bytes
|
||||
:: queued-message-acks: [{
|
||||
:: message-num: 123, // index
|
||||
:: ack: 'ok'
|
||||
:: }, ...],
|
||||
:: packet-pump-state: {
|
||||
:: next-wake: 123456, // ms timestamp
|
||||
:: live: [{
|
||||
:: message-num: 123, // index
|
||||
:: fragment-num: 123, // index
|
||||
:: num-fragments: 123,
|
||||
:: last-sent: 123456, // ms timestamp
|
||||
:: retries: 123,
|
||||
:: skips: 123
|
||||
:: }, ...],
|
||||
:: metrics: {
|
||||
:: rto: 123, // seconds
|
||||
:: rtt: 123, // seconds
|
||||
:: rttvar: 123,
|
||||
:: ssthresh: 123,
|
||||
:: num-live: 123,
|
||||
:: cwnd: 123,
|
||||
:: counter: 123
|
||||
:: }
|
||||
:: }
|
||||
:: }
|
||||
:: -> rcv:
|
||||
:: { bone: 123, // index
|
||||
:: duct: ['/paths', ...] // index
|
||||
:: last-acked: 123,
|
||||
:: last-heard: 123,
|
||||
:: pending-vane-ack: [123, ...],
|
||||
:: live-messages: [{
|
||||
:: message-num: 123, // index
|
||||
:: num-received: 122,
|
||||
:: num-fragments: 123,
|
||||
:: fragments: [123, ...]
|
||||
:: }, ...],
|
||||
:: nax: [123, ...]
|
||||
:: }
|
||||
:: nax: [{
|
||||
:: bone: 123, // index
|
||||
:: duct: ['/paths', ...],
|
||||
:: message-num: 123
|
||||
:: }, ...],
|
||||
:: heeds: [['/paths', ...] ...]
|
||||
:: }
|
||||
::
|
||||
++ known
|
||||
|= peer-state
|
||||
%- pairs
|
||||
:~ 'life'^(numb life)
|
||||
::
|
||||
:- 'route'
|
||||
%+ maybe route
|
||||
|= [direct=? =lane]
|
||||
%- pairs
|
||||
:~ 'direct'^b+direct
|
||||
::
|
||||
:- 'lane'
|
||||
?- -.lane
|
||||
%& (ship p.lane)
|
||||
::
|
||||
%|
|
||||
?~ l=((soft ,[=@tas =@if =@ud]) (cue p.lane))
|
||||
s+(scot %x p.lane)
|
||||
=, u.l
|
||||
(tape "%{(trip tas)}, {(scow %if if)}, {(scow %ud ud)}")
|
||||
==
|
||||
==
|
||||
::
|
||||
:- 'qos'
|
||||
%- pairs
|
||||
:~ 'kind'^s+-.qos
|
||||
'last-contact'^(time last-contact.qos)
|
||||
==
|
||||
::
|
||||
:- 'flows'
|
||||
|^ =/ mix=(list flow)
|
||||
=- (sort - dor)
|
||||
%+ welp
|
||||
(turn ~(tap by snd) (tack %snd))
|
||||
(turn ~(tap by rcv) (tack %rcv))
|
||||
=/ [forward=(list flow) backward=(list flow)]
|
||||
%+ skid mix
|
||||
|= [=bone *]
|
||||
=(0 (mod bone 2))
|
||||
%- pairs
|
||||
:~ ['forward' a+(turn forward build)]
|
||||
['backward' a+(turn backward build)]
|
||||
==
|
||||
::
|
||||
+$ flow
|
||||
$: =bone
|
||||
::
|
||||
$= state
|
||||
$% [%snd message-pump-state]
|
||||
[%rcv message-sink-state]
|
||||
==
|
||||
==
|
||||
::
|
||||
++ tack
|
||||
|* =term
|
||||
|* [=bone =noun]
|
||||
[bone [term noun]]
|
||||
::
|
||||
++ build
|
||||
|= flow
|
||||
^- json
|
||||
%+ frond -.state
|
||||
?- -.state
|
||||
%snd (snd-with-bone ossuary bone +.state)
|
||||
%rcv (rcv-with-bone ossuary bone +.state)
|
||||
==
|
||||
--
|
||||
::
|
||||
:- 'nax'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap in nax) dor) :: sort by bone
|
||||
|= [=bone =message-num]
|
||||
%- pairs
|
||||
:* 'message-num'^(numb message-num)
|
||||
(bone-to-pairs bone ossuary)
|
||||
==
|
||||
::
|
||||
'heeds'^(set-array heeds from-duct)
|
||||
==
|
||||
::
|
||||
++ snd-with-bone
|
||||
|= [=ossuary =bone message-pump-state]
|
||||
^- json
|
||||
%- pairs
|
||||
:* 'current'^(numb current)
|
||||
'next'^(numb next)
|
||||
::
|
||||
:- 'unsent-messages' :: as byte sizes
|
||||
(set-array unsent-messages (cork (cury met 3) numb))
|
||||
::
|
||||
'unsent-fragments'^(numb (lent unsent-fragments)) :: as lent
|
||||
::
|
||||
:- 'queued-message-acks'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap by queued-message-acks) dor) :: sort by msg nr
|
||||
|= [=message-num =ack]
|
||||
%- pairs
|
||||
:~ 'message-num'^(numb message-num)
|
||||
'ack'^s+-.ack
|
||||
==
|
||||
::
|
||||
:- 'packet-pump-state'
|
||||
%- pairs
|
||||
=, packet-pump-state
|
||||
:~ 'next-wake'^(maybe next-wake time)
|
||||
::
|
||||
:- 'live'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap in live) dor) :: sort by msg nr & frg nr
|
||||
|= [live-packet-key live-packet-val]
|
||||
%- pairs
|
||||
:~ 'message-num'^(numb message-num)
|
||||
'fragment-num'^(numb fragment-num)
|
||||
'num-fragments'^(numb num-fragments)
|
||||
'last-sent'^(time last-sent)
|
||||
'retries'^(numb retries)
|
||||
'skips'^(numb skips)
|
||||
==
|
||||
::
|
||||
:- 'metrics'
|
||||
%- pairs
|
||||
=, metrics
|
||||
:~ 'rto'^(numb (div rto ~s1)) ::TODO milliseconds?
|
||||
'rtt'^(numb (div rtt ~s1))
|
||||
'rttvar'^(numb (div rttvar ~s1))
|
||||
'ssthresh'^(numb ssthresh)
|
||||
'num-live'^(numb num-live)
|
||||
'cwnd'^(numb cwnd)
|
||||
'counter'^(numb counter)
|
||||
==
|
||||
==
|
||||
::
|
||||
(bone-to-pairs bone ossuary)
|
||||
==
|
||||
::
|
||||
++ rcv-with-bone
|
||||
|= [=ossuary =bone message-sink-state]
|
||||
^- json
|
||||
%- pairs
|
||||
:* 'last-acked'^(numb last-acked)
|
||||
'last-heard'^(numb last-heard)
|
||||
::
|
||||
:- 'pending-vane-ack'
|
||||
=- a+(turn - numb)
|
||||
(sort (turn ~(tap in pending-vane-ack) head) dor) :: sort by msg #
|
||||
::
|
||||
:- 'live-messages'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap by live-messages) dor) :: sort by msg #
|
||||
|= [=message-num partial-rcv-message]
|
||||
%- pairs
|
||||
:~ 'message-num'^(numb message-num)
|
||||
'num-received'^(numb num-received)
|
||||
'num-fragments'^(numb num-fragments)
|
||||
'fragments'^(set-array ~(key by fragments) numb)
|
||||
==
|
||||
::
|
||||
'nax'^a+(turn (sort ~(tap in nax) dor) numb)
|
||||
::
|
||||
(bone-to-pairs bone ossuary)
|
||||
==
|
||||
::
|
||||
++ bone-to-pairs
|
||||
|= [=bone ossuary]
|
||||
^- (list [@t json])
|
||||
:~ 'bone'^(numb bone)
|
||||
'duct'^(from-duct (~(gut by by-bone) bone ~))
|
||||
==
|
||||
::
|
||||
++ maybe
|
||||
|* [unit=(unit) enjs=$-(* json)]
|
||||
^- json
|
||||
?~ unit ~
|
||||
(enjs u.unit)
|
||||
::
|
||||
++ set-array
|
||||
|* [set=(set) enjs=$-(* json)]
|
||||
^- json
|
||||
a+(turn ~(tap in set) enjs)
|
||||
::
|
||||
++ from-duct
|
||||
|= =duct
|
||||
a+(turn duct path)
|
||||
--
|
||||
--
|
||||
::
|
||||
:: behn
|
||||
::
|
||||
++ v-behn
|
||||
|%
|
||||
++ timers
|
||||
(scry ,(list [date=@da =duct]) %b %timers ~)
|
||||
--
|
||||
::
|
||||
:: clay
|
||||
::
|
||||
::TODO depends on new clay changes (%s care)
|
||||
++ v-clay
|
||||
=, clay
|
||||
|%
|
||||
++ start-path /(scot %p our.bowl)/home/(scot %da now.bowl)
|
||||
::
|
||||
+$ commit
|
||||
[=tako parents=(list tako) children=(list tako) wen=@da content-hash=@uvI]
|
||||
::
|
||||
++ commits-json
|
||||
^- json
|
||||
=+ .^(desks=(set desk) %cd start-path)
|
||||
=/ heads=(list [tako desk])
|
||||
%+ turn ~(tap in desks)
|
||||
|= =desk
|
||||
=+ .^(=dome %cv /(scot %p our.bowl)/[desk]/(scot %da now.bowl))
|
||||
=/ =tako (~(got by hit.dome) let.dome)
|
||||
[tako desk]
|
||||
=/ yakis=(set yaki)
|
||||
%- silt
|
||||
^- (list yaki)
|
||||
%- zing
|
||||
%+ turn heads
|
||||
|= [=tako =desk]
|
||||
(trace-tako tako)
|
||||
=/ commits=(list commit) (yakis-to-commits ~(tap in yakis))
|
||||
=, enjs:format
|
||||
%: pairs
|
||||
head+(pairs (turn heads |=([=tako =desk] (scot %uv tako)^s+desk)))
|
||||
commits+(commits-to-json commits)
|
||||
~
|
||||
==
|
||||
::
|
||||
++ yakis-to-commits
|
||||
|= yakis=(list yaki)
|
||||
^- (list commit)
|
||||
%+ turn yakis
|
||||
|= =yaki
|
||||
:* r.yaki p.yaki
|
||||
=/ candidates
|
||||
%+ turn
|
||||
(skim yakis |=(can=^yaki (lien p.can |=(=tako =(r.yaki tako)))))
|
||||
|= can=^yaki
|
||||
r.can
|
||||
~(tap in (silt candidates))
|
||||
t.yaki
|
||||
.^(@uvI %cs (weld start-path /hash/(scot %uv r.yaki)))
|
||||
==
|
||||
::
|
||||
++ trace-tako
|
||||
|= =tako
|
||||
~+
|
||||
^- (list yaki)
|
||||
=+ .^(=yaki %cs (weld start-path /yaki/(scot %uv tako)))
|
||||
:- yaki
|
||||
(zing (turn p.yaki trace-tako))
|
||||
::
|
||||
++ commits-to-json
|
||||
|= commits=(list commit)
|
||||
^- json
|
||||
:- %a
|
||||
%+ turn
|
||||
%+ sort commits
|
||||
|= [a=commit b=commit]
|
||||
(gte wen.a wen.b)
|
||||
|= =commit
|
||||
(commit-to-json commit)
|
||||
::
|
||||
++ commit-to-json
|
||||
|= =commit
|
||||
^- json
|
||||
=, enjs:format
|
||||
%: pairs
|
||||
'commitHash'^(tako-to-json tako.commit)
|
||||
parents+a+(turn parents.commit tako-to-json)
|
||||
children+a+(turn children.commit tako-to-json)
|
||||
'contentHash'^(tako-to-json content-hash.commit)
|
||||
~
|
||||
==
|
||||
::
|
||||
++ tako-to-json
|
||||
|= =tako
|
||||
^- json
|
||||
s+(scot %uv tako)
|
||||
--
|
||||
::
|
||||
:: eyre
|
||||
::
|
||||
++ v-eyre
|
||||
=, eyre
|
||||
|%
|
||||
++ bindings
|
||||
(scry ,(list [=binding =duct =action]) %e %bindings ~)
|
||||
::
|
||||
++ connections
|
||||
(scry ,(map duct outstanding-connection) %e %connections ~)
|
||||
::
|
||||
++ auth-state
|
||||
(scry authentication-state %e %authentication-state ~)
|
||||
::
|
||||
++ channel-state
|
||||
(scry ^channel-state %e %channel-state ~)
|
||||
::
|
||||
++ render-action
|
||||
|= =action
|
||||
^- json
|
||||
:- %s
|
||||
?+ -.action -.action
|
||||
%gen :((cury cat 3) '+' (spat [desk path]:generator.action))
|
||||
%app (cat 3 ':' app.action)
|
||||
==
|
||||
--
|
||||
::
|
||||
:: helpers
|
||||
::
|
||||
++ poke
|
||||
|= [=wire app=term =mark =vase]
|
||||
^- card
|
||||
[%pass wire %agent [our.bowl app] %poke mark vase]
|
||||
::
|
||||
++ scry
|
||||
|* [=mold care=term =desk =path]
|
||||
.^(mold care (scot %p our.bowl) desk (scot %da now.bowl) path)
|
||||
--
|
20
pkg/arvo/app/debug/index.html
Normal file
20
pkg/arvo/app/debug/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Debug Dashboard</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="/~debug/css/index.css" />
|
||||
<link rel="icon" type="image/png" href="/~launch/img/Favicon.png">
|
||||
</head>
|
||||
|
||||
<body class="w-100 h-100">
|
||||
<div id="root" class="w-100 h-100">
|
||||
</div>
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~debug/js/index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -2,4 +2,4 @@
|
||||
:- %say
|
||||
|= *
|
||||
:- %tang
|
||||
[.^(tank %b /=timers=) ~]
|
||||
[>.^((list [date=@da =duct]) %b /=timers=)< ~]
|
||||
|
@ -90,6 +90,17 @@
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?. ?=([@ %dbug *] path)
|
||||
(on-peek:ag path)
|
||||
?+ path [~ ~]
|
||||
[%u %dbug ~] ``noun+!>(&)
|
||||
[%x %dbug %state ~] ``noun+!>(on-save:ag)
|
||||
[%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)
|
||||
==
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards agent on-init:ag
|
||||
@ -115,8 +126,6 @@
|
||||
=^ cards agent (on-leave:ag path)
|
||||
[cards this]
|
||||
::
|
||||
++ on-peek on-peek:ag
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
|
@ -1,5 +1,7 @@
|
||||
:: Print what your agent is doing.
|
||||
::
|
||||
/- verb
|
||||
::
|
||||
|= [loud=? =agent:gall]
|
||||
=| bowl-print=_|
|
||||
^- agent:gall
|
||||
@ -12,7 +14,7 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-init")
|
||||
=^ cards agent on-init:ag
|
||||
[cards this]
|
||||
[[(emit-event %on-init ~) cards] this]
|
||||
::
|
||||
++ on-save
|
||||
^- vase
|
||||
@ -24,7 +26,7 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-load")
|
||||
=^ cards agent (on-load:ag old-state)
|
||||
[cards this]
|
||||
[[(emit-event %on-load ~) cards] this]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -36,21 +38,26 @@
|
||||
%bowl `this(bowl-print !bowl-print)
|
||||
==
|
||||
=^ cards agent (on-poke:ag mark vase)
|
||||
[cards this]
|
||||
[[(emit-event %on-poke mark) cards] this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-watch on path {<path>}")
|
||||
=^ cards agent (on-watch:ag path)
|
||||
[cards this]
|
||||
=^ cards agent
|
||||
?: ?=([%verb %events ~] path)
|
||||
[~ agent]
|
||||
(on-watch:ag path)
|
||||
[[(emit-event %on-watch path) cards] this]
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-leave on path {<path>}")
|
||||
?: ?=([%verb %event ~] path)
|
||||
[~ this]
|
||||
=^ cards agent (on-leave:ag path)
|
||||
[cards this]
|
||||
[[(emit-event %on-leave path) cards] this]
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
@ -63,21 +70,21 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}")
|
||||
=^ cards agent (on-agent:ag wire sign)
|
||||
[cards this]
|
||||
[[(emit-event %on-agent wire -.sign) cards] this]
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}")
|
||||
=^ cards agent (on-arvo:ag wire sign-arvo)
|
||||
[cards this]
|
||||
[[(emit-event %on-arvo wire [- +<]:sign-arvo) cards] this]
|
||||
::
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-fail with term {<term>}")
|
||||
=^ cards agent (on-fail:ag term tang)
|
||||
[cards this]
|
||||
[[(emit-event %on-fail term) cards] this]
|
||||
--
|
||||
::
|
||||
++ print
|
||||
@ -89,4 +96,9 @@
|
||||
?. loud same
|
||||
%- (slog leaf+tape ~)
|
||||
same
|
||||
::
|
||||
++ emit-event
|
||||
|= =event:verb
|
||||
^- card:agent:gall
|
||||
[%give %fact ~[/verb/events] %verb-event !>(event)]
|
||||
--
|
||||
|
14
pkg/arvo/mar/spider/stop.hoon
Normal file
14
pkg/arvo/mar/spider/stop.hoon
Normal file
@ -0,0 +1,14 @@
|
||||
/- *spider
|
||||
|_ stop=[=tid nice=?]
|
||||
++ grab
|
||||
|%
|
||||
++ noun ,[=tid nice=?]
|
||||
++ json
|
||||
(ot 'tid'^so 'nice'^bo ~):dejs:format
|
||||
--
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ noun stop
|
||||
--
|
||||
--
|
26
pkg/arvo/mar/verb/event.hoon
Normal file
26
pkg/arvo/mar/verb/event.hoon
Normal file
@ -0,0 +1,26 @@
|
||||
/- verb
|
||||
=, dejs:format
|
||||
|_ =event:verb
|
||||
++ grab
|
||||
|%
|
||||
++ noun event:verb
|
||||
--
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ noun event
|
||||
++ json
|
||||
=, enjs:format
|
||||
%+ frond -.event
|
||||
?- -.event
|
||||
%on-init ~
|
||||
%on-load ~
|
||||
%on-poke s+mark.event
|
||||
%on-watch (path path.event)
|
||||
%on-leave (path path.event)
|
||||
%on-agent (pairs 'wire'^(path wire.event) 'sign'^s+sign.event ~)
|
||||
%on-arvo (pairs 'wire'^(path wire.event) 'vane'^s+vane.event 'sign'^s+sign.event ~)
|
||||
%on-fail s+term.event
|
||||
==
|
||||
--
|
||||
--
|
12
pkg/arvo/sur/verb.hoon
Normal file
12
pkg/arvo/sur/verb.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
|%
|
||||
+$ event
|
||||
$% [%on-init ~]
|
||||
[%on-load ~]
|
||||
[%on-poke =mark]
|
||||
[%on-watch =path]
|
||||
[%on-leave =path]
|
||||
[%on-agent =wire sign=term]
|
||||
[%on-arvo =wire vane=term sign=term]
|
||||
[%on-fail =term]
|
||||
==
|
||||
--
|
@ -123,15 +123,8 @@
|
||||
|%
|
||||
+| %atomics
|
||||
::
|
||||
+$ bone @udbone
|
||||
+$ fragment @uwfragment
|
||||
+$ fragment-num @udfragmentnum
|
||||
+$ message-blob @udmessageblob
|
||||
+$ message-num @udmessagenum
|
||||
+$ private-key @uwprivatekey
|
||||
+$ public-key @uwpublickey
|
||||
+$ signature @uwsignature
|
||||
+$ symmetric-key @uwsymmetrickey
|
||||
:: $rank: which kind of ship address, by length
|
||||
::
|
||||
:: 0: galaxy or star -- 2 bytes
|
||||
@ -215,13 +208,6 @@
|
||||
:: $naxplanation: nack trace; explains which message failed and why
|
||||
::
|
||||
+$ naxplanation [=message-num =error]
|
||||
:: $ack: positive ack, nack packet, or nack trace
|
||||
::
|
||||
+$ ack
|
||||
$% [%ok ~]
|
||||
[%nack ~]
|
||||
[%naxplanation =error]
|
||||
==
|
||||
::
|
||||
+| %statics
|
||||
::
|
||||
@ -249,237 +235,6 @@
|
||||
$: veb=_veb-all-off
|
||||
ships=(set ship)
|
||||
==
|
||||
:: $ship-state: all we know about a peer
|
||||
::
|
||||
:: %alien: no PKI data, so enqueue actions to perform once we learn it
|
||||
:: %known: we know their life and public keys, so we have a channel
|
||||
::
|
||||
+$ ship-state
|
||||
$% [%alien alien-agenda]
|
||||
[%known peer-state]
|
||||
==
|
||||
:: $alien-agenda: what to do when we learn a peer's life and keys
|
||||
::
|
||||
:: messages: pleas local vanes have asked us to send
|
||||
:: packets: packets we've tried to send
|
||||
:: heeds: local tracking requests; passed through into $peer-state
|
||||
::
|
||||
+$ alien-agenda
|
||||
$: messages=(list [=duct =plea])
|
||||
packets=(set =blob)
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $peer-state: state for a peer with known life and keys
|
||||
::
|
||||
:: route: transport-layer destination for packets to peer
|
||||
:: qos: quality of service; connection status to peer
|
||||
:: ossuary: bone<->duct mapper
|
||||
:: snd: per-bone message pumps to send messages as fragments
|
||||
:: rcv: per-bone message sinks to assemble messages from fragments
|
||||
:: nax: unprocessed nacks (negative acknowledgments)
|
||||
:: Each value is ~ when we've received the ack packet but not a
|
||||
:: nack-trace, or an error when we've received a nack-trace but
|
||||
:: not the ack packet.
|
||||
::
|
||||
:: When we hear a nack packet or an explanation, if there's no
|
||||
:: entry in .nax, we make a new entry. Otherwise, if this new
|
||||
:: information completes the packet+nack-trace, we remove the
|
||||
:: entry and emit a nack to the local vane that asked us to send
|
||||
:: the message.
|
||||
:: heeds: listeners for %clog notifications
|
||||
::
|
||||
+$ peer-state
|
||||
$: $: =symmetric-key
|
||||
=life
|
||||
=public-key
|
||||
sponsor=ship
|
||||
==
|
||||
route=(unit [direct=? =lane])
|
||||
=qos
|
||||
=ossuary
|
||||
snd=(map bone message-pump-state)
|
||||
rcv=(map bone message-sink-state)
|
||||
nax=(set [=bone =message-num])
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $qos: quality of service; how is our connection to a peer doing?
|
||||
::
|
||||
:: .last-contact: last time we heard from peer, or if %unborn, when
|
||||
:: we first started tracking time
|
||||
::
|
||||
+$ qos
|
||||
$~ [%unborn *@da]
|
||||
[?(%live %dead %unborn) last-contact=@da]
|
||||
:: $ossuary: bone<->duct bijection and .next-bone to map to a duct
|
||||
::
|
||||
:: The first bone is 0. They increment by 4, since each flow includes
|
||||
:: a bit for each message determining forward vs. backward and a
|
||||
:: second bit for whether the message is on the normal flow or the
|
||||
:: associated diagnostic flow (for naxplanations).
|
||||
::
|
||||
:: The least significant bit of a $bone is:
|
||||
:: 1 if "forward", i.e. we send %plea's on this flow, or
|
||||
:: 0 if "backward", i.e. we receive %plea's on this flow.
|
||||
::
|
||||
:: The second-least significant bit is 1 if the bone is a
|
||||
:: naxplanation bone, and 0 otherwise. Only naxplanation
|
||||
:: messages can be sent on a naxplanation bone, as %boon's.
|
||||
::
|
||||
+$ ossuary
|
||||
$: =next=bone
|
||||
by-duct=(map duct bone)
|
||||
by-bone=(map bone duct)
|
||||
==
|
||||
:: $message-pump-state: persistent state for |message-pump
|
||||
::
|
||||
:: Messages queue up in |message-pump's .unsent-messages until they
|
||||
:: can be packetized and fed into |packet-pump for sending. When we
|
||||
:: pop a message off .unsent-messages, we push as many fragments as
|
||||
:: we can into |packet-pump, which sends every packet it eats.
|
||||
:: Packets rejected by |packet-pump are placed in .unsent-fragments.
|
||||
::
|
||||
:: When we hear a packet ack, we send it to |packet-pump to be
|
||||
:: removed from its queue of unacked packets.
|
||||
::
|
||||
:: When we hear a message ack (positive or negative), we treat that
|
||||
:: as though all fragments have been acked. If this message is not
|
||||
:: .current, then this ack is for a future message and .current has
|
||||
:: not yet been acked, so we place the ack in .queued-message-acks.
|
||||
::
|
||||
:: If we hear a message ack before we've sent all the fragments for
|
||||
:: that message, clear .unsent-fragments and have |packet-pump delete
|
||||
:: all sent fragments from the message. If this early message ack was
|
||||
:: positive, print it out because it indicates the peer is not
|
||||
:: behaving properly.
|
||||
::
|
||||
:: If the ack is for the current message, have |packet-pump delete
|
||||
:: all packets from the message, give the message ack back
|
||||
:: to the client vane, increment .current, and check if this next
|
||||
:: message is in .queued-message-acks. If it is, emit the message
|
||||
:: (n)ack, increment .current, and check the next message. Repeat
|
||||
:: until .current is not fully acked.
|
||||
::
|
||||
:: The following equation is always true:
|
||||
:: .next - .current == number of messages in flight
|
||||
::
|
||||
:: At the end of a task, |message-pump sends a %halt task to
|
||||
:: |packet-pump, which can trigger a timer to be set or cleared based
|
||||
:: on congestion control calculations. When the timer fires, it will
|
||||
:: generally cause a packet to be re-sent.
|
||||
::
|
||||
:: Message sequence numbers start at 1 so that the first message will
|
||||
:: be greater than .last-acked.message-sink-state on the receiver.
|
||||
::
|
||||
:: current: sequence number of earliest message sent or being sent
|
||||
:: next: sequence number of next message to send
|
||||
:: unsent-messages: messages to be sent after current message
|
||||
:: unsent-fragments: fragments of current message waiting for sending
|
||||
:: queued-message-acks: future message acks to be applied after current
|
||||
:: packet-pump-state: state of corresponding |packet-pump
|
||||
::
|
||||
+$ message-pump-state
|
||||
$: current=_`message-num`1
|
||||
next=_`message-num`1
|
||||
unsent-messages=(qeu message-blob)
|
||||
unsent-fragments=(list static-fragment)
|
||||
queued-message-acks=(map message-num ack)
|
||||
=packet-pump-state
|
||||
==
|
||||
+$ static-fragment
|
||||
$: =message-num
|
||||
num-fragments=fragment-num
|
||||
=fragment-num
|
||||
=fragment
|
||||
==
|
||||
:: $packet-pump-state: persistent state for |packet-pump
|
||||
::
|
||||
:: next-wake: last timer we've set, or null
|
||||
:: live: packets in flight; sent but not yet acked
|
||||
:: metrics: congestion control information
|
||||
::
|
||||
+$ packet-pump-state
|
||||
$: next-wake=(unit @da)
|
||||
live=(tree [live-packet-key live-packet-val])
|
||||
metrics=pump-metrics
|
||||
==
|
||||
:: $pump-metrics: congestion control state for a |packet-pump
|
||||
::
|
||||
:: This is an Ames adaptation of TCP's Reno congestion control
|
||||
:: algorithm. The information signals and their responses are
|
||||
:: identical to those of the "NewReno" variant of Reno; the
|
||||
:: implementation differs because Ames acknowledgments differ from
|
||||
:: TCP's, because this code uses functional data structures, and
|
||||
:: because TCP's sequence numbers reset when a peer becomes
|
||||
:: unresponsive, whereas Ames sequence numbers only change when a
|
||||
:: ship breaches.
|
||||
::
|
||||
:: A deviation from Reno is +fast-resend-after-ack, which re-sends
|
||||
:: timed-out packets when a peer starts responding again after a
|
||||
:: period of unresponsiveness.
|
||||
::
|
||||
:: If .skips reaches 3, we perform a fast retransmit and fast
|
||||
:: recovery. This corresponds to Reno's handling of "three duplicate
|
||||
:: acks".
|
||||
::
|
||||
:: rto: retransmission timeout
|
||||
:: rtt: roundtrip time estimate, low-passed using EWMA
|
||||
:: rttvar: mean deviation of .rtt, also low-passed with EWMA
|
||||
:: num-live: how many packets sent, awaiting ack
|
||||
:: ssthresh: slow-start threshold
|
||||
:: cwnd: congestion window; max unacked packets
|
||||
::
|
||||
+$ pump-metrics
|
||||
$: rto=_~s1
|
||||
rtt=_~s1
|
||||
rttvar=_~s1
|
||||
ssthresh=_10.000
|
||||
cwnd=_1
|
||||
num-live=@ud
|
||||
counter=@ud
|
||||
==
|
||||
+$ live-packet
|
||||
$: key=live-packet-key
|
||||
val=live-packet-val
|
||||
==
|
||||
+$ live-packet-key
|
||||
$: =message-num
|
||||
=fragment-num
|
||||
==
|
||||
+$ live-packet-val
|
||||
$: packet-state
|
||||
num-fragments=fragment-num
|
||||
=fragment
|
||||
==
|
||||
+$ packet-state
|
||||
$: last-sent=@da
|
||||
retries=@ud
|
||||
skips=@ud
|
||||
==
|
||||
:: $message-sink-state: state of |message-sink to assemble messages
|
||||
::
|
||||
:: last-acked: highest $message-num we've fully acknowledged
|
||||
:: last-heard: highest $message-num we've heard all fragments on
|
||||
:: pending-vane-ack: heard but not processed by local vane
|
||||
:: live-messages: partially received messages
|
||||
::
|
||||
+$ message-sink-state
|
||||
$: last-acked=message-num
|
||||
last-heard=message-num
|
||||
pending-vane-ack=(qeu [=message-num message=*])
|
||||
live-messages=(map message-num partial-rcv-message)
|
||||
nax=(set message-num)
|
||||
==
|
||||
:: $partial-rcv-message: message for which we've received some fragments
|
||||
::
|
||||
:: num-fragments: total number of fragments in this message
|
||||
:: num-received: how many fragments we've received so far
|
||||
:: fragments: fragments we've received, eventually producing a $message
|
||||
::
|
||||
+$ partial-rcv-message
|
||||
$: num-fragments=fragment-num
|
||||
num-received=fragment-num
|
||||
fragments=(map fragment-num fragment)
|
||||
==
|
||||
::
|
||||
+| %dialectics
|
||||
::
|
||||
@ -947,32 +702,19 @@
|
||||
?. =([%& our] why)
|
||||
[~ ~]
|
||||
?+ syd ~
|
||||
%peers
|
||||
?^ tyl [~ ~]
|
||||
:^ ~ ~ %noun
|
||||
!> ^- (map ship ?(%alien %known))
|
||||
(~(run by peers.ames-state) head)
|
||||
::
|
||||
%peer
|
||||
?. ?=([@ ~] tyl) [~ ~]
|
||||
=/ who (slaw %p i.tyl)
|
||||
?~ who [~ ~]
|
||||
=/ per (~(get by peers.ames-state) u.who)
|
||||
=/ res
|
||||
?- per
|
||||
~ %unknown
|
||||
[~ %alien *] %alien
|
||||
[~ %known *]
|
||||
=, u.per
|
||||
:* %known
|
||||
symkeymug=(mug symmetric-key)
|
||||
life=life
|
||||
pubkey=public-key
|
||||
sponsor=sponsor
|
||||
route=route
|
||||
qos=qos
|
||||
ossuary=ossuary
|
||||
snd=~(key by snd)
|
||||
rcv=~(key by rcv)
|
||||
nax=nax
|
||||
heeds=heeds
|
||||
==
|
||||
==
|
||||
``noun+!>(!>(res))
|
||||
?~ peer=(~(get by peers.ames-state) u.who)
|
||||
[~ ~]
|
||||
``noun+!>(u.peer)
|
||||
::
|
||||
%bones
|
||||
?. ?=([@ ~] tyl) [~ ~]
|
||||
|
@ -335,7 +335,7 @@
|
||||
~
|
||||
?. ?=(%timers syd)
|
||||
[~ ~]
|
||||
[~ ~ %noun !>(>(turn (tap:timer-map timers) head)<)]
|
||||
[~ ~ %noun !>((turn (tap:timer-map timers) head))]
|
||||
::
|
||||
++ stay state
|
||||
++ take
|
||||
|
@ -124,131 +124,6 @@
|
||||
::
|
||||
outgoing-duct=duct
|
||||
==
|
||||
:: +outstanding-connection: open http connections not fully complete:
|
||||
::
|
||||
:: This refers to outstanding connections where the connection to
|
||||
:: outside is opened and we are currently waiting on ford or an app to
|
||||
:: produce the results.
|
||||
::
|
||||
+$ outstanding-connection
|
||||
$: :: action: the action that had matched
|
||||
::
|
||||
=action
|
||||
:: inbound-request: the original request which caused this connection
|
||||
::
|
||||
=inbound-request
|
||||
:: response-header: set when we get our first %start
|
||||
::
|
||||
response-header=(unit response-header:http)
|
||||
:: bytes-sent: the total bytes sent in response
|
||||
::
|
||||
bytes-sent=@ud
|
||||
==
|
||||
:: +action: the action to take when a binding matches an incoming request
|
||||
::
|
||||
+$ action
|
||||
$% :: dispatch to a generator
|
||||
::
|
||||
[%gen =generator]
|
||||
:: dispatch to an application
|
||||
::
|
||||
[%app app=term]
|
||||
:: internal authentication page
|
||||
::
|
||||
[%authentication ~]
|
||||
:: gall channel system
|
||||
::
|
||||
[%channel ~]
|
||||
:: respond with the default file not found page
|
||||
::
|
||||
[%four-oh-four ~]
|
||||
==
|
||||
:: +authentication-state: state used in the login system
|
||||
::
|
||||
+$ authentication-state
|
||||
$: :: sessions: a mapping of session cookies to session information
|
||||
::
|
||||
sessions=(map @uv session)
|
||||
==
|
||||
:: +session: server side data about a session
|
||||
::
|
||||
+$ session
|
||||
$: :: expiry-time: when this session expires
|
||||
::
|
||||
:: We check this server side, too, so we aren't relying on the browser
|
||||
:: to properly handle cookie expiration as a security mechanism.
|
||||
::
|
||||
expiry-time=@da
|
||||
::
|
||||
:: TODO: We should add a system for individual capabilities; we should
|
||||
:: mint some sort of long lived cookie for mobile apps which only has
|
||||
:: access to a single application path.
|
||||
==
|
||||
:: channel-state: state used in the channel system
|
||||
::
|
||||
+$ channel-state
|
||||
$: :: session: mapping between an arbitrary key to a channel
|
||||
::
|
||||
session=(map @t channel)
|
||||
:: by-duct: mapping from ducts to session key
|
||||
::
|
||||
duct-to-key=(map duct @t)
|
||||
==
|
||||
:: +timer: a reference to a timer so we can cancel or update it.
|
||||
::
|
||||
+$ timer
|
||||
$: :: date: time when the timer will fire
|
||||
::
|
||||
date=@da
|
||||
:: duct: duct that set the timer so we can cancel
|
||||
::
|
||||
=duct
|
||||
==
|
||||
:: channel: connection to the browser
|
||||
::
|
||||
:: Channels are the main method where a webpage communicates with Gall
|
||||
:: apps. Subscriptions and pokes are issues with PUT requests on a path,
|
||||
:: while GET requests on that same path open a persistent EventSource
|
||||
:: channel.
|
||||
::
|
||||
:: The EventSource API is a sequence number based API that browser provide
|
||||
:: which allow the server to push individual events to the browser over a
|
||||
:: connection held open. In case of reconnection, the browser will send a
|
||||
:: 'Last-Event-Id: ' header to the server; the server then resends all
|
||||
:: events since then.
|
||||
::
|
||||
+$ channel
|
||||
$: :: channel-state: expiration time or the duct currently listening
|
||||
::
|
||||
:: For each channel, there is at most one open EventSource
|
||||
:: connection. A 400 is issues on duplicate attempts to connect to the
|
||||
:: same channel. When an EventSource isn't connected, we set a timer
|
||||
:: to reap the subscriptions. This timer shouldn't be too short
|
||||
:: because the
|
||||
::
|
||||
state=(each timer duct)
|
||||
:: next-id: next sequence number to use
|
||||
::
|
||||
next-id=@ud
|
||||
:: events: unacknowledged events
|
||||
::
|
||||
:: We keep track of all events where we haven't received a
|
||||
:: 'Last-Event-Id: ' response from the client or a per-poke {'ack':
|
||||
:: ...} call. When there's an active EventSource connection on this
|
||||
:: channel, we send the event but we still add it to events because we
|
||||
:: can't assume it got received until we get an acknowledgment.
|
||||
::
|
||||
events=(qeu [id=@ud lines=wall])
|
||||
:: subscriptions: gall subscriptions
|
||||
::
|
||||
:: We maintain a list of subscriptions so if a channel times out, we
|
||||
:: can cancel all the subscriptions we've made.
|
||||
::
|
||||
subscriptions=(map wire [ship=@p app=term =path duc=duct])
|
||||
:: heartbeat: sse heartbeat timer
|
||||
::
|
||||
heartbeat=(unit timer)
|
||||
==
|
||||
:: channel-request: an action requested on a channel
|
||||
::
|
||||
+$ channel-request
|
||||
@ -2462,32 +2337,38 @@
|
||||
[~ ~]
|
||||
?. ?=(%$ -.lot)
|
||||
[~ ~]
|
||||
?. ?=(%host syd)
|
||||
[~ ~]
|
||||
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
||||
^- (unit (unit hart:eyre))
|
||||
?. =(our who)
|
||||
?. =([%da now] p.lot)
|
||||
[~ ~]
|
||||
~& [%r %scry-foreign-host who]
|
||||
~
|
||||
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
||||
?+ p.lot
|
||||
[~ ~]
|
||||
?+ syd [~ ~]
|
||||
%bindings ``noun+!>(bindings.server-state.ax)
|
||||
%connections ``noun+!>(connections.server-state.ax)
|
||||
%authentication-state ``noun+!>(authentication-state.server-state.ax)
|
||||
%channel-state ``noun+!>(channel-state.server-state.ax)
|
||||
::
|
||||
[%tas %fake]
|
||||
``[& [~ 8.443] %& /localhost]
|
||||
::
|
||||
[%tas %real]
|
||||
=* domains domains.server-state.ax
|
||||
=* ports ports.server-state.ax
|
||||
=/ =host:eyre [%& ?^(domains n.domains /localhost)]
|
||||
=/ secure=? &(?=(^ secure.ports) !?=(hoke:eyre host))
|
||||
=/ port=(unit @ud)
|
||||
?. secure
|
||||
?:(=(80 insecure.ports) ~ `insecure.ports)
|
||||
?> ?=(^ secure.ports)
|
||||
?:(=(443 u.secure.ports) ~ secure.ports)
|
||||
``[secure port host]
|
||||
%host
|
||||
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
||||
^- (unit (unit hart:eyre))
|
||||
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
||||
?+ p.lot
|
||||
[~ ~]
|
||||
::
|
||||
[%tas %fake]
|
||||
``[& [~ 8.443] %& /localhost]
|
||||
::
|
||||
[%tas %real]
|
||||
=* domains domains.server-state.ax
|
||||
=* ports ports.server-state.ax
|
||||
=/ =host:eyre [%& ?^(domains n.domains /localhost)]
|
||||
=/ secure=? &(?=(^ secure.ports) !?=(hoke:eyre host))
|
||||
=/ port=(unit @ud)
|
||||
?. secure
|
||||
?:(=(80 insecure.ports) ~ `insecure.ports)
|
||||
?> ?=(^ secure.ports)
|
||||
?:(=(443 u.secure.ports) ~ secure.ports)
|
||||
``[secure port host]
|
||||
==
|
||||
==
|
||||
--
|
||||
|
@ -491,6 +491,259 @@
|
||||
:: payload: semantic message contents
|
||||
::
|
||||
+$ plea [vane=@tas =path payload=*]
|
||||
::
|
||||
:: +| %atomics
|
||||
::
|
||||
+$ bone @udbone
|
||||
+$ fragment @uwfragment
|
||||
+$ fragment-num @udfragmentnum
|
||||
+$ message-blob @udmessageblob
|
||||
+$ message-num @udmessagenum
|
||||
+$ public-key @uwpublickey
|
||||
+$ symmetric-key @uwsymmetrickey
|
||||
::
|
||||
:: +| %kinetics
|
||||
:: $ack: positive ack, nack packet, or nack trace
|
||||
::
|
||||
+$ ack
|
||||
$% [%ok ~]
|
||||
[%nack ~]
|
||||
[%naxplanation =error]
|
||||
==
|
||||
::
|
||||
:: +| %statics
|
||||
:: $ship-state: all we know about a peer
|
||||
::
|
||||
:: %alien: no PKI data, so enqueue actions to perform once we learn it
|
||||
:: %known: we know their life and public keys, so we have a channel
|
||||
::
|
||||
+$ ship-state
|
||||
$% [%alien alien-agenda]
|
||||
[%known peer-state]
|
||||
==
|
||||
:: $alien-agenda: what to do when we learn a peer's life and keys
|
||||
::
|
||||
:: messages: pleas local vanes have asked us to send
|
||||
:: packets: packets we've tried to send
|
||||
:: heeds: local tracking requests; passed through into $peer-state
|
||||
::
|
||||
+$ alien-agenda
|
||||
$: messages=(list [=duct =plea])
|
||||
packets=(set =blob)
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $peer-state: state for a peer with known life and keys
|
||||
::
|
||||
:: route: transport-layer destination for packets to peer
|
||||
:: qos: quality of service; connection status to peer
|
||||
:: ossuary: bone<->duct mapper
|
||||
:: snd: per-bone message pumps to send messages as fragments
|
||||
:: rcv: per-bone message sinks to assemble messages from fragments
|
||||
:: nax: unprocessed nacks (negative acknowledgments)
|
||||
:: Each value is ~ when we've received the ack packet but not a
|
||||
:: nack-trace, or an error when we've received a nack-trace but
|
||||
:: not the ack packet.
|
||||
::
|
||||
:: When we hear a nack packet or an explanation, if there's no
|
||||
:: entry in .nax, we make a new entry. Otherwise, if this new
|
||||
:: information completes the packet+nack-trace, we remove the
|
||||
:: entry and emit a nack to the local vane that asked us to send
|
||||
:: the message.
|
||||
:: heeds: listeners for %clog notifications
|
||||
::
|
||||
+$ peer-state
|
||||
$: $: =symmetric-key
|
||||
=life
|
||||
=public-key
|
||||
sponsor=ship
|
||||
==
|
||||
route=(unit [direct=? =lane])
|
||||
=qos
|
||||
=ossuary
|
||||
snd=(map bone message-pump-state)
|
||||
rcv=(map bone message-sink-state)
|
||||
nax=(set [=bone =message-num])
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $qos: quality of service; how is our connection to a peer doing?
|
||||
::
|
||||
:: .last-contact: last time we heard from peer, or if %unborn, when
|
||||
:: we first started tracking time
|
||||
::
|
||||
+$ qos
|
||||
$~ [%unborn *@da]
|
||||
[?(%live %dead %unborn) last-contact=@da]
|
||||
:: $ossuary: bone<->duct bijection and .next-bone to map to a duct
|
||||
::
|
||||
:: The first bone is 0. They increment by 4, since each flow includes
|
||||
:: a bit for each message determining forward vs. backward and a
|
||||
:: second bit for whether the message is on the normal flow or the
|
||||
:: associated diagnostic flow (for naxplanations).
|
||||
::
|
||||
:: The least significant bit of a $bone is:
|
||||
:: 1 if "forward", i.e. we send %plea's on this flow, or
|
||||
:: 0 if "backward", i.e. we receive %plea's on this flow.
|
||||
::
|
||||
:: The second-least significant bit is 1 if the bone is a
|
||||
:: naxplanation bone, and 0 otherwise. Only naxplanation
|
||||
:: messages can be sent on a naxplanation bone, as %boon's.
|
||||
::
|
||||
+$ ossuary
|
||||
$: =next=bone
|
||||
by-duct=(map duct bone)
|
||||
by-bone=(map bone duct)
|
||||
==
|
||||
:: $message-pump-state: persistent state for |message-pump
|
||||
::
|
||||
:: Messages queue up in |message-pump's .unsent-messages until they
|
||||
:: can be packetized and fed into |packet-pump for sending. When we
|
||||
:: pop a message off .unsent-messages, we push as many fragments as
|
||||
:: we can into |packet-pump, which sends every packet it eats.
|
||||
:: Packets rejected by |packet-pump are placed in .unsent-fragments.
|
||||
::
|
||||
:: When we hear a packet ack, we send it to |packet-pump to be
|
||||
:: removed from its queue of unacked packets.
|
||||
::
|
||||
:: When we hear a message ack (positive or negative), we treat that
|
||||
:: as though all fragments have been acked. If this message is not
|
||||
:: .current, then this ack is for a future message and .current has
|
||||
:: not yet been acked, so we place the ack in .queued-message-acks.
|
||||
::
|
||||
:: If we hear a message ack before we've sent all the fragments for
|
||||
:: that message, clear .unsent-fragments and have |packet-pump delete
|
||||
:: all sent fragments from the message. If this early message ack was
|
||||
:: positive, print it out because it indicates the peer is not
|
||||
:: behaving properly.
|
||||
::
|
||||
:: If the ack is for the current message, have |packet-pump delete
|
||||
:: all packets from the message, give the message ack back
|
||||
:: to the client vane, increment .current, and check if this next
|
||||
:: message is in .queued-message-acks. If it is, emit the message
|
||||
:: (n)ack, increment .current, and check the next message. Repeat
|
||||
:: until .current is not fully acked.
|
||||
::
|
||||
:: The following equation is always true:
|
||||
:: .next - .current == number of messages in flight
|
||||
::
|
||||
:: At the end of a task, |message-pump sends a %halt task to
|
||||
:: |packet-pump, which can trigger a timer to be set or cleared based
|
||||
:: on congestion control calculations. When the timer fires, it will
|
||||
:: generally cause a packet to be re-sent.
|
||||
::
|
||||
:: Message sequence numbers start at 1 so that the first message will
|
||||
:: be greater than .last-acked.message-sink-state on the receiver.
|
||||
::
|
||||
:: current: sequence number of earliest message sent or being sent
|
||||
:: next: sequence number of next message to send
|
||||
:: unsent-messages: messages to be sent after current message
|
||||
:: unsent-fragments: fragments of current message waiting for sending
|
||||
:: queued-message-acks: future message acks to be applied after current
|
||||
:: packet-pump-state: state of corresponding |packet-pump
|
||||
::
|
||||
+$ message-pump-state
|
||||
$: current=_`message-num`1
|
||||
next=_`message-num`1
|
||||
unsent-messages=(qeu message-blob)
|
||||
unsent-fragments=(list static-fragment)
|
||||
queued-message-acks=(map message-num ack)
|
||||
=packet-pump-state
|
||||
==
|
||||
+$ static-fragment
|
||||
$: =message-num
|
||||
num-fragments=fragment-num
|
||||
=fragment-num
|
||||
=fragment
|
||||
==
|
||||
:: $packet-pump-state: persistent state for |packet-pump
|
||||
::
|
||||
:: next-wake: last timer we've set, or null
|
||||
:: live: packets in flight; sent but not yet acked
|
||||
:: metrics: congestion control information
|
||||
::
|
||||
+$ packet-pump-state
|
||||
$: next-wake=(unit @da)
|
||||
live=(tree [live-packet-key live-packet-val])
|
||||
metrics=pump-metrics
|
||||
==
|
||||
:: $pump-metrics: congestion control state for a |packet-pump
|
||||
::
|
||||
:: This is an Ames adaptation of TCP's Reno congestion control
|
||||
:: algorithm. The information signals and their responses are
|
||||
:: identical to those of the "NewReno" variant of Reno; the
|
||||
:: implementation differs because Ames acknowledgments differ from
|
||||
:: TCP's, because this code uses functional data structures, and
|
||||
:: because TCP's sequence numbers reset when a peer becomes
|
||||
:: unresponsive, whereas Ames sequence numbers only change when a
|
||||
:: ship breaches.
|
||||
::
|
||||
:: A deviation from Reno is +fast-resend-after-ack, which re-sends
|
||||
:: timed-out packets when a peer starts responding again after a
|
||||
:: period of unresponsiveness.
|
||||
::
|
||||
:: If .skips reaches 3, we perform a fast retransmit and fast
|
||||
:: recovery. This corresponds to Reno's handling of "three duplicate
|
||||
:: acks".
|
||||
::
|
||||
:: rto: retransmission timeout
|
||||
:: rtt: roundtrip time estimate, low-passed using EWMA
|
||||
:: rttvar: mean deviation of .rtt, also low-passed with EWMA
|
||||
:: num-live: how many packets sent, awaiting ack
|
||||
:: ssthresh: slow-start threshold
|
||||
:: cwnd: congestion window; max unacked packets
|
||||
::
|
||||
+$ pump-metrics
|
||||
$: rto=_~s1
|
||||
rtt=_~s1
|
||||
rttvar=_~s1
|
||||
ssthresh=_10.000
|
||||
cwnd=_1
|
||||
num-live=@ud
|
||||
counter=@ud
|
||||
==
|
||||
+$ live-packet
|
||||
$: key=live-packet-key
|
||||
val=live-packet-val
|
||||
==
|
||||
+$ live-packet-key
|
||||
$: =message-num
|
||||
=fragment-num
|
||||
==
|
||||
+$ live-packet-val
|
||||
$: packet-state
|
||||
num-fragments=fragment-num
|
||||
=fragment
|
||||
==
|
||||
+$ packet-state
|
||||
$: last-sent=@da
|
||||
retries=@ud
|
||||
skips=@ud
|
||||
==
|
||||
:: $message-sink-state: state of |message-sink to assemble messages
|
||||
::
|
||||
:: last-acked: highest $message-num we've fully acknowledged
|
||||
:: last-heard: highest $message-num we've heard all fragments on
|
||||
:: pending-vane-ack: heard but not processed by local vane
|
||||
:: live-messages: partially received messages
|
||||
::
|
||||
+$ message-sink-state
|
||||
$: last-acked=message-num
|
||||
last-heard=message-num
|
||||
pending-vane-ack=(qeu [=message-num message=*])
|
||||
live-messages=(map message-num partial-rcv-message)
|
||||
nax=(set message-num)
|
||||
==
|
||||
:: $partial-rcv-message: message for which we've received some fragments
|
||||
::
|
||||
:: num-fragments: total number of fragments in this message
|
||||
:: num-received: how many fragments we've received so far
|
||||
:: fragments: fragments we've received, eventually producing a $message
|
||||
::
|
||||
+$ partial-rcv-message
|
||||
$: num-fragments=fragment-num
|
||||
num-received=fragment-num
|
||||
fragments=(map fragment-num fragment)
|
||||
==
|
||||
::
|
||||
-- ::ames
|
||||
:: ::::
|
||||
:::: ++behn :: (1b) timekeeping
|
||||
@ -885,6 +1138,112 @@
|
||||
==
|
||||
::
|
||||
--
|
||||
:: +outstanding-connection: open http connections not fully complete:
|
||||
::
|
||||
:: This refers to outstanding connections where the connection to
|
||||
:: outside is opened and we are currently waiting on ford or an app to
|
||||
:: produce the results.
|
||||
::
|
||||
+$ outstanding-connection
|
||||
$: :: action: the action that had matched
|
||||
::
|
||||
=action
|
||||
:: inbound-request: the original request which caused this connection
|
||||
::
|
||||
=inbound-request
|
||||
:: response-header: set when we get our first %start
|
||||
::
|
||||
response-header=(unit response-header:http)
|
||||
:: bytes-sent: the total bytes sent in response
|
||||
::
|
||||
bytes-sent=@ud
|
||||
==
|
||||
:: +authentication-state: state used in the login system
|
||||
::
|
||||
+$ authentication-state
|
||||
$: :: sessions: a mapping of session cookies to session information
|
||||
::
|
||||
sessions=(map @uv session)
|
||||
==
|
||||
:: +session: server side data about a session
|
||||
::
|
||||
+$ session
|
||||
$: :: expiry-time: when this session expires
|
||||
::
|
||||
:: We check this server side, too, so we aren't relying on the browser
|
||||
:: to properly handle cookie expiration as a security mechanism.
|
||||
::
|
||||
expiry-time=@da
|
||||
::
|
||||
:: TODO: We should add a system for individual capabilities; we should
|
||||
:: mint some sort of long lived cookie for mobile apps which only has
|
||||
:: access to a single application path.
|
||||
==
|
||||
:: channel-state: state used in the channel system
|
||||
::
|
||||
+$ channel-state
|
||||
$: :: session: mapping between an arbitrary key to a channel
|
||||
::
|
||||
session=(map @t channel)
|
||||
:: by-duct: mapping from ducts to session key
|
||||
::
|
||||
duct-to-key=(map duct @t)
|
||||
==
|
||||
:: +timer: a reference to a timer so we can cancel or update it.
|
||||
::
|
||||
+$ timer
|
||||
$: :: date: time when the timer will fire
|
||||
::
|
||||
date=@da
|
||||
:: duct: duct that set the timer so we can cancel
|
||||
::
|
||||
=duct
|
||||
==
|
||||
:: channel: connection to the browser
|
||||
::
|
||||
:: Channels are the main method where a webpage communicates with Gall
|
||||
:: apps. Subscriptions and pokes are issues with PUT requests on a path,
|
||||
:: while GET requests on that same path open a persistent EventSource
|
||||
:: channel.
|
||||
::
|
||||
:: The EventSource API is a sequence number based API that browser provide
|
||||
:: which allow the server to push individual events to the browser over a
|
||||
:: connection held open. In case of reconnection, the browser will send a
|
||||
:: 'Last-Event-Id: ' header to the server; the server then resends all
|
||||
:: events since then.
|
||||
::
|
||||
+$ channel
|
||||
$: :: channel-state: expiration time or the duct currently listening
|
||||
::
|
||||
:: For each channel, there is at most one open EventSource
|
||||
:: connection. A 400 is issues on duplicate attempts to connect to the
|
||||
:: same channel. When an EventSource isn't connected, we set a timer
|
||||
:: to reap the subscriptions. This timer shouldn't be too short
|
||||
:: because the
|
||||
::
|
||||
state=(each timer duct)
|
||||
:: next-id: next sequence number to use
|
||||
::
|
||||
next-id=@ud
|
||||
:: events: unacknowledged events
|
||||
::
|
||||
:: We keep track of all events where we haven't received a
|
||||
:: 'Last-Event-Id: ' response from the client or a per-poke {'ack':
|
||||
:: ...} call. When there's an active EventSource connection on this
|
||||
:: channel, we send the event but we still add it to events because we
|
||||
:: can't assume it got received until we get an acknowledgment.
|
||||
::
|
||||
events=(qeu [id=@ud lines=wall])
|
||||
:: subscriptions: gall subscriptions
|
||||
::
|
||||
:: We maintain a list of subscriptions so if a channel times out, we
|
||||
:: can cancel all the subscriptions we've made.
|
||||
::
|
||||
subscriptions=(map wire [ship=@p app=term =path duc=duct])
|
||||
:: heartbeat: sse heartbeat timer
|
||||
::
|
||||
heartbeat=(unit timer)
|
||||
==
|
||||
:: +binding: A rule to match a path.
|
||||
::
|
||||
:: A +binding is a system unique mapping for a path to match. A +binding
|
||||
@ -904,6 +1263,25 @@
|
||||
::
|
||||
path=(list @t)
|
||||
==
|
||||
:: +action: the action to take when a binding matches an incoming request
|
||||
::
|
||||
+$ action
|
||||
$% :: dispatch to a generator
|
||||
::
|
||||
[%gen =generator]
|
||||
:: dispatch to an application
|
||||
::
|
||||
[%app app=term]
|
||||
:: internal authentication page
|
||||
::
|
||||
[%authentication ~]
|
||||
:: gall channel system
|
||||
::
|
||||
[%channel ~]
|
||||
:: respond with the default file not found page
|
||||
::
|
||||
[%four-oh-four ~]
|
||||
==
|
||||
:: +generator: a generator on the local ship that handles requests
|
||||
::
|
||||
:: This refers to a generator on the local ship, run with a set of
|
||||
|
183
pkg/interface/dbug/gulpfile.js
Normal file
183
pkg/interface/dbug/gulpfile.js
Normal file
@ -0,0 +1,183 @@
|
||||
var gulp = require('gulp');
|
||||
var cssimport = require('gulp-cssimport');
|
||||
var rollup = require('gulp-better-rollup');
|
||||
var cssnano = require('cssnano');
|
||||
var postcss = require('gulp-postcss');
|
||||
var sucrase = require('@sucrase/gulp-plugin');
|
||||
var minify = require('gulp-minify');
|
||||
var rename = require('gulp-rename');
|
||||
var del = require('del');
|
||||
|
||||
var resolve = require('rollup-plugin-node-resolve');
|
||||
var commonjs = require('rollup-plugin-commonjs');
|
||||
var rootImport = require('rollup-plugin-root-import');
|
||||
var globals = require('rollup-plugin-node-globals');
|
||||
|
||||
/***
|
||||
Main config options
|
||||
***/
|
||||
|
||||
var urbitrc = require('../urbitrc');
|
||||
|
||||
/***
|
||||
End main config options
|
||||
***/
|
||||
|
||||
gulp.task('css-bundle', function() {
|
||||
let plugins = [
|
||||
cssnano()
|
||||
];
|
||||
return gulp
|
||||
.src('src/index.css')
|
||||
.pipe(cssimport())
|
||||
.pipe(postcss(plugins))
|
||||
.pipe(gulp.dest('../../arvo/app/debug/css'));
|
||||
});
|
||||
|
||||
gulp.task('jsx-transform', function(cb) {
|
||||
return gulp.src('src/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('tile-jsx-transform', function(cb) {
|
||||
return gulp.src('tile/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('js-imports', function(cb) {
|
||||
return gulp.src('dist/index.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component', 'createRef', 'createElement', 'useState', 'useRef', 'useEffect', 'Fragment' ],
|
||||
'node_modules/react-is/index.js': [ 'isValidElementType' ],
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('tile-js-imports', function(cb) {
|
||||
return gulp.src('dist/tile.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component' ],
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('js-minify', function () {
|
||||
return gulp.src('../../arvo/app/debug/js/index.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'));
|
||||
});
|
||||
|
||||
gulp.task('tile-js-minify', function () {
|
||||
return gulp.src('../../arvo/app/debug/js/tile.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'));
|
||||
});
|
||||
|
||||
gulp.task('rename-index-min', function() {
|
||||
return gulp.src('../../arvo/app/debug/js/index-min.js')
|
||||
.pipe(rename('index.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'))
|
||||
});
|
||||
|
||||
gulp.task('rename-tile-min', function() {
|
||||
return gulp.src('../../arvo/app/debug/js/tile-min.js')
|
||||
.pipe(rename('tile.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/debug/js/'))});
|
||||
|
||||
gulp.task('clean-min', function() {
|
||||
return del(['../../arvo/app/debug/js/index-min.js', '../../arvo/app/debug/js/tile-min.js'], {force: true})
|
||||
});
|
||||
|
||||
gulp.task('urbit-copy', function () {
|
||||
let ret = gulp.src('../../arvo/**/*');
|
||||
|
||||
urbitrc.URBIT_PIERS.forEach(function(pier) {
|
||||
ret = ret.pipe(gulp.dest(pier));
|
||||
});
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
gulp.task('js-bundle-dev', gulp.series('jsx-transform', 'js-imports'));
|
||||
gulp.task('tile-js-bundle-dev', gulp.series('tile-jsx-transform', 'tile-js-imports'));
|
||||
gulp.task('js-bundle-prod', gulp.series('jsx-transform', 'js-imports', 'js-minify'))
|
||||
gulp.task('tile-js-bundle-prod',
|
||||
gulp.series('tile-jsx-transform', 'tile-js-imports', 'tile-js-minify'));
|
||||
|
||||
gulp.task('bundle-dev',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-dev',
|
||||
'tile-js-bundle-dev'
|
||||
),
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('bundle-prod',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-prod',
|
||||
'tile-js-bundle-prod',
|
||||
),
|
||||
'rename-index-min',
|
||||
'rename-tile-min',
|
||||
'clean-min',
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('default', gulp.series('bundle-dev'));
|
||||
|
||||
gulp.task('watch', gulp.series('default', function() {
|
||||
gulp.watch('tile/**/*.js', gulp.parallel('tile-js-bundle-dev'));
|
||||
|
||||
gulp.watch('src/**/*.js', gulp.parallel('js-bundle-dev'));
|
||||
gulp.watch('src/**/*.css', gulp.parallel('css-bundle'));
|
||||
|
||||
gulp.watch('../../arvo/**/*', gulp.parallel('urbit-copy'));
|
||||
}));
|
6543
pkg/interface/dbug/package-lock.json
generated
Normal file
6543
pkg/interface/dbug/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
pkg/interface/dbug/package.json
Normal file
43
pkg/interface/dbug/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "urbit-apps",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@sucrase/gulp-plugin": "^2.0.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-cssimport": "^7.0.0",
|
||||
"gulp-minify": "^3.1.0",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"moment": "^2.24.0",
|
||||
"rollup": "^1.6.0",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-root-import": "^0.2.3",
|
||||
"sucrase": "^3.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gitgraph/react": "^1.5.4",
|
||||
"classnames": "^2.2.6",
|
||||
"del": "^5.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"mousetrap": "^1.6.3",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"urbit-ob": "^5.0.0",
|
||||
"urbit-sigil-js": "^1.3.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
}
|
||||
}
|
217
pkg/interface/dbug/src/css/custom.css
Normal file
217
pkg/interface/dbug/src/css/custom.css
Normal file
@ -0,0 +1,217 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
|
||||
margin-block-end: unset;
|
||||
margin-block-start: unset;
|
||||
-webkit-margin-before: unset;
|
||||
-webkit-margin-after: unset;
|
||||
font-family: Inter, sans-serif;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button, summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.inter {
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
.clamp-3 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.clamp-message {
|
||||
max-width: calc(100% - 36px - 1.5rem);
|
||||
}
|
||||
|
||||
.clamp-attachment {
|
||||
overflow: scroll;
|
||||
max-height: 10em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.lh-16 {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: "Source Code Pro", monospace;
|
||||
}
|
||||
|
||||
.list-ship {
|
||||
line-height: 2.2;
|
||||
}
|
||||
|
||||
.bg-welcome-green {
|
||||
background-color: #ECF6F2;
|
||||
}
|
||||
|
||||
.c-default {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.m0a {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mix-blend-diff {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.focus-b--black:focus {
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.embed-container {
|
||||
position: relative;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
|
||||
.embed-container iframe, .embed-container object, .embed-container embed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* spinner */
|
||||
|
||||
.spin-active {
|
||||
animation: spin 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {transform: rotate(0deg);}
|
||||
25% {transform: rotate(90deg);}
|
||||
50% {transform: rotate(180deg);}
|
||||
75% {transform: rotate(270deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
/* toggler checkbox */
|
||||
|
||||
.toggle::after {
|
||||
content: "";
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.toggle.checked::after {
|
||||
left: 14px;
|
||||
}
|
||||
|
||||
/* responsive */
|
||||
@media all and (max-width: 34.375em) {
|
||||
.dn-s {
|
||||
display: none;
|
||||
}
|
||||
.flex-basis-100-s, .flex-basis-full-s {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.h-100-m-40-s {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
.black-s {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 34.375em) {
|
||||
.db-ns {
|
||||
display: block;
|
||||
}
|
||||
.flex-basis-30-ns {
|
||||
flex-basis: 30vw;
|
||||
}
|
||||
.h-100-m-40-ns {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #333;
|
||||
}
|
||||
.bg-black-d {
|
||||
background-color: black;
|
||||
}
|
||||
.white-d {
|
||||
color: white;
|
||||
}
|
||||
.gray1-d {
|
||||
color: #4d4d4d;
|
||||
}
|
||||
.gray2-d {
|
||||
color: #7f7f7f;
|
||||
}
|
||||
.gray3-d {
|
||||
color: #b1b2b3;
|
||||
}
|
||||
.gray4-d {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.bg-gray0-d {
|
||||
background-color: #333;
|
||||
}
|
||||
.bg-gray1-d {
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
.b--gray0-d {
|
||||
border-color: #333;
|
||||
}
|
||||
.b--gray1-d {
|
||||
border-color: #4d4d4d;
|
||||
}
|
||||
.b--gray2-d {
|
||||
border-color: #7f7f7f;
|
||||
}
|
||||
.b--white-d {
|
||||
border-color: #fff;
|
||||
}
|
||||
.bb-d {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
.invert-d {
|
||||
filter: invert(1);
|
||||
}
|
||||
.o-60-d {
|
||||
opacity: .6;
|
||||
}
|
||||
.focus-b--white-d:focus {
|
||||
border-color: #fff;
|
||||
}
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
.hover-bg-gray1-d:hover {
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
}
|
63
pkg/interface/dbug/src/css/fonts.css
Normal file
63
pkg/interface/dbug/src/css/fonts.css
Normal file
@ -0,0 +1,63 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff");
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
1
pkg/interface/dbug/src/css/indigo-static.css
Normal file
1
pkg/interface/dbug/src/css/indigo-static.css
Normal file
File diff suppressed because one or more lines are too long
4
pkg/interface/dbug/src/index.css
Normal file
4
pkg/interface/dbug/src/index.css
Normal file
@ -0,0 +1,4 @@
|
||||
@import "css/indigo-static.css";
|
||||
@import "css/fonts.css";
|
||||
@import "css/custom.css";
|
||||
|
16
pkg/interface/dbug/src/index.js
Normal file
16
pkg/interface/dbug/src/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Root } from '/components/root';
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { subscription } from "/subscription";
|
||||
|
||||
api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
subscription.start();
|
||||
|
||||
ReactDOM.render((
|
||||
<Root />
|
||||
), document.querySelectorAll("#root")[0]);
|
244
pkg/interface/dbug/src/js/api.js
Normal file
244
pkg/interface/dbug/src/js/api.js
Normal file
@ -0,0 +1,244 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import { store } from '/store';
|
||||
import moment from 'moment';
|
||||
import { stringToTa } from './lib/util';
|
||||
|
||||
|
||||
class UrbitApi {
|
||||
setAuthTokens(authTokens) {
|
||||
this.authTokens = authTokens;
|
||||
this.bindPaths = [];
|
||||
|
||||
this.bind = this.bind.bind(this);
|
||||
}
|
||||
|
||||
bind(path, method, ship = this.authTokens.ship, app, success, fail, quit) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = window.urb.subscribe(ship, app, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
quit(qui);
|
||||
});
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.urb.poke(ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dbugAction(data) {
|
||||
return this.action("dbug", "dbug-action", data);
|
||||
}
|
||||
|
||||
bindToVerb(app) {
|
||||
return this.bind('/verb/events', 'PUT', this.authTokens.ship, app,
|
||||
(result) => {
|
||||
result.data.app = app;
|
||||
store.handleEvent({data: { local: { verbResult: result.data }}});
|
||||
},
|
||||
() => {
|
||||
store.handleEvent({data: { local: { verbStatus: {
|
||||
app: app,
|
||||
msg: 'failed to establish verb connection to ' + app
|
||||
}}}});
|
||||
},
|
||||
() => {
|
||||
store.handleEvent({data: { local: { verbStatus: {
|
||||
app: app,
|
||||
msg: 'verb connection to ' + app + ' was dropped'
|
||||
}}}});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getJson(path, localTransform, onFail) {
|
||||
let source = '/~debug' + path + '.json';
|
||||
const query = window.location.href.split('?')[1];
|
||||
if (query) source = source + '?' + query;
|
||||
fetch(source)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
console.error('Network response not ok');
|
||||
onFail();
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
store.handleEvent({data: { local: localTransform(data) }});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`JSON fetch on ${source} failed:`, error);
|
||||
onFail();
|
||||
});
|
||||
}
|
||||
|
||||
wrapLocal(name) {
|
||||
return (data) => {
|
||||
let e = {};
|
||||
e[name] = data;
|
||||
e['status'] = null; // clear previous status
|
||||
return e;
|
||||
};
|
||||
}
|
||||
|
||||
showStatus(what) {
|
||||
return () => {
|
||||
store.handleEvent({data: { local: { 'status': what }}});
|
||||
};
|
||||
}
|
||||
|
||||
// apps
|
||||
|
||||
getApps() {
|
||||
this.getJson('/apps',
|
||||
this.wrapLocal('apps'),
|
||||
this.showStatus('error fetching apps')
|
||||
);
|
||||
}
|
||||
|
||||
getAppDetails(app) {
|
||||
this.getJson('/app/'+app, (data) => {
|
||||
data.app = app;
|
||||
return this.wrapLocal('app')(data);
|
||||
},
|
||||
() => { // on fail
|
||||
store.handleEvent({data: { local: { 'appFailed': app } }});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getAppState(app, state = '') {
|
||||
if (state !== '') {
|
||||
state = '/' + stringToTa(state)
|
||||
}
|
||||
this.getJson('/app/'+app+'/state'+state, (data) => {
|
||||
data.app = app;
|
||||
return this.wrapLocal('appState')(data);
|
||||
},
|
||||
() => { // on fail
|
||||
store.handleEvent({data: { local: { 'appFailed': app } }});
|
||||
});
|
||||
}
|
||||
|
||||
// spider
|
||||
|
||||
getThreads() {
|
||||
this.getJson('/spider/threads',
|
||||
this.wrapLocal('threads'),
|
||||
this.showStatus('error fetching threads')
|
||||
);
|
||||
}
|
||||
|
||||
killThread(tid) {
|
||||
return this.action("spider", "spider-stop", {tid, nice: false})
|
||||
.then(this.getThreads.bind(this));
|
||||
}
|
||||
|
||||
// ames
|
||||
|
||||
getPeers() {
|
||||
this.getJson('/ames/peer',
|
||||
this.wrapLocal('amesPeers'),
|
||||
this.showStatus('error fetching ames peers')
|
||||
);
|
||||
}
|
||||
|
||||
getPeer(who) {
|
||||
this.getJson(`/ames/peer/${who}`, (data) => {
|
||||
data.who = who;
|
||||
return this.wrapLocal('amesPeer')(data);
|
||||
},
|
||||
this.showStatus('error fetching ames details for ' + who)
|
||||
);
|
||||
}
|
||||
|
||||
// behn
|
||||
|
||||
getTimers() {
|
||||
this.getJson('/behn/timers',
|
||||
this.wrapLocal('behnTimers'),
|
||||
this.showStatus('error fetching behn timers')
|
||||
);
|
||||
}
|
||||
|
||||
// clay
|
||||
|
||||
getCommits() {
|
||||
this.getJson('/clay/commits',
|
||||
this.wrapLocal('clayCommits'),
|
||||
this.showStatus('error fetching clay commits')
|
||||
);
|
||||
}
|
||||
|
||||
// eyre
|
||||
|
||||
getBindings() {
|
||||
this.getJson('/eyre/bindings',
|
||||
this.wrapLocal('eyreBindings'),
|
||||
this.showStatus('error fetching eyre bindings')
|
||||
);
|
||||
}
|
||||
|
||||
getConnections() {
|
||||
this.getJson('/eyre/connections',
|
||||
this.wrapLocal('eyreConnections'),
|
||||
this.showStatus('error fetching eyre connections')
|
||||
);
|
||||
}
|
||||
|
||||
getAuthenticationState() {
|
||||
this.getJson('/eyre/authentication',
|
||||
this.wrapLocal('eyreAuthentication'),
|
||||
this.showStatus('error fetching eyre authentication state')
|
||||
);
|
||||
}
|
||||
|
||||
getChannels() {
|
||||
this.getJson('/eyre/channels',
|
||||
this.wrapLocal('eyreChannels'),
|
||||
this.showStatus('error fetching eyre channels')
|
||||
);
|
||||
}
|
||||
|
||||
// local
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
'sidebarToggle': sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let api = new UrbitApi();
|
||||
window.api = api;
|
8
pkg/interface/dbug/src/js/components/loading.js
Normal file
8
pkg/interface/dbug/src/js/components/loading.js
Normal file
@ -0,0 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { MessageScreen } from '/components/lib/message-screen';
|
||||
|
||||
export class LoadingScreen extends Component {
|
||||
render() {
|
||||
return (<MessageScreen text="Loading..."/>);
|
||||
}
|
||||
}
|
15
pkg/interface/dbug/src/js/components/message-screen.js
Normal file
15
pkg/interface/dbug/src/js/components/message-screen.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class MessageScreen extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column bg-white bg-gray0-d dn db-ns">
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f8 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
{this.props.text}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
116
pkg/interface/dbug/src/js/components/root.js
Normal file
116
pkg/interface/dbug/src/js/components/root.js
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { api } from '/api';
|
||||
import { subscription } from '/subscription';
|
||||
import { store } from '/store';
|
||||
import { Skeleton } from '/components/skeleton';
|
||||
import { MessageScreen } from '/components/message-screen';
|
||||
import { Apps } from '/views/apps';
|
||||
import { Spider } from '/views/spider';
|
||||
import { Ames } from '/views/ames';
|
||||
import { Behn } from '/views/behn';
|
||||
import { Clay } from '/views/clay';
|
||||
import { Eyre } from '/views/eyre';
|
||||
import { makeRoutePath } from '../lib/util';
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = store.state;
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// preload spinner asset
|
||||
new Image().src = "/~debug/img/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
|
||||
return (
|
||||
<BrowserRouter><Switch>
|
||||
<Route exact path="/~debug"
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="">
|
||||
<MessageScreen text="select a component on the left" />
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('apps')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="apps">
|
||||
<Apps apps={state.apps} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('spider')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="spider">
|
||||
<Spider threads={state.threads} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('ames')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="ames">
|
||||
<Ames peers={state.peers} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('behn')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="behn">
|
||||
<Behn timers={state.timers} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('clay')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="clay">
|
||||
<Clay commits={state.commits} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Route exact path={makeRoutePath('eyre')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton status={state.status} selected="eyre">
|
||||
<Eyre
|
||||
bindings={state.bindings}
|
||||
connections={state.connections}
|
||||
authentication={state.authentication}
|
||||
channels={state.channels}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
</Switch></BrowserRouter>
|
||||
)
|
||||
}
|
||||
}
|
49
pkg/interface/dbug/src/js/components/searchable-list.js
Normal file
49
pkg/interface/dbug/src/js/components/searchable-list.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class SearchableList extends Component {
|
||||
// expected props:
|
||||
// items: [{key: 'some key', jsx: <w/e>}, ...]
|
||||
// placeholder: ''
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
query: ''
|
||||
};
|
||||
|
||||
this.updateQuery = this.updateQuery.bind(this);
|
||||
}
|
||||
|
||||
updateQuery(event) {
|
||||
this.setState({ query: event.target.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
|
||||
const searchBar = (
|
||||
<input
|
||||
type="text"
|
||||
placeholder={props.placeholder}
|
||||
onChange={this.updateQuery}
|
||||
value={state.query}
|
||||
style={{border: '1px solid black'}}
|
||||
/>
|
||||
);
|
||||
|
||||
let items = props.items.filter(item => {
|
||||
return state.query.split(' ').reduce((match, query) => {
|
||||
return match && item.key.includes(query);
|
||||
}, true);
|
||||
})
|
||||
items = items.map(item =>
|
||||
(<div key={item.key} style={{marginTop: '4px'}}>{item.jsx}</div>)
|
||||
);
|
||||
|
||||
return (<div style={{position: 'relative', border: '1px solid grey', padding: '4px'}}>
|
||||
{props.children}
|
||||
<div>{searchBar} ({items.length})</div>
|
||||
<div>{items.length === 0 ? 'none' : items}</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
79
pkg/interface/dbug/src/js/components/skeleton.js
Normal file
79
pkg/interface/dbug/src/js/components/skeleton.js
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from "react-router-dom";
|
||||
import classnames from 'classnames';
|
||||
import { makeRoutePath } from '../lib/util';
|
||||
|
||||
class SidebarItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let selectedClass = (props.selected)
|
||||
? "bg-gray5 bg-gray1-d"
|
||||
: "pointer hover-bg-gray5 hover-bg-gray1-d";
|
||||
|
||||
return (
|
||||
<Link to={makeRoutePath(props.what, true)} key="what">
|
||||
<div className={"w-100 v-mid f9 ph4 z1 pv1 " + selectedClass}>
|
||||
<p className="f9 dib">{props.what}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let items = [
|
||||
'apps',
|
||||
'spider',
|
||||
'ames',
|
||||
'behn',
|
||||
//TODO 'clay',
|
||||
'eyre'
|
||||
];
|
||||
items = items.map(what => {
|
||||
return (<SidebarItem what={what} selected={props.selected === what}/>);
|
||||
});
|
||||
|
||||
let rightPanelHide = this.props.rightPanelHide
|
||||
? "dn-s" : "";
|
||||
|
||||
const status = props.status
|
||||
? (<div style={{
|
||||
position: 'absolute', right: '16px', bottom: '16px',
|
||||
padding: '8px', border: '1px solid #e22'
|
||||
}}>
|
||||
{props.status}
|
||||
</div>)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="absolute h-100 w-100 mono">
|
||||
<div className="cf w-100 h-100 flex">
|
||||
<div className="bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 flex-shrink-0 mw5-m mw5-l mw5-xl pt3 pt0-m pt0-l pt0-xl relative">
|
||||
<a className="db dn-m dn-l dn-xl f8 pb3 pl3" href="/">⟵ Landscape</a>
|
||||
<div className="overflow-y-scroll h-100">
|
||||
<div className="w-100 bg-transparent">
|
||||
<Link
|
||||
className="dib f9 pointer green2 gray4-d pa4"
|
||||
to={"/~chat/join/~/~dopzod/urbit-help"}>
|
||||
Get help
|
||||
</Link>
|
||||
</div>
|
||||
{items}
|
||||
</div>
|
||||
</div>
|
||||
{status}
|
||||
<div className={"h-100 w-100 flex-auto overflow-scroll relative " + rightPanelHide} style={{
|
||||
flexGrow: 1,
|
||||
padding: '8px'
|
||||
}}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
68
pkg/interface/dbug/src/js/components/subscriptions.js
Normal file
68
pkg/interface/dbug/src/js/components/subscriptions.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { Component } from 'react';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
import { renderDuct } from '../lib/util';
|
||||
|
||||
export class Subscriptions extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
//
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const incoming = props.in.map(inc => {
|
||||
return {key: '~'+inc.ship + ' ' + inc.path, jsx: (
|
||||
<div class="flex">
|
||||
<div class="flex-auto" style={{maxWidth: '10%'}}>
|
||||
~{inc.ship}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '30%'}}>
|
||||
{inc.path}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '60%'}}>
|
||||
{renderDuct(inc.duct)}
|
||||
</div>
|
||||
</div>
|
||||
)};
|
||||
});
|
||||
|
||||
const outgoing = props.out.map(out => {
|
||||
return {key: `~${out.ship} ${out.app} ${out.wire} ${out.path}`, jsx: (
|
||||
<div class="flex">
|
||||
<div class="flex-auto" style={{maxWidth: '35%'}}>
|
||||
{out.wire}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '10%'}}>
|
||||
~{out.ship}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '10%'}}>
|
||||
{out.app}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '35%'}}>
|
||||
{out.path}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '10%'}}>
|
||||
{out.acked ? 'acked' : 'not acked'}
|
||||
</div>
|
||||
</div>
|
||||
)};
|
||||
});
|
||||
|
||||
return (<div>
|
||||
<h4>Incoming</h4>
|
||||
<SearchableList placeholder="ship / path" items={incoming} />
|
||||
<h4>Outgoing</h4>
|
||||
<SearchableList placeholder="ship / app / wire / path" items={outgoing} />
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default Links;
|
36
pkg/interface/dbug/src/js/components/summary.js
Normal file
36
pkg/interface/dbug/src/js/components/summary.js
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Summary extends Component {
|
||||
// expected props:
|
||||
// id: 'id'
|
||||
// summary: <jsx>
|
||||
// details: <jsx>
|
||||
// onOpen: function(id)
|
||||
// onClose: function(id)
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onToggle = this.onToggle.bind(this);
|
||||
}
|
||||
|
||||
onToggle(event) {
|
||||
if (event.target.open) {
|
||||
if (this.props.onOpen) this.props.onOpen(this.props.id);
|
||||
} else {
|
||||
if (this.props.onClose) this.props.onClose(this.props.id);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<details onToggle={this.onToggle} {...props} style={{border: '1px solid black', padding: '4px', position: 'relative', ...props.style}}>
|
||||
<summary>
|
||||
{props.summary}
|
||||
</summary>
|
||||
<div style={{borderTop: '1px solid black'}}>{props.details}</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
}
|
70
pkg/interface/dbug/src/js/lib/util.js
Normal file
70
pkg/interface/dbug/src/js/lib/util.js
Normal file
@ -0,0 +1,70 @@
|
||||
import _ from 'lodash';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export function makeRoutePath(resource, includeQuery = false) {
|
||||
let query = window.location.href.split('?')[1];
|
||||
if (includeQuery && query) {
|
||||
query = '?' + query;
|
||||
} else {
|
||||
query = '';
|
||||
}
|
||||
return '/~debug/' + resource + query;
|
||||
}
|
||||
|
||||
export function msToDa(ms, mil) {
|
||||
const d = new Date(ms);
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
`${(d.getUTCMonth() + 1)}.` +
|
||||
`${fil(d.getUTCDate())}..` +
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
export function renderDuct(duct) {
|
||||
return duct.reduce((a, b) => a + b + ' ', '');
|
||||
}
|
||||
|
||||
// encode the string into @ta-safe format, using logic from +wood.
|
||||
// for example, 'some Chars!' becomes '~.some.~43.hars~21.'
|
||||
// this is equivalent to (scot %t string)
|
||||
//
|
||||
export function stringToTa(string) {
|
||||
let out = '';
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
const char = string[i];
|
||||
let add = '';
|
||||
switch (char) {
|
||||
case ' ':
|
||||
add = '.';
|
||||
break;
|
||||
case '.':
|
||||
add = '~.';
|
||||
break;
|
||||
case '~':
|
||||
add = '~~';
|
||||
break;
|
||||
default:
|
||||
const charCode = string.charCodeAt(i);
|
||||
if (
|
||||
(charCode >= 97 && charCode <= 122) || // a-z
|
||||
(charCode >= 48 && charCode <= 57) || // 0-9
|
||||
char === '-'
|
||||
) {
|
||||
add = char;
|
||||
} else {
|
||||
// TODO behavior for unicode doesn't match +wood's,
|
||||
// but we can probably get away with that for now.
|
||||
add = '~' + charCode.toString(16) + '.';
|
||||
}
|
||||
}
|
||||
out = out + add;
|
||||
}
|
||||
return '~~' + out;
|
||||
}
|
179
pkg/interface/dbug/src/js/reducers/local.js
Normal file
179
pkg/interface/dbug/src/js/reducers/local.js
Normal file
@ -0,0 +1,179 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class LocalReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'local', false);
|
||||
if (data) {
|
||||
this.status(data, state);
|
||||
//
|
||||
this.apps(data, state);
|
||||
this.app(data, state);
|
||||
this.appState(data, state);
|
||||
this.appFailed(data, state);
|
||||
this.verbResult(data, state);
|
||||
this.verbStatus(data, state);
|
||||
//
|
||||
this.threads(data, state);
|
||||
//
|
||||
this.amesPeers(data, state);
|
||||
this.amesPeer(data, state);
|
||||
//
|
||||
this.behnTimers(data, state);
|
||||
//
|
||||
this.clayCommits(data, state);
|
||||
//
|
||||
this.eyreBindings(data, state);
|
||||
this.eyreConnections(data, state);
|
||||
this.eyreAuthentication(data, state);
|
||||
this.eyreChannels(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
status(obj, state) {
|
||||
const data = _.get(obj, 'status', false);
|
||||
if (data) {
|
||||
state.status = data;
|
||||
}
|
||||
}
|
||||
|
||||
// apps
|
||||
|
||||
apps(obj, state) {
|
||||
const data = _.get(obj, 'apps', false);
|
||||
if (data) {
|
||||
Object.keys(data).map(app => {
|
||||
if (!state.apps[app]) {
|
||||
state.apps[app] = data[app];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app(obj, state) {
|
||||
const data = _.get(obj, 'app', false);
|
||||
if (data) {
|
||||
if (state.apps[data.app]) data.state = state.apps[data.app].state;
|
||||
state.apps[data.app] = data;
|
||||
}
|
||||
}
|
||||
|
||||
appState(obj, state) {
|
||||
const data = _.get(obj, 'appState', false);
|
||||
if (data) {
|
||||
state.apps[data.app].state = data.state;
|
||||
}
|
||||
}
|
||||
|
||||
appFailed(obj, state) {
|
||||
const data = _.get(obj, 'appFailed', false);
|
||||
if (data) {
|
||||
console.log('loading app deets failed', data);
|
||||
state.apps[data] = { noDebug: true };
|
||||
}
|
||||
}
|
||||
|
||||
verbResult(obj, state) {
|
||||
const data = _.get(obj, 'verbResult', false);
|
||||
if (data) {
|
||||
if (!state.apps[data.app]) state.apps[data.app] = {};
|
||||
if (!state.apps[data.app].events) state.apps[data.app].events = [];
|
||||
let msg = 'some event';
|
||||
if (data['on-init']) msg = '+on-init';
|
||||
if (data['on-load']) msg = '+on-load';
|
||||
if (data['on-poke']) msg = '+on-poke with mark ' + data['on-poke'];
|
||||
if (data['on-watch']) msg = '+on-watch at path ' + data['on-watch'];
|
||||
if (data['on-leave']) msg = '+on-leave on path ' + data['on-leave'];
|
||||
if (data['on-agent']) msg = '+on-agent at wire ' + data['on-agent'].wire +
|
||||
' with sign ' + data['on-agent'].sign;
|
||||
if (data['on-arvo']) msg = '+on-arvo at wire ' + data['on-arvo'].wire +
|
||||
' from vane ' + data['on-arvo'].vane +
|
||||
' with sign ' + data['on-arvo'].sign;
|
||||
if (data['on-fail']) msg = '+on-fail on ' + data['on-fail'];
|
||||
state.apps[data.app].events.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
verbStatus(obj, state) {
|
||||
const data = _.get(obj, 'verbStatus', false);
|
||||
if (data) {
|
||||
if (!state.apps[data.app]) state.apps[data.app] = {};
|
||||
if (!state.apps[data.app].events) state.apps[data.app].events = [];
|
||||
state.apps[data.app].events.push(data.msg);
|
||||
}
|
||||
}
|
||||
|
||||
// spider
|
||||
|
||||
threads(obj, state) {
|
||||
const data = _.get(obj, 'threads', false);
|
||||
if (data) {
|
||||
state.threads = data;
|
||||
}
|
||||
}
|
||||
|
||||
// ames
|
||||
|
||||
amesPeers(obj, state) {
|
||||
const data = _.get(obj, 'amesPeers', false);
|
||||
if (data) {
|
||||
state.peers.known = data.known;
|
||||
state.peers.alien = data.alien;
|
||||
}
|
||||
}
|
||||
|
||||
amesPeer(obj, state) {
|
||||
const data = _.get(obj, 'amesPeer', false);
|
||||
if (data) {
|
||||
state.peers.deets[data.who] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// behn
|
||||
|
||||
behnTimers(obj, state) {
|
||||
const data = _.get(obj, 'behnTimers', false);
|
||||
if (data) {
|
||||
state.timers = data;
|
||||
}
|
||||
}
|
||||
|
||||
// clay
|
||||
|
||||
clayCommits(obj, state) {
|
||||
const data = _.get(obj, 'clayCommits', false);
|
||||
if (data) {
|
||||
console.log('clay comms', data);
|
||||
state.commits = data;
|
||||
}
|
||||
}
|
||||
|
||||
// eyre
|
||||
|
||||
eyreBindings(obj, state) {
|
||||
const data = _.get(obj, 'eyreBindings', false);
|
||||
if (data) {
|
||||
state.bindings = data;
|
||||
}
|
||||
}
|
||||
|
||||
eyreConnections(obj, state) {
|
||||
const data = _.get(obj, 'eyreConnections', false);
|
||||
if (data) {
|
||||
state.connections = data;
|
||||
}
|
||||
}
|
||||
|
||||
eyreAuthentication(obj, state) {
|
||||
const data = _.get(obj, 'eyreAuthentication', false);
|
||||
if (data) {
|
||||
state.authentication = data;
|
||||
}
|
||||
}
|
||||
|
||||
eyreChannels(obj, state) {
|
||||
const data = _.get(obj, 'eyreChannels', false);
|
||||
if (data) {
|
||||
state.channels = data;
|
||||
}
|
||||
}
|
||||
}
|
44
pkg/interface/dbug/src/js/store.js
Normal file
44
pkg/interface/dbug/src/js/store.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { LocalReducer } from '/reducers/local.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
class Store {
|
||||
constructor() {
|
||||
this.state = {
|
||||
status: null,
|
||||
apps: {},
|
||||
threads: {},
|
||||
peers: { known: [], alien: [], deets: {}},
|
||||
timers: [],
|
||||
commits: [],
|
||||
bindings: [],
|
||||
connections: [],
|
||||
authentication: [],
|
||||
channels: [],
|
||||
sidebarShown: true
|
||||
};
|
||||
|
||||
this.localReducer = new LocalReducer();
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(data) {
|
||||
let json;
|
||||
if (data.data) {
|
||||
json = data.data;
|
||||
} else {
|
||||
json = data;
|
||||
}
|
||||
|
||||
console.log('event', json);
|
||||
this.localReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
window.store = store;
|
31
pkg/interface/dbug/src/js/subscription.js
Normal file
31
pkg/interface/dbug/src/js/subscription.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
//
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
handleQuitSilently(quit) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
handleQuitAndResubscribe(quit) {
|
||||
// TODO: resubscribe
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let subscription = new Subscription();
|
316
pkg/interface/dbug/src/js/views/ames.js
Normal file
316
pkg/interface/dbug/src/js/views/ames.js
Normal file
@ -0,0 +1,316 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { msToDa, renderDuct } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { Summary } from '../components/summary';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
|
||||
export class Ames extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.loadPeers = this.loadPeers.bind(this);
|
||||
this.loadPeerDetails = this.loadPeerDetails.bind(this);
|
||||
this.renderFlow = this.renderFlow.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { known, alien } = this.props.peers;
|
||||
if (known.length === 0 && alien.length === 0) {
|
||||
this.loadPeers();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
loadPeers() {
|
||||
api.getPeers();
|
||||
}
|
||||
|
||||
loadPeerDetails(who) {
|
||||
api.getPeer(who);
|
||||
}
|
||||
|
||||
renderDucts(ducts) {
|
||||
const items = ducts.map(duct => {
|
||||
return {
|
||||
key: duct.join(' '),
|
||||
jsx: (<div>{renderDuct(duct)}</div>)
|
||||
}
|
||||
});
|
||||
return <SearchableList placeholder="duct" items={items}/>
|
||||
}
|
||||
|
||||
renderSnd(snd) {
|
||||
const unsent = snd['unsent-messages'].reduce((a, b) => {
|
||||
return a + b + ' bytes, ';
|
||||
}, 'unsent msg sizes: ');
|
||||
const queuedAcks = snd['queued-message-acks'].map(qa => {
|
||||
return {key: qa['message-num'], jsx: (
|
||||
qa['message-num'] + ': ' + qa.ack
|
||||
)};
|
||||
});
|
||||
const m = snd['packet-pump-state'].metrics;
|
||||
const pumpMetrics = (<>
|
||||
<table><tbody>
|
||||
<tr class="inter">
|
||||
<td>rto</td>
|
||||
<td>rtt</td>
|
||||
<td>rttvar</td>
|
||||
<td>ssthresh</td>
|
||||
<td>num-live</td>
|
||||
<td>cwnd</td>
|
||||
<td>counter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{m.rto}</td>
|
||||
<td>{m.rtt}</td>
|
||||
<td>{m.rttvar}</td>
|
||||
<td>{m.ssthresh}</td>
|
||||
<td>{m['num-live']}</td>
|
||||
<td>{m.cwnd}</td>
|
||||
<td>{m.counter}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</>);
|
||||
|
||||
const liveItems = snd['packet-pump-state'].live.map(live => {
|
||||
return {key: live['message-num']+','+live['fragment-num'], jsx: (
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td>message-num</td>
|
||||
<td>fragment-num</td>
|
||||
<td>num-fragments</td>
|
||||
<td>last-sent</td>
|
||||
<td>retries</td>
|
||||
<td>skips</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{live['message-num']}</td>
|
||||
<td>{live['fragment-num']}</td>
|
||||
<td>{live['num-fragments']}</td>
|
||||
<td>{msToDa(live['last-sent'])}</td>
|
||||
<td>{live.retries}</td>
|
||||
<td>{live.skips}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
)};
|
||||
});
|
||||
const live = (
|
||||
<SearchableList placeholder="msg-num,frag-num" items={liveItems} />
|
||||
);
|
||||
|
||||
const summary = (<>
|
||||
<b>snd</b><br/>
|
||||
{renderDuct(snd.duct)}
|
||||
<table><tbody>
|
||||
<tr class="inter">
|
||||
<td>bone</td>
|
||||
<td>current</td>
|
||||
<td>next</td>
|
||||
<td>next wake</td>
|
||||
<td>total unsent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{snd.bone}</td>
|
||||
<td>{snd.current}</td>
|
||||
<td>{snd.next}</td>
|
||||
<td>{msToDa(snd['packet-pump-state']['next-wake'])}</td>
|
||||
<td>
|
||||
{snd['unsent-messages'].reduce((a,b) => a+b, 0)} bytes
|
||||
({snd['unsent-messages'].length} messages)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</>);
|
||||
const details = (<>
|
||||
{pumpMetrics}
|
||||
{unsent}
|
||||
{queuedAcks}
|
||||
{live}
|
||||
</>);
|
||||
const active = ( snd['unsent-messages'].length > 0 ||
|
||||
snd['packet-pump-state'].live.length > 0 )
|
||||
? 'active, '
|
||||
: '';
|
||||
return {key: 'snd ' + active + snd.bone + ', ' + renderDuct(snd.duct), jsx: (
|
||||
<Summary summary={summary} details={details} />
|
||||
)};
|
||||
}
|
||||
|
||||
renderRcv(rcv) {
|
||||
const pendingVaneAcks = rcv['pending-vane-ack'].reduce((a, b) => {
|
||||
return a + b + ', ';
|
||||
}, 'pending vane acks: ');
|
||||
const nax = rcv.nax.reduce((a, b) => {
|
||||
return a + b + ', ';
|
||||
}, 'nacks: ');
|
||||
const liveItems = rcv['live-messages'].map(live => {
|
||||
return {key: live['message-num'], jsx: (<>
|
||||
Message #{live['message-num']}<br/>
|
||||
{live['num-received']} out of {live['num-fragments']} fragments received:<br/>
|
||||
{live.fragments.reduce((a, b) => a + b + ', ', '')}
|
||||
</>)};
|
||||
});
|
||||
const liveMessages = (<>
|
||||
Live messages:<br/>
|
||||
<SearchableList placeholder="message num" items={liveItems} />
|
||||
</>);
|
||||
|
||||
const summary = (<>
|
||||
<b>rcv</b><br/>
|
||||
{renderDuct(rcv.duct)}
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td>bone</td>
|
||||
<td>last-acked</td>
|
||||
<td>last-heard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{rcv.bone}</td>
|
||||
<td>{rcv['last-acked']}</td>
|
||||
<td>{rcv['last-heard']}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</>);
|
||||
const details = (<>
|
||||
{pendingVaneAcks}<br/>
|
||||
{nax}<br/>
|
||||
{liveMessages}
|
||||
</>);
|
||||
return {key: 'rcv ' + rcv.bone + ', ' + renderDuct(rcv.duct), jsx: (
|
||||
<Summary summary={summary} details={details} />
|
||||
)};
|
||||
}
|
||||
|
||||
renderFlow(flow) {
|
||||
if (flow.snd) return this.renderSnd(flow.snd);
|
||||
if (flow.rcv) return this.renderRcv(flow.rcv);
|
||||
console.log('weird flow', flow);
|
||||
return 'weird flow';
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { known, alien, deets } = props.peers;
|
||||
|
||||
const renderDetails = (who) => {
|
||||
const peer = deets[who];
|
||||
if (!peer) {
|
||||
return 'Loading...';
|
||||
} else if (peer.alien) {
|
||||
return (<>
|
||||
Pending messages: {peer.alien.messages}
|
||||
Pending packets: {peer.alien.packets}
|
||||
Heeds: {this.renderDucts(peer.alien.heeds)}
|
||||
</>);
|
||||
} else if (peer.known) {
|
||||
const p = peer.known;
|
||||
|
||||
const status = (<>
|
||||
<h4 style={{marginTop: '1em'}}>status</h4>
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td class="inter">Life</td>
|
||||
<td>{p.life}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">Route</td>
|
||||
<td>
|
||||
{ p.route
|
||||
? `${p.route.direct ? '' : 'in'}direct, on lane ${p.route.lane}`
|
||||
: 'none'
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">QoS</td>
|
||||
<td>
|
||||
{p.qos.kind},
|
||||
last contact {msToDa(p.qos['last-contact'])}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</>);
|
||||
|
||||
const forwardItems = p.flows.forward.map(this.renderFlow);
|
||||
const forward = (<>
|
||||
<h4 style={{marginTop: '1em'}}>forward</h4>
|
||||
<SearchableList placeholder="bone, duct" items={forwardItems} />
|
||||
</>);
|
||||
|
||||
const backwardItems = p.flows.backward.map(this.renderFlow);
|
||||
const backward = (<>
|
||||
<h4 style={{marginTop: '1em'}}>backward</h4>
|
||||
<SearchableList placeholder="bone, duct" items={backwardItems} />
|
||||
</>);
|
||||
|
||||
const naxItems = p.nax.map(nack => {
|
||||
return {key: nack.bone, jsx: (
|
||||
<div>
|
||||
bone {nack.bone}, message #{nack['message-num']}, duct:<br/>
|
||||
{renderDuct(nack.duct)}
|
||||
</div>
|
||||
)};
|
||||
});
|
||||
const nax = (<>
|
||||
<h4 style={{marginTop: '1em'}}>nax</h4>
|
||||
<SearchableList placeholder="bone" items={naxItems} />
|
||||
</>);
|
||||
|
||||
const heeds = (<>
|
||||
<h4 style={{marginTop: '1em'}}>heeds</h4>
|
||||
{this.renderDucts(p.heeds)}
|
||||
</>);
|
||||
|
||||
return (<>
|
||||
<button
|
||||
style={{position: 'absolute', top: 0, right: 0}}
|
||||
onClick={()=>{this.loadPeerDetails(who)}}
|
||||
>
|
||||
refresh
|
||||
</button>
|
||||
{status}
|
||||
{forward}
|
||||
{backward}
|
||||
{nax}
|
||||
{heeds}
|
||||
</>);
|
||||
} else {
|
||||
console.log('weird peer', peer);
|
||||
return '???';
|
||||
}
|
||||
}
|
||||
|
||||
const knownItems = known.map(who => {
|
||||
return {key: '~'+who, jsx: (<Summary
|
||||
id={who}
|
||||
summary={'~'+who + ' (known)'}
|
||||
details={renderDetails(who)}
|
||||
onOpen={this.loadPeerDetails}
|
||||
/>)};
|
||||
});
|
||||
|
||||
const alienItems = alien.map(who => {
|
||||
return {key: '~'+who, jsx: (<Summary
|
||||
id={who}
|
||||
summary={'~'+who + ' (alien)'}
|
||||
details={renderDetails(who)}
|
||||
onOpen={this.loadPeerDetails}
|
||||
/>)};
|
||||
});
|
||||
|
||||
const items = [...knownItems, ...alienItems];
|
||||
|
||||
return (
|
||||
<SearchableList placeholder="ship name" items={items}>
|
||||
<button onClick={this.loadPeers}>refresh</button>
|
||||
</SearchableList>
|
||||
);
|
||||
}
|
||||
}
|
127
pkg/interface/dbug/src/js/views/apps.js
Normal file
127
pkg/interface/dbug/src/js/views/apps.js
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { makeRoutePath } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
import { Summary } from '../components/summary';
|
||||
|
||||
export class Apps extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
stateQuery: {}
|
||||
};
|
||||
|
||||
this.changeStateQuery = this.changeStateQuery.bind(this);
|
||||
this.loadApps = this.loadApps.bind(this);
|
||||
this.loadAppDetails = this.loadAppDetails.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (Object.keys(this.props.apps).length === 0) {
|
||||
this.loadApps();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
changeStateQuery(app, event) {
|
||||
this.state.stateQuery[app] = event.target.value;
|
||||
this.setState({ stateQuery: this.state.stateQuery });
|
||||
}
|
||||
|
||||
loadApps() {
|
||||
api.getApps();
|
||||
}
|
||||
|
||||
loadAppDetails(app) {
|
||||
api.getAppDetails(app);
|
||||
}
|
||||
|
||||
loadAppState(app) {
|
||||
api.getAppState(app, this.state.stateQuery[app]);
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const apps = Object.keys(props.apps).sort().map(app => {
|
||||
const appData = props.apps[app];
|
||||
const haveDeets = (typeof appData === 'object');
|
||||
const running = haveDeets
|
||||
? true
|
||||
: appData;
|
||||
const runStyle = running
|
||||
? {borderLeft: '3px solid green'}
|
||||
: {borderLeft: '3px solid grey'}
|
||||
|
||||
let deets = null;
|
||||
if (!haveDeets) {
|
||||
deets = running
|
||||
? "Loading..."
|
||||
: "App not running.";
|
||||
} else if (appData.noDebug) {
|
||||
deets = "App doesn't use /lib/dbug";
|
||||
} else {
|
||||
const data = appData;
|
||||
const events = (data.events || []).map(e => {
|
||||
return {key: e, jsx: (<>
|
||||
{e}<br/>
|
||||
</>)};
|
||||
})
|
||||
deets = (<>
|
||||
<button
|
||||
style={{position: 'absolute', top: 0, right: 0}}
|
||||
onClick={()=>{this.loadAppDetails(app)}}
|
||||
>
|
||||
refresh
|
||||
</button>
|
||||
<button onClick={()=>{this.loadAppState(app)}}>query state</button>
|
||||
<textarea
|
||||
class="mono"
|
||||
onChange={(e) => this.changeStateQuery(app, e)}
|
||||
value={state.stateQuery[app]}
|
||||
placeholder="-.-"
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div style={{maxHeight: '500px', overflow: 'scroll'}}>
|
||||
<pre>{(data.state || data.simpleState).join('\n')}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<Subscriptions {...data.subscriptions} />
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={()=>{api.bindToVerb(app)}}>listen to verb</button>
|
||||
<SearchableList placeholder="event description" items={events} />
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
|
||||
const onOpen = running
|
||||
? this.loadAppDetails
|
||||
: null;
|
||||
|
||||
return {key: app, jsx: (
|
||||
<Summary id={app} summary={'%'+app} details={deets} onOpen={onOpen} style={runStyle} />
|
||||
)};
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"h-100 w-100 pa3 pt4 overflow-x-hidden " +
|
||||
"bg-gray0-d white-d flex flex-column"
|
||||
}>
|
||||
<SearchableList placeholder="app name" items={apps}>
|
||||
<button onClick={this.loadApps}>refresh</button>
|
||||
</SearchableList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
56
pkg/interface/dbug/src/js/views/behn.js
Normal file
56
pkg/interface/dbug/src/js/views/behn.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { msToDa, renderDuct } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
|
||||
export class Behn extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
||||
this.loadTimers = this.loadTimers.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { timers } = this.props;
|
||||
if (timers.length === 0) {
|
||||
this.loadTimers();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
loadTimers() {
|
||||
api.getTimers();
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const items = props.timers.map(timer => {
|
||||
const duct = renderDuct(timer.duct);
|
||||
return {key: duct, jsx: (<div class="flex">
|
||||
<div class="flex-auto" style={{maxWidth: '50%'}}>
|
||||
{msToDa(timer.date)}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '50%'}}>
|
||||
{duct}
|
||||
</div>
|
||||
</div>)};
|
||||
});
|
||||
|
||||
return (
|
||||
<table><tbody>
|
||||
<SearchableList placeholder="duct" items={items}>
|
||||
<button onClick={this.loadTimers}>refresh</button>
|
||||
</SearchableList>
|
||||
</tbody></table>
|
||||
);
|
||||
}
|
||||
}
|
143
pkg/interface/dbug/src/js/views/clay.js
Normal file
143
pkg/interface/dbug/src/js/views/clay.js
Normal file
@ -0,0 +1,143 @@
|
||||
import { Gitgraph, templateExtend, TemplateName } from "@gitgraph/react";
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route } from "react-router-dom";
|
||||
import _ from 'lodash';
|
||||
|
||||
export class Clay extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.clickety = this.clickety.bind(this);
|
||||
this.clickCommit = this.clickCommit.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.graph = this.graph.bind(this);
|
||||
this.template = templateExtend(TemplateName.Metro, {
|
||||
branch: {
|
||||
lineWidth: 6,
|
||||
},
|
||||
commit: {
|
||||
spacing: 40,
|
||||
dot: {
|
||||
size: 10,
|
||||
},
|
||||
message: {
|
||||
displayHash: false,
|
||||
displayAuthor: false,
|
||||
font: "normal 16pt monospace",
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
api.getCommits();
|
||||
}
|
||||
|
||||
clickety() {
|
||||
let { commits, gitgraph } = this.props;
|
||||
if ( !commits.commits ) return;
|
||||
|
||||
let commitMap = {};
|
||||
|
||||
commits.commits.forEach(commit => {
|
||||
commitMap[commit.commitHash] = commit;
|
||||
});
|
||||
|
||||
let data = commits.commits.map(com => {
|
||||
console.log(com.commitHash,commits.head);
|
||||
let ref = [];
|
||||
if (com.commitHash in commits.head) {
|
||||
ref = ["HEAD", commits.head[com.commitHash]];
|
||||
}
|
||||
return {
|
||||
refs: ref,
|
||||
hash: com.commitHash.slice(2), // lop off 0v for more unique hash
|
||||
parents: com.parents.map(par => {return par.slice(2);}),
|
||||
onMessageClick: this.clickCommit,
|
||||
subject: "commit: " +
|
||||
com.commitHash.slice(-5) +
|
||||
", content: " +
|
||||
com.contentHash.slice(-5) +
|
||||
", parents: " +
|
||||
com.parents.map(par => {return par.slice(-5);}),
|
||||
author: {
|
||||
name: "me",
|
||||
email: "me",
|
||||
timestamp: 1500000000000,
|
||||
} } });
|
||||
gitgraph.import(data);
|
||||
}
|
||||
|
||||
clickCommit(commit, args) {
|
||||
console.log("click", commit);
|
||||
let val = commit.refs.slice(-1)[0];
|
||||
if (!val) {
|
||||
return
|
||||
} else if (this.bobDesk.value == "") {
|
||||
this.bobDesk.value = val;
|
||||
} else {
|
||||
this.aliDesk.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
submit() {
|
||||
//TODO hook up
|
||||
api.pottery( {
|
||||
ali: this.aliDesk.value,
|
||||
bob: this.bobDesk.value,
|
||||
germ: this.germ.value,
|
||||
});
|
||||
}
|
||||
|
||||
graph(gitgraph) {
|
||||
this.setState({gitgraph: gitgraph});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let textAreaClasses =
|
||||
"f7 mono ba bg-gray0-d white-d pa3 mb2 db " +
|
||||
"focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap "
|
||||
|
||||
const inputs = (<>
|
||||
<textarea
|
||||
ref={ e => { this.bobDesk = e; } }
|
||||
className={textAreaClasses}
|
||||
placeholder="target desk"
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
/>
|
||||
<textarea
|
||||
ref={ e => { this.aliDesk = e; } }
|
||||
className={textAreaClasses}
|
||||
placeholder="source desk"
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
/>
|
||||
<select
|
||||
ref={ e => { this.germ = e; } }
|
||||
className={textAreaClasses}>
|
||||
<option value="mate">%mate: conflict if changed same lines</option>
|
||||
<option value="meet">%meet: conflict if changed same files</option>
|
||||
<option value="meld">%meld: annotate conflicts</option>
|
||||
<option value="fine">%fine: fast-forward (requires ancestor)</option>
|
||||
<option value="this">%this: use target desk's data</option>
|
||||
<option value="that">%that: use source desk's data</option>
|
||||
<option value="init">%init: start new desk (danger!)</option>
|
||||
</select>
|
||||
<button
|
||||
className={textAreaClasses}
|
||||
onClick={this.submit}>
|
||||
Merge!
|
||||
</button>
|
||||
</>);
|
||||
|
||||
this.clickety();
|
||||
return (
|
||||
<div className="cf w-100 flex flex-column pa4 ba-m ba-l ba-xl b--gray2 br1 h-100 h-100-minus-40-m h-100-minus-40-l h-100-minus-40-xl f9 white-d">
|
||||
<Gitgraph options={{template: this.template}}>{this.graph}</Gitgraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
174
pkg/interface/dbug/src/js/views/eyre.js
Normal file
174
pkg/interface/dbug/src/js/views/eyre.js
Normal file
@ -0,0 +1,174 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { msToDa, renderDuct } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
import { Summary } from '../components/summary';
|
||||
|
||||
export class Eyre extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
||||
this.loadBindings = this.loadBindings.bind(this);
|
||||
this.loadConnections = this.loadConnections.bind(this);
|
||||
this.loadAuthenticationState = this.loadAuthenticationState.bind(this);
|
||||
this.loadChannels = this.loadChannels.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { props } = this;
|
||||
if (props.bindings.length === 0) this.loadBindings();
|
||||
if (props.connections.length == 0) this.loadConnections();
|
||||
if (props.authentication.length == 0) this.loadAuthenticationState();
|
||||
if (props.channels.length == 0) this.loadChannels();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
loadBindings() {
|
||||
api.getBindings();
|
||||
}
|
||||
|
||||
loadConnections() {
|
||||
api.getConnections();
|
||||
}
|
||||
|
||||
loadAuthenticationState() {
|
||||
api.getAuthenticationState();
|
||||
}
|
||||
|
||||
loadChannels() {
|
||||
api.getChannels();
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const bindingItems = props.bindings.map(binding => {
|
||||
return {key: binding.location + ' ' + binding.action, jsx: (<div class="flex">
|
||||
<div class="flex-auto" style={{maxWidth: '50%'}}>
|
||||
{binding.location}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '50%'}}>
|
||||
{binding.action}
|
||||
</div>
|
||||
</div>)};
|
||||
});
|
||||
|
||||
const connectionItems = props.connections.map(c => {
|
||||
return {key: c.duct + ' ' + c.action, jsx: (
|
||||
<table style={{borderBottom: '1px solid black'}}><tbody>
|
||||
<tr>
|
||||
<td class="inter">duct</td>
|
||||
<td>{c.duct}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">binding</td>
|
||||
<td>{c.action}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">request</td>
|
||||
<td>
|
||||
from {c.request.source},
|
||||
{c.request.authenticated ? ' ' : ' un'}authenticated and
|
||||
{c.request.secure ? ' ' : ' in'}secure
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">response</td>
|
||||
<td>
|
||||
sent {c.response.sent} bytes.<br/>
|
||||
{!c.response.header ? null : <>
|
||||
status {c.response.header['status-code']}<br/>
|
||||
{c.response.header.headers.reduce((a, b) => a + b + ', ', '')}
|
||||
</>}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
)};
|
||||
});
|
||||
|
||||
const channelItems = props.channels.map(c => {
|
||||
const summary = (<>
|
||||
{c.session}
|
||||
<table style={{borderBottom: '1px solid black'}}><tbody>
|
||||
<tr>
|
||||
<td class="inter">connected?</td>
|
||||
<td>{c.connected
|
||||
? 'connected'
|
||||
: 'disconnected, expires ' + msToDa(c.expiry)
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">next-id</td>
|
||||
<td>{c['next-id']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="inter">unacked</td>
|
||||
<td>{c.unacked.reduce((a, b) => a + b + ', ', '')}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</>);
|
||||
const subscriptionItems = c.subscriptions.map(s => {
|
||||
//NOTE jsx sorta copied from /components/subscriptions
|
||||
return {key: `${s.wire} ${s.app} ${s.ship} ${s.path}`, jsx: (
|
||||
<div class="flex">
|
||||
<div class="flex-auto" style={{maxWidth: '35%'}}>
|
||||
{s.wire}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '15%'}}>
|
||||
~{s.ship}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '15%'}}>
|
||||
{s.app}
|
||||
</div>
|
||||
<div class="flex-auto" style={{maxWidth: '35%'}}>
|
||||
{s.path}
|
||||
</div>
|
||||
</div>
|
||||
)};
|
||||
});
|
||||
return {key: c.session, jsx: (
|
||||
<Summary summary={summary} details={(
|
||||
<SearchableList
|
||||
placeholder="wire, app, ship, path"
|
||||
items={subscriptionItems}
|
||||
/>
|
||||
)} />
|
||||
)};
|
||||
});
|
||||
|
||||
const sessionItems = props.authentication.map(s => {
|
||||
return (<div>
|
||||
{`${s.cookie} expires ${msToDa(s.expiry)}`}
|
||||
</div>);
|
||||
});
|
||||
|
||||
return (<>
|
||||
<h4>Bindings</h4>
|
||||
<SearchableList placeholder="binding" items={bindingItems}>
|
||||
<button onClick={this.loadBindings}>refresh</button>
|
||||
</SearchableList>
|
||||
|
||||
<h4>Connections</h4>
|
||||
<SearchableList placeholder="duct, binding" items={connectionItems}>
|
||||
<button onClick={this.loadConnections}>refresh</button>
|
||||
</SearchableList>
|
||||
|
||||
<h4>Channels</h4>
|
||||
<SearchableList placeholder="session id" items={channelItems}>
|
||||
<button onClick={this.loadChannels}>refresh</button>
|
||||
</SearchableList>
|
||||
|
||||
<h4>Cookies</h4>
|
||||
<button onClick={this.loadAuthenticationState}>refresh</button>
|
||||
{sessionItems}
|
||||
</>);
|
||||
}
|
||||
}
|
60
pkg/interface/dbug/src/js/views/spider.js
Normal file
60
pkg/interface/dbug/src/js/views/spider.js
Normal file
@ -0,0 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { msToDa, renderDuct } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
|
||||
export class Spider extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
||||
this.loadThreads = this.loadThreads.bind(this);
|
||||
this.renderThreads = this.renderThreads.bind(this);
|
||||
this.killThread = this.killThread.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { threads } = this.props;
|
||||
if (Object.keys(threads).length === 0) {
|
||||
this.loadThreads();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
loadThreads() {
|
||||
api.getThreads();
|
||||
}
|
||||
|
||||
killThread(tid) {
|
||||
api.killThread(tid);
|
||||
}
|
||||
|
||||
renderThreads(threads) {
|
||||
return Object.keys(threads).map(thread => {
|
||||
const kids = this.renderThreads(threads[thread]);
|
||||
return (<>
|
||||
<div>
|
||||
<button style={{margin: '4px'}} onClick={()=>{this.killThread(thread)}}>kill</button>
|
||||
{thread}
|
||||
</div>
|
||||
<div style={{paddingLeft: '16px'}}>{kids}</div>
|
||||
</>);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<>
|
||||
<button onClick={this.loadThreads}>refresh</button><br/>
|
||||
{ Object.keys(this.props.threads).length === 0
|
||||
? 'no running threads'
|
||||
: this.renderThreads(this.props.threads)
|
||||
}
|
||||
</>);
|
||||
}
|
||||
}
|
11
pkg/interface/dbug/tile/tile.js
Normal file
11
pkg/interface/dbug/tile/tile.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class DebugTile extends Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
window.debugTile = DebugTile;
|
Loading…
Reference in New Issue
Block a user