-module(spectator). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([tag/2, tag_subject/2, tag_result/2, start_on/1, start/0, main/0]). -export_type([node_connection_error/0]). -type node_connection_error() :: not_distributed_error | failed_to_set_cookie_error | failed_to_connect_error. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 146). -spec tag(gleam@erlang@process:pid_(), binary()) -> gleam@erlang@process:pid_(). tag(Pid, Name) -> spectator_tag_manager:add_tag(Pid, Name), Pid. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 153). -spec tag_subject(gleam@erlang@process:subject(ASVE), binary()) -> gleam@erlang@process:subject(ASVE). tag_subject(Sub, Name) -> Pid = gleam@erlang@process:subject_owner(Sub), tag(Pid, Name), Sub. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 164). -spec tag_result( {ok, gleam@erlang@process:subject(ASVH)} | {error, ASVJ}, binary() ) -> {ok, gleam@erlang@process:subject(ASVH)} | {error, ASVJ}. tag_result(Result, Name) -> case Result of {ok, Sub} -> {ok, tag_subject(Sub, Name)}; Other -> Other end. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 180). -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, _} -> {ok, <<""/utf8>>}; {ok, Node} -> Self = begin _pipe = erlang:node(), gleam_erlang_ffi:identity(_pipe) end, gleam@bool:guard( Self =:= 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), spectator_ffi:set_cookie(Node_atom, Cookie_atom) end, gleam@bool:guard( not Cookie_validation_passed, {error, failed_to_set_cookie_error}, fun() -> gleam@bool:guard( not net_kernel:hidden_connect_node(Node_atom), {error, failed_to_connect_error}, fun() -> {ok, <<"🟢 "/utf8, (erlang:atom_to_binary(Node_atom))/binary>>} end ) end ) end ) end. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 220). -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 ), 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), 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:a( [lustre@attribute:href( <<"/"/utf8>> ), lustre@attribute:class( <<"button"/utf8>> )], [lustre@element@html:text( <<"Return to local node"/utf8>> )] )] )] )] )] ) 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("/Users/jonas/Projects/spectator/src/spectator.gleam", 312). -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(), _assert_subject = lustre:start_actor(App, Params), {ok, Live_component} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"spectator"/utf8>>, function => <<"connect_server_component"/utf8>>, line => 320}) end, tag_subject( Live_component, <<"__spectator_internal Server Component"/utf8>> ), gleam@erlang@process:send( Live_component, lustre@server_component:subscribe( <<"ws"/utf8>>, fun(_capture) -> gleam@erlang@process:send(Self, _capture) end ) ), {Live_component, {some, gleam@erlang@process:selecting( gleam_erlang_ffi:new_selector(), Self, fun(A) -> A end )}} end, Socket_update = fun(Live_component@1, Conn, Msg) -> case Msg of {text, Json} -> Action = gleam@json:decode( Json, fun lustre@server_component:decode_action/1 ), case Action of {ok, Action@1} -> gleam@erlang@process:send(Live_component@1, Action@1); {error, _} -> nil end, gleam@otp@actor:continue(Live_component@1); {binary, _} -> gleam@otp@actor:continue(Live_component@1); {custom, Patch} -> _assert_subject@1 = begin _pipe = Patch, _pipe@1 = lustre@server_component:encode_patch(_pipe), _pipe@2 = gleam@json:to_string(_pipe@1), mist:send_text_frame(Conn, _pipe@2) end, {ok, _} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail@1, module => <<"spectator"/utf8>>, function => <<"connect_server_component"/utf8>>, line => 357}) end, gleam@otp@actor:continue(Live_component@1); closed -> {stop, normal}; shutdown -> {stop, normal} end end, Socket_close = fun(Live_component@2) -> gleam@erlang@process:send(Live_component@2, lustre:shutdown()) end, mist:websocket(Req, Socket_update, Socket_init, Socket_close). -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 41). -spec start_server(integer()) -> {ok, gleam@erlang@process:pid_()} | {error, nil}. start_server(Port) -> Empty_body = {bytes, gleam@bytes_builder:new()}, Not_found = gleam@http@response:set_body( gleam@http@response:new(404), Empty_body ), Server_result = begin _pipe@9 = 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@uri: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>>] -> _assert_subject = gleam_erlang_ffi:priv_directory( <<"spectator"/utf8>> ), {ok, Priv} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"spectator"/utf8>>, function => <<"start_server"/utf8>>, line => 74}) end, Path = <>, _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_builder:new()} ) end ); [] -> _pipe@7 = gleam@http@response:new(302), _pipe@8 = gleam@http@response:prepend_header( _pipe@7, <<"location"/utf8>>, <<"/dashboard"/utf8>> ), gleam@http@response:set_body(_pipe@8, Empty_body); _ -> Not_found end end, _pipe@10 = mist:new(_pipe@9), _pipe@12 = mist:after_start( _pipe@10, 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, (gleam@int:to_string(Port@1))/binary>>/binary, " - Node: "/utf8>>/binary, (erlang:atom_to_binary( begin _pipe@11 = erlang:node(), gleam_erlang_ffi:identity(_pipe@11) end ))/binary>>, gleam@io:println(Message) end ), _pipe@13 = mist:port(_pipe@12, Port), mist:start_http(_pipe@13) end, case Server_result of {ok, Server} -> Server_pid = gleam@erlang@process:subject_owner(Server), tag(Server_pid, <<"__spectator_internal Server"/utf8>>), {ok, Server_pid}; {error, _} -> {error, nil} end. -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 135). -spec start_on(integer()) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}. start_on(Port) -> _pipe = gleam@otp@static_supervisor:new(one_for_one), _pipe@1 = gleam@otp@static_supervisor:add( _pipe, gleam@otp@static_supervisor:worker_child( <<"Spectator Tag Manager"/utf8>>, fun spectator_tag_manager:start_link/0 ) ), _pipe@2 = gleam@otp@static_supervisor:add( _pipe@1, gleam@otp@static_supervisor:worker_child( <<"Spectator Mist Server"/utf8>>, fun() -> start_server(Port) end ) ), gleam@otp@static_supervisor:start_link(_pipe@2). -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 131). -spec start() -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}. start() -> start_on(3000). -file("/Users/jonas/Projects/spectator/src/spectator.gleam", 36). -spec main() -> nil. main() -> _assert_subject = start(), {ok, _} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, value => _assert_fail, module => <<"spectator"/utf8>>, function => <<"main"/utf8>>, line => 37}) end, gleam_erlang_ffi:sleep_forever().