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:
|
Features:
|
||||||
- [X] SSH Key & Password authentication
|
- [X] SSH Key & Password authentication
|
||||||
- [ ] Rate limiting
|
- [X] Session rate limiting
|
||||||
|
- [X] Multi-node support
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import Config
|
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,
|
config :chessh,
|
||||||
ecto_repos: [Chessh.Repo],
|
ecto_repos: [Chessh.Repo],
|
||||||
key_dir: Path.join(Path.dirname(__DIR__), "priv/keys"),
|
key_dir: Path.join(Path.dirname(__DIR__), "priv/keys"),
|
||||||
@ -9,10 +13,8 @@ config :chessh,
|
|||||||
config :chessh, RateLimits,
|
config :chessh, RateLimits,
|
||||||
jail_timeout_ms: 5 * 60 * 1000,
|
jail_timeout_ms: 5 * 60 * 1000,
|
||||||
jail_attempt_threshold: 15,
|
jail_attempt_threshold: 15,
|
||||||
max_concurrent_user_sessions: 5
|
max_concurrent_user_sessions: 5,
|
||||||
|
player_session_message_burst_ms: 3_000,
|
||||||
# This will be redis when scaled across multiple nodes
|
player_session_message_burst_rate: 15
|
||||||
config :hammer,
|
|
||||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
|
||||||
|
|
||||||
import_config "#{config_env()}.exs"
|
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")
|
|> validate_format(:key, ~r/^(?!ssh-dss).+/, message: "DSA keys are not supported")
|
||||||
end
|
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
|
def encode_key(key) do
|
||||||
if is_tuple(key) do
|
if is_tuple(key) do
|
||||||
case key do
|
case key do
|
||||||
@ -39,8 +31,15 @@ defmodule Chessh.Key do
|
|||||||
else
|
else
|
||||||
key
|
key
|
||||||
end
|
end
|
||||||
# Remove comment at end of key
|
|
||||||
|> String.replace(~r/ [^ ]+\@[^ ]+$/, "")
|
|> String.replace(~r/ [^ ]+\@[^ ]+$/, "")
|
||||||
|> String.trim()
|
|> String.trim()
|
||||||
end
|
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
|
end
|
||||||
|
@ -1,27 +1,125 @@
|
|||||||
defmodule Chessh.SSH.Client do
|
defmodule Chessh.SSH.Client do
|
||||||
alias IO.ANSI
|
alias IO.ANSI
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
@default_message [
|
@clear_codes [
|
||||||
ANSI.clear(),
|
ANSI.clear(),
|
||||||
ANSI.reset(),
|
ANSI.reset(),
|
||||||
ANSI.home(),
|
ANSI.home()
|
||||||
["Hello, world"]
|
]
|
||||||
|
|
||||||
|
@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
|
defmodule State do
|
||||||
defstruct tui_pid: nil,
|
defstruct tui_pid: nil,
|
||||||
width: nil,
|
width: 0,
|
||||||
height: nil,
|
height: 0,
|
||||||
player_session: nil,
|
player_session: nil,
|
||||||
state_statck: []
|
buffer: [],
|
||||||
|
state_stack: [{&Chessh.SSH.Client.Menu.render/2, []}]
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init([%State{tui_pid: tui_pid} = state]) do
|
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}
|
{:ok, state}
|
||||||
end
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_msg({:EXIT, client_pid, _reason}, %{client_pid: client_pid} = state) do
|
def handle_msg(
|
||||||
{:stop, state.channel, state}
|
{:EXIT, client_pid, _reason},
|
||||||
|
%{client_pid: client_pid, channel_id: channel_id} = state
|
||||||
|
) do
|
||||||
|
send(client_pid, :quit)
|
||||||
|
{:stop, channel_id, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_msg(
|
def handle_msg(
|
||||||
@ -94,7 +98,7 @@ defmodule Chessh.SSH.Tui do
|
|||||||
state
|
state
|
||||||
) do
|
) do
|
||||||
Logger.debug("DATA #{inspect(data)}")
|
Logger.debug("DATA #{inspect(data)}")
|
||||||
# send(state.client_pid, {:data, data})
|
send(state.client_pid, {:data, data})
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -126,10 +130,11 @@ defmodule Chessh.SSH.Tui do
|
|||||||
def handle_ssh_msg(
|
def handle_ssh_msg(
|
||||||
{:ssh_cm, _connection_handler,
|
{:ssh_cm, _connection_handler,
|
||||||
{:window_change, _channel_id, width, height, _pixwidth, _pixheight}},
|
{:window_change, _channel_id, width, height, _pixwidth, _pixheight}},
|
||||||
state
|
%{client_pid: client_pid} = state
|
||||||
) do
|
) do
|
||||||
Logger.debug("WINDOW CHANGE")
|
Logger.debug("WINDOW CHANGE")
|
||||||
# Chessh.SSH.Client.resize(state.client_pid, width, height)
|
send(client_pid, {:resize, {width, height}})
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
state
|
state
|
||||||
|
@ -6,4 +6,9 @@ defmodule Chessh.Utils do
|
|||||||
|> List.delete_at(-1)
|
|> List.delete_at(-1)
|
||||||
|> to_string()
|
|> to_string()
|
||||||
end
|
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
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user