mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-07 07:30:23 +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
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:de3b622f4ce90a9c2ce73fa233ba356e239a64d8aa4820ea7f48663110aa3fdd
|
oid sha256:6504214aef3dff2d5e9a477239e9f9cafa0e11855be6b6a2b704e7164bb8cb37
|
||||||
size 18821765
|
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
|
:- %say
|
||||||
|= *
|
|= *
|
||||||
:- %tang
|
:- %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
|
++ on-init
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
=^ cards agent on-init:ag
|
=^ cards agent on-init:ag
|
||||||
@ -115,8 +126,6 @@
|
|||||||
=^ cards agent (on-leave:ag path)
|
=^ cards agent (on-leave:ag path)
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
++ on-peek on-peek:ag
|
|
||||||
::
|
|
||||||
++ on-agent
|
++ on-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=wire =sign:agent:gall]
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
:: Print what your agent is doing.
|
:: Print what your agent is doing.
|
||||||
::
|
::
|
||||||
|
/- verb
|
||||||
|
::
|
||||||
|= [loud=? =agent:gall]
|
|= [loud=? =agent:gall]
|
||||||
=| bowl-print=_|
|
=| bowl-print=_|
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
@ -12,7 +14,7 @@
|
|||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-init")
|
%- (print bowl "{<dap.bowl>}: on-init")
|
||||||
=^ cards agent on-init:ag
|
=^ cards agent on-init:ag
|
||||||
[cards this]
|
[[(emit-event %on-init ~) cards] this]
|
||||||
::
|
::
|
||||||
++ on-save
|
++ on-save
|
||||||
^- vase
|
^- vase
|
||||||
@ -24,7 +26,7 @@
|
|||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-load")
|
%- (print bowl "{<dap.bowl>}: on-load")
|
||||||
=^ cards agent (on-load:ag old-state)
|
=^ cards agent (on-load:ag old-state)
|
||||||
[cards this]
|
[[(emit-event %on-load ~) cards] this]
|
||||||
::
|
::
|
||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
@ -36,21 +38,26 @@
|
|||||||
%bowl `this(bowl-print !bowl-print)
|
%bowl `this(bowl-print !bowl-print)
|
||||||
==
|
==
|
||||||
=^ cards agent (on-poke:ag mark vase)
|
=^ cards agent (on-poke:ag mark vase)
|
||||||
[cards this]
|
[[(emit-event %on-poke mark) cards] this]
|
||||||
::
|
::
|
||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-watch on path {<path>}")
|
%- (print bowl "{<dap.bowl>}: on-watch on path {<path>}")
|
||||||
=^ cards agent (on-watch:ag path)
|
=^ cards agent
|
||||||
[cards this]
|
?: ?=([%verb %events ~] path)
|
||||||
|
[~ agent]
|
||||||
|
(on-watch:ag path)
|
||||||
|
[[(emit-event %on-watch path) cards] this]
|
||||||
::
|
::
|
||||||
++ on-leave
|
++ on-leave
|
||||||
|= =path
|
|= =path
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-leave on path {<path>}")
|
%- (print bowl "{<dap.bowl>}: on-leave on path {<path>}")
|
||||||
|
?: ?=([%verb %event ~] path)
|
||||||
|
[~ this]
|
||||||
=^ cards agent (on-leave:ag path)
|
=^ cards agent (on-leave:ag path)
|
||||||
[cards this]
|
[[(emit-event %on-leave path) cards] this]
|
||||||
::
|
::
|
||||||
++ on-peek
|
++ on-peek
|
||||||
|= =path
|
|= =path
|
||||||
@ -63,21 +70,21 @@
|
|||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}")
|
%- (print bowl "{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}")
|
||||||
=^ cards agent (on-agent:ag wire sign)
|
=^ cards agent (on-agent:ag wire sign)
|
||||||
[cards this]
|
[[(emit-event %on-agent wire -.sign) cards] this]
|
||||||
::
|
::
|
||||||
++ on-arvo
|
++ on-arvo
|
||||||
|= [=wire =sign-arvo]
|
|= [=wire =sign-arvo]
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}")
|
%- (print bowl "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}")
|
||||||
=^ cards agent (on-arvo:ag wire sign-arvo)
|
=^ cards agent (on-arvo:ag wire sign-arvo)
|
||||||
[cards this]
|
[[(emit-event %on-arvo wire [- +<]:sign-arvo) cards] this]
|
||||||
::
|
::
|
||||||
++ on-fail
|
++ on-fail
|
||||||
|= [=term =tang]
|
|= [=term =tang]
|
||||||
^- (quip card:agent:gall agent:gall)
|
^- (quip card:agent:gall agent:gall)
|
||||||
%- (print bowl "{<dap.bowl>}: on-fail with term {<term>}")
|
%- (print bowl "{<dap.bowl>}: on-fail with term {<term>}")
|
||||||
=^ cards agent (on-fail:ag term tang)
|
=^ cards agent (on-fail:ag term tang)
|
||||||
[cards this]
|
[[(emit-event %on-fail term) cards] this]
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
++ print
|
++ print
|
||||||
@ -89,4 +96,9 @@
|
|||||||
?. loud same
|
?. loud same
|
||||||
%- (slog leaf+tape ~)
|
%- (slog leaf+tape ~)
|
||||||
same
|
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
|
+| %atomics
|
||||||
::
|
::
|
||||||
+$ bone @udbone
|
|
||||||
+$ fragment @uwfragment
|
|
||||||
+$ fragment-num @udfragmentnum
|
|
||||||
+$ message-blob @udmessageblob
|
|
||||||
+$ message-num @udmessagenum
|
|
||||||
+$ private-key @uwprivatekey
|
+$ private-key @uwprivatekey
|
||||||
+$ public-key @uwpublickey
|
|
||||||
+$ signature @uwsignature
|
+$ signature @uwsignature
|
||||||
+$ symmetric-key @uwsymmetrickey
|
|
||||||
:: $rank: which kind of ship address, by length
|
:: $rank: which kind of ship address, by length
|
||||||
::
|
::
|
||||||
:: 0: galaxy or star -- 2 bytes
|
:: 0: galaxy or star -- 2 bytes
|
||||||
@ -215,13 +208,6 @@
|
|||||||
:: $naxplanation: nack trace; explains which message failed and why
|
:: $naxplanation: nack trace; explains which message failed and why
|
||||||
::
|
::
|
||||||
+$ naxplanation [=message-num =error]
|
+$ naxplanation [=message-num =error]
|
||||||
:: $ack: positive ack, nack packet, or nack trace
|
|
||||||
::
|
|
||||||
+$ ack
|
|
||||||
$% [%ok ~]
|
|
||||||
[%nack ~]
|
|
||||||
[%naxplanation =error]
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
+| %statics
|
+| %statics
|
||||||
::
|
::
|
||||||
@ -249,237 +235,6 @@
|
|||||||
$: veb=_veb-all-off
|
$: veb=_veb-all-off
|
||||||
ships=(set ship)
|
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
|
+| %dialectics
|
||||||
::
|
::
|
||||||
@ -947,32 +702,19 @@
|
|||||||
?. =([%& our] why)
|
?. =([%& our] why)
|
||||||
[~ ~]
|
[~ ~]
|
||||||
?+ syd ~
|
?+ syd ~
|
||||||
|
%peers
|
||||||
|
?^ tyl [~ ~]
|
||||||
|
:^ ~ ~ %noun
|
||||||
|
!> ^- (map ship ?(%alien %known))
|
||||||
|
(~(run by peers.ames-state) head)
|
||||||
|
::
|
||||||
%peer
|
%peer
|
||||||
?. ?=([@ ~] tyl) [~ ~]
|
?. ?=([@ ~] tyl) [~ ~]
|
||||||
=/ who (slaw %p i.tyl)
|
=/ who (slaw %p i.tyl)
|
||||||
?~ who [~ ~]
|
?~ who [~ ~]
|
||||||
=/ per (~(get by peers.ames-state) u.who)
|
?~ peer=(~(get by peers.ames-state) u.who)
|
||||||
=/ res
|
[~ ~]
|
||||||
?- per
|
``noun+!>(u.peer)
|
||||||
~ %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))
|
|
||||||
::
|
::
|
||||||
%bones
|
%bones
|
||||||
?. ?=([@ ~] tyl) [~ ~]
|
?. ?=([@ ~] tyl) [~ ~]
|
||||||
|
@ -335,7 +335,7 @@
|
|||||||
~
|
~
|
||||||
?. ?=(%timers syd)
|
?. ?=(%timers syd)
|
||||||
[~ ~]
|
[~ ~]
|
||||||
[~ ~ %noun !>(>(turn (tap:timer-map timers) head)<)]
|
[~ ~ %noun !>((turn (tap:timer-map timers) head))]
|
||||||
::
|
::
|
||||||
++ stay state
|
++ stay state
|
||||||
++ take
|
++ take
|
||||||
|
@ -124,131 +124,6 @@
|
|||||||
::
|
::
|
||||||
outgoing-duct=duct
|
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: an action requested on a channel
|
||||||
::
|
::
|
||||||
+$ channel-request
|
+$ channel-request
|
||||||
@ -2462,32 +2337,38 @@
|
|||||||
[~ ~]
|
[~ ~]
|
||||||
?. ?=(%$ -.lot)
|
?. ?=(%$ -.lot)
|
||||||
[~ ~]
|
[~ ~]
|
||||||
?. ?=(%host syd)
|
|
||||||
[~ ~]
|
|
||||||
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
|
||||||
^- (unit (unit hart:eyre))
|
|
||||||
?. =(our who)
|
?. =(our who)
|
||||||
?. =([%da now] p.lot)
|
?. =([%da now] p.lot)
|
||||||
[~ ~]
|
[~ ~]
|
||||||
~& [%r %scry-foreign-host who]
|
~& [%r %scry-foreign-host who]
|
||||||
~
|
~
|
||||||
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
?+ syd [~ ~]
|
||||||
?+ p.lot
|
%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]
|
%host
|
||||||
``[& [~ 8.443] %& /localhost]
|
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
||||||
::
|
^- (unit (unit hart:eyre))
|
||||||
[%tas %real]
|
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
||||||
=* domains domains.server-state.ax
|
?+ p.lot
|
||||||
=* ports ports.server-state.ax
|
[~ ~]
|
||||||
=/ =host:eyre [%& ?^(domains n.domains /localhost)]
|
::
|
||||||
=/ secure=? &(?=(^ secure.ports) !?=(hoke:eyre host))
|
[%tas %fake]
|
||||||
=/ port=(unit @ud)
|
``[& [~ 8.443] %& /localhost]
|
||||||
?. secure
|
::
|
||||||
?:(=(80 insecure.ports) ~ `insecure.ports)
|
[%tas %real]
|
||||||
?> ?=(^ secure.ports)
|
=* domains domains.server-state.ax
|
||||||
?:(=(443 u.secure.ports) ~ secure.ports)
|
=* ports ports.server-state.ax
|
||||||
``[secure port host]
|
=/ =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
|
:: payload: semantic message contents
|
||||||
::
|
::
|
||||||
+$ plea [vane=@tas =path payload=*]
|
+$ 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
|
-- ::ames
|
||||||
:: ::::
|
:: ::::
|
||||||
:::: ++behn :: (1b) timekeeping
|
:::: ++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.
|
:: +binding: A rule to match a path.
|
||||||
::
|
::
|
||||||
:: A +binding is a system unique mapping for a path to match. A +binding
|
:: A +binding is a system unique mapping for a path to match. A +binding
|
||||||
@ -904,6 +1263,25 @@
|
|||||||
::
|
::
|
||||||
path=(list @t)
|
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
|
:: +generator: a generator on the local ship that handles requests
|
||||||
::
|
::
|
||||||
:: This refers to a generator on the local ship, run with a set of
|
:: 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