mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 03:23:09 +03:00
dbug: implement debug dashboard
This commit is contained in:
parent
ac494a265e
commit
959884c9cd
716
pkg/arvo/app/dbug.hoon
Normal file
716
pkg/arvo/app/dbug.hoon
Normal file
@ -0,0 +1,716 @@
|
||||
:: 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)
|
||||
[~ this(passcode !<((unit @t) vase))]
|
||||
?. ?=(%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 ~
|
||||
:: /app.json: {appname: running, ...}
|
||||
::
|
||||
[%app ~]
|
||||
%- some
|
||||
%- pairs
|
||||
%+ turn all:apps
|
||||
|= app=term
|
||||
[app b+(running:apps app)]
|
||||
::
|
||||
:: /app/[appname].json: {state: }
|
||||
::
|
||||
[%app @ ~]
|
||||
=* app i.t.site
|
||||
::TODO ?. (dbugable:apps app) ~
|
||||
%- some
|
||||
%- pairs
|
||||
:~ :- 'state'
|
||||
(tank (sell (state:apps app)))
|
||||
::
|
||||
:- '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
|
||||
~! (ship s)
|
||||
:~ '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)
|
||||
==
|
||||
--
|
||||
==
|
||||
::
|
||||
:: /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'
|
||||
:- %s
|
||||
?+ -.action -.action
|
||||
%gen :((cury cat 3) '+' (spat [desk path]:generator.action))
|
||||
%app (cat 3 ':' app.action)
|
||||
==
|
||||
==
|
||||
==
|
||||
::
|
||||
++ 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
|
||||
(scry-dbug vase app /dbug/state)
|
||||
::
|
||||
++ subscriptions
|
||||
=, gall
|
||||
|= app=term
|
||||
^- [out=boat in=bitt]
|
||||
(scry-dbug ,[boat bitt] app /dbug/subscriptions)
|
||||
::
|
||||
++ scry-dbug
|
||||
|* [=mold app=term =path]
|
||||
(scry mold %gx app (snoc `^path`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 structures 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
|
||||
:: 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), %| s+(scot %x p.lane))
|
||||
==
|
||||
::
|
||||
:- 'qos'
|
||||
%- pairs
|
||||
:~ 'kind'^s+-.qos
|
||||
'last-contact'^(time last-contact.qos)
|
||||
==
|
||||
::
|
||||
:- 'snd'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap by snd) aor) :: sort by bone
|
||||
(cury snd-with-bone ossuary)
|
||||
::
|
||||
:- 'rcv'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap by rcv) aor) :: sort by bone
|
||||
(cury rcv-with-bone ossuary)
|
||||
::
|
||||
:- 'nax'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap in nax) aor) :: 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) aor) :: 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) aor) :: 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) aor) :: sort by msg #
|
||||
::
|
||||
:- 'live-messages'
|
||||
:- %a
|
||||
%+ turn (sort ~(tap by live-messages) aor) :: 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'^(set-array nax 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
|
||||
|%
|
||||
++ bindings
|
||||
=, eyre
|
||||
(scry ,(list [=binding =duct =action]) %e %bindings ~)
|
||||
--
|
||||
::
|
||||
:: 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)
|
||||
--
|
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]);
|
210
pkg/interface/dbug/src/js/api.js
Normal file
210
pkg/interface/dbug/src/js/api.js
Normal file
@ -0,0 +1,210 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import { stringToTa } from '/lib/util';
|
||||
import { store } from '/store';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
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) => {
|
||||
console.log('verb result', 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) => {
|
||||
console.log('got data', 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() {
|
||||
//TODO onfail render "failed to fetch apps"
|
||||
//TODO generic "fail" local event that prints to status bar?
|
||||
this.getJson('/app',
|
||||
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 } }});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// spider
|
||||
|
||||
getThreads() {
|
||||
console.log('getting threads');
|
||||
this.getJson('/spider/threads',
|
||||
this.wrapLocal('threads'),
|
||||
this.showStatus('error fetching threads')
|
||||
);
|
||||
}
|
||||
|
||||
// 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')
|
||||
);
|
||||
}
|
||||
|
||||
// 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..."/>);
|
||||
}
|
||||
}
|
111
pkg/interface/dbug/src/js/components/root.js
Normal file
111
pkg/interface/dbug/src/js/components/root.js
Normal file
@ -0,0 +1,111 @@
|
||||
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/lib/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));
|
||||
console.log('built root');
|
||||
}
|
||||
|
||||
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} {...props}/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
</Switch></BrowserRouter>
|
||||
)
|
||||
}
|
||||
}
|
48
pkg/interface/dbug/src/js/components/searchable-list.js
Normal file
48
pkg/interface/dbug/src/js/components/searchable-list.js
Normal file
@ -0,0 +1,48 @@
|
||||
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 === '') || item.key.includes(state.query);
|
||||
})
|
||||
if (items.length === 0) {
|
||||
items = 'none';
|
||||
} else {
|
||||
items = items.map(item => (<div style={{marginTop: '4px'}}>{item.jsx}</div>));
|
||||
}
|
||||
|
||||
return (<div style={{border: '1px solid grey', padding: '4px'}}>
|
||||
<div>{searchBar}</div>
|
||||
<div>{items}</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
81
pkg/interface/dbug/src/js/components/skeleton.js
Normal file
81
pkg/interface/dbug/src/js/components/skeleton.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from "react-router-dom";
|
||||
import classnames from 'classnames';
|
||||
import { HeaderBar } from './lib/header-bar';
|
||||
import { ChannelsSidebar } from './lib/channel-sidebar';
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
37
pkg/interface/dbug/src/js/components/summary.js
Normal file
37
pkg/interface/dbug/src/js/components/summary.js
Normal file
@ -0,0 +1,37 @@
|
||||
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) {
|
||||
console.log('toggle for', this.props.id);
|
||||
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', ...props.style}}>
|
||||
<summary>
|
||||
{props.summary}
|
||||
</summary>
|
||||
<div style={{borderTop: '1px solid black'}}>{props.details}</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
}
|
32
pkg/interface/dbug/src/js/lib/util.js
Normal file
32
pkg/interface/dbug/src/js/lib/util.js
Normal file
@ -0,0 +1,32 @@
|
||||
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 + ' ', '');
|
||||
}
|
142
pkg/interface/dbug/src/js/reducers/local.js
Normal file
142
pkg/interface/dbug/src/js/reducers/local.js
Normal file
@ -0,0 +1,142 @@
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
state.apps = data;
|
||||
}
|
||||
}
|
||||
|
||||
app(obj, state) {
|
||||
const data = _.get(obj, 'app', false);
|
||||
if (data) {
|
||||
state.apps[data.app] = data;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
45
pkg/interface/dbug/src/js/store.js
Normal file
45
pkg/interface/dbug/src/js/store.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { InitialReducer } from '/reducers/initial';
|
||||
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: [],
|
||||
sidebarShown: true
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
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.initialReducer.reduce(json, this.state);
|
||||
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();
|
297
pkg/interface/dbug/src/js/views/ames.js
Normal file
297
pkg/interface/dbug/src/js/views/ames.js
Normal file
@ -0,0 +1,297 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../components/lib/icons/icon-spinner';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
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.state = {
|
||||
opened: new Set()
|
||||
};
|
||||
|
||||
this.loadPeers = this.loadPeers.bind(this);
|
||||
this.loadPeerDetails = this.loadPeerDetails.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}/>
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { known, alien, deets } = props.peers;
|
||||
|
||||
const renderDetails = (who) => {
|
||||
const peer = deets[who];
|
||||
console.log('deets', props.peers, deets, peer, 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 sndItems = p.snd.map(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 = (<>
|
||||
{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}
|
||||
</>);
|
||||
return {key: snd.bone + ', ' + renderDuct(snd.duct), jsx: (
|
||||
<Summary summary={summary} details={details} />
|
||||
)};
|
||||
});
|
||||
const snd = (<>
|
||||
<h4 style={{marginTop: '1em'}}>snd</h4>
|
||||
<SearchableList placeholder="bone, duct" items={sndItems} />
|
||||
</>);
|
||||
|
||||
const rcvItems = p.rcv.map(rcv => {
|
||||
console.log('rcv', 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 = (<>
|
||||
{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.bone + ', ' + renderDuct(rcv.duct), jsx: (
|
||||
<Summary summary={summary} details={details} />
|
||||
)};
|
||||
});
|
||||
const rcv = (<>
|
||||
<h4 style={{marginTop: '1em'}}>rcv</h4>
|
||||
<SearchableList placeholder="bone, duct" items={rcvItems} />
|
||||
</>);
|
||||
|
||||
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 (<>
|
||||
{status}
|
||||
{snd}
|
||||
{rcv}
|
||||
{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} />
|
||||
);
|
||||
}
|
||||
}
|
104
pkg/interface/dbug/src/js/views/apps.js
Normal file
104
pkg/interface/dbug/src/js/views/apps.js
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../components/lib/icons/icon-spinner';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { makeRoutePath, isPatTa, deSig } from '../lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { SearchableList } from '../components/searchable-list';
|
||||
import { Summary } from '../components/summary';
|
||||
|
||||
export class Apps extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
||||
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;
|
||||
//
|
||||
}
|
||||
|
||||
loadApps() {
|
||||
api.getApps();
|
||||
}
|
||||
|
||||
loadAppDetails(app) {
|
||||
api.getAppDetails(app);
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
console.log('render', props.apps, Object.keys(props.apps));
|
||||
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 = (<>
|
||||
<div style={{maxHeight: '500px', overflow: 'scroll'}}>
|
||||
<pre>{data.state.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"
|
||||
}>
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
refresh?
|
||||
</div>
|
||||
<SearchableList placeholder="app name" items={apps} />
|
||||
</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 { Spinner } from '../components/lib/icons/icon-spinner';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
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} />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
56
pkg/interface/dbug/src/js/views/eyre.js
Normal file
56
pkg/interface/dbug/src/js/views/eyre.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../components/lib/icons/icon-spinner';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
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 Eyre extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
||||
this.loadBindings = this.loadBindings.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { bindings } = this.props;
|
||||
if (bindings.length === 0) {
|
||||
this.loadBindings();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
//
|
||||
}
|
||||
|
||||
loadBindings() {
|
||||
api.getBindings();
|
||||
}
|
||||
|
||||
//TODO use classes for styling?
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
const items = 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>)};
|
||||
});
|
||||
|
||||
return (
|
||||
<table><tbody>
|
||||
<SearchableList placeholder="binding" items={items} />
|
||||
</tbody></table>
|
||||
);
|
||||
}
|
||||
}
|
59
pkg/interface/dbug/src/js/views/spider.js
Normal file
59
pkg/interface/dbug/src/js/views/spider.js
Normal file
@ -0,0 +1,59 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../components/lib/icons/icon-spinner';
|
||||
import { Subscriptions } from '../components/subscriptions';
|
||||
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) {
|
||||
console.log('rendering threads', 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() {
|
||||
if (Object.keys(this.props.threads).length === 0)
|
||||
return 'no running threads';
|
||||
return 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