Now a simple logo draws in the center of the terminal, terminal size is limited, and resizing support
This commit is contained in:
parent
2bf058d5db
commit
16281b0e8d
@ -2,4 +2,5 @@
|
||||
|
||||
Features:
|
||||
- [X] SSH Key & Password authentication
|
||||
- [ ] Rate limiting
|
||||
- [X] Session rate limiting
|
||||
- [X] Multi-node support
|
||||
|
@ -1,5 +1,9 @@
|
||||
import Config
|
||||
|
||||
# This will be redis when scaled across multiple nodes
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
|
||||
config :chessh,
|
||||
ecto_repos: [Chessh.Repo],
|
||||
key_dir: Path.join(Path.dirname(__DIR__), "priv/keys"),
|
||||
@ -9,10 +13,8 @@ config :chessh,
|
||||
config :chessh, RateLimits,
|
||||
jail_timeout_ms: 5 * 60 * 1000,
|
||||
jail_attempt_threshold: 15,
|
||||
max_concurrent_user_sessions: 5
|
||||
|
||||
# This will be redis when scaled across multiple nodes
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
max_concurrent_user_sessions: 5,
|
||||
player_session_message_burst_ms: 3_000,
|
||||
player_session_message_burst_rate: 15
|
||||
|
||||
import_config "#{config_env()}.exs"
|
||||
|
@ -20,14 +20,6 @@ defmodule Chessh.Key do
|
||||
|> validate_format(:key, ~r/^(?!ssh-dss).+/, message: "DSA keys are not supported")
|
||||
end
|
||||
|
||||
defp update_encode_key(attrs, field) do
|
||||
if Map.has_key?(attrs, field) do
|
||||
Map.update!(attrs, field, &encode_key/1)
|
||||
else
|
||||
attrs
|
||||
end
|
||||
end
|
||||
|
||||
def encode_key(key) do
|
||||
if is_tuple(key) do
|
||||
case key do
|
||||
@ -39,8 +31,15 @@ defmodule Chessh.Key do
|
||||
else
|
||||
key
|
||||
end
|
||||
# Remove comment at end of key
|
||||
|> String.replace(~r/ [^ ]+\@[^ ]+$/, "")
|
||||
|> String.trim()
|
||||
end
|
||||
|
||||
defp update_encode_key(attrs, field) do
|
||||
if Map.has_key?(attrs, field) do
|
||||
Map.update!(attrs, field, &encode_key/1)
|
||||
else
|
||||
attrs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,27 +1,125 @@
|
||||
defmodule Chessh.SSH.Client do
|
||||
alias IO.ANSI
|
||||
|
||||
require Logger
|
||||
|
||||
use GenServer
|
||||
|
||||
@default_message [
|
||||
@clear_codes [
|
||||
ANSI.clear(),
|
||||
ANSI.reset(),
|
||||
ANSI.home(),
|
||||
["Hello, world"]
|
||||
ANSI.home()
|
||||
]
|
||||
|
||||
@min_terminal_width 64
|
||||
@min_terminal_height 31
|
||||
@max_terminal_width 255
|
||||
@max_terminal_height 127
|
||||
|
||||
@terminal_bad_dim_msg [
|
||||
@clear_codes | "The dimensions of your terminal are not within in the valid range"
|
||||
]
|
||||
|
||||
defmodule State do
|
||||
defstruct tui_pid: nil,
|
||||
width: nil,
|
||||
height: nil,
|
||||
width: 0,
|
||||
height: 0,
|
||||
player_session: nil,
|
||||
state_statck: []
|
||||
buffer: [],
|
||||
state_stack: [{&Chessh.SSH.Client.Menu.render/2, []}]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init([%State{tui_pid: tui_pid} = state]) do
|
||||
send(tui_pid, {:send_data, @default_message})
|
||||
send(tui_pid, {:send_data, render(state)})
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:quit, %State{} = state) do
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(msg, state) do
|
||||
[burst_ms, burst_rate] =
|
||||
Application.get_env(:chessh, RateLimits)
|
||||
|> Keyword.take([:player_session_message_burst_ms, :player_session_message_burst_rate])
|
||||
|> Keyword.values()
|
||||
|
||||
case Hammer.check_rate_inc(
|
||||
"player-session-#{state.player_session.id}-burst-message-rate",
|
||||
burst_ms,
|
||||
burst_rate,
|
||||
1
|
||||
) do
|
||||
{:allow, _count} ->
|
||||
handle(msg, state)
|
||||
|
||||
{:deny, _limit} ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle({:data, data}, %State{tui_pid: tui_pid} = state) do
|
||||
new_state =
|
||||
keymap(data)
|
||||
|> keypress(state)
|
||||
|
||||
send(tui_pid, {:send_data, render(new_state)})
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
def handle({:resize, {width, height}}, %State{tui_pid: tui_pid} = state) do
|
||||
new_state = %State{state | width: width, height: height}
|
||||
|
||||
if height <= @max_terminal_height || width <= @max_terminal_width do
|
||||
send(tui_pid, {:send_data, render(new_state)})
|
||||
end
|
||||
|
||||
{:noreply, new_state}
|
||||
end
|
||||
|
||||
def keypress(:up, state), do: state
|
||||
def keypress(:right, state), do: state
|
||||
def keypress(:down, state), do: state
|
||||
def keypress(:left, state), do: state
|
||||
|
||||
def keypress(:quit, state) do
|
||||
send(self(), :quit)
|
||||
state
|
||||
end
|
||||
|
||||
def keypress(_, state), do: state
|
||||
|
||||
def keymap(key) do
|
||||
case key do
|
||||
# Exit keys - C-c and C-d
|
||||
<<3>> -> :quit
|
||||
<<4>> -> :quit
|
||||
# Arrow keys
|
||||
"\e[A" -> :up
|
||||
"\e[B" -> :down
|
||||
"\e[D" -> :left
|
||||
"\e[C" -> :right
|
||||
x -> x
|
||||
end
|
||||
end
|
||||
|
||||
@spec terminal_size_allowed(any, any) :: boolean
|
||||
def terminal_size_allowed(width, height) do
|
||||
Enum.member?(@min_terminal_width..@max_terminal_width, width) &&
|
||||
Enum.member?(@min_terminal_height..@max_terminal_height, height)
|
||||
end
|
||||
|
||||
defp render(%{width: width, height: height, state_stack: [{render_fn, args} | _tail]} = state) do
|
||||
if terminal_size_allowed(width, height) do
|
||||
[
|
||||
@clear_codes ++
|
||||
render_fn.(state, args)
|
||||
]
|
||||
else
|
||||
@terminal_bad_dim_msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
35
lib/chessh/ssh/renderers/menu.ex
Normal file
35
lib/chessh/ssh/renderers/menu.ex
Normal file
@ -0,0 +1,35 @@
|
||||
defmodule Chessh.SSH.Client.Menu do
|
||||
alias Chessh.SSH.Client.State
|
||||
alias Chessh.Utils
|
||||
|
||||
alias IO.ANSI
|
||||
|
||||
@logo " Simponic's
|
||||
|
||||
dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM
|
||||
88 M mmmmm..M M mmmmm..M M MMMMM MM
|
||||
.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M
|
||||
88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. M M MMMMM MM
|
||||
88. ... 88 88 88. ... M. .MMM' M M. .MMM' M M MMMMM MM
|
||||
`88888P' dP dP `88888P' Mb. .dM Mb. .dM M MMMMM MM
|
||||
MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM"
|
||||
|
||||
def render(
|
||||
%State{width: width, height: height, state_stack: [_current_state | _tail]} = _state,
|
||||
_args
|
||||
) do
|
||||
{logo_width, logo_height} = Utils.text_dim(@logo)
|
||||
|
||||
split = String.split(@logo, "\n")
|
||||
|
||||
Enum.flat_map(
|
||||
Enum.zip(0..(length(split) - 1), split),
|
||||
fn {i, x} ->
|
||||
[
|
||||
ANSI.cursor(div(height - logo_height, 2) + i, div(width - logo_width, 2)),
|
||||
"#{x}\n"
|
||||
]
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
@ -64,8 +64,12 @@ defmodule Chessh.SSH.Tui do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_msg({:EXIT, client_pid, _reason}, %{client_pid: client_pid} = state) do
|
||||
{:stop, state.channel, state}
|
||||
def handle_msg(
|
||||
{:EXIT, client_pid, _reason},
|
||||
%{client_pid: client_pid, channel_id: channel_id} = state
|
||||
) do
|
||||
send(client_pid, :quit)
|
||||
{:stop, channel_id, state}
|
||||
end
|
||||
|
||||
def handle_msg(
|
||||
@ -94,7 +98,7 @@ defmodule Chessh.SSH.Tui do
|
||||
state
|
||||
) do
|
||||
Logger.debug("DATA #{inspect(data)}")
|
||||
# send(state.client_pid, {:data, data})
|
||||
send(state.client_pid, {:data, data})
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@ -126,10 +130,11 @@ defmodule Chessh.SSH.Tui do
|
||||
def handle_ssh_msg(
|
||||
{:ssh_cm, _connection_handler,
|
||||
{:window_change, _channel_id, width, height, _pixwidth, _pixheight}},
|
||||
state
|
||||
%{client_pid: client_pid} = state
|
||||
) do
|
||||
Logger.debug("WINDOW CHANGE")
|
||||
# Chessh.SSH.Client.resize(state.client_pid, width, height)
|
||||
send(client_pid, {:resize, {width, height}})
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
state
|
||||
|
@ -6,4 +6,9 @@ defmodule Chessh.Utils do
|
||||
|> List.delete_at(-1)
|
||||
|> to_string()
|
||||
end
|
||||
|
||||
def text_dim(text) do
|
||||
split = String.split(text, "\n")
|
||||
{Enum.reduce(split, 0, fn x, acc -> max(acc, String.length(x)) end), length(split)}
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user