Current section
Files
Jump to
Current section
Files
src/spectator.erl
-module(spectator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spectator.gleam").
-export([tag/2, tag_subject/2, tag_result/2, start_on/2, start/0, main/0]).
-export_type([node_connection_error/0, socket/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type node_connection_error() :: not_distributed_error |
failed_to_set_cookie_error |
failed_to_connect_error.
-type socket(ACGN) :: {socket,
lustre:runtime(ACGN),
gleam@erlang@process:subject(lustre@runtime@transport:client_message(ACGN))}.
-file("src/spectator.gleam", 164).
?DOC(
" Tag a process given by PID with a name for easier identification in the spectator UI.\n"
" You must call `start` before calling this function.\n"
).
-spec tag(gleam@erlang@process:pid_(), binary()) -> gleam@erlang@process:pid_().
tag(Pid, Name) ->
spectator_tag_manager:add_tag(Pid, Name),
Pid.
-file("src/spectator.gleam", 171).
?DOC(
" Tag a process given by subject with a name for easier identification in the spectator UI.\n"
" You must call `start` before calling this function.\n"
).
-spec tag_subject(gleam@erlang@process:subject(ACGU), binary()) -> gleam@erlang@process:subject(ACGU).
tag_subject(Sub, Name) ->
Pid@1 = case gleam@erlang@process:subject_owner(Sub) of
{ok, Pid} -> Pid;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"tag_subject"/utf8>>,
line => 175,
value => _assert_fail,
start => 5771,
'end' => 5818,
pattern_start => 5782,
pattern_end => 5789})
end,
tag(Pid@1, Name),
Sub.
-file("src/spectator.gleam", 182).
?DOC(
" Tag a process given by subject result with a name for easier identification in the spectator UI.\n"
" You must call `start` before calling this function.\n"
).
-spec tag_result(
{ok, gleam@erlang@process:subject(ACGX)} | {error, ACGZ},
binary()
) -> {ok, gleam@erlang@process:subject(ACGX)} | {error, ACGZ}.
tag_result(Result, Name) ->
case Result of
{ok, Sub} ->
{ok, tag_subject(Sub, Name)};
Other ->
Other
end.
-file("src/spectator.gleam", 198).
-spec validate_node_connection(list({binary(), binary()})) -> {ok, binary()} |
{error, node_connection_error()}.
validate_node_connection(Params) ->
Node_res = spectator@internal@common:get_param(Params, <<"node"/utf8>>),
case Node_res of
{error, _} ->
Self = begin
_pipe = erlang:node(),
_pipe@1 = gleam_erlang_ffi:identity(_pipe),
erlang:atom_to_binary(_pipe@1)
end,
{ok, Self};
{ok, Node} ->
Self@1 = begin
_pipe@2 = erlang:node(),
gleam_erlang_ffi:identity(_pipe@2)
end,
gleam@bool:guard(
Self@1 =:= erlang:binary_to_atom(<<"nonode@nohost"/utf8>>),
{error, not_distributed_error},
fun() ->
Node_atom = erlang:binary_to_atom(Node),
Cookie_validation_passed = case spectator@internal@common:get_param(
Params,
<<"cookie"/utf8>>
) of
{error, _} ->
true;
{ok, Cookie} ->
Cookie_atom = erlang:binary_to_atom(Cookie),
gleam@result:unwrap(
spectator_ffi:set_cookie(Node_atom, Cookie_atom),
false
)
end,
gleam@bool:guard(
not Cookie_validation_passed,
{error, failed_to_set_cookie_error},
fun() ->
gleam@bool:guard(
not gleam@result:unwrap(
spectator_ffi:hidden_connect_node(Node_atom),
false
),
{error, failed_to_connect_error},
fun() ->
{ok, erlang:atom_to_binary(Node_atom)}
end
)
end
)
end
)
end.
-file("src/spectator.gleam", 241).
-spec connect_widget_script() -> lustre@vdom@vnode:element(any()).
connect_widget_script() ->
lustre@element@html:script(
[lustre@attribute:src(<<"/connect-widget.js"/utf8>>)],
<<""/utf8>>
).
-file("src/spectator.gleam", 245).
-spec render_server_component(binary(), binary(), list({binary(), binary()})) -> gleam@http@response:response(mist:response_data()).
render_server_component(Title, Path, Params) ->
Res = gleam@http@response:new(200),
Styles = spectator@internal@common:static_file(<<"styles.css"/utf8>>),
Html = case validate_node_connection(Params) of
{ok, Connection_name} ->
lustre@element@html:html(
[],
[lustre@element@html:head(
[],
[lustre@element@html:title([], Title),
lustre@server_component:script(),
lustre@element@html:meta(
[lustre@attribute:attribute(
<<"charset"/utf8>>,
<<"utf-8"/utf8>>
)]
),
lustre@element@html:link(
[lustre@attribute:rel(<<"icon"/utf8>>),
lustre@attribute:href(
<<"/favicon.svg"/utf8>>
),
lustre@attribute:type_(
<<"image/svg+xml"/utf8>>
)]
),
lustre@element@html:style([], Styles)]
),
lustre@element@html:body(
[],
[spectator@internal@views@navbar:render(
Title,
Connection_name,
begin
_pipe = spectator@internal@common:sanitize_params(
Params
),
spectator@internal@common:encode_params(
_pipe
)
end
),
connect_widget_script(),
lustre@element:element(
<<"lustre-server-component"/utf8>>,
[lustre@server_component:route(
<<<<"/"/utf8, Path/binary>>/binary,
(spectator@internal@common:encode_params(
Params
))/binary>>
)],
[]
)]
)]
);
{error, Connection_error} ->
lustre@element@html:html(
[],
[lustre@element@html:head(
[],
[lustre@element@html:title(
[],
<<Title/binary, " - Connection Failed"/utf8>>
),
lustre@element@html:meta(
[lustre@attribute:attribute(
<<"charset"/utf8>>,
<<"utf-8"/utf8>>
)]
),
lustre@element@html:link(
[lustre@attribute:rel(<<"icon"/utf8>>),
lustre@attribute:href(
<<"/favicon.svg"/utf8>>
),
lustre@attribute:type_(
<<"image/svg+xml"/utf8>>
)]
),
lustre@element@html:style([], Styles)]
),
lustre@element@html:body(
[],
[spectator@internal@views@navbar:render(
Title,
<<"Connection Failed"/utf8>>,
begin
_pipe@1 = spectator@internal@common:sanitize_params(
Params
),
spectator@internal@common:encode_params(
_pipe@1
)
end
),
lustre@element@html:'div'(
[lustre@attribute:class(
<<"component-error"/utf8>>
)],
[lustre@element@html:'div'(
[],
[lustre@element@html:text(
<<"Node connection failed:"/utf8>>
)]
),
lustre@element@html:'div'(
[],
[lustre@element@html:text(
case Connection_error of
not_distributed_error ->
<<"Node is not distributed, cannot connect to other nodes. Please start the spectator instance in distributed mode by setting a node name."/utf8>>;
failed_to_set_cookie_error ->
<<"Failed to set cookie, could not apply the cookie to the node"/utf8>>;
failed_to_connect_error ->
<<"Failed to connect to node, please check the node name and cookie"/utf8>>
end
)]
),
lustre@element@html:'div'(
[],
[lustre@element@html:button(
[lustre@attribute:class(
<<"change-target-button"/utf8>>
)],
[lustre@element@html:text(
<<"Change Inspection Target"/utf8>>
)]
)]
)]
),
connect_widget_script()]
)]
)
end,
gleam@http@response:set_body(
Res,
begin
_pipe@2 = Html,
_pipe@3 = lustre@element:to_document_string(_pipe@2),
_pipe@4 = gleam_stdlib:wrap_list(_pipe@3),
{bytes, _pipe@4}
end
).
-file("src/spectator.gleam", 346).
-spec connect_server_component(
gleam@http@request:request(mist@internal@http:connection()),
fun(() -> lustre:app(list({binary(), binary()}), any(), any())),
list({binary(), binary()})
) -> gleam@http@response:response(mist:response_data()).
connect_server_component(Req, Lustre_application, Params) ->
Socket_init = fun(_) ->
Self = gleam@erlang@process:new_subject(),
App = Lustre_application(),
Component@1 = case lustre:start_server_component(App, Params) of
{ok, Component} -> Component;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"connect_server_component"/utf8>>,
line => 355,
value => _assert_fail,
start => 10863,
'end' => 10932,
pattern_start => 10874,
pattern_end => 10887})
end,
tag_subject(
lustre@server_component:subject(Component@1),
<<"__spectator_internal Server Component"/utf8>>
),
_pipe = lustre@server_component:register_subject(Self),
gleam@erlang@process:send(Component@1, _pipe),
Selector = begin
_pipe@1 = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select(_pipe@1, Self)
end,
{{socket, Component@1, Self}, {some, Selector}}
end,
Socket_update = fun(State, Msg, Conn) -> case Msg of
{text, Json} ->
Action = gleam@json:parse(
Json,
lustre@server_component:runtime_message_decoder()
),
case Action of
{ok, Action@1} ->
gleam@erlang@process:send(
erlang:element(2, State),
Action@1
);
{error, _} ->
nil
end,
mist:continue(State);
{binary, _} ->
mist:continue(State);
{custom, Patch} ->
case begin
_pipe@2 = Patch,
_pipe@3 = lustre@server_component:client_message_to_json(
_pipe@2
),
_pipe@4 = gleam@json:to_string(_pipe@3),
mist:send_text_frame(Conn, _pipe@4)
end of
{ok, _} -> nil;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"connect_server_component"/utf8>>,
line => 387,
value => _assert_fail@1,
start => 11801,
'end' => 11961,
pattern_start => 11812,
pattern_end => 11817})
end,
mist:continue(State);
closed ->
mist:stop();
shutdown ->
mist:stop()
end end,
Socket_close = fun(State@1) ->
_pipe@5 = lustre@server_component:deregister_subject(
erlang:element(3, State@1)
),
gleam@erlang@process:send(erlang:element(2, State@1), _pipe@5),
_pipe@6 = lustre:shutdown(),
gleam@erlang@process:send(erlang:element(2, State@1), _pipe@6)
end,
mist:websocket(Req, Socket_update, Socket_init, Socket_close).
-file("src/spectator.gleam", 42).
-spec mist_supervised(binary(), integer()) -> gleam@otp@supervision:child_specification(gleam@otp@static_supervisor:supervisor()).
mist_supervised(Host, Port) ->
Empty_body = {bytes, gleam@bytes_tree:new()},
Not_found = gleam@http@response:set_body(
gleam@http@response:new(404),
Empty_body
),
_pipe@14 = fun(Req) ->
Query_params = begin
_pipe = gleam@http@request:get_query(Req),
gleam@result:unwrap(_pipe, [])
end,
case gleam@http@request:path_segments(Req) of
[<<"dashboard"/utf8>>] ->
render_server_component(
<<"Dashboard"/utf8>>,
<<"dashboard-feed"/utf8>>,
Query_params
);
[<<"processes"/utf8>>] ->
render_server_component(
<<"Processes"/utf8>>,
<<"process-feed"/utf8>>,
Query_params
);
[<<"ets"/utf8>>] ->
render_server_component(
<<"ETS"/utf8>>,
<<"ets-feed"/utf8>>,
Query_params
);
[<<"ets"/utf8>>, Table] ->
render_server_component(
<<"ETS"/utf8>>,
<<"ets-feed/"/utf8, Table/binary>>,
Query_params
);
[<<"ports"/utf8>>] ->
render_server_component(
<<"Ports"/utf8>>,
<<"port-feed"/utf8>>,
Query_params
);
[<<"dashboard-feed"/utf8>>] ->
connect_server_component(
Req,
fun spectator@internal@components@dashboard_live:app/0,
Query_params
);
[<<"process-feed"/utf8>>] ->
connect_server_component(
Req,
fun spectator@internal@components@processes_live:app/0,
Query_params
);
[<<"ets-feed"/utf8>>] ->
connect_server_component(
Req,
fun spectator@internal@components@ets_overview_live:app/0,
Query_params
);
[<<"ets-feed"/utf8>>, Table@1] ->
connect_server_component(
Req,
fun spectator@internal@components@ets_table_live:app/0,
[{<<"table_name"/utf8>>,
begin
_pipe@1 = gleam_stdlib:percent_decode(Table@1),
gleam@result:unwrap(_pipe@1, <<""/utf8>>)
end} |
Query_params]
);
[<<"port-feed"/utf8>>] ->
connect_server_component(
Req,
fun spectator@internal@components@ports_live:app/0,
Query_params
);
[<<"favicon.svg"/utf8>>] ->
Priv@1 = case gleam_erlang_ffi:priv_directory(
<<"spectator"/utf8>>
) of
{ok, Priv} -> Priv;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"mist_supervised"/utf8>>,
line => 78,
value => _assert_fail,
start => 2808,
'end' => 2869,
pattern_start => 2819,
pattern_end => 2827})
end,
Path = <<Priv@1/binary, "/lucy_spectator.svg"/utf8>>,
_pipe@2 = mist:send_file(Path, 0, none),
_pipe@5 = gleam@result:map(
_pipe@2,
fun(Favicon) -> _pipe@3 = gleam@http@response:new(200),
_pipe@4 = gleam@http@response:prepend_header(
_pipe@3,
<<"content-type"/utf8>>,
<<"image/svg+xml"/utf8>>
),
gleam@http@response:set_body(_pipe@4, Favicon) end
),
gleam@result:lazy_unwrap(
_pipe@5,
fun() -> _pipe@6 = gleam@http@response:new(404),
gleam@http@response:set_body(
_pipe@6,
{bytes, gleam@bytes_tree:new()}
) end
);
[<<"connect-widget.js"/utf8>>] ->
Priv@3 = case gleam_erlang_ffi:priv_directory(
<<"spectator"/utf8>>
) of
{ok, Priv@2} -> Priv@2;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"mist_supervised"/utf8>>,
line => 92,
value => _assert_fail@1,
start => 3350,
'end' => 3411,
pattern_start => 3361,
pattern_end => 3369})
end,
Path@1 = <<Priv@3/binary, "/connect-widget.js"/utf8>>,
_pipe@7 = mist:send_file(Path@1, 0, none),
_pipe@10 = gleam@result:map(
_pipe@7,
fun(Script) -> _pipe@8 = gleam@http@response:new(200),
_pipe@9 = gleam@http@response:prepend_header(
_pipe@8,
<<"content-type"/utf8>>,
<<"application/javascript"/utf8>>
),
gleam@http@response:set_body(_pipe@9, Script) end
),
gleam@result:lazy_unwrap(
_pipe@10,
fun() -> _pipe@11 = gleam@http@response:new(404),
gleam@http@response:set_body(
_pipe@11,
{bytes, gleam@bytes_tree:new()}
) end
);
[] ->
_pipe@12 = gleam@http@response:new(302),
_pipe@13 = gleam@http@response:prepend_header(
_pipe@12,
<<"location"/utf8>>,
<<"/dashboard"/utf8>>
),
gleam@http@response:set_body(_pipe@13, Empty_body);
_ ->
Not_found
end
end,
_pipe@15 = mist:new(_pipe@14),
_pipe@17 = mist:after_start(
_pipe@15,
fun(Port@1, Scheme, Interface) ->
Address = case Interface of
{ip_v6, _, _, _, _, _, _, _, _} ->
<<<<"["/utf8,
(mist:ip_address_to_string(Interface))/binary>>/binary,
"]"/utf8>>;
_ ->
mist:ip_address_to_string(Interface)
end,
Message = <<<<<<<<<<<<<<"🔍 Spectator is listening on "/utf8,
(gleam@http:scheme_to_string(Scheme))/binary>>/binary,
"://"/utf8>>/binary,
Address/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(Port@1))/binary>>/binary,
" - Node: "/utf8>>/binary,
(erlang:atom_to_binary(
begin
_pipe@16 = erlang:node(),
gleam_erlang_ffi:identity(_pipe@16)
end
))/binary>>,
gleam_stdlib:println(Message)
end
),
_pipe@18 = mist:port(_pipe@17, Port),
_pipe@19 = mist:bind(_pipe@18, Host),
mist:supervised(_pipe@19).
-file("src/spectator.gleam", 142).
-spec start_on(binary(), integer()) -> {ok,
gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
{error, gleam@otp@actor:start_error()}.
start_on(Host, Port) ->
_pipe = gleam@otp@static_supervisor:new(one_for_one),
_pipe@1 = gleam@otp@static_supervisor:add(
_pipe,
gleam@otp@supervision:worker(
fun() -> case spectator_tag_manager:start_link() of
{ok, Pid} ->
{ok, {started, Pid, nil}};
{error, Error} ->
{error,
{init_failed,
<<"Failed to start tag manager: "/utf8,
(gleam@string:inspect(Error))/binary>>}}
end end
)
),
_pipe@2 = gleam@otp@static_supervisor:add(
_pipe@1,
mist_supervised(Host, Port)
),
gleam@otp@static_supervisor:start(_pipe@2).
-file("src/spectator.gleam", 138).
?DOC(" Start the spectator application on port 3000\n").
-spec start() -> {ok,
gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
{error, gleam@otp@actor:start_error()}.
start() ->
start_on(<<"127.0.0.1"/utf8>>, 3000).
-file("src/spectator.gleam", 37).
?DOC(
" Entrypoint for running spectator from the command line.\n"
" This will start the spectator application on port 3000 and never return.\n"
).
-spec main() -> nil.
main() ->
case start() of
{ok, _} -> nil;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"spectator"/utf8>>,
function => <<"main"/utf8>>,
line => 38,
value => _assert_fail,
start => 1200,
'end' => 1226,
pattern_start => 1211,
pattern_end => 1216})
end,
gleam_erlang_ffi:sleep_forever().