mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
Merge pull request #2956 from urbit/master
Merge master into release/next-userspace
This commit is contained in:
commit
f54c5066d4
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:73263c69a394d2325c70af2db87d104cb3a172795eb3c683f69a4b110a39310c
|
||||
size 12877913
|
||||
oid sha256:58deed8e9b8cd2c84d092cb8f638b9881cb0d12b97f7c719339e3604c9e9d1d2
|
||||
size 13826033
|
||||
|
@ -11,18 +11,31 @@
|
||||
::
|
||||
/- view=chat-view, hook=chat-hook,
|
||||
*permission-store, *group-store, *invite-store,
|
||||
*rw-security, sole-sur=sole
|
||||
/+ sole-lib=sole, default-agent, verb, dbug, store=chat-store,
|
||||
auto=language-server-complete
|
||||
*rw-security, sole
|
||||
/+ shoe, default-agent, verb, dbug, store=chat-store
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ card card:shoe
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-1
|
||||
$% state-2
|
||||
state-1
|
||||
state-0
|
||||
==
|
||||
::
|
||||
+$ state-2
|
||||
$: %2
|
||||
grams=(list mail) :: all messages
|
||||
known=(set [target serial]) :: known message lookup
|
||||
count=@ud :: (lent grams)
|
||||
bound=(map target glyph) :: bound circle glyphs
|
||||
binds=(jug glyph target) :: circle glyph lookup
|
||||
audience=(set target) :: active targets
|
||||
settings=(set term) :: frontend flags
|
||||
width=@ud :: display width
|
||||
timez=(pair ? @ud) :: timezone adjustment
|
||||
==
|
||||
::
|
||||
+$ state-1
|
||||
$: %1
|
||||
grams=(list mail) :: all messages
|
||||
@ -34,7 +47,7 @@
|
||||
settings=(set term) :: frontend flags
|
||||
width=@ud :: display width
|
||||
timez=(pair ? @ud) :: timezone adjustment
|
||||
cli=state=sole-share:sole-sur :: console state
|
||||
cli=state=sole-share:sole :: console state
|
||||
eny=@uvJ :: entropy
|
||||
==
|
||||
::
|
||||
@ -48,7 +61,7 @@
|
||||
settings=(set term) :: frontend flags
|
||||
width=@ud :: display width
|
||||
timez=(pair ? @ud) :: timezone adjustment
|
||||
cli=state=sole-share:sole-sur :: console state
|
||||
cli=state=sole-share:sole :: console state
|
||||
eny=@uvJ :: entropy
|
||||
==
|
||||
::
|
||||
@ -91,18 +104,20 @@
|
||||
== ::
|
||||
::
|
||||
--
|
||||
=| state-1
|
||||
=| state-2
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
%+ verb |
|
||||
^- agent:gall
|
||||
%- (agent:shoe command)
|
||||
^- (shoe:shoe command)
|
||||
=<
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
talk-core +>
|
||||
tc ~(. talk-core(eny eny.bowl) bowl)
|
||||
tc ~(. talk-core bowl)
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
des ~(. (default:shoe this command) bowl)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
@ -124,18 +139,9 @@
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%noun (poke-noun:tc !<(* vase))
|
||||
%sole-action (poke-sole-action:tc !<(sole-action:sole-sur vase))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
=^ cards state (peer:tc path)
|
||||
[cards this]
|
||||
::
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
@ -159,8 +165,33 @@
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ command-parser
|
||||
|= sole-id=@ta
|
||||
parser:sh:tc
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
tab-list:sh:tc
|
||||
::
|
||||
++ on-command
|
||||
|= [sole-id=@ta =command]
|
||||
=^ cards state
|
||||
(work:sh:tc command)
|
||||
[cards this]
|
||||
::
|
||||
++ on-connect
|
||||
|= sole-id=@ta
|
||||
^- (quip card _this)
|
||||
[[prompt:sh-out:tc ~] this]
|
||||
::
|
||||
++ can-connect can-connect:des
|
||||
++ on-disconnect on-disconnect:des
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
@ -183,13 +214,9 @@
|
||||
?: (~(has by wex.bowl) [/chat-store our-self %chat-store]) ~
|
||||
~[connect]
|
||||
::
|
||||
^- state-1
|
||||
?- -.u.old
|
||||
%1
|
||||
=? width.u.old =(0 width.u.old) 80
|
||||
u.old(bound (~(gas by *(map target glyph)) ~(tap by bound.u.old)))
|
||||
::
|
||||
?(~ ^)
|
||||
^- state-2
|
||||
=? u.old ?=(?(~ ^) -.u.old)
|
||||
^- state-1
|
||||
:- %1
|
||||
%= u.old
|
||||
grams ~ ::NOTE this only impacts historic message lookup in chat-cli
|
||||
@ -221,7 +248,18 @@
|
||||
|= t=[ship path]
|
||||
`target`[| t]
|
||||
==
|
||||
==
|
||||
::
|
||||
=? u.old ?=(%1 -.u.old)
|
||||
^- state-2
|
||||
=, u.old
|
||||
:* %2
|
||||
grams known count
|
||||
bound binds audience
|
||||
settings width timez
|
||||
==
|
||||
::
|
||||
?> ?=(%2 -.u.old)
|
||||
u.old
|
||||
:: +catch-up: process all chat-store state
|
||||
::
|
||||
++ catch-up
|
||||
@ -247,7 +285,8 @@
|
||||
^- card
|
||||
[%pass /invites %agent [our.bowl %invite-store] %watch /invitatory/chat]
|
||||
::
|
||||
++ our-self (name:title our.bowl)
|
||||
::TODO better moon support. (name:title our.bowl)
|
||||
++ our-self our.bowl
|
||||
:: +target-to-path: prepend ship to the path
|
||||
::
|
||||
++ target-to-path
|
||||
@ -282,28 +321,6 @@
|
||||
?: ?=(%catch-up a)
|
||||
catch-up
|
||||
[~ state]
|
||||
:: +poke-sole-action: handle cli input
|
||||
::
|
||||
++ poke-sole-action
|
||||
::TODO use id.act to support multiple separate sessions
|
||||
|= [act=sole-action:sole-sur]
|
||||
^- (quip card _state)
|
||||
(sole:sh-in act)
|
||||
:: +peer: accept only cli subscriptions from ourselves
|
||||
::
|
||||
++ peer
|
||||
|= =path
|
||||
^- (quip card _state)
|
||||
?. (team:title our-self src.bowl)
|
||||
~| [%peer-talk-stranger src.bowl]
|
||||
!!
|
||||
?. ?=([%sole *] path)
|
||||
~| [%peer-talk-strange path]
|
||||
!!
|
||||
:: display a fresh prompt
|
||||
:- [prompt:sh-out ~]
|
||||
:: start with fresh sole state
|
||||
state(state.cli *sole-share:sole-sur)
|
||||
:: +handle-invite-update: get new invites
|
||||
::
|
||||
++ handle-invite-update
|
||||
@ -423,132 +440,16 @@
|
||||
count +(count)
|
||||
==
|
||||
::
|
||||
:: +sh-in: handle user input
|
||||
:: +sh: shoe handling
|
||||
::
|
||||
++ sh-in
|
||||
::NOTE interestingly, adding =, sh-out breaks compliation
|
||||
++ sh
|
||||
|%
|
||||
:: +sole: apply sole action
|
||||
::
|
||||
++ sole
|
||||
|= act=sole-action:sole-sur
|
||||
^- (quip card _state)
|
||||
?- -.dat.act
|
||||
%det (edit +.dat.act)
|
||||
%clr [~ state]
|
||||
%ret obey
|
||||
%tab (tab +.dat.act)
|
||||
==
|
||||
:: +tab-list: static list of autocomplete entries
|
||||
++ tab-list
|
||||
^- (list (option:auto tank))
|
||||
:~
|
||||
[%join leaf+";join ~ship/chat-name (glyph)"]
|
||||
[%leave leaf+";leave ~ship/chat-name"]
|
||||
::
|
||||
[%create leaf+";create [type] /chat-name (glyph)"]
|
||||
[%delete leaf+";delete /chat-name"]
|
||||
[%invite leaf+";invite /chat-name ~ships"]
|
||||
[%banish leaf+";banish /chat-name ~ships"]
|
||||
::
|
||||
[%bind leaf+";bind [glyph] ~ship/chat-name"]
|
||||
[%unbind leaf+";unbind [glyph]"]
|
||||
[%what leaf+";what (~ship/chat-name) (glyph)"]
|
||||
::
|
||||
[%settings leaf+";settings"]
|
||||
[%set leaf+";set key (value)"]
|
||||
[%unset leaf+";unset key"]
|
||||
::
|
||||
[%chats leaf+";chats"]
|
||||
[%help leaf+";help"]
|
||||
==
|
||||
++ tab
|
||||
|= pos=@ud
|
||||
^- (quip card _state)
|
||||
?: ?| =(~ buf.state.cli)
|
||||
!=(';' -.buf.state.cli)
|
||||
==
|
||||
:_ state
|
||||
[(effect:sh-out [%bel ~]) ~]
|
||||
::
|
||||
=+ (get-id:auto pos (tufa buf.state.cli))
|
||||
=/ needle=term
|
||||
(fall id '')
|
||||
?: &(!=(pos 1) =(0 (met 3 needle)))
|
||||
[~ state] :: autocomplete empty command iff user at start of command
|
||||
=/ options=(list (option:auto tank))
|
||||
(search-prefix:auto needle tab-list)
|
||||
=/ advance=term
|
||||
(longest-match:auto options)
|
||||
=/ to-send=tape
|
||||
(trip (rsh 3 (met 3 needle) advance))
|
||||
=/ send-pos
|
||||
(add pos (met 3 (fall forward '')))
|
||||
=| moves=(list card)
|
||||
=? moves ?=(^ options)
|
||||
[(tab:sh-out options) moves]
|
||||
=| fxs=(list sole-effect:sole-sur)
|
||||
|- ^- (quip card _state)
|
||||
?~ to-send
|
||||
[(flop moves) state]
|
||||
=^ char state.cli
|
||||
(~(transmit sole-lib state.cli) [%ins send-pos `@c`i.to-send])
|
||||
%_ $
|
||||
moves [(effect:sh-out %det char) moves]
|
||||
send-pos +(send-pos)
|
||||
to-send t.to-send
|
||||
==
|
||||
:: +edit: apply sole edit
|
||||
::
|
||||
:: called when typing into the cli prompt.
|
||||
:: applies the change and does sanitizing.
|
||||
::
|
||||
++ edit
|
||||
|= cal=sole-change:sole-sur
|
||||
^- (quip card _state)
|
||||
=^ inv state.cli (~(transceive sole-lib state.cli) cal)
|
||||
=+ fix=(sanity inv buf.state.cli)
|
||||
?~ lit.fix
|
||||
[~ state]
|
||||
:: just capital correction
|
||||
?~ err.fix
|
||||
(slug fix)
|
||||
:: allow interior edits and deletes
|
||||
?. &(?=($del -.inv) =(+(p.inv) (lent buf.state.cli)))
|
||||
[~ state]
|
||||
(slug fix)
|
||||
:: +sanity: check input sanity
|
||||
::
|
||||
:: parses cli prompt using +read.
|
||||
:: if invalid, produces error correction description, for use with +slug.
|
||||
::
|
||||
++ sanity
|
||||
|= [inv=sole-edit:sole-sur buf=(list @c)]
|
||||
^- [lit=(list sole-edit:sole-sur) err=(unit @u)]
|
||||
=+ res=(rose (tufa buf) read)
|
||||
?: ?=(%& -.res) [~ ~]
|
||||
[[inv]~ `p.res]
|
||||
:: +slug: apply error correction to prompt input
|
||||
::
|
||||
++ slug
|
||||
|= [lit=(list sole-edit:sole-sur) err=(unit @u)]
|
||||
^- (quip card _state)
|
||||
?~ lit [~ state]
|
||||
=^ lic state.cli
|
||||
%- ~(transmit sole-lib state.cli)
|
||||
^- sole-edit:sole-sur
|
||||
?~(t.lit i.lit [%mor lit])
|
||||
:_ state
|
||||
:_ ~
|
||||
%+ effect:sh-out %mor
|
||||
:- [%det lic]
|
||||
?~(err ~ [%err u.err]~)
|
||||
:: +read: command parser
|
||||
::
|
||||
:: parses the command line buffer.
|
||||
:: produces commands which can be executed by +work.
|
||||
::
|
||||
++ read
|
||||
++ parser
|
||||
|^
|
||||
%+ knee *command |. ~+
|
||||
=- ;~(pose ;~(pfix mic -) message)
|
||||
@ -731,7 +632,7 @@
|
||||
::
|
||||
++ text
|
||||
%+ cook crip
|
||||
(plus ;~(less (jest '•') next))
|
||||
(plus next)
|
||||
:: +expr: parse expression into [cord hoon]
|
||||
::
|
||||
++ expr
|
||||
@ -740,33 +641,29 @@
|
||||
%+ stag (crip q.tub)
|
||||
wide:(vang & [&1:% &2:% (scot %da now.bowl) |3:%])
|
||||
--
|
||||
:: +obey: apply result
|
||||
:: +tab-list: command descriptions
|
||||
::
|
||||
:: called upon hitting return in the prompt.
|
||||
:: if input is invalid, +slug is called.
|
||||
:: otherwise, the appropriate work is done and
|
||||
:: the command (if any) gets echoed to the user.
|
||||
::
|
||||
++ obey
|
||||
^- (quip card _state)
|
||||
=+ buf=buf.state.cli
|
||||
=+ fix=(sanity [%nop ~] buf)
|
||||
?^ lit.fix
|
||||
(slug fix)
|
||||
=+ jub=(rust (tufa buf) read)
|
||||
?~ jub [[(effect:sh-out %bel ~) ~] state]
|
||||
=^ cal state.cli (~(transmit sole-lib state.cli) [%set ~])
|
||||
=^ cards state (work u.jub)
|
||||
:_ state
|
||||
%+ weld
|
||||
^- (list card)
|
||||
:: echo commands into scrollback
|
||||
?. =(`0 (find ";" buf)) ~
|
||||
[(note:sh-out (tufa `(list @)`buf)) ~]
|
||||
:_ cards
|
||||
%+ effect:sh-out %mor
|
||||
:~ [%nex ~]
|
||||
[%det cal]
|
||||
++ tab-list
|
||||
^- (list [@t tank])
|
||||
:~
|
||||
[%join leaf+";join ~ship/chat-name (glyph)"]
|
||||
[%leave leaf+";leave ~ship/chat-name"]
|
||||
::
|
||||
[%create leaf+";create [type] /chat-name (glyph)"]
|
||||
[%delete leaf+";delete /chat-name"]
|
||||
[%invite leaf+";invite /chat-name ~ships"]
|
||||
[%banish leaf+";banish /chat-name ~ships"]
|
||||
::
|
||||
[%bind leaf+";bind [glyph] ~ship/chat-name"]
|
||||
[%unbind leaf+";unbind [glyph]"]
|
||||
[%what leaf+";what (~ship/chat-name) (glyph)"]
|
||||
::
|
||||
[%settings leaf+";settings"]
|
||||
[%set leaf+";set key (value)"]
|
||||
[%unset leaf+";unset key"]
|
||||
::
|
||||
[%chats leaf+";chats"]
|
||||
[%help leaf+";help"]
|
||||
==
|
||||
:: +work: run user command
|
||||
::
|
||||
@ -961,7 +858,7 @@
|
||||
^- (quip card _state)
|
||||
~! bowl
|
||||
=/ =serial (shaf %msg-uid eny.bowl)
|
||||
:_ state(eny (shax eny.bowl))
|
||||
:_ state
|
||||
^- (list card)
|
||||
%+ turn ~(tap in audience)
|
||||
|= =target
|
||||
@ -1039,7 +936,7 @@
|
||||
::
|
||||
++ set-width
|
||||
|= w=@ud
|
||||
[~ state(width w)]
|
||||
[~ state(width (max 40 w))]
|
||||
:: +set-timezone: configure timestamp printing adjustment
|
||||
::
|
||||
++ set-timezone
|
||||
@ -1119,23 +1016,16 @@
|
||||
--
|
||||
--
|
||||
::
|
||||
:: +sh-out: output to the cli
|
||||
:: +sh-out: ouput to session
|
||||
::
|
||||
++ sh-out
|
||||
|%
|
||||
:: +effect: console effect card
|
||||
:: +effect: console effect card for all listeners
|
||||
::
|
||||
++ effect
|
||||
|= fec=sole-effect:sole-sur
|
||||
|= effect=sole-effect:sole
|
||||
^- card
|
||||
::TODO don't hard-code session id 'drum' here
|
||||
[%give %fact ~[/sole/drum] %sole-effect !>(fec)]
|
||||
:: +tab: print tab-complete list
|
||||
::
|
||||
++ tab
|
||||
|= options=(list [cord tank])
|
||||
^- card
|
||||
(effect %tab options)
|
||||
[%shoe ~ %sole effect]
|
||||
:: +print: puts some text into the cli as-is
|
||||
::
|
||||
++ print
|
||||
@ -1308,13 +1198,14 @@
|
||||
:: +mr: render messages
|
||||
::
|
||||
++ mr
|
||||
=, sole
|
||||
|_ $: source=target
|
||||
envelope:store
|
||||
==
|
||||
:: +activate: produce sole-effect for printing message details
|
||||
::
|
||||
++ render-activate
|
||||
^- sole-effect:sole-sur
|
||||
^- sole-effect
|
||||
~[%mor [%tan meta] body]
|
||||
:: +meta: render message metadata (serial, timestamp, author, target)
|
||||
::
|
||||
@ -1327,7 +1218,7 @@
|
||||
:: +body: long-form render of message contents
|
||||
::
|
||||
++ body
|
||||
|- ^- sole-effect:sole-sur
|
||||
|- ^- sole-effect
|
||||
?- -.letter
|
||||
?(%text %me)
|
||||
=/ pre=tape ?:(?=(%me -.letter) "@ " "")
|
||||
@ -1339,7 +1230,7 @@
|
||||
%code
|
||||
=/ texp=tape ['>' ' ' (trip expression.letter)]
|
||||
:- %mor
|
||||
|- ^- (list sole-effect:sole-sur)
|
||||
|- ^- (list sole-effect)
|
||||
?: =("" texp) [tan+output.letter ~]
|
||||
=/ newl (find "\0a" texp)
|
||||
?~ newl [txt+texp $(texp "")]
|
||||
@ -1452,8 +1343,13 @@
|
||||
~(glyph tr source)
|
||||
=/ lis=(list tape)
|
||||
%+ simple-wrap
|
||||
~| [%weird-text `@`+.letter]
|
||||
`tape``(list @)`(tuba (trip +.letter))
|
||||
=/ result=(each tape tang)
|
||||
%- mule |.
|
||||
`(list @)`(tuba (trip +.letter))
|
||||
?- -.result
|
||||
%& p.result
|
||||
%| "[[msg rendering error]]"
|
||||
==
|
||||
(sub wyd (min (div wyd 2) (lent pef)))
|
||||
=+ lef=(lent pef)
|
||||
=+ ?:((gth (lent lis) 0) (snag 0 lis) "")
|
||||
@ -1485,6 +1381,8 @@
|
||||
^- (list tape)
|
||||
?~ txt ~
|
||||
=/ [end=@ud nex=?]
|
||||
=+ ret=(find "\0a" (scag +(wid) `tape`txt))
|
||||
?^ ret [u.ret &]
|
||||
?: (lte (lent txt) wid) [(lent txt) &]
|
||||
=+ ace=(find " " (flop (scag +(wid) `tape`txt)))
|
||||
?~ ace [wid |]
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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)
|
||||
--
|
1
pkg/arvo/app/debug/css/index.css
Normal file
1
pkg/arvo/app/debug/css/index.css
Normal file
File diff suppressed because one or more lines are too long
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>
|
1
pkg/arvo/app/debug/js/index.js
Normal file
1
pkg/arvo/app/debug/js/index.js
Normal file
File diff suppressed because one or more lines are too long
1
pkg/arvo/app/debug/js/tile.js
Normal file
1
pkg/arvo/app/debug/js/tile.js
Normal file
File diff suppressed because one or more lines are too long
@ -12,9 +12,10 @@
|
||||
=> |% :: external structures
|
||||
++ id @tasession :: session id
|
||||
++ house :: all state
|
||||
$: $5
|
||||
$: $6
|
||||
egg/@u :: command count
|
||||
hoc/(map id session) :: conversations
|
||||
acl/(set ship) :: remote access whitelist
|
||||
== ::
|
||||
++ session :: per conversation
|
||||
$: say/sole-share :: command-line state
|
||||
@ -1349,9 +1350,12 @@
|
||||
!>(state)
|
||||
::
|
||||
++ on-load
|
||||
|= =old-state=vase
|
||||
=/ old-state !<(house old-state-vase)
|
||||
`..on-init(state old-state)
|
||||
|= old=vase
|
||||
?: ?=(%6 +<.old)
|
||||
`..on-init(state !<(house old))
|
||||
=/ old-5 !<([%5 egg=@u hoc=(map id session)] old)
|
||||
=/ =house [%6 egg.old-5 hoc.old-5 *(set ship)]
|
||||
`..on-init(state house)
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -1359,6 +1363,7 @@
|
||||
=^ moves state
|
||||
^- (quip card:agent:gall house)
|
||||
?+ mark ~|([%dojo-poke-bad-mark mark] !!)
|
||||
::
|
||||
%sole-action
|
||||
=/ act !<(sole-action vase)
|
||||
he-abet:(~(he-type he hid id.act ~ (~(got by hoc) id.act)) act)
|
||||
@ -1367,8 +1372,17 @@
|
||||
=+ !<([=id =command:lens] vase)
|
||||
he-abet:(~(he-lens he hid id ~ (~(got by hoc) id)) command)
|
||||
::
|
||||
%json
|
||||
~& jon=!<(json vase)
|
||||
%allow-remote-login
|
||||
=/ who !<(@p vase)
|
||||
`state(acl (~(put in acl) who))
|
||||
::
|
||||
%revoke-remote-login
|
||||
=/ who !<(@p vase)
|
||||
:_ state(acl (~(del in acl) who))
|
||||
[%give %kick ~ `who]~
|
||||
::
|
||||
%list-remote-logins
|
||||
~& acl
|
||||
`state
|
||||
::
|
||||
%wipe
|
||||
@ -1390,8 +1404,9 @@
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall _..on-init)
|
||||
~? !=(our.hid src.hid) [%dojo-peer-stranger src.hid]
|
||||
?> (team:title our.hid src.hid)
|
||||
?> ?| (team:title our.hid src.hid)
|
||||
(~(has in acl) src.hid)
|
||||
==
|
||||
?> ?=([%sole @ ~] path)
|
||||
=/ id i.t.path
|
||||
=? hoc (~(has by hoc) id)
|
||||
|
@ -43,9 +43,9 @@
|
||||
!:
|
||||
=> |% ::
|
||||
++ hood-old :: unified old-state
|
||||
{?($1 $2 $3 $4) lac/(map @tas hood-part-old)} ::
|
||||
{?($1 $2 $3 $4 $5) lac/(map @tas hood-part-old)}
|
||||
++ hood-1 :: unified state
|
||||
{$4 lac/(map @tas hood-part)} ::
|
||||
{$5 lac/(map @tas hood-part)} ::
|
||||
++ hood-good :: extract specific
|
||||
=+ hed=$:hood-head
|
||||
|@ ++ $
|
||||
@ -151,7 +151,8 @@
|
||||
%1 ((wrap on-load):from-drum:(help hid) %1)
|
||||
%2 ((wrap on-load):from-drum:(help hid) %2)
|
||||
%3 ((wrap on-load):from-drum:(help hid) %3)
|
||||
%4 `lac
|
||||
%4 ((wrap on-load):from-drum:(help hid) %4)
|
||||
%5 `lac
|
||||
==
|
||||
[cards ..on-init]
|
||||
::
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
70
pkg/arvo/app/shoe.hoon
Normal file
70
pkg/arvo/app/shoe.hoon
Normal file
@ -0,0 +1,70 @@
|
||||
:: shoe: example usage of /lib/shoe
|
||||
::
|
||||
:: the app supports one command: "demo".
|
||||
:: running this command renders some text on all sole clients.
|
||||
::
|
||||
/+ shoe, verb, dbug, default-agent
|
||||
|%
|
||||
+$ state-0 [%0 ~]
|
||||
+$ command ~
|
||||
::
|
||||
+$ card card:shoe
|
||||
--
|
||||
=| state-0
|
||||
=* state -
|
||||
::
|
||||
%+ verb |
|
||||
%- agent:dbug
|
||||
^- agent:gall
|
||||
%- (agent:shoe command)
|
||||
^- (shoe:shoe command)
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
des ~(. (default:shoe this command) bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old=vase
|
||||
^- (quip card _this)
|
||||
[~ this]
|
||||
::
|
||||
++ on-poke on-poke:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ command-parser
|
||||
|= sole-id=@ta
|
||||
^+ |~(nail *(like command))
|
||||
(cold ~ (jest 'demo'))
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
^- (list [@t tank])
|
||||
:~ ['demo' leaf+"run example command"]
|
||||
==
|
||||
::
|
||||
++ on-command
|
||||
|= [sole-id=@ta =command]
|
||||
^- (quip card _this)
|
||||
=- [[%shoe ~ %sole -]~ this]
|
||||
=/ =tape "{(scow %p src.bowl)} ran the command"
|
||||
?. =(src our):bowl
|
||||
[%txt tape]
|
||||
[%klr [[`%br ~ `%g] [(crip tape)]~]~]
|
||||
::
|
||||
++ can-connect
|
||||
|= sole-id=@ta
|
||||
^- ?
|
||||
?| =(~zod src.bowl)
|
||||
(team:title [our src]:bowl)
|
||||
==
|
||||
::
|
||||
++ on-connect on-connect:des
|
||||
++ on-disconnect on-disconnect:des
|
||||
--
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
9
pkg/arvo/gen/dojo/acl.hoon
Normal file
9
pkg/arvo/gen/dojo/acl.hoon
Normal file
@ -0,0 +1,9 @@
|
||||
:: acl: list the ships that are allowed to link to dojo
|
||||
::
|
||||
/? 310
|
||||
::
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ bec=beak]
|
||||
[~ ~]
|
||||
==
|
||||
[%list-remote-logins ~]
|
9
pkg/arvo/gen/dojo/allow-remote-login.hoon
Normal file
9
pkg/arvo/gen/dojo/allow-remote-login.hoon
Normal file
@ -0,0 +1,9 @@
|
||||
:: allow-remote-login: allow a ship to link to dojo
|
||||
::
|
||||
/? 310
|
||||
::
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ bec=beak]
|
||||
[[=ship ~] ~]
|
||||
==
|
||||
[%allow-remote-login ship]
|
10
pkg/arvo/gen/dojo/revoke-remote-login.hoon
Normal file
10
pkg/arvo/gen/dojo/revoke-remote-login.hoon
Normal file
@ -0,0 +1,10 @@
|
||||
:: revoke-remote-login: revoke a ship's right to link to dojo,
|
||||
:: kicking the ship if it is currently linked
|
||||
::
|
||||
/? 310
|
||||
::
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ bec=beak]
|
||||
[[=ship ~] ~]
|
||||
==
|
||||
[%revoke-remote-login ship]
|
5
pkg/arvo/gen/hood/ames-wake.hoon
Normal file
5
pkg/arvo/gen/hood/ames-wake.hoon
Normal file
@ -0,0 +1,5 @@
|
||||
:: Set timers for any ames flows that lack them
|
||||
::
|
||||
:- %say
|
||||
|= [^ ~ ~]
|
||||
[%helm-ames-wake ~]
|
@ -2,4 +2,4 @@
|
||||
:- %say
|
||||
|= *
|
||||
:- %tang
|
||||
[.^(tank %b %) ~]
|
||||
[>.^((list [date=@da =duct]) %b /=timers=)< ~]
|
||||
|
@ -90,6 +90,17 @@
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?. ?=([@ %dbug *] path)
|
||||
(on-peek:ag path)
|
||||
?+ path [~ ~]
|
||||
[%u %dbug ~] ``noun+!>(&)
|
||||
[%x %dbug %state ~] ``noun+!>(on-save:ag)
|
||||
[%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)
|
||||
==
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards agent on-init:ag
|
||||
@ -115,8 +126,6 @@
|
||||
=^ cards agent (on-leave:ag path)
|
||||
[cards this]
|
||||
::
|
||||
++ on-peek on-peek:ag
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
|
@ -225,51 +225,27 @@
|
||||
==
|
||||
::
|
||||
++ on-load
|
||||
|= ver=?(%1 %2 %3)
|
||||
?- ver
|
||||
%1
|
||||
=< se-abet =< se-view
|
||||
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
|
||||
=< (se-born %home %goad)
|
||||
=< (se-born %home %metadata-store)
|
||||
=< (se-born %home %metadata-hook)
|
||||
=< (se-born %home %contact-store)
|
||||
=< (se-born %home %contact-hook)
|
||||
=< (se-born %home %contact-view)
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
::
|
||||
%2
|
||||
=< se-abet =< se-view
|
||||
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
|
||||
=< (se-born %home %metadata-store)
|
||||
=< (se-born %home %metadata-hook)
|
||||
=< (se-born %home %contact-store)
|
||||
=< (se-born %home %contact-hook)
|
||||
=< (se-born %home %contact-view)
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
::
|
||||
%3
|
||||
=< se-abet =< se-view
|
||||
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
|
||||
=< (se-born %home %metadata-store)
|
||||
=< (se-born %home %metadata-hook)
|
||||
=< (se-born %home %contact-store)
|
||||
=< (se-born %home %contact-hook)
|
||||
=< (se-born %home %contact-view)
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
==
|
||||
|= ver=?(%1 %2 %3 %4)
|
||||
=< se-abet =< se-view
|
||||
=? . (lte ver %3)
|
||||
=. ver %4
|
||||
=. ..on-load
|
||||
=< (se-emit %pass /kiln %arvo %g %sear ~wisrut-nocsub)
|
||||
=< (se-born %home %goad)
|
||||
=< (se-born %home %metadata-store)
|
||||
=< (se-born %home %metadata-hook)
|
||||
=< (se-born %home %contact-store)
|
||||
=< (se-born %home %contact-hook)
|
||||
=< (se-born %home %contact-view)
|
||||
=< (se-born %home %link-store)
|
||||
=< (se-born %home %link-proxy-hook)
|
||||
=< (se-born %home %link-listen-hook)
|
||||
=< (se-born %home %link-view)
|
||||
(se-born %home %s3-store)
|
||||
.
|
||||
?> ?=(%4 ver)
|
||||
=> (se-drop:(se-pull our.hid %dojo) | our.hid %dojo)
|
||||
(se-drop:(se-pull our.hid %chat-cli) | our.hid %chat-cli)
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|= {way/wire saw/(unit tang)}
|
||||
@ -633,8 +609,9 @@
|
||||
::
|
||||
++ se-peer :: send a peer
|
||||
|= gyl/gill:gall
|
||||
=/ =path /sole/(cat 3 'drum_' (scot %p our.hid))
|
||||
%- se-emit(fug (~(put by fug) gyl ~))
|
||||
[%pass (en-gill gyl) %agent gyl %watch /sole/drum]
|
||||
[%pass (en-gill gyl) %agent gyl %watch path]
|
||||
::
|
||||
++ se-pull :: cancel subscription
|
||||
|= gyl/gill:gall
|
||||
@ -663,6 +640,8 @@
|
||||
^+ +>
|
||||
(ta-poke %sole-action !>(act))
|
||||
::
|
||||
++ ta-id (cat 3 'drum_' (scot %p our.hid)) :: per-ship duct id
|
||||
::
|
||||
++ ta-aro :: hear arrow
|
||||
|= key/?($d $l $r $u)
|
||||
^+ +>
|
||||
@ -703,7 +682,7 @@
|
||||
|= ted/sole-edit
|
||||
^+ +>
|
||||
%^ ta-act
|
||||
%drum
|
||||
ta-id
|
||||
%det
|
||||
[[his.ven.say.inp own.ven.say.inp] (sham buf.say.inp) ted]
|
||||
::
|
||||
@ -715,7 +694,7 @@
|
||||
.(str.u.ris (scag (dec (lent str.u.ris)) str.u.ris))
|
||||
?: =(0 pos.inp)
|
||||
?~ buf.say.inp
|
||||
(ta-act %drum %clr ~)
|
||||
(ta-act ta-id %clr ~)
|
||||
ta-bel
|
||||
(ta-hom %del (dec pos.inp))
|
||||
::
|
||||
@ -1003,10 +982,10 @@
|
||||
==
|
||||
::
|
||||
++ ta-ret :: hear return
|
||||
(ta-act %drum %ret ~)
|
||||
(ta-act ta-id %ret ~)
|
||||
::
|
||||
++ ta-tab :: hear tab
|
||||
(ta-act %drum %tab pos.inp)
|
||||
(ta-act ta-id %tab pos.inp)
|
||||
::
|
||||
++ ta-ser :: reverse search
|
||||
|= ext/(list @c)
|
||||
|
@ -185,6 +185,10 @@
|
||||
|= veb=(list verb:ames) =< abet
|
||||
(emit %pass /helm %arvo %a %spew veb)
|
||||
::
|
||||
++ poke-ames-wake
|
||||
|= ~ =< abet
|
||||
(emit %pass /helm %arvo %a %stir '')
|
||||
::
|
||||
++ poke-knob
|
||||
|= [error-tag=@tas level=?(%hush %soft %loud)] =< abet
|
||||
(emit %pass /helm %arvo %d %knob error-tag level)
|
||||
@ -205,6 +209,7 @@
|
||||
%helm-send-hi =;(f (f !<(_+<.f vase)) poke-send-hi)
|
||||
%helm-ames-sift =;(f (f !<(_+<.f vase)) poke-ames-sift)
|
||||
%helm-ames-verb =;(f (f !<(_+<.f vase)) poke-ames-verb)
|
||||
%helm-ames-wake =;(f (f !<(_+<.f vase)) poke-ames-wake)
|
||||
%helm-verb =;(f (f !<(_+<.f vase)) poke-verb)
|
||||
%helm-knob =;(f (f !<(_+<.f vase)) poke-knob)
|
||||
%helm-rekey =;(f (f !<(_+<.f vase)) poke-rekey)
|
||||
|
331
pkg/arvo/lib/shoe.hoon
Normal file
331
pkg/arvo/lib/shoe.hoon
Normal file
@ -0,0 +1,331 @@
|
||||
:: shoe: console application library
|
||||
::
|
||||
:: /lib/sole: draw some characters
|
||||
:: /lib/shoe: draw the rest of the fscking app
|
||||
::
|
||||
:: call +agent with a type, then call the resulting function with a core
|
||||
:: of the shape described in +shoe.
|
||||
:: you may produce classic gall cards and "shoe-effects", shorthands for
|
||||
:: sending cli events to connected clients.
|
||||
:: default implementations for the shoe-specific arms are in +default.
|
||||
:: for a simple usage example, see /app/shoe.
|
||||
::
|
||||
/- *sole
|
||||
/+ sole, auto=language-server-complete
|
||||
|%
|
||||
+$ state-0
|
||||
$: %0
|
||||
soles=(map @ta sole-share)
|
||||
==
|
||||
:: $card: standard gall cards plus shoe effects
|
||||
::
|
||||
+$ card
|
||||
$% card:agent:gall
|
||||
[%shoe sole-ids=(list @ta) effect=shoe-effect] :: ~ sends to all soles
|
||||
==
|
||||
:: $shoe-effect: easier sole-effects
|
||||
::
|
||||
+$ shoe-effect
|
||||
$% [%sole effect=sole-effect]
|
||||
::TODO complex screen-draw effects
|
||||
==
|
||||
:: +shoe: gall agent core with extra arms
|
||||
::
|
||||
++ shoe
|
||||
|* command-type=mold
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
++ command-parser
|
||||
|~ sole-id=@ta
|
||||
|~(nail *(like command-type))
|
||||
::
|
||||
++ tab-list
|
||||
|~ sole-id=@ta
|
||||
:: (list [@t tank])
|
||||
*(list (option:auto tank))
|
||||
::
|
||||
++ on-command
|
||||
|~ [sole-id=@ta command=command-type]
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ can-connect
|
||||
|~ sole-id=@ta
|
||||
*?
|
||||
::
|
||||
++ on-connect
|
||||
|~ sole-id=@ta
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-disconnect
|
||||
|~ sole-id=@ta
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
::NOTE standard gall agent arms below, though they may produce %shoe cards
|
||||
::
|
||||
++ on-init
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-save
|
||||
*vase
|
||||
::
|
||||
++ on-load
|
||||
|~ vase
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-poke
|
||||
|~ [mark vase]
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-watch
|
||||
|~ path
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-leave
|
||||
|~ path
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-peek
|
||||
|~ path
|
||||
*(unit (unit cage))
|
||||
::
|
||||
++ on-agent
|
||||
|~ [wire sign:agent:gall]
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-arvo
|
||||
|~ [wire sign-arvo]
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-fail
|
||||
|~ [term tang]
|
||||
*(quip card _^|(..on-init))
|
||||
--
|
||||
:: +default: bare-minimum implementations of shoe arms
|
||||
::
|
||||
++ default
|
||||
|* [shoe=* command-type=mold]
|
||||
|_ =bowl:gall
|
||||
++ command-parser
|
||||
(easy *command-type)
|
||||
::
|
||||
++ tab-list
|
||||
~
|
||||
::
|
||||
++ on-command
|
||||
|= [sole-id=@ta command=command-type]
|
||||
[~ shoe]
|
||||
::
|
||||
++ can-connect
|
||||
|= sole-id=@ta
|
||||
(team:title [our src]:bowl)
|
||||
::
|
||||
++ on-connect
|
||||
|= sole-id=@ta
|
||||
[~ shoe]
|
||||
::
|
||||
++ on-disconnect
|
||||
|= sole-id=@ta
|
||||
[~ shoe]
|
||||
--
|
||||
:: +agent: creates wrapper core that handles sole events and calls shoe arms
|
||||
::
|
||||
++ agent
|
||||
|* command-type=mold
|
||||
|= =(shoe command-type)
|
||||
=| state-0
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=>
|
||||
|%
|
||||
++ deal
|
||||
|= cards=(list card)
|
||||
%+ turn cards
|
||||
|= =card
|
||||
^- card:agent:gall
|
||||
?. ?=(%shoe -.card) card
|
||||
?- -.effect.card
|
||||
%sole
|
||||
=- [%give %fact - %sole-effect !>(effect.effect.card)]
|
||||
%+ turn
|
||||
?^ sole-ids.card sole-ids.card
|
||||
~(tap in ~(key by soles))
|
||||
|= sole-id=@ta
|
||||
/sole/[sole-id]
|
||||
==
|
||||
--
|
||||
::
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
og ~(. shoe bowl)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards shoe on-init:og
|
||||
[(deal cards) this]
|
||||
::
|
||||
++ on-save !>([%shoe-app on-save:og state])
|
||||
::
|
||||
++ on-load
|
||||
|= old-state=vase
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
:: we could be upgrading from a shoe-less app, in which case the vase
|
||||
:: contains inner application state instead of our +on-save.
|
||||
:: to distinguish between the two, we check for the presence of our own
|
||||
:: +on-save tag in the vase.
|
||||
::
|
||||
?. ?=([%shoe-app ^] q.old-state)
|
||||
=^ cards shoe (on-load:og old-state)
|
||||
[(deal cards) this]
|
||||
=^ old-inner state +:!<([%shoe-app vase state-0] old-state)
|
||||
=^ cards shoe (on-load:og old-inner)
|
||||
[(deal cards) this]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=(%sole-action mark)
|
||||
=^ cards shoe (on-poke:og mark vase)
|
||||
[(deal cards) this]
|
||||
::
|
||||
=/ act !<(sole-action vase)
|
||||
=* sole-id id.act
|
||||
=/ cli-state=sole-share
|
||||
(~(gut by soles) sole-id *sole-share)
|
||||
|^ =^ [cards=(list card) =_cli-state] shoe
|
||||
?- -.dat.act
|
||||
%det [(apply-edit +.dat.act) shoe]
|
||||
%clr [[~ cli-state] shoe]
|
||||
%ret run-command
|
||||
%tab [(tab +.dat.act) shoe]
|
||||
==
|
||||
:- (deal cards)
|
||||
this(soles (~(put by soles) sole-id cli-state))
|
||||
::
|
||||
++ effect
|
||||
|= =sole-effect
|
||||
^- card
|
||||
[%shoe [sole-id]~ %sole sole-effect]
|
||||
::
|
||||
++ apply-edit
|
||||
|= =sole-change
|
||||
^- (quip card _cli-state)
|
||||
=^ inverse cli-state
|
||||
(~(transceive sole cli-state) sole-change)
|
||||
:: res: & for fully parsed, | for parsing failure at location
|
||||
::
|
||||
=/ res=(each (unit) @ud)
|
||||
%+ rose (tufa buf.cli-state)
|
||||
(command-parser:og sole-id)
|
||||
?: ?=(%& -.res) [~ cli-state]
|
||||
:: parsing failed
|
||||
::
|
||||
?. &(?=(%del -.inverse) =(+(p.inverse) (lent buf.cli-state)))
|
||||
:: if edit was somewhere in the middle, let it happen anyway
|
||||
::
|
||||
[~ cli-state]
|
||||
:: if edit was insertion at buffer tail, revert it
|
||||
::
|
||||
=^ undo cli-state
|
||||
(~(transmit sole cli-state) inverse)
|
||||
:_ cli-state
|
||||
:_ ~
|
||||
%+ effect %mor
|
||||
:~ [%det undo] :: undo edit
|
||||
[%err p.res] :: cursor to error location
|
||||
==
|
||||
::
|
||||
++ run-command
|
||||
^+ [[*(list card) cli-state] shoe]
|
||||
=/ cmd=(unit command-type)
|
||||
%+ rust (tufa buf.cli-state)
|
||||
(command-parser:og sole-id)
|
||||
?~ cmd
|
||||
[[[(effect %bel ~)]~ cli-state] shoe]
|
||||
=^ cards shoe (on-command:og sole-id u.cmd)
|
||||
:: clear buffer
|
||||
::
|
||||
=^ clear cli-state (~(transmit sole cli-state) [%set ~])
|
||||
=- [[[- cards] cli-state] shoe]
|
||||
%+ effect %mor
|
||||
:~ [%nex ~]
|
||||
[%det clear]
|
||||
==
|
||||
::
|
||||
::NOTE cargo-culted
|
||||
++ tab
|
||||
|= pos=@ud
|
||||
^- (quip card _cli-state)
|
||||
=+ (get-id:auto pos (tufa buf.cli-state))
|
||||
=/ needle=term
|
||||
(fall id %$)
|
||||
:: autocomplete empty command iff user at start of command
|
||||
::
|
||||
=/ options=(list (option:auto tank))
|
||||
(search-prefix:auto needle (tab-list:og sole-id))
|
||||
=/ advance=term
|
||||
(longest-match:auto options)
|
||||
=/ to-send=tape
|
||||
%- trip
|
||||
(rsh 3 (met 3 needle) advance)
|
||||
=/ send-pos=@ud
|
||||
%+ add pos
|
||||
(met 3 (fall forward ''))
|
||||
=| cards=(list card)
|
||||
=? cards ?=(^ options)
|
||||
[(effect %tab options) cards]
|
||||
|- ^- (quip card _cli-state)
|
||||
?~ to-send
|
||||
[(flop cards) cli-state]
|
||||
=^ char cli-state
|
||||
(~(transmit sole cli-state) [%ins send-pos `@c`i.to-send])
|
||||
%_ $
|
||||
cards [(effect %det char) cards]
|
||||
send-pos +(send-pos)
|
||||
to-send t.to-send
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=([%sole @ ~] path)
|
||||
=^ cards shoe
|
||||
(on-watch:og path)
|
||||
[(deal cards) this]
|
||||
=* sole-id i.t.path
|
||||
?> (can-connect:og sole-id)
|
||||
=. soles (~(put by soles) sole-id *sole-share)
|
||||
=^ cards shoe
|
||||
(on-connect:og sole-id)
|
||||
:_ this
|
||||
%- deal
|
||||
:_ cards
|
||||
[%shoe [sole-id]~ %sole %pro & dap.bowl "> "]
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards shoe (on-leave:og path)
|
||||
[(deal cards) this]
|
||||
::
|
||||
++ on-peek on-peek:og
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards shoe (on-agent:og wire sign)
|
||||
[(deal cards) this]
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards shoe (on-arvo:og wire sign-arvo)
|
||||
[(deal cards) this]
|
||||
::
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
=^ cards shoe (on-fail:og term tang)
|
||||
[(deal cards) this]
|
||||
--
|
||||
--
|
@ -99,7 +99,7 @@
|
||||
|= sole-change
|
||||
^- {sole-edit sole-share}
|
||||
?. &(=(his.ler his.ven) (lte own.ler own.ven))
|
||||
~& [%receive-sync his+[his.ler his.ven] own+[own.ler own.ven]]
|
||||
~| [%receive-sync his+[his.ler his.ven] own+[own.ler own.ven]]
|
||||
!!
|
||||
?> &(=(his.ler his.ven) (lte own.ler own.ven))
|
||||
?> |(!=(own.ler own.ven) =(`@`0 haw) =(haw (sham buf)))
|
||||
|
@ -1,5 +1,7 @@
|
||||
:: Print what your agent is doing.
|
||||
::
|
||||
/- verb
|
||||
::
|
||||
|= [loud=? =agent:gall]
|
||||
=| bowl-print=_|
|
||||
^- agent:gall
|
||||
@ -12,7 +14,7 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-init")
|
||||
=^ cards agent on-init:ag
|
||||
[cards this]
|
||||
[[(emit-event %on-init ~) cards] this]
|
||||
::
|
||||
++ on-save
|
||||
^- vase
|
||||
@ -24,7 +26,7 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-load")
|
||||
=^ cards agent (on-load:ag old-state)
|
||||
[cards this]
|
||||
[[(emit-event %on-load ~) cards] this]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -36,21 +38,26 @@
|
||||
%bowl `this(bowl-print !bowl-print)
|
||||
==
|
||||
=^ cards agent (on-poke:ag mark vase)
|
||||
[cards this]
|
||||
[[(emit-event %on-poke mark) cards] this]
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-watch on path {<path>}")
|
||||
=^ cards agent (on-watch:ag path)
|
||||
[cards this]
|
||||
=^ cards agent
|
||||
?: ?=([%verb %events ~] path)
|
||||
[~ agent]
|
||||
(on-watch:ag path)
|
||||
[[(emit-event %on-watch path) cards] this]
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-leave on path {<path>}")
|
||||
?: ?=([%verb %event ~] path)
|
||||
[~ this]
|
||||
=^ cards agent (on-leave:ag path)
|
||||
[cards this]
|
||||
[[(emit-event %on-leave path) cards] this]
|
||||
::
|
||||
++ on-peek
|
||||
|= =path
|
||||
@ -63,21 +70,21 @@
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}")
|
||||
=^ cards agent (on-agent:ag wire sign)
|
||||
[cards this]
|
||||
[[(emit-event %on-agent wire -.sign) cards] this]
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}")
|
||||
=^ cards agent (on-arvo:ag wire sign-arvo)
|
||||
[cards this]
|
||||
[[(emit-event %on-arvo wire [- +<]:sign-arvo) cards] this]
|
||||
::
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-fail with term {<term>}")
|
||||
=^ cards agent (on-fail:ag term tang)
|
||||
[cards this]
|
||||
[[(emit-event %on-fail term) cards] this]
|
||||
--
|
||||
::
|
||||
++ print
|
||||
@ -89,4 +96,9 @@
|
||||
?. loud same
|
||||
%- (slog leaf+tape ~)
|
||||
same
|
||||
::
|
||||
++ emit-event
|
||||
|= =event:verb
|
||||
^- card:agent:gall
|
||||
[%give %fact ~[/verb/events] %verb-event !>(event)]
|
||||
--
|
||||
|
14
pkg/arvo/mar/spider/stop.hoon
Normal file
14
pkg/arvo/mar/spider/stop.hoon
Normal file
@ -0,0 +1,14 @@
|
||||
/- *spider
|
||||
|_ stop=[=tid nice=?]
|
||||
++ grab
|
||||
|%
|
||||
++ noun ,[=tid nice=?]
|
||||
++ json
|
||||
(ot 'tid'^so 'nice'^bo ~):dejs:format
|
||||
--
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ noun stop
|
||||
--
|
||||
--
|
26
pkg/arvo/mar/verb/event.hoon
Normal file
26
pkg/arvo/mar/verb/event.hoon
Normal file
@ -0,0 +1,26 @@
|
||||
/- verb
|
||||
=, dejs:format
|
||||
|_ =event:verb
|
||||
++ grab
|
||||
|%
|
||||
++ noun event:verb
|
||||
--
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ noun event
|
||||
++ json
|
||||
=, enjs:format
|
||||
%+ frond -.event
|
||||
?- -.event
|
||||
%on-init ~
|
||||
%on-load ~
|
||||
%on-poke s+mark.event
|
||||
%on-watch (path path.event)
|
||||
%on-leave (path path.event)
|
||||
%on-agent (pairs 'wire'^(path wire.event) 'sign'^s+sign.event ~)
|
||||
%on-arvo (pairs 'wire'^(path wire.event) 'vane'^s+vane.event 'sign'^s+sign.event ~)
|
||||
%on-fail s+term.event
|
||||
==
|
||||
--
|
||||
--
|
12
pkg/arvo/sur/verb.hoon
Normal file
12
pkg/arvo/sur/verb.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
|%
|
||||
+$ event
|
||||
$% [%on-init ~]
|
||||
[%on-load ~]
|
||||
[%on-poke =mark]
|
||||
[%on-watch =path]
|
||||
[%on-leave =path]
|
||||
[%on-agent =wire sign=term]
|
||||
[%on-arvo =wire vane=term sign=term]
|
||||
[%on-fail =term]
|
||||
==
|
||||
--
|
@ -121,267 +121,10 @@
|
||||
=>
|
||||
~% %ames-generics ..is ~
|
||||
|%
|
||||
+| %generics
|
||||
:: $mk-item: constructor for +ordered-map item type
|
||||
::
|
||||
++ mk-item |$ [key val] [key=key val=val]
|
||||
:: +ordered-map: treap with user-specified horizontal order
|
||||
::
|
||||
:: Conceptually smaller items go on the left, so the item with the
|
||||
:: smallest key can be popped off the head. If $key is `@` and
|
||||
:: .compare is +lte, then the numerically smallest item is the head.
|
||||
::
|
||||
++ ordered-map
|
||||
|* [key=mold val=mold]
|
||||
=> |%
|
||||
+$ item (mk-item key val)
|
||||
--
|
||||
:: +compare: item comparator for horizontal order
|
||||
::
|
||||
|= compare=$-([key key] ?)
|
||||
|%
|
||||
:: +check-balance: verify horizontal and vertical orderings
|
||||
::
|
||||
++ check-balance
|
||||
=| [l=(unit key) r=(unit key)]
|
||||
|= a=(tree item)
|
||||
^- ?
|
||||
:: empty tree is valid
|
||||
::
|
||||
?~ a %.y
|
||||
:: nonempty trees must maintain several criteria
|
||||
::
|
||||
?& :: if .n.a is left of .u.l, assert horizontal comparator
|
||||
::
|
||||
?~(l %.y (compare key.n.a u.l))
|
||||
:: if .n.a is right of .u.r, assert horizontal comparator
|
||||
::
|
||||
?~(r %.y (compare u.r key.n.a))
|
||||
:: if .a is not leftmost element, assert vertical order between
|
||||
:: .l.a and .n.a and recurse to the left with .n.a as right
|
||||
:: neighbor
|
||||
::
|
||||
?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a)))
|
||||
:: if .a is not rightmost element, assert vertical order
|
||||
:: between .r.a and .n.a and recurse to the right with .n.a as
|
||||
:: left neighbor
|
||||
::
|
||||
?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a)))
|
||||
==
|
||||
:: +put: ordered item insert
|
||||
::
|
||||
++ put
|
||||
|= [a=(tree item) =key =val]
|
||||
^- (tree item)
|
||||
:: base case: replace null with single-item tree
|
||||
::
|
||||
?~ a [n=[key val] l=~ r=~]
|
||||
:: base case: overwrite existing .key with new .val
|
||||
::
|
||||
?: =(key.n.a key) a(val.n val)
|
||||
:: if item goes on left, recurse left then rebalance vertical order
|
||||
::
|
||||
?: (compare key key.n.a)
|
||||
=/ l $(a l.a)
|
||||
?> ?=(^ l)
|
||||
?: (mor key.n.a key.n.l)
|
||||
a(l l)
|
||||
l(r a(l r.l))
|
||||
:: item goes on right; recurse right then rebalance vertical order
|
||||
::
|
||||
=/ r $(a r.a)
|
||||
?> ?=(^ r)
|
||||
?: (mor key.n.a key.n.r)
|
||||
a(r r)
|
||||
r(l a(r l.r))
|
||||
:: +peek: produce head (smallest item) or null
|
||||
::
|
||||
++ peek
|
||||
|= a=(tree item)
|
||||
^- (unit item)
|
||||
::
|
||||
?~ a ~
|
||||
?~ l.a `n.a
|
||||
$(a l.a)
|
||||
:: +pop: produce .head (smallest item) and .rest or crash if empty
|
||||
::
|
||||
++ pop
|
||||
|= a=(tree item)
|
||||
^- [head=item rest=(tree item)]
|
||||
::
|
||||
?~ a !!
|
||||
?~ l.a [n.a r.a]
|
||||
::
|
||||
=/ l $(a l.a)
|
||||
:- head.l
|
||||
:: load .rest.l back into .a and rebalance
|
||||
::
|
||||
?: |(?=(~ rest.l) (mor key.n.a key.n.rest.l))
|
||||
a(l rest.l)
|
||||
rest.l(r a(r r.rest.l))
|
||||
:: +del: delete .key from .a if it exists, producing value iff deleted
|
||||
::
|
||||
++ del
|
||||
|= [a=(tree item) =key]
|
||||
^- [(unit val) (tree item)]
|
||||
::
|
||||
?~ a [~ ~]
|
||||
:: we found .key at the root; delete and rebalance
|
||||
::
|
||||
?: =(key key.n.a)
|
||||
[`val.n.a (nip a)]
|
||||
:: recurse left or right to find .key
|
||||
::
|
||||
?: (compare key key.n.a)
|
||||
=+ [found lef]=$(a l.a)
|
||||
[found a(l lef)]
|
||||
=+ [found rig]=$(a r.a)
|
||||
[found a(r rig)]
|
||||
:: +nip: remove root; for internal use
|
||||
::
|
||||
++ nip
|
||||
|= a=(tree item)
|
||||
^- (tree item)
|
||||
::
|
||||
?> ?=(^ a)
|
||||
:: delete .n.a; merge and balance .l.a and .r.a
|
||||
::
|
||||
|- ^- (tree item)
|
||||
?~ l.a r.a
|
||||
?~ r.a l.a
|
||||
?: (mor key.n.l.a key.n.r.a)
|
||||
l.a(r $(l.a r.l.a))
|
||||
r.a(l $(r.a l.r.a))
|
||||
:: +traverse: stateful partial inorder traversal
|
||||
::
|
||||
:: Mutates .state on each run of .f. Starts at .start key, or if
|
||||
:: .start is ~, starts at the head (item with smallest key). Stops
|
||||
:: when .f produces .stop=%.y. Traverses from smaller to larger
|
||||
:: keys. Each run of .f can replace an item's value or delete the
|
||||
:: item.
|
||||
::
|
||||
++ traverse
|
||||
|* state=mold
|
||||
|= $: a=(tree item)
|
||||
=state
|
||||
f=$-([state item] [(unit val) ? state])
|
||||
==
|
||||
^+ [state a]
|
||||
:: acc: accumulator
|
||||
::
|
||||
:: .stop: set to %.y by .f when done traversing
|
||||
:: .state: threaded through each run of .f and produced by +abet
|
||||
::
|
||||
=/ acc [stop=`?`%.n state=state]
|
||||
=< abet =< main
|
||||
|%
|
||||
++ abet [state.acc a]
|
||||
:: +main: main recursive loop; performs a partial inorder traversal
|
||||
::
|
||||
++ main
|
||||
^+ .
|
||||
:: stop if empty or we've been told to stop
|
||||
::
|
||||
?~ a .
|
||||
?: stop.acc .
|
||||
:: inorder traversal: left -> node -> right, until .f sets .stop
|
||||
::
|
||||
=> left
|
||||
?: stop.acc .
|
||||
=> node
|
||||
?: stop.acc .
|
||||
right
|
||||
:: +node: run .f on .n.a, updating .a, .state, and .stop
|
||||
::
|
||||
++ node
|
||||
^+ .
|
||||
:: run .f on node, updating .stop.acc and .state.acc
|
||||
::
|
||||
=^ res acc
|
||||
?> ?=(^ a)
|
||||
(f state.acc n.a)
|
||||
:: apply update to .a from .f's product
|
||||
::
|
||||
=. a
|
||||
:: if .f requested node deletion, merge and balance .l.a and .r.a
|
||||
::
|
||||
?~ res (nip a)
|
||||
:: we kept the node; replace its .val; order is unchanged
|
||||
::
|
||||
?> ?=(^ a)
|
||||
a(val.n u.res)
|
||||
::
|
||||
..node
|
||||
:: +left: recurse on left subtree, copying mutant back into .l.a
|
||||
::
|
||||
++ left
|
||||
^+ .
|
||||
?~ a .
|
||||
=/ lef main(a l.a)
|
||||
lef(a a(l a.lef))
|
||||
:: +right: recurse on right subtree, copying mutant back into .r.a
|
||||
::
|
||||
++ right
|
||||
^+ .
|
||||
?~ a .
|
||||
=/ rig main(a r.a)
|
||||
rig(a a(r a.rig))
|
||||
--
|
||||
:: +tap: convert to list, smallest to largest
|
||||
::
|
||||
++ tap
|
||||
|= a=(tree item)
|
||||
^- (list item)
|
||||
::
|
||||
=| b=(list item)
|
||||
|- ^+ b
|
||||
?~ a b
|
||||
::
|
||||
$(a l.a, b [n.a $(a r.a)])
|
||||
:: +gas: put a list of items
|
||||
::
|
||||
++ gas
|
||||
|= [a=(tree item) b=(list item)]
|
||||
^- (tree item)
|
||||
::
|
||||
?~ b a
|
||||
$(b t.b, a (put a i.b))
|
||||
:: +uni: unify two ordered maps
|
||||
::
|
||||
:: .b takes precedence over .a if keys overlap.
|
||||
::
|
||||
++ uni
|
||||
|= [a=(tree item) b=(tree item)]
|
||||
^- (tree item)
|
||||
::
|
||||
?~ b a
|
||||
?~ a b
|
||||
?: =(key.n.a key.n.b)
|
||||
::
|
||||
[n=n.b l=$(a l.a, b l.b) r=$(a r.a, b r.b)]
|
||||
::
|
||||
?: (mor key.n.a key.n.b)
|
||||
::
|
||||
?: (compare key.n.b key.n.a)
|
||||
$(l.a $(a l.a, r.b ~), b r.b)
|
||||
$(r.a $(a r.a, l.b ~), b l.b)
|
||||
::
|
||||
?: (compare key.n.a key.n.b)
|
||||
$(l.b $(b l.b, r.a ~), a r.a)
|
||||
$(r.b $(b r.b, l.a ~), a l.a)
|
||||
--
|
||||
::
|
||||
+| %atomics
|
||||
::
|
||||
+$ bone @udbone
|
||||
+$ fragment @uwfragment
|
||||
+$ fragment-num @udfragmentnum
|
||||
+$ message-blob @udmessageblob
|
||||
+$ message-num @udmessagenum
|
||||
+$ private-key @uwprivatekey
|
||||
+$ public-key @uwpublickey
|
||||
+$ signature @uwsignature
|
||||
+$ symmetric-key @uwsymmetrickey
|
||||
:: $rank: which kind of ship address, by length
|
||||
::
|
||||
:: 0: galaxy or star -- 2 bytes
|
||||
@ -465,13 +208,6 @@
|
||||
:: $naxplanation: nack trace; explains which message failed and why
|
||||
::
|
||||
+$ naxplanation [=message-num =error]
|
||||
:: $ack: positive ack, nack packet, or nack trace
|
||||
::
|
||||
+$ ack
|
||||
$% [%ok ~]
|
||||
[%nack ~]
|
||||
[%naxplanation =error]
|
||||
==
|
||||
::
|
||||
+| %statics
|
||||
::
|
||||
@ -499,237 +235,6 @@
|
||||
$: veb=_veb-all-off
|
||||
ships=(set ship)
|
||||
==
|
||||
:: $ship-state: all we know about a peer
|
||||
::
|
||||
:: %alien: no PKI data, so enqueue actions to perform once we learn it
|
||||
:: %known: we know their life and public keys, so we have a channel
|
||||
::
|
||||
+$ ship-state
|
||||
$% [%alien alien-agenda]
|
||||
[%known peer-state]
|
||||
==
|
||||
:: $alien-agenda: what to do when we learn a peer's life and keys
|
||||
::
|
||||
:: messages: pleas local vanes have asked us to send
|
||||
:: packets: packets we've tried to send
|
||||
:: heeds: local tracking requests; passed through into $peer-state
|
||||
::
|
||||
+$ alien-agenda
|
||||
$: messages=(list [=duct =plea])
|
||||
packets=(set =blob)
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $peer-state: state for a peer with known life and keys
|
||||
::
|
||||
:: route: transport-layer destination for packets to peer
|
||||
:: qos: quality of service; connection status to peer
|
||||
:: ossuary: bone<->duct mapper
|
||||
:: snd: per-bone message pumps to send messages as fragments
|
||||
:: rcv: per-bone message sinks to assemble messages from fragments
|
||||
:: nax: unprocessed nacks (negative acknowledgments)
|
||||
:: Each value is ~ when we've received the ack packet but not a
|
||||
:: nack-trace, or an error when we've received a nack-trace but
|
||||
:: not the ack packet.
|
||||
::
|
||||
:: When we hear a nack packet or an explanation, if there's no
|
||||
:: entry in .nax, we make a new entry. Otherwise, if this new
|
||||
:: information completes the packet+nack-trace, we remove the
|
||||
:: entry and emit a nack to the local vane that asked us to send
|
||||
:: the message.
|
||||
:: heeds: listeners for %clog notifications
|
||||
::
|
||||
+$ peer-state
|
||||
$: $: =symmetric-key
|
||||
=life
|
||||
=public-key
|
||||
sponsor=ship
|
||||
==
|
||||
route=(unit [direct=? =lane])
|
||||
=qos
|
||||
=ossuary
|
||||
snd=(map bone message-pump-state)
|
||||
rcv=(map bone message-sink-state)
|
||||
nax=(set [=bone =message-num])
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $qos: quality of service; how is our connection to a peer doing?
|
||||
::
|
||||
:: .last-contact: last time we heard from peer, or if %unborn, when
|
||||
:: we first started tracking time
|
||||
::
|
||||
+$ qos
|
||||
$~ [%unborn *@da]
|
||||
[?(%live %dead %unborn) last-contact=@da]
|
||||
:: $ossuary: bone<->duct bijection and .next-bone to map to a duct
|
||||
::
|
||||
:: The first bone is 0. They increment by 4, since each flow includes
|
||||
:: a bit for each message determining forward vs. backward and a
|
||||
:: second bit for whether the message is on the normal flow or the
|
||||
:: associated diagnostic flow (for naxplanations).
|
||||
::
|
||||
:: The least significant bit of a $bone is:
|
||||
:: 1 if "forward", i.e. we send %plea's on this flow, or
|
||||
:: 0 if "backward", i.e. we receive %plea's on this flow.
|
||||
::
|
||||
:: The second-least significant bit is 1 if the bone is a
|
||||
:: naxplanation bone, and 0 otherwise. Only naxplanation
|
||||
:: messages can be sent on a naxplanation bone, as %boon's.
|
||||
::
|
||||
+$ ossuary
|
||||
$: =next=bone
|
||||
by-duct=(map duct bone)
|
||||
by-bone=(map bone duct)
|
||||
==
|
||||
:: $message-pump-state: persistent state for |message-pump
|
||||
::
|
||||
:: Messages queue up in |message-pump's .unsent-messages until they
|
||||
:: can be packetized and fed into |packet-pump for sending. When we
|
||||
:: pop a message off .unsent-messages, we push as many fragments as
|
||||
:: we can into |packet-pump, which sends every packet it eats.
|
||||
:: Packets rejected by |packet-pump are placed in .unsent-fragments.
|
||||
::
|
||||
:: When we hear a packet ack, we send it to |packet-pump to be
|
||||
:: removed from its queue of unacked packets.
|
||||
::
|
||||
:: When we hear a message ack (positive or negative), we treat that
|
||||
:: as though all fragments have been acked. If this message is not
|
||||
:: .current, then this ack is for a future message and .current has
|
||||
:: not yet been acked, so we place the ack in .queued-message-acks.
|
||||
::
|
||||
:: If we hear a message ack before we've sent all the fragments for
|
||||
:: that message, clear .unsent-fragments and have |packet-pump delete
|
||||
:: all sent fragments from the message. If this early message ack was
|
||||
:: positive, print it out because it indicates the peer is not
|
||||
:: behaving properly.
|
||||
::
|
||||
:: If the ack is for the current message, have |packet-pump delete
|
||||
:: all packets from the message, give the message ack back
|
||||
:: to the client vane, increment .current, and check if this next
|
||||
:: message is in .queued-message-acks. If it is, emit the message
|
||||
:: (n)ack, increment .current, and check the next message. Repeat
|
||||
:: until .current is not fully acked.
|
||||
::
|
||||
:: The following equation is always true:
|
||||
:: .next - .current == number of messages in flight
|
||||
::
|
||||
:: At the end of a task, |message-pump sends a %halt task to
|
||||
:: |packet-pump, which can trigger a timer to be set or cleared based
|
||||
:: on congestion control calculations. When the timer fires, it will
|
||||
:: generally cause a packet to be re-sent.
|
||||
::
|
||||
:: Message sequence numbers start at 1 so that the first message will
|
||||
:: be greater than .last-acked.message-sink-state on the receiver.
|
||||
::
|
||||
:: current: sequence number of earliest message sent or being sent
|
||||
:: next: sequence number of next message to send
|
||||
:: unsent-messages: messages to be sent after current message
|
||||
:: unsent-fragments: fragments of current message waiting for sending
|
||||
:: queued-message-acks: future message acks to be applied after current
|
||||
:: packet-pump-state: state of corresponding |packet-pump
|
||||
::
|
||||
+$ message-pump-state
|
||||
$: current=_`message-num`1
|
||||
next=_`message-num`1
|
||||
unsent-messages=(qeu message-blob)
|
||||
unsent-fragments=(list static-fragment)
|
||||
queued-message-acks=(map message-num ack)
|
||||
=packet-pump-state
|
||||
==
|
||||
+$ static-fragment
|
||||
$: =message-num
|
||||
num-fragments=fragment-num
|
||||
=fragment-num
|
||||
=fragment
|
||||
==
|
||||
:: $packet-pump-state: persistent state for |packet-pump
|
||||
::
|
||||
:: next-wake: last timer we've set, or null
|
||||
:: live: packets in flight; sent but not yet acked
|
||||
:: metrics: congestion control information
|
||||
::
|
||||
+$ packet-pump-state
|
||||
$: next-wake=(unit @da)
|
||||
live=(tree [live-packet-key live-packet-val])
|
||||
metrics=pump-metrics
|
||||
==
|
||||
:: $pump-metrics: congestion control state for a |packet-pump
|
||||
::
|
||||
:: This is an Ames adaptation of TCP's Reno congestion control
|
||||
:: algorithm. The information signals and their responses are
|
||||
:: identical to those of the "NewReno" variant of Reno; the
|
||||
:: implementation differs because Ames acknowledgments differ from
|
||||
:: TCP's, because this code uses functional data structures, and
|
||||
:: because TCP's sequence numbers reset when a peer becomes
|
||||
:: unresponsive, whereas Ames sequence numbers only change when a
|
||||
:: ship breaches.
|
||||
::
|
||||
:: A deviation from Reno is +fast-resend-after-ack, which re-sends
|
||||
:: timed-out packets when a peer starts responding again after a
|
||||
:: period of unresponsiveness.
|
||||
::
|
||||
:: If .skips reaches 3, we perform a fast retransmit and fast
|
||||
:: recovery. This corresponds to Reno's handling of "three duplicate
|
||||
:: acks".
|
||||
::
|
||||
:: rto: retransmission timeout
|
||||
:: rtt: roundtrip time estimate, low-passed using EWMA
|
||||
:: rttvar: mean deviation of .rtt, also low-passed with EWMA
|
||||
:: num-live: how many packets sent, awaiting ack
|
||||
:: ssthresh: slow-start threshold
|
||||
:: cwnd: congestion window; max unacked packets
|
||||
::
|
||||
+$ pump-metrics
|
||||
$: rto=_~s1
|
||||
rtt=_~s1
|
||||
rttvar=_~s1
|
||||
ssthresh=_10.000
|
||||
cwnd=_1
|
||||
num-live=@ud
|
||||
counter=@ud
|
||||
==
|
||||
+$ live-packet
|
||||
$: key=live-packet-key
|
||||
val=live-packet-val
|
||||
==
|
||||
+$ live-packet-key
|
||||
$: =message-num
|
||||
=fragment-num
|
||||
==
|
||||
+$ live-packet-val
|
||||
$: packet-state
|
||||
num-fragments=fragment-num
|
||||
=fragment
|
||||
==
|
||||
+$ packet-state
|
||||
$: last-sent=@da
|
||||
retries=@ud
|
||||
skips=@ud
|
||||
==
|
||||
:: $message-sink-state: state of |message-sink to assemble messages
|
||||
::
|
||||
:: last-acked: highest $message-num we've fully acknowledged
|
||||
:: last-heard: highest $message-num we've heard all fragments on
|
||||
:: pending-vane-ack: heard but not processed by local vane
|
||||
:: live-messages: partially received messages
|
||||
::
|
||||
+$ message-sink-state
|
||||
$: last-acked=message-num
|
||||
last-heard=message-num
|
||||
pending-vane-ack=(qeu [=message-num message=*])
|
||||
live-messages=(map message-num partial-rcv-message)
|
||||
nax=(set message-num)
|
||||
==
|
||||
:: $partial-rcv-message: message for which we've received some fragments
|
||||
::
|
||||
:: num-fragments: total number of fragments in this message
|
||||
:: num-received: how many fragments we've received so far
|
||||
:: fragments: fragments we've received, eventually producing a $message
|
||||
::
|
||||
+$ partial-rcv-message
|
||||
$: num-fragments=fragment-num
|
||||
num-received=fragment-num
|
||||
fragments=(map fragment-num fragment)
|
||||
==
|
||||
::
|
||||
+| %dialectics
|
||||
::
|
||||
@ -1094,6 +599,8 @@
|
||||
%jilt (on-jilt:event-core ship.task)
|
||||
%sift (on-sift:event-core ships.task)
|
||||
%spew (on-spew:event-core veb.task)
|
||||
%stir (on-stir:event-core arg.task)
|
||||
%trim on-trim:event-core
|
||||
%vega on-vega:event-core
|
||||
%wegh on-wegh:event-core
|
||||
%plea (on-plea:event-core [ship plea]:task)
|
||||
@ -1196,32 +703,19 @@
|
||||
?. =([%& our] why)
|
||||
[~ ~]
|
||||
?+ syd ~
|
||||
%peers
|
||||
?^ tyl [~ ~]
|
||||
:^ ~ ~ %noun
|
||||
!> ^- (map ship ?(%alien %known))
|
||||
(~(run by peers.ames-state) head)
|
||||
::
|
||||
%peer
|
||||
?. ?=([@ ~] tyl) [~ ~]
|
||||
=/ who (slaw %p i.tyl)
|
||||
?~ who [~ ~]
|
||||
=/ per (~(get by peers.ames-state) u.who)
|
||||
=/ res
|
||||
?- per
|
||||
~ %unknown
|
||||
[~ %alien *] %alien
|
||||
[~ %known *]
|
||||
=, u.per
|
||||
:* %known
|
||||
symkeymug=(mug symmetric-key)
|
||||
life=life
|
||||
pubkey=public-key
|
||||
sponsor=sponsor
|
||||
route=route
|
||||
qos=qos
|
||||
ossuary=ossuary
|
||||
snd=~(key by snd)
|
||||
rcv=~(key by rcv)
|
||||
nax=nax
|
||||
heeds=heeds
|
||||
==
|
||||
==
|
||||
``noun+!>(!>(res))
|
||||
?~ peer=(~(get by peers.ames-state) u.who)
|
||||
[~ ~]
|
||||
``noun+!>(u.peer)
|
||||
::
|
||||
%bones
|
||||
?. ?=([@ ~] tyl) [~ ~]
|
||||
@ -1326,6 +820,51 @@
|
||||
%rot acc(rot %.y)
|
||||
==
|
||||
event-core
|
||||
:: +on-stir: start timers for any flow that lack them
|
||||
::
|
||||
:: .arg is unused, meant to ease future debug commands
|
||||
::
|
||||
++ on-stir
|
||||
|= arg=@t
|
||||
=/ states=(list [ship peer-state])
|
||||
%+ murn ~(tap by peers.ames-state)
|
||||
|= [=ship =ship-state]
|
||||
^- (unit [^ship peer-state])
|
||||
?. ?=(%known -.ship-state)
|
||||
~
|
||||
`[ship +.ship-state]
|
||||
=/ snds=(list (list [ship bone message-pump-state]))
|
||||
%+ turn states
|
||||
|= [=ship peer-state]
|
||||
%+ turn ~(tap by snd)
|
||||
|= [=bone =message-pump-state]
|
||||
[ship bone message-pump-state]
|
||||
=/ next-wakes
|
||||
%+ turn `(list [ship bone message-pump-state])`(zing snds)
|
||||
|= [=ship =bone message-pump-state]
|
||||
[ship bone next-wake.packet-pump-state]
|
||||
=/ next-real-wakes=(list [=ship =bone =@da])
|
||||
%+ murn next-wakes
|
||||
|= [=ship =bone tym=(unit @da)]
|
||||
^- (unit [^ship ^bone @da])
|
||||
?~(tym ~ `[ship bone u.tym])
|
||||
=/ timers
|
||||
%- silt
|
||||
;; (list [@da ^duct])
|
||||
=< q.q %- need %- need
|
||||
%- scry-gate
|
||||
[[%141 %noun] ~ %b [[our %timers da+now] /]]
|
||||
=/ to-stir
|
||||
%+ skip next-real-wakes
|
||||
|= [=ship =bone =@da]
|
||||
(~(has in timers) [da `^duct`~[a+(make-pump-timer-wire ship bone) /ames]])
|
||||
~& [%stirring to-stir]
|
||||
|- ^+ event-core
|
||||
?~ to-stir
|
||||
event-core
|
||||
=/ =wire (make-pump-timer-wire [ship bone]:i.to-stir)
|
||||
=. event-core (emit duct %pass wire %b %wait da.i.to-stir)
|
||||
$(to-stir t.to-stir)
|
||||
:: +on-crud: handle event failure; print to dill
|
||||
::
|
||||
++ on-crud
|
||||
@ -1841,7 +1380,7 @@
|
||||
++ on-wegh
|
||||
^+ event-core
|
||||
::
|
||||
=+ [known alien]=(skid ~(tap by peers.ames-state) |=(^ =(%known +<-)))
|
||||
=+ [known alien]=(skid ~(val by peers.ames-state) |=(^ =(%known +<-)))
|
||||
::
|
||||
%- emit
|
||||
:^ duct %give %mass
|
||||
@ -1863,8 +1402,10 @@
|
||||
(scry-gate [%141 %noun] ~ %j `beam`[[our %turf %da now] /])
|
||||
::
|
||||
(emit unix-duct.ames-state %give %turf turfs)
|
||||
:: +on-trim: handle request to free memory
|
||||
:: +on-vega: handle kernel reload
|
||||
::
|
||||
++ on-trim event-core
|
||||
++ on-vega event-core
|
||||
:: +enqueue-alien-todo: helper to enqueue a pending request
|
||||
::
|
||||
@ -1890,6 +1431,9 @@
|
||||
::
|
||||
?: already-pending
|
||||
event-core
|
||||
:: NB: we specifically look for this wire in +public-keys-give in
|
||||
:: Jael. if you change it here, you must change it there.
|
||||
::
|
||||
(emit duct %pass /public-keys %j %public-keys [n=ship ~ ~])
|
||||
:: +send-blob: fire packet at .ship and maybe sponsors
|
||||
::
|
||||
@ -2117,12 +1661,45 @@
|
||||
==
|
||||
now
|
||||
::
|
||||
=/ =message-blob (jam payload)
|
||||
=/ =message-blob (dedup-message (jam payload))
|
||||
=. peer-core (run-message-pump bone %memo message-blob)
|
||||
::
|
||||
?: &(=(%boon valence) ?=(?(%dead %unborn) -.qos.peer-state))
|
||||
check-clog
|
||||
peer-core
|
||||
:: +dedup-message: replace with any existing copy of this message
|
||||
::
|
||||
++ dedup-message
|
||||
|= =message-blob
|
||||
^+ message-blob
|
||||
?: (lte (met 13 message-blob) 1)
|
||||
message-blob
|
||||
=/ peers-l=(list [=ship =ship-state]) ~(tap by peers.ames-state)
|
||||
|- ^+ message-blob
|
||||
=* peer-loop $
|
||||
?~ peers-l
|
||||
message-blob
|
||||
?. ?=(%known -.ship-state.i.peers-l)
|
||||
peer-loop(peers-l t.peers-l)
|
||||
=/ snd-l=(list [=bone =message-pump-state])
|
||||
~(tap by snd.ship-state.i.peers-l)
|
||||
|- ^+ message-blob
|
||||
=* bone-loop $
|
||||
?~ snd-l
|
||||
peer-loop(peers-l t.peers-l)
|
||||
=/ blob-l=(list ^message-blob)
|
||||
~(tap to unsent-messages.message-pump-state.i.snd-l)
|
||||
|- ^+ message-blob
|
||||
=* blob-loop $
|
||||
?^ blob-l
|
||||
?: =(i.blob-l message-blob)
|
||||
i.blob-l
|
||||
blob-loop(blob-l t.blob-l)
|
||||
?~ unsent-fragments.message-pump-state.i.snd-l
|
||||
bone-loop(snd-l t.snd-l)
|
||||
?: =(message-blob fragment.i.unsent-fragments.message-pump-state.i.snd-l)
|
||||
`@`fragment.i.unsent-fragments.message-pump-state.i.snd-l
|
||||
bone-loop(snd-l t.snd-l)
|
||||
:: +on-wake: handle timer expiration
|
||||
::
|
||||
++ on-wake
|
||||
@ -2141,6 +1718,11 @@
|
||||
peer-core
|
||||
?~ next-wake.packet-pump-state.u.message-pump-state
|
||||
peer-core
|
||||
:: If we crashed because we woke up too early, assume another
|
||||
:: timer is already set.
|
||||
::
|
||||
?: (lth now.channel u.next-wake.packet-pump-state.u.message-pump-state)
|
||||
peer-core
|
||||
::
|
||||
=/ =wire (make-pump-timer-wire her.channel bone)
|
||||
(emit duct %pass wire %b %wait (add now.channel ~s30))
|
||||
@ -3275,20 +2857,24 @@
|
||||
(lte fragment-num.a fragment-num.b)
|
||||
:: +split-message: split message into kilobyte-sized fragments
|
||||
::
|
||||
:: We don't literally split it here since that would allocate many
|
||||
:: large atoms with no structural sharing. Instead, each
|
||||
:: static-fragment has the entire message and a counter. In
|
||||
:: +encrypt, we interpret this to get the actual fragment.
|
||||
::
|
||||
++ split-message
|
||||
|= [=message-num =message-blob]
|
||||
^- (list static-fragment)
|
||||
::
|
||||
=/ fragments=(list fragment) (rip 13 message-blob)
|
||||
=/ num-fragments=fragment-num (lent fragments)
|
||||
=/ num-fragments=fragment-num (met 13 message-blob)
|
||||
=| counter=@
|
||||
::
|
||||
|- ^- (list static-fragment)
|
||||
?~ fragments ~
|
||||
?: (gte counter num-fragments)
|
||||
~
|
||||
::
|
||||
:- [message-num num-fragments counter i.fragments]
|
||||
::
|
||||
$(fragments t.fragments, counter +(counter))
|
||||
:- [message-num num-fragments counter `@`message-blob]
|
||||
$(counter +(counter))
|
||||
:: +assemble-fragments: concatenate fragments into a $message
|
||||
::
|
||||
++ assemble-fragments
|
||||
@ -3377,6 +2963,17 @@
|
||||
|= [=symmetric-key plaintext=shut-packet]
|
||||
^- @
|
||||
::
|
||||
=. meat.plaintext
|
||||
?. ?& ?=(%& -.meat.plaintext)
|
||||
(gth (met 13 fragment.p.meat.plaintext) 1)
|
||||
==
|
||||
meat.plaintext
|
||||
%= meat.plaintext
|
||||
fragment.p
|
||||
%^ end 13 1
|
||||
%^ rsh 13 fragment-num.p.meat.plaintext
|
||||
fragment.p.meat.plaintext
|
||||
==
|
||||
(en:crub:crypto symmetric-key (jam plaintext))
|
||||
:: +decrypt: decrypt packet content to a $shut-packet or die
|
||||
::
|
||||
|
@ -20,12 +20,15 @@
|
||||
==
|
||||
::
|
||||
+$ behn-state
|
||||
$: timers=(list timer)
|
||||
$: %2
|
||||
timers=(tree [key=@da val=(qeu duct)])
|
||||
unix-duct=duct
|
||||
next-wake=(unit @da)
|
||||
drips=drip-manager
|
||||
==
|
||||
::
|
||||
++ timer-map ((ordered-map ,@da ,(qeu duct)) lte)
|
||||
::
|
||||
+$ drip-manager
|
||||
$: count=@ud
|
||||
movs=(map @ud vase)
|
||||
@ -119,22 +122,24 @@
|
||||
^+ [moves state]
|
||||
:: no-op on spurious but innocuous unix wakeups
|
||||
::
|
||||
?~ timers.state
|
||||
?: =(~ timers.state)
|
||||
~? ?=(^ error) %behn-wake-no-timer^u.error
|
||||
[moves state]
|
||||
:: if we errored, pop the timer and notify the client vane of the error
|
||||
::
|
||||
?^ error
|
||||
=< set-unix-wake
|
||||
(emit-vane-wake(timers.state t.timers.state) duct.i.timers.state error)
|
||||
=^ =timer timers.state pop-timer
|
||||
(emit-vane-wake duct.timer error)
|
||||
:: if unix woke us too early, retry by resetting the unix wakeup timer
|
||||
::
|
||||
?: (gth date.i.timers.state now)
|
||||
=/ [=timer later-timers=_timers.state] pop-timer
|
||||
?: (gth date.timer now)
|
||||
set-unix-wake(next-wake.state ~)
|
||||
:: pop first timer, tell vane it has elapsed, and adjust next unix wakeup
|
||||
::
|
||||
=< set-unix-wake
|
||||
(emit-vane-wake(timers.state t.timers.state) duct.i.timers.state ~)
|
||||
(emit-vane-wake(timers.state later-timers) duct.timer ~)
|
||||
:: +wegh: produce memory usage report for |mass
|
||||
::
|
||||
++ wegh
|
||||
@ -184,58 +189,76 @@
|
||||
::
|
||||
++ set-unix-wake
|
||||
=< [moves state]
|
||||
~% %set-unix-wake ..is ~ |-
|
||||
^+ event-core
|
||||
::
|
||||
=* next-wake next-wake.state
|
||||
=* timers timers.state
|
||||
:: if no timers, cancel existing wakeup timer or no-op
|
||||
::
|
||||
?~ timers
|
||||
=/ first=(unit [date=@da *]) (peek:timer-map timers.state)
|
||||
?~ first
|
||||
?~ next-wake
|
||||
event-core
|
||||
(emit-doze ~)
|
||||
:: if :next-wake is in the past or not soon enough, reset it
|
||||
::
|
||||
?^ next-wake
|
||||
?: &((gte date.i.timers u.next-wake) (lte now u.next-wake))
|
||||
?: &((gte date.u.first u.next-wake) (lte now u.next-wake))
|
||||
event-core
|
||||
(emit-doze `date.i.timers)
|
||||
(emit-doze `date.u.first)
|
||||
:: there was no unix wakeup timer; set one
|
||||
::
|
||||
(emit-doze `date.i.timers)
|
||||
:: +set-timer: set a timer, maintaining the sort order of the :timers list
|
||||
(emit-doze `date.u.first)
|
||||
:: +pop-timer: dequeue and produce earliest timer
|
||||
::
|
||||
++ pop-timer
|
||||
^+ [*timer timers.state]
|
||||
=^ [date=@da dux=(qeu ^duct)] timers.state (pop:timer-map timers.state)
|
||||
=^ dut dux ~(get to dux)
|
||||
:- [date dut]
|
||||
?: =(~ dux)
|
||||
timers.state
|
||||
(put:timer-map timers.state date dux)
|
||||
:: +set-timer: set a timer, maintaining order
|
||||
::
|
||||
++ set-timer
|
||||
=* timers timers.state
|
||||
~% %set-timer ..is ~
|
||||
|= t=timer
|
||||
^+ timers
|
||||
::
|
||||
?~ timers
|
||||
~[t]
|
||||
:: ignore duplicates
|
||||
::
|
||||
?: =(t i.timers)
|
||||
timers
|
||||
:: timers at the same date form a fifo queue
|
||||
::
|
||||
?: (lth date.t date.i.timers)
|
||||
[t timers]
|
||||
::
|
||||
[i.timers $(timers t.timers)]
|
||||
^+ timers.state
|
||||
=/ found (find-ducts date.t)
|
||||
(put:timer-map timers.state date.t (~(put to found) duct.t))
|
||||
:: +find-ducts: get timers at date
|
||||
::
|
||||
:: TODO: move to +ordered-map
|
||||
::
|
||||
++ find-ducts
|
||||
|= date=@da
|
||||
^- (qeu ^duct)
|
||||
?~ timers.state ~
|
||||
?: =(date key.n.timers.state)
|
||||
val.n.timers.state
|
||||
?: (lte date key.n.timers.state)
|
||||
$(timers.state l.timers.state)
|
||||
$(timers.state r.timers.state)
|
||||
:: +unset-timer: cancel a timer; if it already expired, no-op
|
||||
::
|
||||
++ unset-timer
|
||||
=* timers timers.state
|
||||
|= t=timer
|
||||
^+ timers
|
||||
:: if we don't have this timer, no-op
|
||||
::
|
||||
?~ timers
|
||||
~
|
||||
?: =(i.timers t)
|
||||
t.timers
|
||||
::
|
||||
[i.timers $(timers t.timers)]
|
||||
^+ timers.state
|
||||
=/ [found=? dux=(qeu ^duct)]
|
||||
=/ dux (find-ducts date.t)
|
||||
|- ^- [found=? dux=(qeu ^duct)]
|
||||
?~ dux |+~
|
||||
?: =(duct.t n.dux) &+~(nip to `(qeu ^duct)`dux)
|
||||
=^ found-left=? l.dux $(dux l.dux)
|
||||
?: found-left &+dux
|
||||
=^ found-rite=? r.dux $(dux r.dux)
|
||||
[found-rite dux]
|
||||
?. found timers.state
|
||||
?: =(~ dux)
|
||||
+:(del:timer-map timers.state date.t)
|
||||
(put:timer-map timers.state date.t dux)
|
||||
--
|
||||
--
|
||||
::
|
||||
@ -248,6 +271,7 @@
|
||||
:: +call: handle a +task:able:behn request
|
||||
::
|
||||
++ call
|
||||
~% %behn-call ..is ~
|
||||
|= $: hen=duct
|
||||
dud=(unit goof)
|
||||
type=*
|
||||
@ -283,10 +307,80 @@
|
||||
:: +load: migrate an old state to a new behn version
|
||||
::
|
||||
++ load
|
||||
|= old=behn-state
|
||||
|^
|
||||
|= old=state
|
||||
^+ behn-gate
|
||||
::
|
||||
=? old ?=(^ -.old)
|
||||
(ket-to-1 old)
|
||||
=? old ?=(~ -.old)
|
||||
(load-0-to-1 old)
|
||||
=? old ?=(%1 -.old)
|
||||
(load-1-to-2 old)
|
||||
?> ?=(%2 -.old)
|
||||
behn-gate(state old)
|
||||
::
|
||||
++ state
|
||||
$^ behn-state-ket
|
||||
$% behn-state-0
|
||||
behn-state-1
|
||||
behn-state
|
||||
==
|
||||
::
|
||||
++ load-1-to-2
|
||||
|= old=behn-state-1
|
||||
^- behn-state
|
||||
=; new-timers old(- %2, timers new-timers)
|
||||
=/ timers=(list timer) ~(tap in ~(key by timers.old))
|
||||
%+ roll timers
|
||||
|= [t=timer acc=(tree [@da (qeu duct)])]
|
||||
^+ acc
|
||||
=| mock=behn-state
|
||||
=. timers.mock acc
|
||||
=/ event-core (per-event *[@p @da duct] mock)
|
||||
(set-timer:event-core t)
|
||||
::
|
||||
++ timer-map-1
|
||||
%- (ordered-map ,timer ,~)
|
||||
|= [a=timer b=timer]
|
||||
(lth date.a date.b)
|
||||
::
|
||||
+$ behn-state-1
|
||||
$: %1
|
||||
timers=(tree [timer ~])
|
||||
unix-duct=duct
|
||||
next-wake=(unit @da)
|
||||
drips=drip-manager
|
||||
==
|
||||
::
|
||||
+$ behn-state-0
|
||||
$: ~
|
||||
unix-duct=duct
|
||||
next-wake=(unit @da)
|
||||
drips=drip-manager
|
||||
==
|
||||
::
|
||||
+$ behn-state-ket
|
||||
$: timers=(list timer)
|
||||
unix-duct=duct
|
||||
next-wake=(unit @da)
|
||||
drips=drip-manager
|
||||
==
|
||||
::
|
||||
++ ket-to-1
|
||||
|= old=behn-state-ket
|
||||
^- behn-state-1
|
||||
:- %1
|
||||
%= old
|
||||
timers
|
||||
%+ gas:timer-map-1 *(tree [timer ~])
|
||||
(turn timers.old |=(=timer [timer ~]))
|
||||
==
|
||||
::
|
||||
++ load-0-to-1
|
||||
|= old=behn-state-0
|
||||
^- behn-state-1
|
||||
[%1 old]
|
||||
--
|
||||
:: +scry: view timer state
|
||||
::
|
||||
:: TODO: not referentially transparent w.r.t. elapsed timers,
|
||||
@ -298,7 +392,15 @@
|
||||
::
|
||||
?. ?=(%& -.why)
|
||||
~
|
||||
[~ ~ %tank !>(>timers<)]
|
||||
?. ?=(%timers syd)
|
||||
[~ ~]
|
||||
=/ tiz=(list [@da duct])
|
||||
%- zing
|
||||
%+ turn (tap:timer-map timers)
|
||||
|= [date=@da q=(qeu duct)]
|
||||
%+ turn ~(tap to q)
|
||||
|=(d=duct [date d])
|
||||
[~ ~ %noun !>(tiz)]
|
||||
::
|
||||
++ stay state
|
||||
++ take
|
||||
@ -313,4 +415,3 @@
|
||||
(take-drip:event-core (slav %ud i.t.tea) error.q.hin)
|
||||
[moves behn-gate]
|
||||
--
|
||||
|
||||
|
@ -124,131 +124,6 @@
|
||||
::
|
||||
outgoing-duct=duct
|
||||
==
|
||||
:: +outstanding-connection: open http connections not fully complete:
|
||||
::
|
||||
:: This refers to outstanding connections where the connection to
|
||||
:: outside is opened and we are currently waiting on ford or an app to
|
||||
:: produce the results.
|
||||
::
|
||||
+$ outstanding-connection
|
||||
$: :: action: the action that had matched
|
||||
::
|
||||
=action
|
||||
:: inbound-request: the original request which caused this connection
|
||||
::
|
||||
=inbound-request
|
||||
:: response-header: set when we get our first %start
|
||||
::
|
||||
response-header=(unit response-header:http)
|
||||
:: bytes-sent: the total bytes sent in response
|
||||
::
|
||||
bytes-sent=@ud
|
||||
==
|
||||
:: +action: the action to take when a binding matches an incoming request
|
||||
::
|
||||
+$ action
|
||||
$% :: dispatch to a generator
|
||||
::
|
||||
[%gen =generator]
|
||||
:: dispatch to an application
|
||||
::
|
||||
[%app app=term]
|
||||
:: internal authentication page
|
||||
::
|
||||
[%authentication ~]
|
||||
:: gall channel system
|
||||
::
|
||||
[%channel ~]
|
||||
:: respond with the default file not found page
|
||||
::
|
||||
[%four-oh-four ~]
|
||||
==
|
||||
:: +authentication-state: state used in the login system
|
||||
::
|
||||
+$ authentication-state
|
||||
$: :: sessions: a mapping of session cookies to session information
|
||||
::
|
||||
sessions=(map @uv session)
|
||||
==
|
||||
:: +session: server side data about a session
|
||||
::
|
||||
+$ session
|
||||
$: :: expiry-time: when this session expires
|
||||
::
|
||||
:: We check this server side, too, so we aren't relying on the browser
|
||||
:: to properly handle cookie expiration as a security mechanism.
|
||||
::
|
||||
expiry-time=@da
|
||||
::
|
||||
:: TODO: We should add a system for individual capabilities; we should
|
||||
:: mint some sort of long lived cookie for mobile apps which only has
|
||||
:: access to a single application path.
|
||||
==
|
||||
:: channel-state: state used in the channel system
|
||||
::
|
||||
+$ channel-state
|
||||
$: :: session: mapping between an arbitrary key to a channel
|
||||
::
|
||||
session=(map @t channel)
|
||||
:: by-duct: mapping from ducts to session key
|
||||
::
|
||||
duct-to-key=(map duct @t)
|
||||
==
|
||||
:: +timer: a reference to a timer so we can cancel or update it.
|
||||
::
|
||||
+$ timer
|
||||
$: :: date: time when the timer will fire
|
||||
::
|
||||
date=@da
|
||||
:: duct: duct that set the timer so we can cancel
|
||||
::
|
||||
=duct
|
||||
==
|
||||
:: channel: connection to the browser
|
||||
::
|
||||
:: Channels are the main method where a webpage communicates with Gall
|
||||
:: apps. Subscriptions and pokes are issues with PUT requests on a path,
|
||||
:: while GET requests on that same path open a persistent EventSource
|
||||
:: channel.
|
||||
::
|
||||
:: The EventSource API is a sequence number based API that browser provide
|
||||
:: which allow the server to push individual events to the browser over a
|
||||
:: connection held open. In case of reconnection, the browser will send a
|
||||
:: 'Last-Event-Id: ' header to the server; the server then resends all
|
||||
:: events since then.
|
||||
::
|
||||
+$ channel
|
||||
$: :: channel-state: expiration time or the duct currently listening
|
||||
::
|
||||
:: For each channel, there is at most one open EventSource
|
||||
:: connection. A 400 is issues on duplicate attempts to connect to the
|
||||
:: same channel. When an EventSource isn't connected, we set a timer
|
||||
:: to reap the subscriptions. This timer shouldn't be too short
|
||||
:: because the
|
||||
::
|
||||
state=(each timer duct)
|
||||
:: next-id: next sequence number to use
|
||||
::
|
||||
next-id=@ud
|
||||
:: events: unacknowledged events
|
||||
::
|
||||
:: We keep track of all events where we haven't received a
|
||||
:: 'Last-Event-Id: ' response from the client or a per-poke {'ack':
|
||||
:: ...} call. When there's an active EventSource connection on this
|
||||
:: channel, we send the event but we still add it to events because we
|
||||
:: can't assume it got received until we get an acknowledgment.
|
||||
::
|
||||
events=(qeu [id=@ud lines=wall])
|
||||
:: subscriptions: gall subscriptions
|
||||
::
|
||||
:: We maintain a list of subscriptions so if a channel times out, we
|
||||
:: can cancel all the subscriptions we've made.
|
||||
::
|
||||
subscriptions=(map wire [ship=@p app=term =path duc=duct])
|
||||
:: heartbeat: sse heartbeat timer
|
||||
::
|
||||
heartbeat=(unit timer)
|
||||
==
|
||||
:: channel-request: an action requested on a channel
|
||||
::
|
||||
+$ channel-request
|
||||
@ -2462,32 +2337,38 @@
|
||||
[~ ~]
|
||||
?. ?=(%$ -.lot)
|
||||
[~ ~]
|
||||
?. ?=(%host syd)
|
||||
[~ ~]
|
||||
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
||||
^- (unit (unit hart:eyre))
|
||||
?. =(our who)
|
||||
?. =([%da now] p.lot)
|
||||
[~ ~]
|
||||
~& [%r %scry-foreign-host who]
|
||||
~
|
||||
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
||||
?+ p.lot
|
||||
[~ ~]
|
||||
?+ syd [~ ~]
|
||||
%bindings ``noun+!>(bindings.server-state.ax)
|
||||
%connections ``noun+!>(connections.server-state.ax)
|
||||
%authentication-state ``noun+!>(authentication-state.server-state.ax)
|
||||
%channel-state ``noun+!>(channel-state.server-state.ax)
|
||||
::
|
||||
[%tas %fake]
|
||||
``[& [~ 8.443] %& /localhost]
|
||||
::
|
||||
[%tas %real]
|
||||
=* domains domains.server-state.ax
|
||||
=* ports ports.server-state.ax
|
||||
=/ =host:eyre [%& ?^(domains n.domains /localhost)]
|
||||
=/ secure=? &(?=(^ secure.ports) !?=(hoke:eyre host))
|
||||
=/ port=(unit @ud)
|
||||
?. secure
|
||||
?:(=(80 insecure.ports) ~ `insecure.ports)
|
||||
?> ?=(^ secure.ports)
|
||||
?:(=(443 u.secure.ports) ~ secure.ports)
|
||||
``[secure port host]
|
||||
%host
|
||||
%- (lift (lift |=(a=hart:eyre [%hart !>(a)])))
|
||||
^- (unit (unit hart:eyre))
|
||||
=. p.lot ?.(=([%da now] p.lot) p.lot [%tas %real])
|
||||
?+ p.lot
|
||||
[~ ~]
|
||||
::
|
||||
[%tas %fake]
|
||||
``[& [~ 8.443] %& /localhost]
|
||||
::
|
||||
[%tas %real]
|
||||
=* domains domains.server-state.ax
|
||||
=* ports ports.server-state.ax
|
||||
=/ =host:eyre [%& ?^(domains n.domains /localhost)]
|
||||
=/ secure=? &(?=(^ secure.ports) !?=(hoke:eyre host))
|
||||
=/ port=(unit @ud)
|
||||
?. secure
|
||||
?:(=(80 insecure.ports) ~ `insecure.ports)
|
||||
?> ?=(^ secure.ports)
|
||||
?:(=(443 u.secure.ports) ~ secure.ports)
|
||||
``[secure port host]
|
||||
==
|
||||
==
|
||||
--
|
||||
|
@ -55,7 +55,7 @@
|
||||
++ state
|
||||
$: :: state version
|
||||
::
|
||||
%4
|
||||
%5
|
||||
:: agents by ship
|
||||
::
|
||||
=agents
|
||||
@ -423,6 +423,12 @@
|
||||
=. mo-core (mo-untrack-ship ship)
|
||||
=. mo-core (mo-filter-queue ship)
|
||||
=/ agents=(list [name=term =running-agent]) ~(tap by running.agents.state)
|
||||
=. outstanding.agents.state
|
||||
%- malt
|
||||
%+ skip ~(tap by outstanding.agents.state)
|
||||
|= [[=wire duct] (qeu remote-request)]
|
||||
=(/sys/way/(scot %p ship) (scag 3 wire))
|
||||
::
|
||||
|- ^+ mo-core
|
||||
?~ agents
|
||||
mo-core
|
||||
@ -1110,31 +1116,30 @@
|
||||
++ ap-ducts-from-paths
|
||||
|= [target-paths=(list path) target-ship=(unit ship)]
|
||||
^- (list duct)
|
||||
?: &(?=(~ target-paths) ?=(~ target-ship))
|
||||
~[agent-duct]
|
||||
%- zing
|
||||
%+ turn target-paths
|
||||
|= =path
|
||||
(ap-ducts-from-path `path target-ship)
|
||||
:: +ap-ducts-from-path: get ducts subscribed to path
|
||||
::
|
||||
++ ap-ducts-from-path
|
||||
|= [target-path=(unit path) target-ship=(unit ship)]
|
||||
^- (list duct)
|
||||
?: &(?=(~ target-path) ?=(~ target-ship))
|
||||
~[agent-duct]
|
||||
%+ murn ~(tap by incoming.subscribers.current-agent)
|
||||
|= [=duct =ship =path]
|
||||
^- (unit ^duct)
|
||||
?~ target-ship
|
||||
?: =(target-path `path)
|
||||
`duct
|
||||
~
|
||||
?~ target-path
|
||||
?~ target-paths
|
||||
?~ target-ship
|
||||
~[agent-duct]
|
||||
%+ murn ~(tap by incoming.subscribers.current-agent)
|
||||
|= [=duct =ship =path]
|
||||
^- (unit ^duct)
|
||||
?: =(target-ship `ship)
|
||||
`duct
|
||||
~
|
||||
?: &(=(target-path `path) =(target-ship `ship))
|
||||
%- zing
|
||||
%+ turn target-paths
|
||||
|= =path
|
||||
(ap-ducts-from-path path target-ship)
|
||||
:: +ap-ducts-from-path: get ducts subscribed to path
|
||||
::
|
||||
++ ap-ducts-from-path
|
||||
|= [target-path=path target-ship=(unit ship)]
|
||||
^- (list duct)
|
||||
%+ murn ~(tap by incoming.subscribers.current-agent)
|
||||
|= [=duct =ship =path]
|
||||
^- (unit ^duct)
|
||||
?: ?& =(target-path path)
|
||||
|(=(target-ship ~) =(target-ship `ship))
|
||||
==
|
||||
`duct
|
||||
~
|
||||
:: +ap-apply: apply effect.
|
||||
@ -1555,7 +1560,9 @@
|
||||
=. ap-core
|
||||
=/ =tang
|
||||
~[leaf+"subscribe wire not unique" >agent-name< >short-wire< >dock<]
|
||||
%- (slog >out=outgoing.subscribers.current-agent< tang)
|
||||
=/ have
|
||||
(~(got by outgoing.subscribers.current-agent) short-wire dock)
|
||||
%- (slog >out=have< tang)
|
||||
(ap-error %watch-not-unique tang)
|
||||
$(moves t.moves)
|
||||
=. outgoing.subscribers.current-agent
|
||||
@ -1679,16 +1686,32 @@
|
||||
=? all-state ?=(%3 -.all-state)
|
||||
(state-3-to-4 all-state)
|
||||
::
|
||||
?> ?=(%4 -.all-state)
|
||||
=? all-state ?=(%4 -.all-state)
|
||||
(state-4-to-5 all-state)
|
||||
::
|
||||
?> ?=(%5 -.all-state)
|
||||
gall-payload(state all-state)
|
||||
::
|
||||
:: +all-state: upgrade path
|
||||
::
|
||||
++ all-state $%(state-0 state-1 state-2 state-3 ^state)
|
||||
++ all-state $%(state-0 state-1 state-2 state-3 state-4 ^state)
|
||||
::
|
||||
++ state-4-to-5
|
||||
|= =state-4
|
||||
^- ^state
|
||||
%= state-4
|
||||
- %5
|
||||
outstanding.agents ~
|
||||
==
|
||||
::
|
||||
++ state-4
|
||||
$: %4
|
||||
=agents
|
||||
==
|
||||
::
|
||||
++ state-3-to-4
|
||||
|= =state-3
|
||||
^- ^state
|
||||
^- state-4
|
||||
%= state-3
|
||||
- %4
|
||||
outstanding.agents ~
|
||||
|
@ -664,17 +664,30 @@
|
||||
::
|
||||
++ public-keys-give
|
||||
|= [yen=(set duct) =public-keys-result]
|
||||
=+ yez=~(tap in yen)
|
||||
|^
|
||||
=+ yez=(sort ~(tap in yen) sorter)
|
||||
|- ^+ this-su
|
||||
?~ yez this-su
|
||||
=* d i.yez
|
||||
=. this-su
|
||||
?. &(?=([[%a @ @ *] *] d) !=(%pubs i.t.i.d))
|
||||
?. &(?=([[%a @ @ *] *] d) !=(%public-keys i.t.i.d))
|
||||
%- emit
|
||||
[d %give %public-keys public-keys-result]
|
||||
%- emit
|
||||
[d %give %boon %public-keys-result public-keys-result]
|
||||
$(yez t.yez)
|
||||
::
|
||||
:: We want to notify Ames, then Clay, then Gall. This happens to
|
||||
:: be alphabetical, but this is mostly a coincidence.
|
||||
::
|
||||
++ sorter
|
||||
|= [a=duct b=duct]
|
||||
?. ?=([[@ *] *] a)
|
||||
|
|
||||
?. ?=([[@ *] *] b)
|
||||
&
|
||||
(lth i.i.a i.i.b)
|
||||
--
|
||||
::
|
||||
++ get-source
|
||||
|= who=@p
|
||||
@ -866,7 +879,8 @@
|
||||
:: if changing rift upward, then signal a breach
|
||||
::
|
||||
=? ..feel
|
||||
?& ?=(%rift -.a-diff)
|
||||
?& ?=(^ maybe-point)
|
||||
?=(%rift -.a-diff)
|
||||
(gth to.a-diff rift.point)
|
||||
==
|
||||
%+ public-keys-give
|
||||
|
@ -410,6 +410,8 @@
|
||||
$>(%init vane-task)
|
||||
[%sift ships=(list ship)]
|
||||
[%spew veb=(list verb)]
|
||||
[%stir arg=@t]
|
||||
$>(%trim vane-task)
|
||||
$>(%vega vane-task)
|
||||
$>(%wegh vane-task)
|
||||
==
|
||||
@ -490,6 +492,259 @@
|
||||
:: payload: semantic message contents
|
||||
::
|
||||
+$ plea [vane=@tas =path payload=*]
|
||||
::
|
||||
:: +| %atomics
|
||||
::
|
||||
+$ bone @udbone
|
||||
+$ fragment @uwfragment
|
||||
+$ fragment-num @udfragmentnum
|
||||
+$ message-blob @udmessageblob
|
||||
+$ message-num @udmessagenum
|
||||
+$ public-key @uwpublickey
|
||||
+$ symmetric-key @uwsymmetrickey
|
||||
::
|
||||
:: +| %kinetics
|
||||
:: $ack: positive ack, nack packet, or nack trace
|
||||
::
|
||||
+$ ack
|
||||
$% [%ok ~]
|
||||
[%nack ~]
|
||||
[%naxplanation =error]
|
||||
==
|
||||
::
|
||||
:: +| %statics
|
||||
:: $ship-state: all we know about a peer
|
||||
::
|
||||
:: %alien: no PKI data, so enqueue actions to perform once we learn it
|
||||
:: %known: we know their life and public keys, so we have a channel
|
||||
::
|
||||
+$ ship-state
|
||||
$% [%alien alien-agenda]
|
||||
[%known peer-state]
|
||||
==
|
||||
:: $alien-agenda: what to do when we learn a peer's life and keys
|
||||
::
|
||||
:: messages: pleas local vanes have asked us to send
|
||||
:: packets: packets we've tried to send
|
||||
:: heeds: local tracking requests; passed through into $peer-state
|
||||
::
|
||||
+$ alien-agenda
|
||||
$: messages=(list [=duct =plea])
|
||||
packets=(set =blob)
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $peer-state: state for a peer with known life and keys
|
||||
::
|
||||
:: route: transport-layer destination for packets to peer
|
||||
:: qos: quality of service; connection status to peer
|
||||
:: ossuary: bone<->duct mapper
|
||||
:: snd: per-bone message pumps to send messages as fragments
|
||||
:: rcv: per-bone message sinks to assemble messages from fragments
|
||||
:: nax: unprocessed nacks (negative acknowledgments)
|
||||
:: Each value is ~ when we've received the ack packet but not a
|
||||
:: nack-trace, or an error when we've received a nack-trace but
|
||||
:: not the ack packet.
|
||||
::
|
||||
:: When we hear a nack packet or an explanation, if there's no
|
||||
:: entry in .nax, we make a new entry. Otherwise, if this new
|
||||
:: information completes the packet+nack-trace, we remove the
|
||||
:: entry and emit a nack to the local vane that asked us to send
|
||||
:: the message.
|
||||
:: heeds: listeners for %clog notifications
|
||||
::
|
||||
+$ peer-state
|
||||
$: $: =symmetric-key
|
||||
=life
|
||||
=public-key
|
||||
sponsor=ship
|
||||
==
|
||||
route=(unit [direct=? =lane])
|
||||
=qos
|
||||
=ossuary
|
||||
snd=(map bone message-pump-state)
|
||||
rcv=(map bone message-sink-state)
|
||||
nax=(set [=bone =message-num])
|
||||
heeds=(set duct)
|
||||
==
|
||||
:: $qos: quality of service; how is our connection to a peer doing?
|
||||
::
|
||||
:: .last-contact: last time we heard from peer, or if %unborn, when
|
||||
:: we first started tracking time
|
||||
::
|
||||
+$ qos
|
||||
$~ [%unborn *@da]
|
||||
[?(%live %dead %unborn) last-contact=@da]
|
||||
:: $ossuary: bone<->duct bijection and .next-bone to map to a duct
|
||||
::
|
||||
:: The first bone is 0. They increment by 4, since each flow includes
|
||||
:: a bit for each message determining forward vs. backward and a
|
||||
:: second bit for whether the message is on the normal flow or the
|
||||
:: associated diagnostic flow (for naxplanations).
|
||||
::
|
||||
:: The least significant bit of a $bone is:
|
||||
:: 1 if "forward", i.e. we send %plea's on this flow, or
|
||||
:: 0 if "backward", i.e. we receive %plea's on this flow.
|
||||
::
|
||||
:: The second-least significant bit is 1 if the bone is a
|
||||
:: naxplanation bone, and 0 otherwise. Only naxplanation
|
||||
:: messages can be sent on a naxplanation bone, as %boon's.
|
||||
::
|
||||
+$ ossuary
|
||||
$: =next=bone
|
||||
by-duct=(map duct bone)
|
||||
by-bone=(map bone duct)
|
||||
==
|
||||
:: $message-pump-state: persistent state for |message-pump
|
||||
::
|
||||
:: Messages queue up in |message-pump's .unsent-messages until they
|
||||
:: can be packetized and fed into |packet-pump for sending. When we
|
||||
:: pop a message off .unsent-messages, we push as many fragments as
|
||||
:: we can into |packet-pump, which sends every packet it eats.
|
||||
:: Packets rejected by |packet-pump are placed in .unsent-fragments.
|
||||
::
|
||||
:: When we hear a packet ack, we send it to |packet-pump to be
|
||||
:: removed from its queue of unacked packets.
|
||||
::
|
||||
:: When we hear a message ack (positive or negative), we treat that
|
||||
:: as though all fragments have been acked. If this message is not
|
||||
:: .current, then this ack is for a future message and .current has
|
||||
:: not yet been acked, so we place the ack in .queued-message-acks.
|
||||
::
|
||||
:: If we hear a message ack before we've sent all the fragments for
|
||||
:: that message, clear .unsent-fragments and have |packet-pump delete
|
||||
:: all sent fragments from the message. If this early message ack was
|
||||
:: positive, print it out because it indicates the peer is not
|
||||
:: behaving properly.
|
||||
::
|
||||
:: If the ack is for the current message, have |packet-pump delete
|
||||
:: all packets from the message, give the message ack back
|
||||
:: to the client vane, increment .current, and check if this next
|
||||
:: message is in .queued-message-acks. If it is, emit the message
|
||||
:: (n)ack, increment .current, and check the next message. Repeat
|
||||
:: until .current is not fully acked.
|
||||
::
|
||||
:: The following equation is always true:
|
||||
:: .next - .current == number of messages in flight
|
||||
::
|
||||
:: At the end of a task, |message-pump sends a %halt task to
|
||||
:: |packet-pump, which can trigger a timer to be set or cleared based
|
||||
:: on congestion control calculations. When the timer fires, it will
|
||||
:: generally cause a packet to be re-sent.
|
||||
::
|
||||
:: Message sequence numbers start at 1 so that the first message will
|
||||
:: be greater than .last-acked.message-sink-state on the receiver.
|
||||
::
|
||||
:: current: sequence number of earliest message sent or being sent
|
||||
:: next: sequence number of next message to send
|
||||
:: unsent-messages: messages to be sent after current message
|
||||
:: unsent-fragments: fragments of current message waiting for sending
|
||||
:: queued-message-acks: future message acks to be applied after current
|
||||
:: packet-pump-state: state of corresponding |packet-pump
|
||||
::
|
||||
+$ message-pump-state
|
||||
$: current=_`message-num`1
|
||||
next=_`message-num`1
|
||||
unsent-messages=(qeu message-blob)
|
||||
unsent-fragments=(list static-fragment)
|
||||
queued-message-acks=(map message-num ack)
|
||||
=packet-pump-state
|
||||
==
|
||||
+$ static-fragment
|
||||
$: =message-num
|
||||
num-fragments=fragment-num
|
||||
=fragment-num
|
||||
=fragment
|
||||
==
|
||||
:: $packet-pump-state: persistent state for |packet-pump
|
||||
::
|
||||
:: next-wake: last timer we've set, or null
|
||||
:: live: packets in flight; sent but not yet acked
|
||||
:: metrics: congestion control information
|
||||
::
|
||||
+$ packet-pump-state
|
||||
$: next-wake=(unit @da)
|
||||
live=(tree [live-packet-key live-packet-val])
|
||||
metrics=pump-metrics
|
||||
==
|
||||
:: $pump-metrics: congestion control state for a |packet-pump
|
||||
::
|
||||
:: This is an Ames adaptation of TCP's Reno congestion control
|
||||
:: algorithm. The information signals and their responses are
|
||||
:: identical to those of the "NewReno" variant of Reno; the
|
||||
:: implementation differs because Ames acknowledgments differ from
|
||||
:: TCP's, because this code uses functional data structures, and
|
||||
:: because TCP's sequence numbers reset when a peer becomes
|
||||
:: unresponsive, whereas Ames sequence numbers only change when a
|
||||
:: ship breaches.
|
||||
::
|
||||
:: A deviation from Reno is +fast-resend-after-ack, which re-sends
|
||||
:: timed-out packets when a peer starts responding again after a
|
||||
:: period of unresponsiveness.
|
||||
::
|
||||
:: If .skips reaches 3, we perform a fast retransmit and fast
|
||||
:: recovery. This corresponds to Reno's handling of "three duplicate
|
||||
:: acks".
|
||||
::
|
||||
:: rto: retransmission timeout
|
||||
:: rtt: roundtrip time estimate, low-passed using EWMA
|
||||
:: rttvar: mean deviation of .rtt, also low-passed with EWMA
|
||||
:: num-live: how many packets sent, awaiting ack
|
||||
:: ssthresh: slow-start threshold
|
||||
:: cwnd: congestion window; max unacked packets
|
||||
::
|
||||
+$ pump-metrics
|
||||
$: rto=_~s1
|
||||
rtt=_~s1
|
||||
rttvar=_~s1
|
||||
ssthresh=_10.000
|
||||
cwnd=_1
|
||||
num-live=@ud
|
||||
counter=@ud
|
||||
==
|
||||
+$ live-packet
|
||||
$: key=live-packet-key
|
||||
val=live-packet-val
|
||||
==
|
||||
+$ live-packet-key
|
||||
$: =message-num
|
||||
=fragment-num
|
||||
==
|
||||
+$ live-packet-val
|
||||
$: packet-state
|
||||
num-fragments=fragment-num
|
||||
=fragment
|
||||
==
|
||||
+$ packet-state
|
||||
$: last-sent=@da
|
||||
retries=@ud
|
||||
skips=@ud
|
||||
==
|
||||
:: $message-sink-state: state of |message-sink to assemble messages
|
||||
::
|
||||
:: last-acked: highest $message-num we've fully acknowledged
|
||||
:: last-heard: highest $message-num we've heard all fragments on
|
||||
:: pending-vane-ack: heard but not processed by local vane
|
||||
:: live-messages: partially received messages
|
||||
::
|
||||
+$ message-sink-state
|
||||
$: last-acked=message-num
|
||||
last-heard=message-num
|
||||
pending-vane-ack=(qeu [=message-num message=*])
|
||||
live-messages=(map message-num partial-rcv-message)
|
||||
nax=(set message-num)
|
||||
==
|
||||
:: $partial-rcv-message: message for which we've received some fragments
|
||||
::
|
||||
:: num-fragments: total number of fragments in this message
|
||||
:: num-received: how many fragments we've received so far
|
||||
:: fragments: fragments we've received, eventually producing a $message
|
||||
::
|
||||
+$ partial-rcv-message
|
||||
$: num-fragments=fragment-num
|
||||
num-received=fragment-num
|
||||
fragments=(map fragment-num fragment)
|
||||
==
|
||||
::
|
||||
-- ::ames
|
||||
:: ::::
|
||||
:::: ++behn :: (1b) timekeeping
|
||||
@ -884,6 +1139,112 @@
|
||||
==
|
||||
::
|
||||
--
|
||||
:: +outstanding-connection: open http connections not fully complete:
|
||||
::
|
||||
:: This refers to outstanding connections where the connection to
|
||||
:: outside is opened and we are currently waiting on ford or an app to
|
||||
:: produce the results.
|
||||
::
|
||||
+$ outstanding-connection
|
||||
$: :: action: the action that had matched
|
||||
::
|
||||
=action
|
||||
:: inbound-request: the original request which caused this connection
|
||||
::
|
||||
=inbound-request
|
||||
:: response-header: set when we get our first %start
|
||||
::
|
||||
response-header=(unit response-header:http)
|
||||
:: bytes-sent: the total bytes sent in response
|
||||
::
|
||||
bytes-sent=@ud
|
||||
==
|
||||
:: +authentication-state: state used in the login system
|
||||
::
|
||||
+$ authentication-state
|
||||
$: :: sessions: a mapping of session cookies to session information
|
||||
::
|
||||
sessions=(map @uv session)
|
||||
==
|
||||
:: +session: server side data about a session
|
||||
::
|
||||
+$ session
|
||||
$: :: expiry-time: when this session expires
|
||||
::
|
||||
:: We check this server side, too, so we aren't relying on the browser
|
||||
:: to properly handle cookie expiration as a security mechanism.
|
||||
::
|
||||
expiry-time=@da
|
||||
::
|
||||
:: TODO: We should add a system for individual capabilities; we should
|
||||
:: mint some sort of long lived cookie for mobile apps which only has
|
||||
:: access to a single application path.
|
||||
==
|
||||
:: channel-state: state used in the channel system
|
||||
::
|
||||
+$ channel-state
|
||||
$: :: session: mapping between an arbitrary key to a channel
|
||||
::
|
||||
session=(map @t channel)
|
||||
:: by-duct: mapping from ducts to session key
|
||||
::
|
||||
duct-to-key=(map duct @t)
|
||||
==
|
||||
:: +timer: a reference to a timer so we can cancel or update it.
|
||||
::
|
||||
+$ timer
|
||||
$: :: date: time when the timer will fire
|
||||
::
|
||||
date=@da
|
||||
:: duct: duct that set the timer so we can cancel
|
||||
::
|
||||
=duct
|
||||
==
|
||||
:: channel: connection to the browser
|
||||
::
|
||||
:: Channels are the main method where a webpage communicates with Gall
|
||||
:: apps. Subscriptions and pokes are issues with PUT requests on a path,
|
||||
:: while GET requests on that same path open a persistent EventSource
|
||||
:: channel.
|
||||
::
|
||||
:: The EventSource API is a sequence number based API that browser provide
|
||||
:: which allow the server to push individual events to the browser over a
|
||||
:: connection held open. In case of reconnection, the browser will send a
|
||||
:: 'Last-Event-Id: ' header to the server; the server then resends all
|
||||
:: events since then.
|
||||
::
|
||||
+$ channel
|
||||
$: :: channel-state: expiration time or the duct currently listening
|
||||
::
|
||||
:: For each channel, there is at most one open EventSource
|
||||
:: connection. A 400 is issues on duplicate attempts to connect to the
|
||||
:: same channel. When an EventSource isn't connected, we set a timer
|
||||
:: to reap the subscriptions. This timer shouldn't be too short
|
||||
:: because the
|
||||
::
|
||||
state=(each timer duct)
|
||||
:: next-id: next sequence number to use
|
||||
::
|
||||
next-id=@ud
|
||||
:: events: unacknowledged events
|
||||
::
|
||||
:: We keep track of all events where we haven't received a
|
||||
:: 'Last-Event-Id: ' response from the client or a per-poke {'ack':
|
||||
:: ...} call. When there's an active EventSource connection on this
|
||||
:: channel, we send the event but we still add it to events because we
|
||||
:: can't assume it got received until we get an acknowledgment.
|
||||
::
|
||||
events=(qeu [id=@ud lines=wall])
|
||||
:: subscriptions: gall subscriptions
|
||||
::
|
||||
:: We maintain a list of subscriptions so if a channel times out, we
|
||||
:: can cancel all the subscriptions we've made.
|
||||
::
|
||||
subscriptions=(map wire [ship=@p app=term =path duc=duct])
|
||||
:: heartbeat: sse heartbeat timer
|
||||
::
|
||||
heartbeat=(unit timer)
|
||||
==
|
||||
:: +binding: A rule to match a path.
|
||||
::
|
||||
:: A +binding is a system unique mapping for a path to match. A +binding
|
||||
@ -903,6 +1264,25 @@
|
||||
::
|
||||
path=(list @t)
|
||||
==
|
||||
:: +action: the action to take when a binding matches an incoming request
|
||||
::
|
||||
+$ action
|
||||
$% :: dispatch to a generator
|
||||
::
|
||||
[%gen =generator]
|
||||
:: dispatch to an application
|
||||
::
|
||||
[%app app=term]
|
||||
:: internal authentication page
|
||||
::
|
||||
[%authentication ~]
|
||||
:: gall channel system
|
||||
::
|
||||
[%channel ~]
|
||||
:: respond with the default file not found page
|
||||
::
|
||||
[%four-oh-four ~]
|
||||
==
|
||||
:: +generator: a generator on the local ship that handles requests
|
||||
::
|
||||
:: This refers to a generator on the local ship, run with a set of
|
||||
@ -5537,9 +5917,9 @@
|
||||
==
|
||||
;~ pose
|
||||
;~ pfix
|
||||
(just 'e')
|
||||
(mask "eE")
|
||||
;~ plug
|
||||
;~(pose (cold | hep) (easy &))
|
||||
;~(pose (cold | hep) (cold & lus) (easy &))
|
||||
;~ pose
|
||||
;~(pfix (plus (just '0')) dim:ag)
|
||||
dim:ag
|
||||
@ -6469,8 +6849,16 @@
|
||||
;~ plug
|
||||
;~(pfix (plus whit) name)
|
||||
;~ pose
|
||||
(ifix [;~(plug tis yel) yel] (star ;~(less yel escp)))
|
||||
(ifix [;~(plug tis say) say] (star ;~(less say escp)))
|
||||
%+ ifix
|
||||
:_ yel
|
||||
;~(plug (ifix [. .]:(star whit) tis) yel)
|
||||
(star ;~(less yel escp))
|
||||
::
|
||||
%+ ifix
|
||||
:_ say
|
||||
;~(plug (ifix [. .]:(star whit) tis) say)
|
||||
(star ;~(less say escp))
|
||||
::
|
||||
(easy ~)
|
||||
==
|
||||
==
|
||||
@ -7227,6 +7615,254 @@
|
||||
$(pops [oldest pops])
|
||||
--
|
||||
--
|
||||
:: $mk-item: constructor for +ordered-map item type
|
||||
::
|
||||
++ mk-item |$ [key val] [key=key val=val]
|
||||
:: +ordered-map: treap with user-specified horizontal order
|
||||
::
|
||||
:: Conceptually smaller items go on the left, so the item with the
|
||||
:: smallest key can be popped off the head. If $key is `@` and
|
||||
:: .compare is +lte, then the numerically smallest item is the head.
|
||||
::
|
||||
++ ordered-map
|
||||
|* [key=mold val=mold]
|
||||
=> |%
|
||||
+$ item (mk-item key val)
|
||||
--
|
||||
:: +compare: item comparator for horizontal order
|
||||
::
|
||||
|= compare=$-([key key] ?)
|
||||
|%
|
||||
:: +check-balance: verify horizontal and vertical orderings
|
||||
::
|
||||
++ check-balance
|
||||
=| [l=(unit key) r=(unit key)]
|
||||
|= a=(tree item)
|
||||
^- ?
|
||||
:: empty tree is valid
|
||||
::
|
||||
?~ a %.y
|
||||
:: nonempty trees must maintain several criteria
|
||||
::
|
||||
?& :: if .n.a is left of .u.l, assert horizontal comparator
|
||||
::
|
||||
?~(l %.y (compare key.n.a u.l))
|
||||
:: if .n.a is right of .u.r, assert horizontal comparator
|
||||
::
|
||||
?~(r %.y (compare u.r key.n.a))
|
||||
:: if .a is not leftmost element, assert vertical order between
|
||||
:: .l.a and .n.a and recurse to the left with .n.a as right
|
||||
:: neighbor
|
||||
::
|
||||
?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a)))
|
||||
:: if .a is not rightmost element, assert vertical order
|
||||
:: between .r.a and .n.a and recurse to the right with .n.a as
|
||||
:: left neighbor
|
||||
::
|
||||
?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a)))
|
||||
==
|
||||
:: +put: ordered item insert
|
||||
::
|
||||
++ put
|
||||
|= [a=(tree item) =key =val]
|
||||
^- (tree item)
|
||||
:: base case: replace null with single-item tree
|
||||
::
|
||||
?~ a [n=[key val] l=~ r=~]
|
||||
:: base case: overwrite existing .key with new .val
|
||||
::
|
||||
?: =(key.n.a key) a(val.n val)
|
||||
:: if item goes on left, recurse left then rebalance vertical order
|
||||
::
|
||||
?: (compare key key.n.a)
|
||||
=/ l $(a l.a)
|
||||
?> ?=(^ l)
|
||||
?: (mor key.n.a key.n.l)
|
||||
a(l l)
|
||||
l(r a(l r.l))
|
||||
:: item goes on right; recurse right then rebalance vertical order
|
||||
::
|
||||
=/ r $(a r.a)
|
||||
?> ?=(^ r)
|
||||
?: (mor key.n.a key.n.r)
|
||||
a(r r)
|
||||
r(l a(r l.r))
|
||||
:: +peek: produce head (smallest item) or null
|
||||
::
|
||||
++ peek
|
||||
|= a=(tree item)
|
||||
^- (unit item)
|
||||
::
|
||||
?~ a ~
|
||||
?~ l.a `n.a
|
||||
$(a l.a)
|
||||
:: +pop: produce .head (smallest item) and .rest or crash if empty
|
||||
::
|
||||
++ pop
|
||||
|= a=(tree item)
|
||||
^- [head=item rest=(tree item)]
|
||||
::
|
||||
?~ a !!
|
||||
?~ l.a [n.a r.a]
|
||||
::
|
||||
=/ l $(a l.a)
|
||||
:- head.l
|
||||
:: load .rest.l back into .a and rebalance
|
||||
::
|
||||
?: |(?=(~ rest.l) (mor key.n.a key.n.rest.l))
|
||||
a(l rest.l)
|
||||
rest.l(r a(r r.rest.l))
|
||||
:: +del: delete .key from .a if it exists, producing value iff deleted
|
||||
::
|
||||
++ del
|
||||
|= [a=(tree item) =key]
|
||||
^- [(unit val) (tree item)]
|
||||
::
|
||||
?~ a [~ ~]
|
||||
:: we found .key at the root; delete and rebalance
|
||||
::
|
||||
?: =(key key.n.a)
|
||||
[`val.n.a (nip a)]
|
||||
:: recurse left or right to find .key
|
||||
::
|
||||
?: (compare key key.n.a)
|
||||
=+ [found lef]=$(a l.a)
|
||||
[found a(l lef)]
|
||||
=+ [found rig]=$(a r.a)
|
||||
[found a(r rig)]
|
||||
:: +nip: remove root; for internal use
|
||||
::
|
||||
++ nip
|
||||
|= a=(tree item)
|
||||
^- (tree item)
|
||||
::
|
||||
?> ?=(^ a)
|
||||
:: delete .n.a; merge and balance .l.a and .r.a
|
||||
::
|
||||
|- ^- (tree item)
|
||||
?~ l.a r.a
|
||||
?~ r.a l.a
|
||||
?: (mor key.n.l.a key.n.r.a)
|
||||
l.a(r $(l.a r.l.a))
|
||||
r.a(l $(r.a l.r.a))
|
||||
:: +traverse: stateful partial inorder traversal
|
||||
::
|
||||
:: Mutates .state on each run of .f. Starts at .start key, or if
|
||||
:: .start is ~, starts at the head (item with smallest key). Stops
|
||||
:: when .f produces .stop=%.y. Traverses from smaller to larger
|
||||
:: keys. Each run of .f can replace an item's value or delete the
|
||||
:: item.
|
||||
::
|
||||
++ traverse
|
||||
|* state=mold
|
||||
|= $: a=(tree item)
|
||||
=state
|
||||
f=$-([state item] [(unit val) ? state])
|
||||
==
|
||||
^+ [state a]
|
||||
:: acc: accumulator
|
||||
::
|
||||
:: .stop: set to %.y by .f when done traversing
|
||||
:: .state: threaded through each run of .f and produced by +abet
|
||||
::
|
||||
=/ acc [stop=`?`%.n state=state]
|
||||
=< abet =< main
|
||||
|%
|
||||
++ abet [state.acc a]
|
||||
:: +main: main recursive loop; performs a partial inorder traversal
|
||||
::
|
||||
++ main
|
||||
^+ .
|
||||
:: stop if empty or we've been told to stop
|
||||
::
|
||||
?~ a .
|
||||
?: stop.acc .
|
||||
:: inorder traversal: left -> node -> right, until .f sets .stop
|
||||
::
|
||||
=> left
|
||||
?: stop.acc .
|
||||
=> node
|
||||
?: stop.acc .
|
||||
right
|
||||
:: +node: run .f on .n.a, updating .a, .state, and .stop
|
||||
::
|
||||
++ node
|
||||
^+ .
|
||||
:: run .f on node, updating .stop.acc and .state.acc
|
||||
::
|
||||
=^ res acc
|
||||
?> ?=(^ a)
|
||||
(f state.acc n.a)
|
||||
:: apply update to .a from .f's product
|
||||
::
|
||||
=. a
|
||||
:: if .f requested node deletion, merge and balance .l.a and .r.a
|
||||
::
|
||||
?~ res (nip a)
|
||||
:: we kept the node; replace its .val; order is unchanged
|
||||
::
|
||||
?> ?=(^ a)
|
||||
a(val.n u.res)
|
||||
::
|
||||
..node
|
||||
:: +left: recurse on left subtree, copying mutant back into .l.a
|
||||
::
|
||||
++ left
|
||||
^+ .
|
||||
?~ a .
|
||||
=/ lef main(a l.a)
|
||||
lef(a a(l a.lef))
|
||||
:: +right: recurse on right subtree, copying mutant back into .r.a
|
||||
::
|
||||
++ right
|
||||
^+ .
|
||||
?~ a .
|
||||
=/ rig main(a r.a)
|
||||
rig(a a(r a.rig))
|
||||
--
|
||||
:: +tap: convert to list, smallest to largest
|
||||
::
|
||||
++ tap
|
||||
|= a=(tree item)
|
||||
^- (list item)
|
||||
::
|
||||
=| b=(list item)
|
||||
|- ^+ b
|
||||
?~ a b
|
||||
::
|
||||
$(a l.a, b [n.a $(a r.a)])
|
||||
:: +gas: put a list of items
|
||||
::
|
||||
++ gas
|
||||
|= [a=(tree item) b=(list item)]
|
||||
^- (tree item)
|
||||
::
|
||||
?~ b a
|
||||
$(b t.b, a (put a i.b))
|
||||
:: +uni: unify two ordered maps
|
||||
::
|
||||
:: .b takes precedence over .a if keys overlap.
|
||||
::
|
||||
++ uni
|
||||
|= [a=(tree item) b=(tree item)]
|
||||
^- (tree item)
|
||||
::
|
||||
?~ b a
|
||||
?~ a b
|
||||
?: =(key.n.a key.n.b)
|
||||
::
|
||||
[n=n.b l=$(a l.a, b l.b) r=$(a r.a, b r.b)]
|
||||
::
|
||||
?: (mor key.n.a key.n.b)
|
||||
::
|
||||
?: (compare key.n.b key.n.a)
|
||||
$(l.a $(a l.a, r.b ~), b r.b)
|
||||
$(r.a $(a r.a, l.b ~), b l.b)
|
||||
::
|
||||
?: (compare key.n.a key.n.b)
|
||||
$(l.b $(b l.b, r.a ~), a r.a)
|
||||
$(r.b $(b r.b, l.a ~), a l.a)
|
||||
--
|
||||
:: ::
|
||||
:::: ++userlib :: (2u) non-vane utils
|
||||
:: ::::
|
||||
|
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;
|
@ -34,4 +34,8 @@ cd ../link
|
||||
npm install
|
||||
gulp bundle-prod
|
||||
|
||||
cd ../dbug
|
||||
npm install
|
||||
gulp bundle-prod
|
||||
|
||||
set +x
|
||||
|
Loading…
Reference in New Issue
Block a user