diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index d98a04b4003b..4fcc170889e6 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -903,6 +903,23 @@ set name = "Create or modify area" create_area(usr) +/datum/admins/proc/observe_follow(atom/movable/AM) + if(!isobserver(owner.mob) && !check_rights(R_ADMIN)) + return + var/can_ghost = TRUE + if(!isobserver(owner.mob)) + can_ghost = owner.admin_ghost() + + if(!can_ghost) + return + var/mob/dead/observer/A = owner.mob + var/mob/living/silicon/ai/I = AM //yogs start - adminfollow now follows AI eyes instead of the core + if(istype(I) && I.eyeobj) + A.ManualFollow(I.eyeobj) + else + A.ManualFollow(AM) //yogs stop - adminfollow now follows AI eyes instead of the core + + // // //ALL DONE diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index ccd9148f813c..b71782e3cbc6 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -101,7 +101,8 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/cmd_create_centcom, /datum/admins/proc/cmd_admin_fuckrads, /client/proc/admincryo, - /client/proc/cmd_admin_dress + /client/proc/cmd_admin_dress, + /client/proc/disconnect_panel ) GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel)) GLOBAL_PROTECT(admin_verbs_ban) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 9aaf3bd46e56..37d6f60f3768 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1242,24 +1242,8 @@ show_player_panel(M) else if(href_list["adminplayerobservefollow"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) - - var/client/C = usr.client - var/can_ghost = TRUE - if(!isobserver(usr)) - can_ghost = C.admin_ghost() - - if(!can_ghost) - return - var/mob/dead/observer/A = C.mob - var/mob/living/silicon/ai/I = AM //yogs start - adminfollow now follows AI eyes instead of the core - if(istype(I) && I.eyeobj) - A.ManualFollow(I.eyeobj) - else - A.ManualFollow(AM) //yogs stop - adminfollow now follows AI eyes instead of the core + observe_follow(AM) else if(href_list["admingetmovable"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/admin/verbs/disconnectpanel.dm b/code/modules/admin/verbs/disconnectpanel.dm new file mode 100644 index 000000000000..70dcf91eb873 --- /dev/null +++ b/code/modules/admin/verbs/disconnectpanel.dm @@ -0,0 +1,124 @@ +GLOBAL_LIST_EMPTY(connection_logs) + +/datum/connection_log + var/datum/connection_entry/last_data_point + var/first_login + var/list/datum/connection_entry/data_points = list() + +/datum/connection_log/New() + first_login = world.time + +/datum/connection_log/proc/logout(mob/C) + var/datum/connection_entry/CE = new() + CE.disconnected = world.time + CE.disconnect_type = C.type + CE.living = isliving(C) + CE.job = C.mind?.assigned_role || "Ghost" + last_data_point = CE + data_points |= CE + +/datum/connection_log/proc/login() + if(last_data_point) + last_data_point.connected = world.time + +/datum/connection_entry + var/disconnected + var/connected + var/disconnect_type + var/living = FALSE + var/job + +/client/proc/disconnect_panel() + set name = "Disconnect Panel" + set desc = "Panel showing information about the currently disconnected players" + set category = "Admin" + if(!check_rights(R_ADMIN)) + return + new /datum/disconnect_panel(usr) + +/datum/disconnect_panel + var/client/holder + +/datum/disconnect_panel/New(user) + if(user) + setup(user) + +/datum/disconnect_panel/proc/setup(user) + if(istype(user,/client)) + holder = user + else + var/mob/user_mob = user + holder = user_mob.client + + ui_interact(holder.mob) + +/datum/disconnect_panel/ui_state(mob/user) + return GLOB.admin_state + +/datum/disconnect_panel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DisconnectPanel") + ui.open() + +/datum/disconnect_panel/ui_data(mob/user) + . = list() + .["world_time"] = world.time + .["users"] = list() + for(var/ckey in GLOB.connection_logs) + var/datum/connection_log/CL = GLOB.connection_logs[ckey] + if(!CL.last_data_point) + continue + + var/ckey_data = list() + ckey_data["ckey"] = ckey + ckey_data["connected"] = !!CL.last_data_point.connected + ckey_data["last"] = entry2ui(CL.last_data_point) + ckey_data["history"] = list() + + for(var/datum/connection_entry/entry in CL.data_points) + ckey_data["history"] += list(entry2ui(entry)) + + .["users"] += list(ckey_data) + +/datum/disconnect_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + var/ckey = params["ckey"] + switch(action) + if("follow") + var/mob/M = ckey2mob(ckey) + if(!M) + return + usr.client.holder.observe_follow(M) + if("player-panel") + var/mob/M = ckey2mob(ckey) + if(!M) + return + usr.client.holder.show_player_panel(M) + if("pm") + usr.client.cmd_admin_pm(ckey) + if("notes") + if(!check_rights(R_ADMIN)) + return + browse_messages(target_ckey = ckey, agegate = TRUE) + if("cryo") + var/mob/M = ckey2mob(ckey) + if(!M) + return + usr.client.admincryo(M) + +/datum/disconnect_panel/proc/ckey2mob(ckey) + var/client/C = GLOB.directory[ckey] + if(C) + return C.mob + for(var/mob/M in GLOB.mob_list) + if(M.ckey == ckey) + return M + +/datum/disconnect_panel/proc/entry2ui(datum/connection_entry/entry) + . = list() + .["disconnect"] = entry.disconnected + .["connect"] = entry.connected + .["type"] = entry.disconnect_type + .["living"] = entry.living + .["job"] = entry.job diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 6bc22bf4ac35..7abea7d3f3e4 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -559,6 +559,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(params["discordlink"]) do_discord_link(params["discordlink"]) + var/datum/connection_log/CL = GLOB.connection_logs[ckey] + if(CL) + CL.login() + else + GLOB.connection_logs[ckey] = new/datum/connection_log() ////////////// //DISCONNECT// @@ -593,6 +598,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( GLOB.ahelp_tickets.ClientLogout(src) GLOB.directory -= ckey GLOB.clients -= src + + var/datum/connection_log/CL = GLOB.connection_logs[ckey] + if(CL) + CL.logout(mob) + QDEL_LIST_ASSOC_VAL(char_render_holders) if(movingmob != null) movingmob.client_mobs_in_contents -= mob @@ -600,6 +610,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( seen_messages = null Master.UpdateTickRate() world.sync_logout_with_db(connection_number) // yogs - logout logging + return ..() /client/proc/set_client_age_from_db(connectiontopic) diff --git a/tgui/packages/tgui/interfaces/DisconnectPanel.js b/tgui/packages/tgui/interfaces/DisconnectPanel.js new file mode 100644 index 000000000000..ddd4a059f695 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DisconnectPanel.js @@ -0,0 +1,71 @@ +import { useBackend } from '../backend'; +import { Section, Collapsible, Button } from '../components'; +import { Window } from '../layouts'; + +const sec2time = function (seconds) { + let h = Math.floor(seconds / 3600); + let m = Math.floor(seconds % 3600 / 60).toString().padStart(2, "0"); + let s = Math.floor(seconds % 3600 % 60).toString().padStart(2, "0"); + return h + ":" + m + ":" + s; +}; + +export const DisconnectPanel = (props, context) => { + const { act, data } = useBackend(context); + + data.users.sort((a, b) => { + if (a.connected !== b.connected) { + return a.connected - b.connected; + } + if (a.connected) { + return a.last.connect - b.last.connect; + } + return a.last.disconnect - b.last.disconnect; + }); + + return ( + + +
+ {data.users.map(user => ( + +
+ + + + + + + {user.history.reverse().map(datapoint => ( +
+ {sec2time(datapoint.disconnect/10)}-{sec2time(datapoint.connect/10)} + ({sec2time(((datapoint.connect || data.world_time) + - datapoint.disconnect)/10)}) + As {datapoint.job} ({datapoint.type}) +
+ ))} +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/yogstation.dme b/yogstation.dme index 998d54abe85a..c1367aba2a16 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -1279,6 +1279,7 @@ #include "code\modules\admin\verbs\deadsay.dm" #include "code\modules\admin\verbs\debug.dm" #include "code\modules\admin\verbs\diagnostics.dm" +#include "code\modules\admin\verbs\disconnectpanel.dm" #include "code\modules\admin\verbs\fps.dm" #include "code\modules\admin\verbs\getlogs.dm" #include "code\modules\admin\verbs\ghost_pool_protection.dm"