diff options
| -rw-r--r-- | ar/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua | 283 | ||||
| -rw-r--r-- | mac/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua | 283 | ||||
| -rw-r--r-- | mac/.config/shell/aliasrc | 2 | ||||
| -rwxr-xr-x | mac/.local/bin/ylog | 169 |
4 files changed, 525 insertions, 212 deletions
diff --git a/ar/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua b/ar/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua index b42a588..e9788e0 100644 --- a/ar/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua +++ b/ar/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua @@ -1,118 +1,199 @@ return { - "amitds1997/remote-nvim.nvim", - version = "*", - dependencies = { - "nvim-lua/plenary.nvim", - "MunifTanjim/nui.nvim", - "nvim-telescope/telescope.nvim", - }, - config = true, - init = function() - local ok, wk = pcall(require, "which-key") - if ok then - wk.add({ { "<localleader>r", group = "SSH Remote" } }) - end - end, - keys = { - { "<localleader>rs", "<cmd>RemoteStart<CR>", desc = "Start/Connect", mode = "n", silent = true }, - { "<localleader>rx", "<cmd>RemoteStop<CR>", desc = "Stop/Close", mode = "n", silent = true }, - { "<localleader>ri", "<cmd>RemoteInfo<CR>", desc = "Info/Progress Viewer", mode = "n", silent = true }, - { "<localleader>rc", "<cmd>RemoteCleanup<CR>", desc = "Cleanup workspace", mode = "n", silent = true }, + { + "amitds1997/remote-nvim.nvim", + version = "*", + dependencies = { + "nvim-lua/plenary.nvim", + "MunifTanjim/nui.nvim", + "nvim-telescope/telescope.nvim", + }, + config = true, + init = function() + local ok, wk = pcall(require, "which-key") + if ok then + wk.add({ { "<localleader>r", group = "SSH Remote" } }) + end + end, + keys = { + { "<localleader>rs", "<cmd>RemoteStart<CR>", desc = "Start/Connect", mode = "n", silent = true }, + { "<localleader>rx", "<cmd>RemoteStop<CR>", desc = "Stop/Close", mode = "n", silent = true }, + { "<localleader>ri", "<cmd>RemoteInfo<CR>", desc = "Info/Progress Viewer", mode = "n", silent = true }, + { "<localleader>rC", "<cmd>RemoteCleanup<CR>", desc = "Cleanup workspace", mode = "n", silent = true }, - -- Delete saved config: show a list, allow multi-select, then run :RemoteConfigDel <name> - { - "<localleader>rd", - function() - local function get_names() - -- use command-line completion if the command supports it - local list = vim.fn.getcompletion("RemoteConfigDel ", "cmdline") or {} - return list - end + -- Delete saved config: show a list, allow multi-select, then run :RemoteConfigDel <name> + { + "<localleader>rd", + function() + local function get_names() + -- use command-line completion if the command supports it + local list = vim.fn.getcompletion("RemoteConfigDel ", "cmdline") or {} + return list + end - local names = get_names() - if #names == 0 then - return vim.notify( - "No saved remote configs found (completion empty)", - vim.log.levels.INFO, - { title = "Remote" } - ) - end + local names = get_names() + if #names == 0 then + return vim.notify( + "No saved remote configs found (completion empty)", + vim.log.levels.INFO, + { title = "Remote" } + ) + end - -- Prefer Telescope if present - local ok_picker, pickers = pcall(require, "telescope.pickers") - if ok_picker then - local finders = require("telescope.finders") - local conf = require("telescope.config").values - local actions = require("telescope.actions") - local state = require("telescope.actions.state") + -- Prefer Telescope if present + local ok_picker, pickers = pcall(require, "telescope.pickers") + if ok_picker then + local finders = require("telescope.finders") + local conf = require("telescope.config").values + local actions = require("telescope.actions") + local state = require("telescope.actions.state") - pickers - .new({}, { - prompt_title = "Delete Remote Config(s)", - finder = finders.new_table(names), - sorter = conf.generic_sorter({}), - attach_mappings = function(_, map) - local function run_delete(prompt_bufnr, picked) - local function del(name) - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) - end - if picked and #picked > 0 then - for _, entry in ipairs(picked) do - del(entry.value or entry[1] or entry.text) + pickers + .new({}, { + prompt_title = "Delete Remote Config(s)", + finder = finders.new_table(names), + sorter = conf.generic_sorter({}), + attach_mappings = function(_, map) + local function run_delete(prompt_bufnr, picked) + local function del(name) + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) end - else - local entry = state.get_selected_entry() - if entry then - del(entry.value or entry[1] or entry.text) + if picked and #picked > 0 then + for _, entry in ipairs(picked) do + del(entry.value or entry[1] or entry.text) + end + else + local entry = state.get_selected_entry() + if entry then + del(entry.value or entry[1] or entry.text) + end end + actions.close(prompt_bufnr) end - actions.close(prompt_bufnr) - end - actions.select_default:replace(function(bufnr) - local picker = state.get_current_picker(bufnr) - local multi = picker:get_multi_selection() - run_delete(bufnr, multi) - end) + actions.select_default:replace(function(bufnr) + local picker = state.get_current_picker(bufnr) + local multi = picker:get_multi_selection() + run_delete(bufnr, multi) + end) - -- quality of life: <C-d> also deletes without closing (optional) - map("i", "<C-d>", function(bufnr) - local picker = state.get_current_picker(bufnr) - local multi = picker:get_multi_selection() - local function del(name) - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) - end - if multi and #multi > 0 then - for _, e in ipairs(multi) do - del(e.value or e[1] or e.text) + -- quality of life: <C-d> also deletes without closing (optional) + map("i", "<C-d>", function(bufnr) + local picker = state.get_current_picker(bufnr) + local multi = picker:get_multi_selection() + local function del(name) + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) end - else - local e = state.get_selected_entry() - if e then - del(e.value or e[1] or e.text) + if multi and #multi > 0 then + for _, e in ipairs(multi) do + del(e.value or e[1] or e.text) + end + else + local e = state.get_selected_entry() + if e then + del(e.value or e[1] or e.text) + end end - end - -- keep picker open - end) + -- keep picker open + end) - return true - end, - }) - :find() - else - -- Fallback: vim.ui.select (single delete) - vim.ui.select(names, { prompt = "Select remote config to delete" }, function(choice) - if choice and choice ~= "" then - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { choice } }, {}) - end - end) - end - end, - desc = "Delete saved config (pick from list)", - mode = "n", - silent = true, + return true + end, + }) + :find() + else + -- Fallback: vim.ui.select (single delete) + vim.ui.select(names, { prompt = "Select remote config to delete" }, function(choice) + if choice and choice ~= "" then + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { choice } }, {}) + end + end) + end + end, + desc = "Delete saved config (pick from list)", + mode = "n", + silent = true, + }, + + { "<localleader>rl", "<cmd>RemoteLog<CR>", desc = "Open log", mode = "n", silent = true }, + }, + }, + { + "nosduco/remote-sshfs.nvim", + dependencies = { "nvim-telescope/telescope.nvim", "nvim-lua/plenary.nvim" }, + opts = { + -- Refer to the configuration section below + -- or leave empty for defaults }, + config = function() + require("remote-sshfs").setup({ + connections = { + ssh_configs = { -- which ssh configs to parse for hosts list + vim.fn.expand("$HOME") .. "/.ssh/config", + "/etc/ssh/ssh_config", + -- "/path/to/custom/ssh_config" + }, + ssh_known_hosts = vim.fn.expand("$HOME") .. "/.ssh/known_hosts", + -- NOTE: Can define ssh_configs similarly to include all configs in a folder + -- ssh_configs = vim.split(vim.fn.globpath(vim.fn.expand "$HOME" .. "/.ssh/configs", "*"), "\n") + sshfs_args = { -- arguments to pass to the sshfs command + "-o reconnect", + "-o ConnectTimeout=5", + }, + }, + mounts = { + base_dir = vim.fn.expand("$HOME") .. "/.sshfs/", -- base directory for mount points + unmount_on_exit = true, -- run sshfs as foreground, will unmount on vim exit + }, + handlers = { + on_connect = { + change_dir = true, -- when connected change vim working directory to mount point + }, + on_disconnect = { + clean_mount_folders = false, -- remove mount point folder on disconnect/unmount + }, + on_edit = {}, -- not yet implemented + }, + ui = { + select_prompts = false, -- not yet implemented + confirm = { + connect = true, -- prompt y/n when host is selected to connect to + change_dir = false, -- prompt y/n to change working directory on connection (only applicable if handlers.on_connect.change_dir is enabled) + }, + }, + log = { + enabled = false, -- enable logging + truncate = false, -- truncate logs + types = { -- enabled log types + all = false, + util = false, + handler = false, + sshfs = false, + }, + }, + }) + local api = require("remote-sshfs.api") + vim.keymap.set("n", "<localleader>rc", api.connect, { desc = "Connect SSH" }) + vim.keymap.set("n", "<localleader>rd", api.disconnect, { desc = "Disconnect SSH" }) + vim.keymap.set("n", "<localleader>re", api.edit, { desc = "Edit SSH" }) - { "<localleader>rl", "<cmd>RemoteLog<CR>", desc = "Open log", mode = "n", silent = true }, + -- (optional) Override telescope find_files and live_grep to make dynamic based on if connected to host + local builtin = require("telescope.builtin") + local connections = require("remote-sshfs.connections") + vim.keymap.set("n", "<localleader>rf", function() + if connections.is_connected() then + api.find_files() + else + builtin.find_files() + end + end, { desc = "Find SSH files" }) + vim.keymap.set("n", "<localleader>rg", function() + if connections.is_connected() then + api.live_grep() + else + builtin.live_grep() + end + end, { desc = "Live SSH grep" }) + require("telescope").load_extension("remote-sshfs") + end, }, } diff --git a/mac/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua b/mac/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua index b42a588..e9788e0 100644 --- a/mac/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua +++ b/mac/.config/TheSiahxyz/lua/TheSiahxyz/plugins/ssh.lua @@ -1,118 +1,199 @@ return { - "amitds1997/remote-nvim.nvim", - version = "*", - dependencies = { - "nvim-lua/plenary.nvim", - "MunifTanjim/nui.nvim", - "nvim-telescope/telescope.nvim", - }, - config = true, - init = function() - local ok, wk = pcall(require, "which-key") - if ok then - wk.add({ { "<localleader>r", group = "SSH Remote" } }) - end - end, - keys = { - { "<localleader>rs", "<cmd>RemoteStart<CR>", desc = "Start/Connect", mode = "n", silent = true }, - { "<localleader>rx", "<cmd>RemoteStop<CR>", desc = "Stop/Close", mode = "n", silent = true }, - { "<localleader>ri", "<cmd>RemoteInfo<CR>", desc = "Info/Progress Viewer", mode = "n", silent = true }, - { "<localleader>rc", "<cmd>RemoteCleanup<CR>", desc = "Cleanup workspace", mode = "n", silent = true }, + { + "amitds1997/remote-nvim.nvim", + version = "*", + dependencies = { + "nvim-lua/plenary.nvim", + "MunifTanjim/nui.nvim", + "nvim-telescope/telescope.nvim", + }, + config = true, + init = function() + local ok, wk = pcall(require, "which-key") + if ok then + wk.add({ { "<localleader>r", group = "SSH Remote" } }) + end + end, + keys = { + { "<localleader>rs", "<cmd>RemoteStart<CR>", desc = "Start/Connect", mode = "n", silent = true }, + { "<localleader>rx", "<cmd>RemoteStop<CR>", desc = "Stop/Close", mode = "n", silent = true }, + { "<localleader>ri", "<cmd>RemoteInfo<CR>", desc = "Info/Progress Viewer", mode = "n", silent = true }, + { "<localleader>rC", "<cmd>RemoteCleanup<CR>", desc = "Cleanup workspace", mode = "n", silent = true }, - -- Delete saved config: show a list, allow multi-select, then run :RemoteConfigDel <name> - { - "<localleader>rd", - function() - local function get_names() - -- use command-line completion if the command supports it - local list = vim.fn.getcompletion("RemoteConfigDel ", "cmdline") or {} - return list - end + -- Delete saved config: show a list, allow multi-select, then run :RemoteConfigDel <name> + { + "<localleader>rd", + function() + local function get_names() + -- use command-line completion if the command supports it + local list = vim.fn.getcompletion("RemoteConfigDel ", "cmdline") or {} + return list + end - local names = get_names() - if #names == 0 then - return vim.notify( - "No saved remote configs found (completion empty)", - vim.log.levels.INFO, - { title = "Remote" } - ) - end + local names = get_names() + if #names == 0 then + return vim.notify( + "No saved remote configs found (completion empty)", + vim.log.levels.INFO, + { title = "Remote" } + ) + end - -- Prefer Telescope if present - local ok_picker, pickers = pcall(require, "telescope.pickers") - if ok_picker then - local finders = require("telescope.finders") - local conf = require("telescope.config").values - local actions = require("telescope.actions") - local state = require("telescope.actions.state") + -- Prefer Telescope if present + local ok_picker, pickers = pcall(require, "telescope.pickers") + if ok_picker then + local finders = require("telescope.finders") + local conf = require("telescope.config").values + local actions = require("telescope.actions") + local state = require("telescope.actions.state") - pickers - .new({}, { - prompt_title = "Delete Remote Config(s)", - finder = finders.new_table(names), - sorter = conf.generic_sorter({}), - attach_mappings = function(_, map) - local function run_delete(prompt_bufnr, picked) - local function del(name) - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) - end - if picked and #picked > 0 then - for _, entry in ipairs(picked) do - del(entry.value or entry[1] or entry.text) + pickers + .new({}, { + prompt_title = "Delete Remote Config(s)", + finder = finders.new_table(names), + sorter = conf.generic_sorter({}), + attach_mappings = function(_, map) + local function run_delete(prompt_bufnr, picked) + local function del(name) + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) end - else - local entry = state.get_selected_entry() - if entry then - del(entry.value or entry[1] or entry.text) + if picked and #picked > 0 then + for _, entry in ipairs(picked) do + del(entry.value or entry[1] or entry.text) + end + else + local entry = state.get_selected_entry() + if entry then + del(entry.value or entry[1] or entry.text) + end end + actions.close(prompt_bufnr) end - actions.close(prompt_bufnr) - end - actions.select_default:replace(function(bufnr) - local picker = state.get_current_picker(bufnr) - local multi = picker:get_multi_selection() - run_delete(bufnr, multi) - end) + actions.select_default:replace(function(bufnr) + local picker = state.get_current_picker(bufnr) + local multi = picker:get_multi_selection() + run_delete(bufnr, multi) + end) - -- quality of life: <C-d> also deletes without closing (optional) - map("i", "<C-d>", function(bufnr) - local picker = state.get_current_picker(bufnr) - local multi = picker:get_multi_selection() - local function del(name) - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) - end - if multi and #multi > 0 then - for _, e in ipairs(multi) do - del(e.value or e[1] or e.text) + -- quality of life: <C-d> also deletes without closing (optional) + map("i", "<C-d>", function(bufnr) + local picker = state.get_current_picker(bufnr) + local multi = picker:get_multi_selection() + local function del(name) + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { name } }, {}) end - else - local e = state.get_selected_entry() - if e then - del(e.value or e[1] or e.text) + if multi and #multi > 0 then + for _, e in ipairs(multi) do + del(e.value or e[1] or e.text) + end + else + local e = state.get_selected_entry() + if e then + del(e.value or e[1] or e.text) + end end - end - -- keep picker open - end) + -- keep picker open + end) - return true - end, - }) - :find() - else - -- Fallback: vim.ui.select (single delete) - vim.ui.select(names, { prompt = "Select remote config to delete" }, function(choice) - if choice and choice ~= "" then - vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { choice } }, {}) - end - end) - end - end, - desc = "Delete saved config (pick from list)", - mode = "n", - silent = true, + return true + end, + }) + :find() + else + -- Fallback: vim.ui.select (single delete) + vim.ui.select(names, { prompt = "Select remote config to delete" }, function(choice) + if choice and choice ~= "" then + vim.api.nvim_cmd({ cmd = "RemoteConfigDel", args = { choice } }, {}) + end + end) + end + end, + desc = "Delete saved config (pick from list)", + mode = "n", + silent = true, + }, + + { "<localleader>rl", "<cmd>RemoteLog<CR>", desc = "Open log", mode = "n", silent = true }, + }, + }, + { + "nosduco/remote-sshfs.nvim", + dependencies = { "nvim-telescope/telescope.nvim", "nvim-lua/plenary.nvim" }, + opts = { + -- Refer to the configuration section below + -- or leave empty for defaults }, + config = function() + require("remote-sshfs").setup({ + connections = { + ssh_configs = { -- which ssh configs to parse for hosts list + vim.fn.expand("$HOME") .. "/.ssh/config", + "/etc/ssh/ssh_config", + -- "/path/to/custom/ssh_config" + }, + ssh_known_hosts = vim.fn.expand("$HOME") .. "/.ssh/known_hosts", + -- NOTE: Can define ssh_configs similarly to include all configs in a folder + -- ssh_configs = vim.split(vim.fn.globpath(vim.fn.expand "$HOME" .. "/.ssh/configs", "*"), "\n") + sshfs_args = { -- arguments to pass to the sshfs command + "-o reconnect", + "-o ConnectTimeout=5", + }, + }, + mounts = { + base_dir = vim.fn.expand("$HOME") .. "/.sshfs/", -- base directory for mount points + unmount_on_exit = true, -- run sshfs as foreground, will unmount on vim exit + }, + handlers = { + on_connect = { + change_dir = true, -- when connected change vim working directory to mount point + }, + on_disconnect = { + clean_mount_folders = false, -- remove mount point folder on disconnect/unmount + }, + on_edit = {}, -- not yet implemented + }, + ui = { + select_prompts = false, -- not yet implemented + confirm = { + connect = true, -- prompt y/n when host is selected to connect to + change_dir = false, -- prompt y/n to change working directory on connection (only applicable if handlers.on_connect.change_dir is enabled) + }, + }, + log = { + enabled = false, -- enable logging + truncate = false, -- truncate logs + types = { -- enabled log types + all = false, + util = false, + handler = false, + sshfs = false, + }, + }, + }) + local api = require("remote-sshfs.api") + vim.keymap.set("n", "<localleader>rc", api.connect, { desc = "Connect SSH" }) + vim.keymap.set("n", "<localleader>rd", api.disconnect, { desc = "Disconnect SSH" }) + vim.keymap.set("n", "<localleader>re", api.edit, { desc = "Edit SSH" }) - { "<localleader>rl", "<cmd>RemoteLog<CR>", desc = "Open log", mode = "n", silent = true }, + -- (optional) Override telescope find_files and live_grep to make dynamic based on if connected to host + local builtin = require("telescope.builtin") + local connections = require("remote-sshfs.connections") + vim.keymap.set("n", "<localleader>rf", function() + if connections.is_connected() then + api.find_files() + else + builtin.find_files() + end + end, { desc = "Find SSH files" }) + vim.keymap.set("n", "<localleader>rg", function() + if connections.is_connected() then + api.live_grep() + else + builtin.live_grep() + end + end, { desc = "Live SSH grep" }) + require("telescope").load_extension("remote-sshfs") + end, }, } diff --git a/mac/.config/shell/aliasrc b/mac/.config/shell/aliasrc index 9fc3aab..d1fe206 100644 --- a/mac/.config/shell/aliasrc +++ b/mac/.config/shell/aliasrc @@ -500,5 +500,7 @@ alias szs="source ${XDG_CONFIG_HOME:-${HOME}/.config}/zsh/.zshrc" alias ylogu='ylog -s recordings -c us' +alias ylogh='ylog -s hidden -c us | grep $(LC_TIME=C date +%d/%b)' +alias ylogi='ylog -s hidden -c us | grep $(LC_TIME=C date -v-1d +%d/%b)' alias ylogt='ylog -s recordings -c us | grep $(LC_TIME=C date +%d/%b)' alias ylogy='ylog -s recordings -c us | grep $(LC_TIME=C date -v-1d +%d/%b)' diff --git a/mac/.local/bin/ylog b/mac/.local/bin/ylog index f15fb3b..d147eb8 100755 --- a/mac/.local/bin/ylog +++ b/mac/.local/bin/ylog @@ -9,7 +9,8 @@ SCOPE="all" # all|access|recordings EXCL_FIREFOX=1 # 1 = exclude Firefox lines by default EXCLUDES="59.19.56.8" # default exclude pattern ADD_EXCLUDES="" -LINE_LIMIT=30 # default number of lines when TARGET=all +LINE_LIMIT=100 # default number of lines when TARGET=all +DATE_FILTER="" # date filter: "2" = 2 days ago, "~2" = last 2 days to today usage() { cat <<'EOF' @@ -40,6 +41,11 @@ Options: Only applies when TARGET=all e.g. -l 50 → show last 50 lines per file + -d DATE Filter by date + e.g. -d 2 → logs from 2 days ago only + e.g. -d 1 → logs from 1 day ago only + e.g. -d ~2 → logs from 2 days ago to today + -h Show this help Examples: @@ -47,11 +53,13 @@ Examples: ylog -s recordings # Recordings logs only, last 10 lines each ylog -c kr -t 1.2.3.4 # Search specific IP in Korean logs ylog -t all -l 50 # All logs, last 50 lines each + ylog -d 1 # Logs from 1 day ago only + ylog -d ~2 # Logs from 2 days ago to today EOF exit 0 } -while getopts "t:c:s:nx:l:h" opt; do +while getopts "t:c:s:nx:l:d:h" opt; do case "$opt" in t) TARGET="$OPTARG" ;; c) COUNTRY="$OPTARG" ;; @@ -60,6 +68,7 @@ while getopts "t:c:s:nx:l:h" opt; do x) ADD_EXCLUDES="${ADD_EXCLUDES} $OPTARG" ;; l) LINE_LIMIT="$OPTARG" ;; + d) DATE_FILTER="$OPTARG" ;; h) usage ;; *) usage ;; esac @@ -78,6 +87,7 @@ TARGET="'"$TARGET"'" ESC_TARGET="'"$esc_target"'" EXCL_FIREFOX='"$EXCL_FIREFOX"' LINE_LIMIT='"$LINE_LIMIT"' +DATE_FILTER="'"$DATE_FILTER"'" # collect files pick_files() { @@ -104,6 +114,11 @@ pick_files() { [ -e "$q" ] && printf "%s\n" "$q" done fi + if [ "$SCOPE" = "hidden" ] || [ "$SCOPE" = "all" ]; then + for q in "$LOG_DIR/hidden.access.log" "$LOG_DIR/hidden.access.log".*; do + [ -e "$q" ] && printf "%s\n" "$q" + done + fi } # build exclude regex @@ -137,12 +152,19 @@ if [ ! -s "$FILES_TMP" ]; then exit 0 fi -echo "[SCAN] Target: \"$TARGET\" Country: $COUNTRY Scope: $SCOPE" +if [ -n "$DATE_FILTER" ]; then + echo "[SCAN] Target: \"$TARGET\" Country: $COUNTRY Scope: $SCOPE Date: $DATE_FILTER" +else + echo "[SCAN] Target: \"$TARGET\" Country: $COUNTRY Scope: $SCOPE" +fi echo "[FILES]" cat "$FILES_TMP" EXRE="$(build_exre || true)" +RESULTS_TMP="/tmp/.ylog_results_$$" +rm -f "$RESULTS_TMP" + found=0 for f in $(cat "$FILES_TMP"); do [ -e "$f" ] || continue @@ -160,17 +182,144 @@ for f in $(cat "$FILES_TMP"); do [ "$EXCL_FIREFOX" -eq 1 ] && cmd="$cmd | grep -vi firefox" if [ "$TARGET" = "all" ]; then - if sh -c "$cmd | tail -n $LINE_LIMIT"; then - found=1 - fi + sh -c "$cmd | tail -n $LINE_LIMIT" >> "$RESULTS_TMP" 2>/dev/null && found=1 else - if sh -c "$cmd"; then - found=1 - fi + sh -c "$cmd" >> "$RESULTS_TMP" 2>/dev/null && found=1 fi done -rm -f "$FILES_TMP" +# Deduplicate: keep only the latest log for same IP+URI within same minute +# Format: IP - user [datetime] "METHOD URI PROTOCOL" status bytes ... +if [ -f "$RESULTS_TMP" ] && [ -s "$RESULTS_TMP" ]; then + # Date filtering: calculate target dates + TARGET_DATE="" + START_DATE="" + if [ -n "$DATE_FILTER" ]; then + if [ "$(printf '%s' "$DATE_FILTER" | cut -c1)" = "~" ]; then + # Range mode: ~N means last N days to today + DAYS=$(printf '%s' "$DATE_FILTER" | sed 's/^~//') + if [ -n "$DAYS" ] && [ "$DAYS" -gt 0 ] 2>/dev/null; then + START_DATE=$(date -d "$DAYS days ago" +%d/%b/%Y 2>/dev/null || date -v-${DAYS}d +%d/%b/%Y 2>/dev/null || echo "") + [ -z "$START_DATE" ] && START_DATE="" + fi + else + # Single date mode: N means N days ago only + DAYS="$DATE_FILTER" + if [ -n "$DAYS" ] && [ "$DAYS" -ge 0 ] 2>/dev/null; then + TARGET_DATE=$(date -d "$DAYS days ago" +%d/%b/%Y 2>/dev/null || date -v-${DAYS}d +%d/%b/%Y 2>/dev/null || echo "") + [ -z "$TARGET_DATE" ] && TARGET_DATE="" + fi + fi + fi + + awk -v date_filter="${DATE_FILTER:-}" -v target_date="${TARGET_DATE:-}" -v start_date="${START_DATE:-}" " + function parse_date(date_str, parts, month_num) { + # date_str format: \"DD/MMM/YYYY\" + split(date_str, parts, \"/\") + month_names = \"JanFebMarAprMayJunJulAugSepOctNovDec\" + month_num = index(month_names, parts[2]) + month_num = (month_num + 2) / 3 # Convert to 1-12 + # Return YYYYMMDD for easy comparison + return sprintf(\"%04d%02d%02d\", parts[3], month_num, parts[1]) + } + + BEGIN { + today = strftime(\"%Y%m%d\") + filter_target = \"\" + date_range_start = \"\" + date_range_end = \"\" + + if (date_filter != \"\" && date_filter ~ /^~/) { + # Range mode: extract days + if (start_date != \"\" && split(start_date, sd_parts, \"/\") == 3) { + month_names = \"JanFebMarAprMayJunJulAugSepOctNovDec\" + month_num = index(month_names, sd_parts[2]) + if (month_num > 0) { + month_num = (month_num + 2) / 3 + start_date_num = sprintf(\"%04d%02d%02d\", sd_parts[3], month_num, sd_parts[1]) + date_range_start = start_date_num + date_range_end = today + } + } + } else if (date_filter != \"\" && target_date != \"\") { + # Single date mode + if (split(target_date, td_parts, \"/\") == 3) { + month_names = \"JanFebMarAprMayJunJulAugSepOctNovDec\" + month_num = index(month_names, td_parts[2]) + if (month_num > 0) { + month_num = (month_num + 2) / 3 + target_date_num = sprintf(\"%04d%02d%02d\", td_parts[3], month_num, td_parts[1]) + filter_target = target_date_num + } + } + } + } + { + line = \$0 + + # Extract datetime [DD/MMM/YYYY:HH:MM:SS + datetime = \"\" + minute_part = \"\" + pos = match(line, /\\[[0-9]{2}\\/[A-Z][a-z]{2}\\/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2}/) + if (pos > 0) { + datetime = substr(line, pos + 1, 20) + date_part = substr(datetime, 1, 11) # DD/MMM/YYYY + minute_part = substr(datetime, 1, 17) # DD/MMM/YYYY:HH:MM + + # Date filtering + if (filter_target != \"\" || date_range_start != \"\") { + line_date_num = parse_date(date_part) + if (filter_target != \"\" && line_date_num != filter_target) { + next # Skip if not matching target date + } + if (date_range_start != \"\" && (line_date_num < date_range_start || line_date_num > date_range_end)) { + next # Skip if outside date range + } + } + } + + # Extract IP (first field before \" - \") + ip = \"\" + pos = match(line, /^[0-9a-fA-F:.]+/) + if (pos > 0) { + ip = substr(line, pos, RLENGTH) + } + + # Extract URI (between quotes after method) + uri = \"\" + # Find the quoted request part: \"METHOD URI PROTOCOL\" + # Use split on quotes to find the request section + split(line, parts, \"\\\"\") + if (length(parts) >= 2) { + # parts[2] should be \"METHOD URI PROTOCOL\" + n = split(parts[2], req_parts, \" \") + if (n >= 2) { + uri = req_parts[2] + } + } + + # Create key: IP + minute + URI + key = ip \"|\" minute_part \"|\" uri + + # Store latest line for each key (compare by full line for latest) + if (!(key in latest) || line > latest[key]) { + latest[key] = line + timestamp[key] = datetime + } + } + END { + # Output: simple approach - print all unique entries + # Since we want chronological order, we need to sort by timestamp + # For simplicity, just print the unique entries + for (key in latest) { + print latest[key] + } + } + " "$RESULTS_TMP" | sort + found=1 +fi + +rm -f "$FILES_TMP" "$RESULTS_TMP" if [ "$TARGET" != "all" ] && [ "$found" -eq 0 ]; then echo "[INFO] No matches found (or filtered out)." >&2 |
