mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-08-22 12:47:16 +01:00
Tickets System
This commit is contained in:
+22
-15
@@ -29,19 +29,22 @@
|
||||
check_antagonists()
|
||||
return
|
||||
|
||||
if(href_list["ahelp"])
|
||||
// CHOMPedit Start - Tickets System
|
||||
if(href_list["ticket"])
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT))
|
||||
return
|
||||
|
||||
var/ahelp_ref = href_list["ahelp"]
|
||||
var/datum/admin_help/AH = locate(ahelp_ref)
|
||||
if(AH)
|
||||
AH.Action(href_list["ahelp_action"])
|
||||
var/ticket_ref = href_list["ticket"]
|
||||
var/datum/ticket/T = locate(ticket_ref)
|
||||
if(T)
|
||||
T.Action(href_list["ticket_action"])
|
||||
else
|
||||
to_chat(usr, "Ticket [ahelp_ref] has been deleted!")
|
||||
to_chat(usr, "Ticket [ticket_ref] has been deleted!")
|
||||
|
||||
else if(href_list["ahelp_tickets"])
|
||||
GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"]))
|
||||
else if(href_list["tickets"])
|
||||
GLOB.tickets.BrowseTickets(text2num(href_list["tickets"]))
|
||||
|
||||
// CHOMPedit End
|
||||
|
||||
mentor_commands(href, href_list, src)
|
||||
|
||||
@@ -166,7 +169,7 @@
|
||||
if(admin_ranks.len)
|
||||
new_rank = tgui_input_list(usr, "Please select a rank", "New rank", (admin_ranks|"*New Rank*"))
|
||||
else
|
||||
new_rank = tgui_input_list(usr, "Please select a rank", "New rank", list("Game Master","Head Admin","Game Admin", "Trial Admin", "Admin Observer","Moderator","Mentor","Badmin","Retired Admin","Event Manager","Developer","DevMod","*New Rank*")) //CHOMP Edit bandaid fix to assigning titles because we're having some funky database issues, I think. Other option is to manually edit database entry for someone's title.
|
||||
new_rank = tgui_input_list(usr, "Please select a rank", "New rank", list("Game Master","Head Admin","Game Admin", "Trial Admin", "Admin Observer","Moderator","Mentor","Badmin","Retired Admin","Event Manager","Developer","DevMod","*New Rank*")) //CHOMP Edit bandaid fix to assigning titles because we're having some funky database issues, I think. Other option is to manually edit database entry for someone's title.
|
||||
|
||||
var/rights = 0
|
||||
if(D)
|
||||
@@ -935,10 +938,12 @@
|
||||
to_chat(M, "<span class='filter_system warning'>No ban appeals URL has been set.</span>")
|
||||
log_admin("[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.")
|
||||
message_admins("<font color='blue'>[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.</font>")
|
||||
var/datum/admin_help/AH = M.client ? M.client.current_ticket : null
|
||||
if(AH)
|
||||
AH.Resolve()
|
||||
// CHOMPedit Start - Tickets System
|
||||
var/datum/ticket/T = M.client ? M.client.current_ticket : null
|
||||
if(T)
|
||||
T.Resolve()
|
||||
qdel(M.client)
|
||||
// CHOMPedit End
|
||||
//qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends.
|
||||
if("No")
|
||||
if(!check_rights(R_BAN)) return
|
||||
@@ -963,9 +968,11 @@
|
||||
message_admins("<font color='blue'>[usr.client.ckey] has banned [M.ckey].\nReason: [reason]\nThis is a permanent ban.</font>")
|
||||
feedback_inc("ban_perma",1)
|
||||
DB_ban_record(BANTYPE_PERMA, M, -1, reason)
|
||||
var/datum/admin_help/AH = M.client ? M.client.current_ticket : null
|
||||
if(AH)
|
||||
AH.Resolve()
|
||||
// CHOMPedit Start - Tickets System
|
||||
var/datum/ticket/T = M.client ? M.client.current_ticket : null
|
||||
if(T)
|
||||
T.Resolve()
|
||||
// CHOMPedit End
|
||||
qdel(M.client)
|
||||
//qdel(M)
|
||||
if("Cancel")
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
|
||||
CHOMPedit - This file has been excluded from the compilation.
|
||||
Reason: Replaced with "Tickets System". Main logic has been moved to: modular_chomp/modules/tickets/tickets.dm
|
||||
|
||||
*/
|
||||
|
||||
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||||
|
||||
//CHOMPEdit Begin
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
|
||||
CHOMPedit - This file has been excluded from the compilation.
|
||||
Reason: Replaced with "Tickets System"
|
||||
|
||||
*/
|
||||
|
||||
/datum/admin_help/proc/send2adminchat()
|
||||
if(!config.chat_webhook_url)
|
||||
return
|
||||
|
||||
@@ -53,19 +53,19 @@
|
||||
to_chat(src, "<span class='pm warning'>Error: Admin-PM: Client not found.</span>")
|
||||
return
|
||||
|
||||
var/datum/admin_help/AH = C.current_ticket
|
||||
var/datum/ticket/T = C.current_ticket // CHOMPedit - Ticket System
|
||||
|
||||
if(AH)
|
||||
if(T) // CHOMPedit - Ticket System
|
||||
message_admins("<span class='pm'>[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.</span>")
|
||||
var/msg = tgui_input_text(src,"Message:", "Private message to [key_name(C, 0, 0)]")
|
||||
if (!msg)
|
||||
message_admins("<span class='pm'>[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.</span>")
|
||||
return
|
||||
cmd_admin_pm(whom, msg, AH)
|
||||
cmd_admin_pm(whom, msg, T) // CHOMPedit - Ticket System
|
||||
|
||||
//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
|
||||
//Fetching a message if needed. src is the sender and C is the target client
|
||||
/client/proc/cmd_admin_pm(whom, msg, datum/admin_help/AH)
|
||||
/client/proc/cmd_admin_pm(whom, msg, datum/ticket/T) // CHOMPedit - Ticket System
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, "<span class='pm warning'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
|
||||
return
|
||||
@@ -171,7 +171,7 @@
|
||||
else
|
||||
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
|
||||
if(!recipient.current_ticket)
|
||||
new /datum/admin_help(msg, recipient, TRUE)
|
||||
new /datum/ticket(msg, recipient, TRUE, 0) // CHOMPedit - Ticket System
|
||||
|
||||
to_chat(recipient, "<span class='pm warning' size='4'><b>-- Administrator private message --</b></span>")
|
||||
to_chat(recipient, "<span class='pm warning'>Admin PM from-<b>[key_name(src, recipient, 0)]</b>: [msg]</span>")
|
||||
@@ -214,7 +214,7 @@
|
||||
/proc/IrcPm(target,msg,sender)
|
||||
var/client/C = GLOB.directory[target]
|
||||
|
||||
var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target)
|
||||
var/datum/ticket/ticket = C ? C.current_ticket : GLOB.tickets.CKey2ActiveTicket(target) // CHOMPedit - Ticket System
|
||||
var/compliant_msg = trim(lowertext(msg))
|
||||
var/irc_tagged = "[sender](IRC)"
|
||||
var/list/splits = splittext(compliant_msg, " ")
|
||||
|
||||
@@ -186,8 +186,7 @@
|
||||
GLOB.clients += src
|
||||
GLOB.directory[ckey] = src
|
||||
|
||||
GLOB.ahelp_tickets.ClientLogin(src)
|
||||
GLOB.mhelp_tickets.ClientLogin(src)
|
||||
GLOB.tickets.ClientLogin(src) // CHOMPedit - Tickets System
|
||||
|
||||
//Admin Authorisation
|
||||
holder = admin_datums[ckey]
|
||||
@@ -280,8 +279,7 @@
|
||||
if (mentorholder)
|
||||
mentorholder.owner = null
|
||||
GLOB.mentors -= src
|
||||
GLOB.ahelp_tickets.ClientLogout(src)
|
||||
GLOB.mhelp_tickets.ClientLogout(src)
|
||||
GLOB.tickets.ClientLogout(src) // CHOMPedit - Tickets System
|
||||
GLOB.directory -= ckey
|
||||
GLOB.clients -= src
|
||||
return ..()
|
||||
|
||||
@@ -116,16 +116,18 @@ var/list/mentor_verbs_default = list(
|
||||
to_chat(C, create_text_tag("mentor", "MENTOR:", C) + " <span class='mentor_channel'><span class='name'>[src]</span>: <span class='message'>[msg]</span></span>")
|
||||
|
||||
/proc/mentor_commands(href, href_list, client/C)
|
||||
if(href_list["mhelp"])
|
||||
var/mhelp_ref = href_list["mhelp"]
|
||||
var/datum/mentor_help/MH = locate(mhelp_ref)
|
||||
if (MH && istype(MH, /datum/mentor_help))
|
||||
MH.Action(href_list["mhelp_action"])
|
||||
// CHOMPedit Start - Tickets System
|
||||
if(href_list["ticket"])
|
||||
var/ticket_ref = href_list["ticket"]
|
||||
var/datum/ticket/T = locate(ticket_ref)
|
||||
if (T && istype(T, /datum/ticket))
|
||||
T.Action(href_list["ticket_action"])
|
||||
else
|
||||
to_chat(C, "Ticket [mhelp_ref] has been deleted!")
|
||||
to_chat(C, "Ticket [ticket_ref] has been deleted!")
|
||||
|
||||
if (href_list["mhelp_tickets"])
|
||||
GLOB.mhelp_tickets.BrowseTickets(text2num(href_list["mhelp_tickets"]))
|
||||
if (href_list["tickets"])
|
||||
GLOB.tickets.BrowseTickets(text2num(href_list["tickets"]))
|
||||
// CHOMPedit End
|
||||
|
||||
|
||||
/datum/mentor/Topic(href, href_list)
|
||||
@@ -158,15 +160,15 @@ var/list/mentor_verbs_default = list(
|
||||
to_chat(src, "<span class='pm warning'>Error: Mentor-PM: Client not found.</span>")
|
||||
return
|
||||
|
||||
var/datum/mentor_help/MH = C.current_mentorhelp
|
||||
var/datum/ticket/T = C.current_ticket // CHOMPedit - Ticket System
|
||||
|
||||
if(MH)
|
||||
if(T) // CHOMPedit - Ticket System
|
||||
message_mentors("<span class='mentor_channel'>[src] has started replying to [C]'s mentor help.</span>")
|
||||
var/msg = tgui_input_text(src,"Message:", "Private message to [C]")
|
||||
if (!msg)
|
||||
message_mentors("<span class='mentor_channel'>[src] has cancelled their reply to [C]'s mentor help.</span>")
|
||||
return
|
||||
cmd_mentor_pm(whom, msg, MH)
|
||||
cmd_mentor_pm(whom, msg, T) // CHOMPedit - Ticket System
|
||||
|
||||
/proc/has_mentor_powers(client/C)
|
||||
return C.holder || C.mentorholder
|
||||
@@ -189,7 +191,7 @@ var/list/mentor_verbs_default = list(
|
||||
adminhelp(msg)
|
||||
|
||||
|
||||
/client/proc/cmd_mentor_pm(whom, msg, datum/mentor_help/MH)
|
||||
/client/proc/cmd_mentor_pm(whom, msg, datum/ticket/T) // CHOMPedit - Ticket System
|
||||
set category = "Admin"
|
||||
set name = "Mentor-PM"
|
||||
set hidden = 1
|
||||
@@ -199,7 +201,7 @@ var/list/mentor_verbs_default = list(
|
||||
return
|
||||
|
||||
//Not a mentor and no open ticket
|
||||
if(!has_mentor_powers(src) && !current_mentorhelp)
|
||||
if(!has_mentor_powers(src) && !current_ticket) // CHOMPedit - Ticket System
|
||||
to_chat(src, "<span class='pm warning'>You can no longer reply to this ticket, please open another one by using the Mentorhelp verb if need be.</span>")
|
||||
to_chat(src, "<span class='pm notice'>Message: [msg]</span>")
|
||||
return
|
||||
@@ -229,11 +231,11 @@ var/list/mentor_verbs_default = list(
|
||||
to_chat(src, msg)
|
||||
else
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_mentorhelp.MessageNoRecipient(msg)
|
||||
current_ticket.MessageNoRecipient(msg) // CHOMPedit - Ticket System
|
||||
return
|
||||
|
||||
//Has mentor powers but the recipient no longer has an open ticket
|
||||
if(has_mentor_powers(src) && !recipient.current_mentorhelp)
|
||||
if(has_mentor_powers(src) && !recipient.current_ticket) // CHOMPedit - Ticket System
|
||||
to_chat(src, "<span class='pm warning'>You can no longer reply to this ticket.</span>")
|
||||
to_chat(src, "<span class='pm notice'>Message: [msg]</span>")
|
||||
return
|
||||
@@ -247,17 +249,19 @@ var/list/mentor_verbs_default = list(
|
||||
|
||||
var/interaction_message = "<span class='pm notice'>Mentor-PM from-<b>[src]</b> to-<b>[recipient]</b>: [msg]</span>"
|
||||
|
||||
if (recipient.current_mentorhelp && !has_mentor_powers(recipient))
|
||||
recipient.current_mentorhelp.AddInteraction(interaction_message)
|
||||
if (src.current_mentorhelp && !has_mentor_powers(src))
|
||||
src.current_mentorhelp.AddInteraction(interaction_message)
|
||||
// CHOMPedit Start - Ticket System
|
||||
if (recipient.current_ticket && !has_mentor_powers(recipient))
|
||||
recipient.current_ticket.AddInteraction(interaction_message)
|
||||
if (src.current_ticket && !has_mentor_powers(src))
|
||||
src.current_ticket.AddInteraction(interaction_message)
|
||||
|
||||
// It's a little fucky if they're both mentors, but while admins may need to adminhelp I don't really see any reason a mentor would have to mentorhelp since you can literally just ask any other mentors online
|
||||
if (has_mentor_powers(recipient) && has_mentor_powers(src))
|
||||
if (recipient.current_mentorhelp)
|
||||
recipient.current_mentorhelp.AddInteraction(interaction_message)
|
||||
if (src.current_mentorhelp)
|
||||
src.current_mentorhelp.AddInteraction(interaction_message)
|
||||
if (recipient.current_ticket)
|
||||
recipient.current_ticket.AddInteraction(interaction_message)
|
||||
if (src.current_ticket)
|
||||
src.current_ticket.AddInteraction(interaction_message)
|
||||
// CHOMPedit End
|
||||
|
||||
to_chat(recipient, "<i><span class='mentor'>Mentor-PM from-<b><a href='?mentorhelp_msg=\ref[src]'>[src]</a></b>: [msg]</span></i>")
|
||||
to_chat(src, "<i><span class='mentor'>Mentor-PM to-<b>[recipient]</b>: [msg]</span></i>")
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
/client/var/datum/mentor_help/current_mentorhelp
|
||||
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
GLOBAL_DATUM_INIT(mhelp_tickets, /datum/mentor_help_tickets, new)
|
||||
|
||||
/datum/mentor_help_tickets
|
||||
var/list/active_tickets = list()
|
||||
var/list/resolved_tickets = list()
|
||||
|
||||
var/obj/effect/statclick/mticket_list/astatclick = new(null, null, AHELP_ACTIVE)
|
||||
var/obj/effect/statclick/mticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
|
||||
|
||||
/datum/mentor_help_tickets/Destroy()
|
||||
QDEL_LIST(active_tickets)
|
||||
QDEL_LIST(resolved_tickets)
|
||||
QDEL_NULL(astatclick)
|
||||
QDEL_NULL(rstatclick)
|
||||
return ..()
|
||||
|
||||
//private
|
||||
/datum/mentor_help_tickets/proc/ListInsert(datum/mentor_help/new_ticket)
|
||||
var/list/mticket_list
|
||||
switch(new_ticket.state)
|
||||
if(AHELP_ACTIVE)
|
||||
mticket_list = active_tickets
|
||||
if(AHELP_RESOLVED)
|
||||
mticket_list = resolved_tickets
|
||||
else
|
||||
CRASH("Invalid ticket state: [new_ticket.state]")
|
||||
var/num_closed = mticket_list.len
|
||||
if(num_closed)
|
||||
for(var/I in 1 to num_closed)
|
||||
var/datum/mentor_help/MH = mticket_list[I]
|
||||
if(MH.id > new_ticket.id)
|
||||
mticket_list.Insert(I, new_ticket)
|
||||
return
|
||||
mticket_list += new_ticket
|
||||
|
||||
//opens the ticket listings, only two states here
|
||||
/datum/mentor_help_tickets/proc/BrowseTickets(state)
|
||||
var/list/l2b
|
||||
var/title
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
l2b = active_tickets
|
||||
title = "Active Tickets"
|
||||
if(AHELP_RESOLVED)
|
||||
l2b = resolved_tickets
|
||||
title = "Resolved Tickets"
|
||||
if(!l2b)
|
||||
return
|
||||
var/list/dat = list("<html><head><title>[title]</title></head>")
|
||||
dat += "<A HREF='?_src_=mentorholder;[HrefToken()];mhelp_tickets=[state]'>Refresh</A><br><br>"
|
||||
for(var/datum/mentor_help/MH as anything in l2b)
|
||||
dat += "<span class='adminnotice'><span class='adminhelp'>Ticket #[MH.id]</span>: <A HREF='?_src_=mentorholder;mhelp=\ref[MH];[HrefToken()];mhelp_action=ticket'>[MH.initiator_ckey]: [MH.name]</A></span><br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=mhelp_list[state];size=600x480")
|
||||
|
||||
//Tickets statpanel
|
||||
/datum/mentor_help_tickets/proc/stat_entry()
|
||||
var/num_disconnected = 0
|
||||
stat("== Mentor Tickets ==")
|
||||
stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
|
||||
for(var/datum/mentor_help/MH as anything in active_tickets)
|
||||
if(MH.initiator)
|
||||
stat("#[MH.id]. [MH.initiator_ckey]:", MH.statclick.update())
|
||||
else
|
||||
++num_disconnected
|
||||
if(num_disconnected)
|
||||
stat("Disconnected:", astatclick.update("[num_disconnected]"))
|
||||
stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
|
||||
|
||||
//Reassociate still open ticket if one exists
|
||||
/datum/mentor_help_tickets/proc/ClientLogin(client/C)
|
||||
C.current_mentorhelp = CKey2ActiveTicket(C.ckey)
|
||||
if(C.current_mentorhelp)
|
||||
C.current_mentorhelp.AddInteraction("Client reconnected.")
|
||||
C.current_mentorhelp.initiator = C
|
||||
|
||||
//Dissasociate ticket
|
||||
/datum/mentor_help_tickets/proc/ClientLogout(client/C)
|
||||
if(C.current_mentorhelp)
|
||||
C.current_mentorhelp.AddInteraction("Client disconnected.")
|
||||
C.current_mentorhelp.initiator = null
|
||||
C.current_mentorhelp = null
|
||||
|
||||
//Get a ticket given a ckey
|
||||
/datum/mentor_help_tickets/proc/CKey2ActiveTicket(ckey)
|
||||
for(var/datum/admin_help/MH as anything in active_tickets)
|
||||
if(MH.initiator_ckey == ckey)
|
||||
return MH
|
||||
|
||||
//
|
||||
//TICKET LIST STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/mticket_list
|
||||
var/current_state
|
||||
|
||||
/obj/effect/statclick/mticket_list/New(loc, name, state)
|
||||
current_state = state
|
||||
..()
|
||||
|
||||
/obj/effect/statclick/mticket_list/Click()
|
||||
GLOB.mhelp_tickets.BrowseTickets(current_state)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/mentor_help
|
||||
var/id
|
||||
var/name
|
||||
var/state = AHELP_ACTIVE
|
||||
|
||||
var/opened_at
|
||||
var/closed_at
|
||||
|
||||
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
||||
var/initiator_ckey
|
||||
var/initiator_key_name
|
||||
|
||||
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
||||
|
||||
var/obj/effect/statclick/ahelp/statclick
|
||||
|
||||
var/static/ticket_counter = 0
|
||||
|
||||
//call this on its own to create a ticket, don't manually assign current_mentorhelp
|
||||
//msg is the title of the ticket: usually the ahelp text
|
||||
/datum/mentor_help/New(msg, client/C)
|
||||
//clean the input msg
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg || !C || !C.mob)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
id = ++ticket_counter
|
||||
opened_at = world.time
|
||||
|
||||
name = msg
|
||||
|
||||
initiator = C
|
||||
initiator_ckey = C.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_mentorhelp) //This is a bug
|
||||
log_debug("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.Resolve()
|
||||
initiator.current_mentorhelp = src
|
||||
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
|
||||
log_admin("Mentorhelp: [key_name(C)]: [msg]")
|
||||
MessageNoRecipient(msg)
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, "<i><span class='mentor'>Mentor-PM to-<b>Mentors</b>: [name]</span></i>")
|
||||
|
||||
GLOB.mhelp_tickets.active_tickets += src
|
||||
|
||||
/datum/mentor_help/Destroy()
|
||||
RemoveActive()
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
return ..()
|
||||
|
||||
/datum/mentor_help/proc/AddInteraction(formatted_message)
|
||||
_interactions += "[gameTimestamp()]: [formatted_message]"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/ClosureLinks(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
. = " (<A HREF='?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=resolve'>RSLVE</A>)"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/LinkedReplyName(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A HREF='?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=reply'>[initiator_ckey]</A>"
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A HREF='?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=[action]'>[msg]</A>"
|
||||
|
||||
//message from the initiator without a target, all people with mentor powers will see this
|
||||
/datum/mentor_help/proc/MessageNoRecipient(msg)
|
||||
var/ref_src = "\ref[src]"
|
||||
var/chat_msg = "<span class='notice'>(<A HREF='?_src_=mentorholder;mhelp=[ref_src];[HrefToken()];mhelp_action=escalate'>ESCALATE</A>) Ticket [TicketHref("#[id]", ref_src)]<b>: [LinkedReplyName(ref_src)]:</b> [msg]</span>"
|
||||
AddInteraction("<font color='red'>[LinkedReplyName(ref_src)]: [msg]</font>")
|
||||
for (var/client/C in GLOB.mentors)
|
||||
if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
for (var/client/C in GLOB.admins)
|
||||
if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
message_mentors(chat_msg)
|
||||
|
||||
//Reopen a closed ticket
|
||||
/datum/mentor_help/proc/Reopen()
|
||||
if(state == AHELP_ACTIVE)
|
||||
to_chat(usr, "<span class='warning'>This ticket is already open.</span>")
|
||||
return
|
||||
|
||||
if(GLOB.mhelp_tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
to_chat(usr, "<span class='warning'>This user already has an active ticket, cannot reopen this one.</span>")
|
||||
return
|
||||
|
||||
statclick = new(null, src)
|
||||
GLOB.mhelp_tickets.active_tickets += src
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
switch(state)
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("mhelp_resolve")
|
||||
state = AHELP_ACTIVE
|
||||
closed_at = null
|
||||
if(initiator)
|
||||
initiator.current_mentorhelp = src
|
||||
|
||||
AddInteraction("<font color='purple'>Reopened by [usr.ckey]</font>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='purple'>Ticket [TicketHref("#[id]")] was reopened by [usr.ckey].</font></span>")
|
||||
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [usr.ckey].</span>"
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
feedback_inc("mhelp_reopen")
|
||||
TicketPanel() //can only be done from here, so refresh it
|
||||
|
||||
//private
|
||||
/datum/mentor_help/proc/RemoveActive()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
closed_at = world.time
|
||||
QDEL_NULL(statclick)
|
||||
GLOB.mhelp_tickets.active_tickets -= src
|
||||
if(initiator && initiator.current_mentorhelp == src)
|
||||
initiator.current_mentorhelp = null
|
||||
|
||||
//Mark open ticket as resolved/legitimate, returns mentorhelp verb
|
||||
/datum/mentor_help/proc/Resolve(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_RESOLVED
|
||||
GLOB.mhelp_tickets.ListInsert(src)
|
||||
|
||||
AddInteraction("<span class='filter_adminlog'><font color='green'>Resolved by [usr.ckey].</font></span>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='green'>Ticket [TicketHref("#[id]")] was marked resolved by [usr.ckey].</font></span>")
|
||||
if(!silent)
|
||||
feedback_inc("mhelp_resolve")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] resolved by [usr.ckey]"
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
|
||||
//Show the ticket panel
|
||||
/datum/mentor_help/proc/TicketPanel()
|
||||
tgui_interact(usr.client.mob)
|
||||
|
||||
/datum/mentor_help/proc/TicketPanelLegacy()
|
||||
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Mentor Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += "<b>State: "
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += "<font color='red'>OPEN</font>"
|
||||
if(AHELP_RESOLVED)
|
||||
dat += "<font color='green'>RESOLVED</font>"
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "</b>[GLOB.TAB][TicketHref("Refresh", ref_src)]"
|
||||
if(state != AHELP_ACTIVE)
|
||||
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
|
||||
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += "<b>Actions:</b> [Context(ref_src)]<br>"
|
||||
else
|
||||
dat += "<b>DISCONNECTED</b>[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br><b>Log:</b><br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=mhelp[id];size=620x480")
|
||||
|
||||
/datum/mentor_help/tgui_fallback(payload)
|
||||
if(..())
|
||||
return
|
||||
|
||||
TicketPanelLegacy()
|
||||
|
||||
/datum/mentor_help/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "MentorTicketPanel", "Ticket #[id] - [LinkedReplyName("\ref[src]")]")
|
||||
ui.open()
|
||||
|
||||
/datum/mentor_help/tgui_state(mob/user)
|
||||
return GLOB.tgui_mentor_state
|
||||
|
||||
/datum/mentor_help/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = id
|
||||
|
||||
var/ref_src = "\ref[src]"
|
||||
data["title"] = name
|
||||
data["name"] = LinkedReplyName(ref_src)
|
||||
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
data["state"] = "open"
|
||||
if(AHELP_RESOLVED)
|
||||
data["state"] = "resolved"
|
||||
else
|
||||
data["state"] = "unknown"
|
||||
|
||||
data["opened_at"] = (world.time - opened_at)
|
||||
data["closed_at"] = (world.time - closed_at)
|
||||
data["opened_at_date"] = gameTimestamp(wtime = opened_at)
|
||||
data["closed_at_date"] = gameTimestamp(wtime = closed_at)
|
||||
|
||||
data["actions"] = Context(ref_src)
|
||||
|
||||
data["log"] = _interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/mentor_help/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("escalate")
|
||||
Escalate()
|
||||
. = TRUE
|
||||
if("reopen")
|
||||
Reopen()
|
||||
. = TRUE
|
||||
if("legacy")
|
||||
TicketPanelLegacy()
|
||||
. = TRUE
|
||||
|
||||
//Kick ticket to admins
|
||||
/datum/mentor_help/proc/Escalate()
|
||||
if(tgui_alert(usr, "Really escalate this ticket to admins? No mentors will ever be able to interact with it again if you do.","Escalate",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if (src.initiator == null) // You can't escalate a mentorhelp of someone who's logged out because it won't create the adminhelp properly
|
||||
to_chat(usr, "<span class='pm warning'>Error: client not found, unable to escalate.</span>")
|
||||
return
|
||||
var/datum/admin_help/AH = new /datum/admin_help(src.name, src.initiator, FALSE)
|
||||
message_mentors("[usr.ckey] escalated Ticket [TicketHref("#[id]")]")
|
||||
log_admin("[key_name(usr)] escalated mentorhelp [src.name]")
|
||||
to_chat(src.initiator, "<span class='mentor'>[usr.ckey] escalated your mentorhelp to admins.</span>")
|
||||
AH._interactions = src._interactions
|
||||
GLOB.mhelp_tickets.active_tickets -= src
|
||||
GLOB.mhelp_tickets.resolved_tickets -= src
|
||||
qdel(src)
|
||||
|
||||
/datum/mentor_help/proc/Context(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
if(state != AHELP_RESOLVED)
|
||||
. += " (<A HREF='?_src_=mentorholder;[HrefToken()];mhelp=[ref_src];mhelp_action=escalate'>ESCALATE</A>)"
|
||||
|
||||
//Forwarded action from admin/Topic OR mentor/Topic depending on which rank the caller has
|
||||
/datum/mentor_help/proc/Action(action)
|
||||
switch(action)
|
||||
if("ticket")
|
||||
TicketPanel()
|
||||
if("reply")
|
||||
usr.client.cmd_mhelp_reply(initiator)
|
||||
if("resolve")
|
||||
Resolve()
|
||||
if("reopen")
|
||||
Reopen()
|
||||
if("escalate")
|
||||
Escalate()
|
||||
|
||||
//
|
||||
// TICKET STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/mhelp
|
||||
var/datum/mentor_help/mhelp_datum
|
||||
|
||||
/obj/effect/statclick/mhelp/New(loc, datum/mentor_help/MH)
|
||||
mhelp_datum = MH
|
||||
..(loc)
|
||||
|
||||
/obj/effect/statclick/mhelp/update()
|
||||
return ..(mhelp_datum.name)
|
||||
|
||||
/obj/effect/statclick/mhelp/Click()
|
||||
mhelp_datum.TicketPanel()
|
||||
|
||||
/obj/effect/statclick/mhelp/Destroy()
|
||||
mhelp_datum = null
|
||||
return ..()
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/mentorhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Mentorhelp"
|
||||
|
||||
if(say_disabled) //This is here to try to identify lag problems
|
||||
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
||||
return
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, "<span class='danger'>Error: Mentor-PM: You cannot send adminhelps (Muted).</span>")
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
src.verbs -= /client/verb/mentorhelp
|
||||
spawn(600)
|
||||
src.verbs += /client/verb/mentorhelp // 1 minute cool-down for mentorhelps
|
||||
|
||||
feedback_add_details("admin_verb","Mentorhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_mentorhelp)
|
||||
if(tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No")) != "No")
|
||||
if(current_mentorhelp)
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_mentorhelp.MessageNoRecipient(msg)
|
||||
to_chat(usr, "<span class='adminnotice'><span class='mentor'>Mentor-PM to-<b>Mentors</b>: [msg]</span></span>")
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
||||
else
|
||||
current_mentorhelp.AddInteraction("[usr.ckey] opened a new ticket.")
|
||||
current_mentorhelp.Resolve()
|
||||
|
||||
new /datum/mentor_help(msg, src, FALSE)
|
||||
|
||||
//admin proc
|
||||
/client/proc/cmd_mentor_ticket_panel()
|
||||
set name = "Mentor Ticket List"
|
||||
set category = "Admin"
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", list("Active Tickets", "Resolved Tickets")))
|
||||
if("Active Tickets")
|
||||
browse_to = AHELP_ACTIVE
|
||||
if("Resolved Tickets")
|
||||
browse_to = AHELP_RESOLVED
|
||||
else
|
||||
return
|
||||
|
||||
GLOB.mhelp_tickets.BrowseTickets(browse_to)
|
||||
|
||||
/proc/message_mentors(var/msg)
|
||||
msg = "<span class='mentor_channel'><span class='prefix'>Mentor:</span> <span class=\"message\">[msg]</span></span>"
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
to_chat(C, msg)
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, msg)
|
||||
@@ -685,8 +685,9 @@
|
||||
for(var/datum/controller/subsystem/SS in Master.subsystems)
|
||||
SS.stat_entry()
|
||||
|
||||
if(statpanel("Tickets"))
|
||||
GLOB.ahelp_tickets.stat_entry()
|
||||
// CHOMPedit - Ticket System
|
||||
//if(statpanel("Tickets"))
|
||||
//GLOB.ahelp_tickets.stat_entry()
|
||||
|
||||
|
||||
if(length(GLOB.sdql2_queries))
|
||||
@@ -696,9 +697,9 @@
|
||||
Q.generate_stat()
|
||||
|
||||
|
||||
if(has_mentor_powers(client))
|
||||
if(has_mentor_powers(client) || client.holder) // CHOMPedit - Ticket System
|
||||
if(statpanel("Tickets"))
|
||||
GLOB.mhelp_tickets.stat_entry()
|
||||
GLOB.tickets.stat_entry() // CHOMPedit - Ticket System
|
||||
|
||||
if(listed_turf && client)
|
||||
if(!TurfAdjacent(listed_turf))
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/obj/screen/alert/open_ticket
|
||||
icon = 'modular_chomp/icons/logo.dmi'
|
||||
name = "Admin Chat Request"
|
||||
desc = "A Administrator would like to chat with you. \
|
||||
Click here to begin."
|
||||
icon_state = "32x32"
|
||||
var/clicked_once = FALSE
|
||||
|
||||
/obj/screen/alert/open_ticket/Click()
|
||||
if(!usr || !usr.client) return
|
||||
|
||||
// Open a new chat with the user
|
||||
var/datum/ticket_chat/TC = new()
|
||||
TC.T = usr.client.current_ticket
|
||||
TC.tgui_interact(usr.client.mob)
|
||||
|
||||
desc = "A Administrator would like to chat with you. \
|
||||
Click here to continue the conversation."
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* tgui state: ticket_state
|
||||
*
|
||||
* Grants the user UI_INTERACTIVE, if a ticket is open.
|
||||
**/
|
||||
|
||||
GLOBAL_DATUM_INIT(tgui_ticket_state, /datum/tgui_state/ticket_state, new)
|
||||
|
||||
/datum/tgui_state/ticket_state/can_use_topic(src_object, mob/user)
|
||||
//if (user.client.current_ticket)
|
||||
// return STATUS_INTERACTIVE
|
||||
//return STATUS_CLOSE
|
||||
return STATUS_INTERACTIVE
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/mentorhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Mentorhelp"
|
||||
|
||||
if(say_disabled) //This is here to try to identify lag problems
|
||||
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
||||
return
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, "<span class='danger'>Error: Mentor-PM: You cannot send adminhelps (Muted).</span>")
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
src.verbs -= /client/verb/mentorhelp
|
||||
spawn(600)
|
||||
src.verbs += /client/verb/mentorhelp // 1 minute cool-down for mentorhelps
|
||||
|
||||
feedback_add_details("admin_verb","Mentorhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_ticket)
|
||||
if(tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No")) != "No")
|
||||
if(current_ticket)
|
||||
log_admin("Mentorhelp: [key_name(src)]: [msg]")
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(usr, "<span class='adminnotice'><span class='mentor'>Mentor-PM to-<b>Mentors</b>: [msg]</span></span>")
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
||||
else
|
||||
current_ticket.AddInteraction("[usr.ckey] opened a new ticket.")
|
||||
current_ticket.Resolve()
|
||||
|
||||
new /datum/ticket(msg, src, FALSE, 1)
|
||||
|
||||
//admin proc
|
||||
/client/proc/cmd_mentor_ticket_panel()
|
||||
set name = "Mentor Ticket List"
|
||||
set category = "Admin"
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", list("Active Tickets", "Resolved Tickets")))
|
||||
if("Active Tickets")
|
||||
browse_to = AHELP_ACTIVE
|
||||
if("Resolved Tickets")
|
||||
browse_to = AHELP_RESOLVED
|
||||
else
|
||||
return
|
||||
|
||||
GLOB.tickets.BrowseTickets(browse_to)
|
||||
|
||||
/proc/message_mentors(var/msg)
|
||||
msg = "<span class='mentor_channel'><span class='prefix'>Mentor:</span> <span class=\"message\">[msg]</span></span>"
|
||||
|
||||
for(var/client/C in GLOB.mentors)
|
||||
to_chat(C, msg)
|
||||
for(var/client/C in GLOB.admins)
|
||||
to_chat(C, msg)
|
||||
|
||||
//
|
||||
// CLIENT PROCS
|
||||
//
|
||||
|
||||
/client/verb/adminhelp(msg as text)
|
||||
set category = "Admin"
|
||||
set name = "Adminhelp"
|
||||
|
||||
if(say_disabled) //This is here to try to identify lag problems
|
||||
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
|
||||
return
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(src, "<span class='danger'>Error: Admin-PM: You cannot send adminhelps (Muted).</span>")
|
||||
return
|
||||
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
|
||||
return
|
||||
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
//remove out adminhelp verb temporarily to prevent spamming of admins.
|
||||
src.verbs -= /client/verb/adminhelp
|
||||
spawn(1200)
|
||||
src.verbs += /client/verb/adminhelp // 2 minute cool-down for adminhelps
|
||||
|
||||
feedback_add_details("admin_verb","Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(current_ticket)
|
||||
if(tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No")) != "No")
|
||||
if(current_ticket)
|
||||
current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(usr, "<span class='adminnotice'>PM to-<b>Admins</b>: [msg]</span>")
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
||||
else
|
||||
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
||||
current_ticket.Close()
|
||||
|
||||
new /datum/ticket(msg, src, FALSE, 0)
|
||||
|
||||
//admin proc
|
||||
/client/proc/cmd_admin_ticket_panel()
|
||||
set name = "Show Ticket List"
|
||||
set category = "Admin"
|
||||
|
||||
if(!check_rights(R_ADMIN|R_MOD|R_DEBUG|R_EVENT, TRUE))
|
||||
return
|
||||
|
||||
var/browse_to
|
||||
|
||||
switch(tgui_input_list(usr, "Display which ticket list?", "List Choice", list("Active Tickets", "Closed Tickets", "Resolved Tickets")))
|
||||
if("Active Tickets")
|
||||
browse_to = AHELP_ACTIVE
|
||||
if("Closed Tickets")
|
||||
browse_to = AHELP_CLOSED
|
||||
if("Resolved Tickets")
|
||||
browse_to = AHELP_RESOLVED
|
||||
else
|
||||
return
|
||||
|
||||
GLOB.tickets.BrowseTickets(browse_to)
|
||||
|
||||
//// VOREstation Additions Below
|
||||
|
||||
/datum/ticket/proc/send2adminchat()
|
||||
if(!config.chat_webhook_url)
|
||||
return
|
||||
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/afkmins = adm["afk"]
|
||||
var/list/allmins = adm["total"]
|
||||
|
||||
spawn(0) //Unreliable world.Exports()
|
||||
var/query_string = "type=adminhelp"
|
||||
query_string += "&key=[url_encode(config.chat_webhook_key)]"
|
||||
query_string += "&from=[url_encode(key_name(initiator))]"
|
||||
query_string += "&msg=[url_encode(html_decode(name))]"
|
||||
query_string += "&admin_number=[allmins.len]"
|
||||
query_string += "&admin_number_afk=[afkmins.len]"
|
||||
world.Export("[config.chat_webhook_url]?[query_string]")
|
||||
|
||||
/client/verb/adminspice()
|
||||
set category = "Admin"
|
||||
set name = "Request Spice"
|
||||
set desc = "Request admins to spice round up for you"
|
||||
|
||||
//handle muting and automuting
|
||||
if(prefs.muted & MUTE_ADMINHELP)
|
||||
to_chat(usr, "<span class='danger'>Error: You cannot request spice (muted from adminhelps).</span>")
|
||||
return
|
||||
|
||||
if(tgui_alert(usr, "Are you sure you want to request the admins spice things up for you? You accept the consequences if you do.","Spicy!",list("Yes","No")) != "No")
|
||||
message_admins("[ADMIN_FULLMONTY(usr)] has requested the round be spiced up a little.")
|
||||
to_chat(usr, "<span class='notice'>You have requested some more spice in your round.</span>")
|
||||
else
|
||||
to_chat(usr, "<span class='notice'>Spice request cancelled.</span>")
|
||||
return
|
||||
|
||||
//if they requested spice, then remove spice verb temporarily to prevent spamming
|
||||
usr.verbs -= /client/verb/adminspice
|
||||
spawn(10 MINUTES)
|
||||
if(usr) // In case we left in the 10 minute cooldown
|
||||
usr.verbs += /client/verb/adminspice // 10 minute cool-down for spice request
|
||||
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
|
||||
CHOMPedit - This file has been excluded from the compilation.
|
||||
Reason: Replaced with "Tickets System". Main logic has been moved to: modular_chomp/modules/tickets/tickets.dm
|
||||
|
||||
*/
|
||||
|
||||
/client/var/datum/ticket/current_ticket //the current ticket the (usually) not-admin client is dealing with
|
||||
/client/var/datum/ticket/selected_ticket //the current ticket being viewed in the Tickets Panel (usually) admin/mentor client
|
||||
|
||||
// CHOMPEdit Begin
|
||||
/proc/get_ahelp_channel()
|
||||
var/datum/tgs_api/v5/api = TGS_READ_GLOBAL(tgs)
|
||||
if(istype(api) && config.ahelp_channel_tag)
|
||||
for(var/datum/tgs_chat_channel/channel in api.chat_channels)
|
||||
if(channel.custom_tag == config.ahelp_channel_tag)
|
||||
return list(channel)
|
||||
return 0
|
||||
|
||||
/proc/ahelp_discord_message(var/message)
|
||||
if(!message)
|
||||
return
|
||||
if(config.discord_ahelps_disabled)
|
||||
return
|
||||
var/datum/tgs_chat_channel/ahelp_channel = get_ahelp_channel()
|
||||
if(ahelp_channel)
|
||||
world.TgsChatBroadcast(message,ahelp_channel)
|
||||
else
|
||||
world.TgsTargetedChatBroadcast(message,TRUE)
|
||||
// CHOMPEdit End
|
||||
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
GLOBAL_DATUM_INIT(tickets, /datum/tickets, new)
|
||||
|
||||
/datum/tickets
|
||||
var/list/active_tickets = list()
|
||||
var/list/closed_tickets = list()
|
||||
var/list/resolved_tickets = list()
|
||||
|
||||
var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE)
|
||||
var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED)
|
||||
var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
|
||||
|
||||
/datum/tickets/Destroy()
|
||||
QDEL_LIST(active_tickets)
|
||||
QDEL_LIST(closed_tickets)
|
||||
QDEL_LIST(resolved_tickets)
|
||||
QDEL_NULL(astatclick)
|
||||
QDEL_NULL(cstatclick)
|
||||
QDEL_NULL(rstatclick)
|
||||
return ..()
|
||||
|
||||
//private
|
||||
/datum/tickets/proc/ListInsert(datum/ticket/new_ticket)
|
||||
var/list/ticket_list
|
||||
switch(new_ticket.state)
|
||||
if(AHELP_ACTIVE)
|
||||
ticket_list = active_tickets
|
||||
if(AHELP_CLOSED)
|
||||
ticket_list = closed_tickets
|
||||
if(AHELP_RESOLVED)
|
||||
ticket_list = resolved_tickets
|
||||
else
|
||||
CRASH("Invalid ticket state: [new_ticket.state]")
|
||||
var/num_closed = ticket_list.len
|
||||
if(num_closed)
|
||||
for(var/I in 1 to num_closed)
|
||||
var/datum/ticket/T = ticket_list[I]
|
||||
if(T.id > new_ticket.id)
|
||||
ticket_list.Insert(I, new_ticket)
|
||||
return
|
||||
ticket_list += new_ticket
|
||||
|
||||
/datum/tickets/proc/BrowseTickets(state)
|
||||
tgui_interact(usr)
|
||||
|
||||
//opens the ticket listings for one of the 3 states
|
||||
/datum/tickets/proc/BrowseTicketsLegacy(state)
|
||||
var/list/l2b
|
||||
var/title
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
l2b = active_tickets
|
||||
title = "Active Tickets"
|
||||
if(AHELP_CLOSED)
|
||||
l2b = closed_tickets
|
||||
title = "Closed Tickets"
|
||||
if(AHELP_RESOLVED)
|
||||
l2b = resolved_tickets
|
||||
title = "Resolved Tickets"
|
||||
if(!l2b)
|
||||
return
|
||||
var/list/dat = list("<html><head><title>[title]</title></head>")
|
||||
dat += "<A HREF='?_src_=holder;[HrefToken()];ahelp_tickets=[state]'>Refresh</A><br><br>"
|
||||
for(var/datum/ticket/T as anything in l2b)
|
||||
dat += "<span class='adminnotice'><span class='adminhelp'>Ticket #[T.id]</span>: <A HREF='?_src_=holder;ahelp=\ref[T];[HrefToken()];ahelp_action=ticket'>[T.initiator_key_name]: [T.name]</A></span><br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
|
||||
|
||||
//Tickets statpanel
|
||||
/datum/tickets/proc/stat_entry()
|
||||
var/num_disconnected = 0
|
||||
stat("== Tickets ==")
|
||||
stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
if(T.initiator)
|
||||
var/type = "N/A"
|
||||
switch(T.level)
|
||||
if(0)
|
||||
type = "ADM"
|
||||
if(1)
|
||||
type = "MEN"
|
||||
|
||||
if(usr.client.holder || T.level > 0)
|
||||
stat("\[[type]\] #[T.id]. [T.initiator_key_name]:", T.statclick.update())
|
||||
else
|
||||
++num_disconnected
|
||||
if(num_disconnected)
|
||||
stat("Disconnected:", astatclick.update("[num_disconnected]"))
|
||||
stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]"))
|
||||
stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
|
||||
|
||||
//Reassociate still open ticket if one exists
|
||||
/datum/tickets/proc/ClientLogin(client/C)
|
||||
C.current_ticket = CKey2ActiveTicket(C.ckey)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.AddInteraction("Client reconnected.")
|
||||
C.current_ticket.initiator = C
|
||||
|
||||
//Dissasociate ticket
|
||||
/datum/tickets/proc/ClientLogout(client/C)
|
||||
if(C.current_ticket)
|
||||
C.current_ticket.AddInteraction("Client disconnected.")
|
||||
C.current_ticket.initiator = null
|
||||
C.current_ticket = null
|
||||
|
||||
//Get a ticket given a ckey
|
||||
/datum/tickets/proc/CKey2ActiveTicket(ckey)
|
||||
if(!usr?.client.holder || !has_mentor_powers(usr?.client))
|
||||
message_admins("[usr] has attempted to look up a ticket with CKEY [ckey] without sufficent privileges.")
|
||||
return
|
||||
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
if(T.initiator_ckey == ckey)
|
||||
return T
|
||||
|
||||
//Get a ticket by ticket id
|
||||
/datum/tickets/proc/ID2Ticket(id)
|
||||
if(!usr?.client.holder || !has_mentor_powers(usr?.client))
|
||||
message_admins("[usr] has attempted to look up a ticket with ID [id] without sufficent privileges.")
|
||||
return
|
||||
|
||||
for(var/datum/ticket/T as anything in active_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
for(var/datum/ticket/T as anything in resolved_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
for(var/datum/ticket/T as anything in closed_tickets)
|
||||
if(T.id == id)
|
||||
return T
|
||||
|
||||
//
|
||||
//TICKET LIST STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/ticket_list
|
||||
var/current_state
|
||||
|
||||
/obj/effect/statclick/ticket_list/New(loc, name, state)
|
||||
current_state = state
|
||||
..()
|
||||
|
||||
/obj/effect/statclick/ticket_list/Click()
|
||||
GLOB.tickets.BrowseTickets(current_state)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/ticket
|
||||
var/id
|
||||
var/name
|
||||
var/level = 0 // 0 = Admin, 1 = Mentor
|
||||
var/list/tags
|
||||
var/state = AHELP_ACTIVE
|
||||
|
||||
var/opened_at
|
||||
var/closed_at
|
||||
|
||||
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
|
||||
var/handler = "/Unassigned\\" // The admin handling the ticket
|
||||
var/initiator_ckey
|
||||
var/initiator_key_name
|
||||
|
||||
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
|
||||
|
||||
var/obj/effect/statclick/ticket/statclick
|
||||
|
||||
var/static/ticket_counter = 0
|
||||
|
||||
/*
|
||||
//call this on its own to create a ticket, don't manually assign current_mentorhelp
|
||||
//msg is the title of the ticket: usually the ahelp text
|
||||
/datum/mentor_help/New(msg, client/C)
|
||||
initiator_ckey = C.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_mentorhelp) //This is a bug
|
||||
log_debug("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_mentorhelp.Resolve()
|
||||
initiator.current_mentorhelp = src
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
log_admin("Mentorhelp: [key_name(C)]: [msg]")
|
||||
MessageNoRecipient(msg)
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, "<i><span class='mentor'>Mentor-PM to-<b>Mentors</b>: [name]</span></i>")
|
||||
GLOB.mhelp_tickets.active_tickets += src */
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Create a new Ticket.
|
||||
* Call this on its own to create a ticket, don't manually assign current_ticket
|
||||
*
|
||||
* required msg string The title of the ticket: usually the ahelp text
|
||||
* required C client The object or datum which owns the UI.
|
||||
* required is_bwoink boolean TRUE if this ticket was started by an admin PM
|
||||
* required level integer The level of the ticket. 0 = Admin, 1 = Mentor
|
||||
*/
|
||||
/datum/ticket/New(msg, client/C, is_bwoink, ticket_level)
|
||||
//clean the input msg
|
||||
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
|
||||
if(!msg || !C || !C.mob)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
id = ++ticket_counter
|
||||
opened_at = world.time
|
||||
|
||||
name = msg
|
||||
|
||||
level = ticket_level
|
||||
|
||||
initiator = C
|
||||
initiator_ckey = initiator.ckey
|
||||
initiator_key_name = key_name(initiator, FALSE, TRUE)
|
||||
if(initiator.current_ticket) //This is a bug
|
||||
log_debug("Multiple ahelp current_tickets")
|
||||
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
|
||||
initiator.current_ticket.Close()
|
||||
initiator.current_ticket = src
|
||||
|
||||
var/parsed_message = keywords_lookup(msg)
|
||||
|
||||
statclick = new(null, src)
|
||||
_interactions = list()
|
||||
|
||||
if(is_bwoink)
|
||||
AddInteraction("<font color='blue'>[key_name_admin(usr)] PM'd [LinkedReplyName()]</font>")
|
||||
message_admins("<font color='blue'>Ticket [TicketHref("#[id]")] created</font>")
|
||||
else
|
||||
MessageNoRecipient(parsed_message)
|
||||
send2adminchat() //VOREStation Add
|
||||
//show it to the person adminhelping too
|
||||
to_chat(C, "<span class='adminnotice'>PM to-<b>Admins</b>: [name]</span>")
|
||||
|
||||
//send it to irc if nobody is on and tell us how many were on
|
||||
var/admin_number_present = send2irc_adminless_only(initiator_ckey, name)
|
||||
log_admin("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
|
||||
if(admin_number_present <= 0)
|
||||
to_chat(C, "<span class='notice'>No active admins are online, your adminhelp was sent to the admin discord.</span>") //VOREStation Edit
|
||||
send2adminchat() //VOREStation Add
|
||||
//YW EDIT START
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/activemins = adm["present"]
|
||||
var activeMins = activemins.len
|
||||
if(is_bwoink)
|
||||
ahelp_discord_message("ADMINHELP: FROM: [key_name_admin(usr)] TO [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") //CHOMPEdit
|
||||
else
|
||||
ahelp_discord_message("ADMINHELP: FROM: [initiator_ckey]/[initiator_key_name] - MSG: **[msg]** - Heard by [activeMins] NON-AFK staff members.") //CHOMPEdit
|
||||
//YW EDIT END
|
||||
|
||||
// Also send it to discord since that's the hip cool thing now.
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) ticket opened.",
|
||||
"body" = "[key_name(initiator)] has opened a ticket. \n[msg]",
|
||||
"color" = COLOR_WEBHOOK_POOR
|
||||
)
|
||||
)
|
||||
|
||||
GLOB.tickets.active_tickets += src
|
||||
|
||||
// Open a new chat with the user
|
||||
//var/datum/ticket_chat/TC = new()
|
||||
//TC.T = src
|
||||
//TC.tgui_interact(C.mob)
|
||||
C.mob.throw_alert("open ticket", /obj/screen/alert/open_ticket)
|
||||
|
||||
/datum/ticket/Destroy()
|
||||
RemoveActive()
|
||||
GLOB.tickets.closed_tickets -= src
|
||||
GLOB.tickets.resolved_tickets -= src
|
||||
return ..()
|
||||
|
||||
/datum/ticket/proc/AddInteraction(formatted_message)
|
||||
var/curinteraction = "[gameTimestamp()]: [formatted_message]"
|
||||
if(config.discord_ahelps_all) //CHOMPEdit
|
||||
ahelp_discord_message("ADMINHELP: TICKETID:[id] [strip_html_properly(curinteraction)]") //CHOMPEdit
|
||||
_interactions += curinteraction
|
||||
|
||||
/datum/ticket/proc/TicketPanel()
|
||||
tgui_interact(usr.client.mob)
|
||||
|
||||
//private
|
||||
/datum/ticket/proc/FullMonty(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
if(initiator && initiator.mob)
|
||||
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
|
||||
else
|
||||
. = "Initiator disconnected."
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
|
||||
//private
|
||||
/datum/ticket/proc/ClosureLinks(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
|
||||
if(level == 0)
|
||||
. = " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=reject'>REJT</A>)"
|
||||
. += " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=icissue'>IC</A>)"
|
||||
. += " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=close'>CLOSE</A>)"
|
||||
. += " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=resolve'>RSLVE</A>)"
|
||||
. += " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=handleissue'>HANDLE</A>)"
|
||||
else
|
||||
. = " (<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=resolve'>RSLVE</A>)"
|
||||
|
||||
//private
|
||||
/datum/ticket/proc/LinkedReplyName(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A HREF='?_src_=holder;ticket=[ref_src];ticket_action=reply'>[initiator_key_name]</A>"
|
||||
|
||||
//private
|
||||
/datum/ticket/proc/TicketHref(msg, ref_src, action = "ticket")
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
return "<A HREF='?_src_=holder;ticket=[ref_src];[HrefToken(TRUE)];ticket_action=[action]'>[msg]</A>"
|
||||
|
||||
/*
|
||||
var/chat_msg = "<span class='notice'>(<A HREF='?_src_=mentorholder;mhelp=[ref_src];[HrefToken(TRUE)];mhelp_action=escalate'>ESCALATE</A>) Ticket [TicketHref("#[id]", ref_src)]<b>: [LinkedReplyName(ref_src)]:</b> [msg]</span>"
|
||||
*/
|
||||
|
||||
//message from the initiator without a target, all admins will see this
|
||||
//won't bug irc
|
||||
/datum/ticket/proc/MessageNoRecipient(msg)
|
||||
var/ref_src = "\ref[src]"
|
||||
var/chat_msg = "<span class='adminnotice'><span class='adminhelp'>Ticket [TicketHref("#[id]", ref_src)]</span><b>: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:</b> [msg]</span>"
|
||||
|
||||
AddInteraction("<font color='red'>[LinkedReplyName(ref_src)]: [msg]</font>")
|
||||
//send this msg to all admins
|
||||
|
||||
if(level == 1)
|
||||
for (var/client/C in GLOB.mentors)
|
||||
if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
for (var/client/C in GLOB.admins)
|
||||
if (C.is_preference_enabled(/datum/client_preference/play_mentorhelp_ping))
|
||||
C << 'sound/effects/mentorhelp.mp3'
|
||||
message_mentors(chat_msg)
|
||||
else if(level == 0)
|
||||
for(var/client/X in GLOB.admins)
|
||||
if(X.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping))
|
||||
X << 'sound/effects/adminhelp.ogg'
|
||||
window_flash(X)
|
||||
to_chat(X, chat_msg)
|
||||
|
||||
/*
|
||||
//Reopen a closed ticket
|
||||
/datum/mentor_help/proc/Reopen()
|
||||
switch(state)
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("mhelp_resolve")
|
||||
AddInteraction("<font color='purple'>Reopened by [usr.ckey]</font>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='purple'>Ticket [TicketHref("#[id]")] was reopened by [usr.ckey].</font></span>")
|
||||
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [usr.ckey].</span>"
|
||||
message_mentors(msg)
|
||||
log_admin(msg)
|
||||
*/
|
||||
|
||||
//Reopen a closed ticket
|
||||
/datum/ticket/proc/Reopen()
|
||||
if(state == AHELP_ACTIVE)
|
||||
to_chat(usr, "<span class='warning'>This ticket is already open.</span>")
|
||||
return
|
||||
|
||||
if(GLOB.tickets.CKey2ActiveTicket(initiator_ckey))
|
||||
to_chat(usr, "<span class='warning'>This user already has an active ticket, cannot reopen this one.</span>")
|
||||
return
|
||||
|
||||
statclick = new(null, src)
|
||||
GLOB.tickets.active_tickets += src
|
||||
GLOB.tickets.closed_tickets -= src
|
||||
GLOB.tickets.resolved_tickets -= src
|
||||
switch(state)
|
||||
if(AHELP_CLOSED)
|
||||
feedback_dec("ticket_close")
|
||||
if(AHELP_RESOLVED)
|
||||
feedback_dec("ticket_resolve")
|
||||
state = AHELP_ACTIVE
|
||||
closed_at = null
|
||||
if(initiator)
|
||||
initiator.current_ticket = src
|
||||
|
||||
AddInteraction("<font color='purple'>Reopened by [key_name_admin(usr)]</font>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='purple'>Ticket [TicketHref("#[id]")] was reopened by [key_name(usr,FALSE,FALSE)].</font></span>")
|
||||
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].</span>"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
feedback_inc("ticket_reopen")
|
||||
TicketPanel() //can only be done from here, so refresh it
|
||||
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) reopened.",
|
||||
"body" = "Reopened by [key_name(usr)]."
|
||||
)
|
||||
)
|
||||
|
||||
//private
|
||||
/datum/ticket/proc/RemoveActive()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
closed_at = world.time
|
||||
QDEL_NULL(statclick)
|
||||
GLOB.tickets.active_tickets -= src
|
||||
if(initiator && initiator.current_ticket == src)
|
||||
initiator.current_ticket = null
|
||||
|
||||
//Mark open ticket as closed/meme
|
||||
/datum/ticket/proc/Close(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_CLOSED
|
||||
GLOB.tickets.ListInsert(src)
|
||||
AddInteraction("<span class='filter_adminlog'><font color='red'>Closed by [key_name_admin(usr)].</font></span>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='red'>Ticket [TicketHref("#[id]")] was closed by [key_name(usr,FALSE,FALSE)].</font></span>")
|
||||
if(!silent)
|
||||
feedback_inc("ahelp_close")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name_admin(usr)]."
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) closed.",
|
||||
"body" = "Closed by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_BAD
|
||||
)
|
||||
)
|
||||
initiator.mob.clear_alert("open ticket")
|
||||
|
||||
//Mark open ticket as resolved/legitimate, returns ahelp verb
|
||||
/datum/ticket/proc/Resolve(silent = FALSE)
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
RemoveActive()
|
||||
state = AHELP_RESOLVED
|
||||
GLOB.tickets.ListInsert(src)
|
||||
|
||||
AddInteraction("<span class='filter_adminlog'><font color='green'>Resolved by [key_name_admin(usr)].</font></span>")
|
||||
if(initiator)
|
||||
to_chat(initiator, "<span class='filter_adminlog'><font color='green'>Ticket [TicketHref("#[id]")] was marked resolved by [key_name(usr,FALSE,FALSE)].</font></span>")
|
||||
if(!silent)
|
||||
feedback_inc("ticket_resolve")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name_admin(usr)]"
|
||||
if(type == 1)
|
||||
message_mentors(msg)
|
||||
else if (type == 0)
|
||||
message_admins(msg)
|
||||
|
||||
log_admin(msg)
|
||||
if(type == 1)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) resolved.",
|
||||
"body" = "Marked as Resolved by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_GOOD
|
||||
)
|
||||
)
|
||||
|
||||
//Close and return ahelp verb, use if ticket is incoherent
|
||||
/datum/ticket/proc/Reject(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
if(initiator)
|
||||
if(initiator.is_preference_enabled(/datum/client_preference/holder/play_adminhelp_ping))
|
||||
initiator << 'sound/effects/adminhelp.ogg'
|
||||
|
||||
to_chat(initiator, "<span class='filter_pm'><font color='red' size='4'><b>- AdminHelp Rejected! -</b></font><br>\
|
||||
<font color='red'><b>Your admin help was rejected.</b></font><br>\
|
||||
Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.</span>")
|
||||
|
||||
feedback_inc("ahelp_reject")
|
||||
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
AddInteraction("Rejected by [key_name_admin(usr)].")
|
||||
Close(silent = TRUE)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) rejected.",
|
||||
"body" = "Rejected by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_BAD
|
||||
)
|
||||
)
|
||||
|
||||
//Resolve ticket with IC Issue message
|
||||
/datum/ticket/proc/ICIssue(key_name = key_name_admin(usr))
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
var/msg = "<span class='filter_pm'><font color='red' size='4'><b>- AdminHelp marked as IC issue! -</b></font><br>"
|
||||
msg += "<font color='red'><b>This is something that can be solved ICly, and does not currently require staff intervention.</b></font><br>"
|
||||
msg += "<font color='red'>Your AdminHelp may also be unanswerable due to ongoing events.</font></span>"
|
||||
|
||||
if(initiator)
|
||||
to_chat(initiator, msg)
|
||||
|
||||
feedback_inc("ahelp_icissue")
|
||||
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
AddInteraction("Marked as IC issue by [key_name_admin(usr)]")
|
||||
Resolve(silent = TRUE)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) marked as IC issue.",
|
||||
"body" = "Marked as IC Issue by [key_name(usr)].",
|
||||
"color" = COLOR_WEBHOOK_BAD
|
||||
)
|
||||
)
|
||||
|
||||
//Resolve ticket with IC Issue message
|
||||
/datum/ticket/proc/HandleIssue()
|
||||
if(state != AHELP_ACTIVE)
|
||||
return
|
||||
|
||||
if(handler == key_name(usr, FALSE, TRUE))
|
||||
to_chat("<font color='red'>You are already handling this ticket.</font>")
|
||||
return
|
||||
|
||||
var/msg = "<font color='red'>Your AdminHelp is being handled by [key_name(usr,FALSE,FALSE)] please be patient.</font>"
|
||||
|
||||
if(initiator)
|
||||
to_chat(initiator, msg)
|
||||
|
||||
feedback_inc("ahelp_handling")
|
||||
msg = "Ticket [TicketHref("#[id]")] being handled by [key_name(usr,FALSE,FALSE)]"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
AddInteraction("[key_name_admin(usr)] is now handling this ticket.")
|
||||
handler = key_name(usr, FALSE, TRUE)
|
||||
SSwebhooks.send(
|
||||
WEBHOOK_AHELP_SENT,
|
||||
list(
|
||||
"name" = "Ticket ([id]) (Game ID: [game_id]) being handled.",
|
||||
"body" = "[key_name(usr)] is now handling the ticket."
|
||||
)
|
||||
)
|
||||
|
||||
/datum/ticket/proc/Retitle()
|
||||
var/new_title = tgui_input_text(usr, "Enter a title for the ticket", "Rename Ticket", name)
|
||||
if(new_title)
|
||||
name = new_title
|
||||
//not saying the original name cause it could be a long ass message
|
||||
var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
TicketPanel() //we have to be here to do this
|
||||
|
||||
//Kick ticket to next level
|
||||
/datum/ticket/proc/Escalate()
|
||||
if(tgui_alert(usr, "Really escalate this ticket to admins? No mentors will ever be able to interact with it again if you do.","Escalate",list("Yes","No")) != "Yes")
|
||||
return
|
||||
if (src.initiator == null) // You can't escalate a mentorhelp of someone who's logged out because it won't create the adminhelp properly
|
||||
to_chat(usr, "<span class='pm warning'>Error: client not found, unable to escalate.</span>")
|
||||
return
|
||||
|
||||
level = level - 1
|
||||
|
||||
message_mentors("[usr.ckey] escalated Ticket [TicketHref("#[id]")]")
|
||||
log_admin("[key_name(usr)] escalated ticket [src.name]")
|
||||
to_chat(src.initiator, "<span class='mentor'>[usr.ckey] escalated your ticket to admins.</span>")
|
||||
|
||||
/datum/ticket/proc/Context(ref_src)
|
||||
if(!ref_src)
|
||||
ref_src = "\ref[src]"
|
||||
if(state == AHELP_ACTIVE)
|
||||
. += ClosureLinks(ref_src)
|
||||
if(state != AHELP_RESOLVED)
|
||||
. += " (<A HREF='?_src_=mentorholder;mhelp=[ref_src];mhelp_action=escalate'>ESCALATE</A>)"
|
||||
|
||||
//Forwarded action from admin/Topic
|
||||
/datum/ticket/proc/Action(action)
|
||||
testing("Ahelp action: [action]")
|
||||
switch(action)
|
||||
if("ticket")
|
||||
TicketPanel()
|
||||
if("retitle")
|
||||
Retitle()
|
||||
if("reject")
|
||||
Reject()
|
||||
if("reply")
|
||||
usr.client.cmd_ahelp_reply(initiator)
|
||||
if("icissue")
|
||||
ICIssue()
|
||||
if("close")
|
||||
Close()
|
||||
if("resolve")
|
||||
Resolve()
|
||||
if("handleissue")
|
||||
HandleIssue()
|
||||
if("reopen")
|
||||
Reopen()
|
||||
if("escalate")
|
||||
Escalate()
|
||||
|
||||
//
|
||||
// TICKET STATCLICK
|
||||
//
|
||||
|
||||
/obj/effect/statclick/ticket
|
||||
var/datum/ticket/ticket_datum
|
||||
|
||||
/obj/effect/statclick/ticket/New(loc, datum/ticket/T)
|
||||
ticket_datum = T
|
||||
..(loc)
|
||||
|
||||
/obj/effect/statclick/ticket/update()
|
||||
return ..(ticket_datum.name)
|
||||
|
||||
/obj/effect/statclick/ticket/Click()
|
||||
ticket_datum.TicketPanel()
|
||||
|
||||
/obj/effect/statclick/ticket/Destroy()
|
||||
ticket_datum = null
|
||||
return ..()
|
||||
|
||||
//
|
||||
// LOGGING
|
||||
//
|
||||
|
||||
//Use this proc when an admin takes action that may be related to an open ticket on what
|
||||
//what can be a client, ckey, or mob
|
||||
/proc/admin_ticket_log(what, message)
|
||||
var/client/C
|
||||
var/mob/Mob = what
|
||||
if(istype(Mob))
|
||||
C = Mob.client
|
||||
else
|
||||
C = what
|
||||
if(istype(C) && C.current_ticket)
|
||||
C.current_ticket.AddInteraction(message)
|
||||
return C.current_ticket
|
||||
if(istext(what)) //ckey
|
||||
var/datum/ticket/T = GLOB.tickets.CKey2ActiveTicket(what)
|
||||
if(T)
|
||||
T.AddInteraction(message)
|
||||
return T
|
||||
|
||||
//
|
||||
// HELPER PROCS
|
||||
//
|
||||
|
||||
/proc/get_admin_counts(requiredflags = R_BAN)
|
||||
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
|
||||
for(var/client/X in GLOB.admins)
|
||||
.["total"] += X
|
||||
if(requiredflags != 0 && !check_rights(rights_required = requiredflags, show_msg = FALSE, C = X))
|
||||
.["noflags"] += X
|
||||
else if(X.is_afk())
|
||||
.["afk"] += X
|
||||
else if(X.holder.fakekey)
|
||||
.["stealth"] += X
|
||||
else
|
||||
.["present"] += X
|
||||
|
||||
/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
|
||||
var/list/adm = get_admin_counts()
|
||||
var/list/activemins = adm["present"]
|
||||
. = activemins.len
|
||||
if(. <= 0)
|
||||
var/final = ""
|
||||
var/list/afkmins = adm["afk"]
|
||||
var/list/stealthmins = adm["stealth"]
|
||||
var/list/powerlessmins = adm["noflags"]
|
||||
var/list/allmins = adm["total"]
|
||||
if(!afkmins.len && !stealthmins.len && !powerlessmins.len)
|
||||
final = "[msg] - No admins online"
|
||||
else
|
||||
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
|
||||
send2irc(source,final)
|
||||
|
||||
/proc/ircadminwho()
|
||||
var/list/message = list("Admins: ")
|
||||
var/list/admin_keys = list()
|
||||
for(var/client/C as anything in GLOB.admins)
|
||||
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
|
||||
|
||||
for(var/admin in admin_keys)
|
||||
if(LAZYLEN(admin_keys) > 1)
|
||||
message += ", [admin]"
|
||||
else
|
||||
message += "[admin]"
|
||||
|
||||
return jointext(message, "")
|
||||
|
||||
/proc/keywords_lookup(msg,irc)
|
||||
|
||||
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
|
||||
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
|
||||
|
||||
//explode the input msg into a list
|
||||
var/list/msglist = splittext(msg, " ")
|
||||
|
||||
//generate keywords lookup
|
||||
var/list/surnames = list()
|
||||
var/list/forenames = list()
|
||||
var/list/ckeys = list()
|
||||
var/founds = ""
|
||||
for(var/mob/M in mob_list)
|
||||
var/list/indexing = list(M.real_name, M.name)
|
||||
if(M.mind)
|
||||
indexing += M.mind.name
|
||||
|
||||
for(var/string in indexing)
|
||||
var/list/L = splittext(string, " ")
|
||||
var/surname_found = 0
|
||||
//surnames
|
||||
for(var/i=L.len, i>=1, i--)
|
||||
var/word = ckey(L[i])
|
||||
if(word)
|
||||
surnames[word] = M
|
||||
surname_found = i
|
||||
break
|
||||
//forenames
|
||||
for(var/i=1, i<surname_found, i++)
|
||||
var/word = ckey(L[i])
|
||||
if(word)
|
||||
forenames[word] = M
|
||||
//ckeys
|
||||
ckeys[M.ckey] = M
|
||||
|
||||
var/ai_found = 0
|
||||
msg = ""
|
||||
var/list/mobs_found = list()
|
||||
for(var/original_word in msglist)
|
||||
var/word = ckey(original_word)
|
||||
if(word)
|
||||
if(!(word in adminhelp_ignored_words))
|
||||
if(word == "ai")
|
||||
ai_found = 1
|
||||
else
|
||||
var/mob/found = ckeys[word]
|
||||
if(!found)
|
||||
found = surnames[word]
|
||||
if(!found)
|
||||
found = forenames[word]
|
||||
if(found)
|
||||
if(!(found in mobs_found))
|
||||
mobs_found += found
|
||||
if(!ai_found && isAI(found))
|
||||
ai_found = 1
|
||||
var/is_antag = 0
|
||||
if(found.mind && found.mind.special_role)
|
||||
is_antag = 1
|
||||
founds += "Name: [found.name]([found.real_name]) Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
|
||||
msg += "[original_word]<font size='1' color='[is_antag ? "red" : "black"]'>(<A HREF='?_src_=holder;adminmoreinfo=\ref[found]'>?</A>|<A HREF='?_src_=holder;adminplayerobservefollow=\ref[found]'>F</A>)</font> "
|
||||
continue
|
||||
msg += "[original_word] "
|
||||
if(irc)
|
||||
if(founds == "")
|
||||
return "Search Failed"
|
||||
else
|
||||
return founds
|
||||
|
||||
return msg
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
//PLAYERSIDE TICKET UI
|
||||
//
|
||||
|
||||
/datum/ticket_chat
|
||||
var/datum/ticket/T
|
||||
|
||||
/datum/ticket_chat/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "TicketChat", "Ticket #[T.id] - [T.LinkedReplyName("\ref[T]")]")
|
||||
ui.open()
|
||||
user.clear_alert("open ticket")
|
||||
|
||||
/datum/ticket_chat/tgui_close(mob/user)
|
||||
. = ..()
|
||||
if(user.client.current_ticket)
|
||||
user.throw_alert("open ticket", /obj/screen/alert/open_ticket)
|
||||
|
||||
/datum/ticket_chat/tgui_state(mob/user)
|
||||
return GLOB.tgui_ticket_state
|
||||
|
||||
/datum/ticket_chat/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = T.id
|
||||
|
||||
data["level"] = T.level
|
||||
data["handler"] = T.handler
|
||||
|
||||
data["log"] = T._interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/ticket_chat/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("send_msg")
|
||||
if(params["msg"])
|
||||
return
|
||||
|
||||
usr.client.cmd_admin_pm(usr.client, sanitize(params["msg"]), T)
|
||||
. = TRUE
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
//TICKET MANAGER
|
||||
//
|
||||
|
||||
/datum/tickets/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "TicketsPanel", "Tickets")
|
||||
ui.open()
|
||||
|
||||
/datum/tickets/tgui_state(mob/user)
|
||||
return GLOB.tgui_admin_state
|
||||
|
||||
/datum/tickets/proc/get_ticket_state(state)
|
||||
var/ticket_state
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
ticket_state = "open"
|
||||
// TODO: Mentor tickets cannot be resolved
|
||||
if(AHELP_RESOLVED)
|
||||
ticket_state = "resolved"
|
||||
if(AHELP_CLOSED)
|
||||
ticket_state = "closed"
|
||||
else
|
||||
ticket_state = "unknown"
|
||||
|
||||
return ticket_state
|
||||
|
||||
/datum/tickets/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
var/list/tickets = list()
|
||||
|
||||
var/selected_ticket = null
|
||||
|
||||
if(user.client.selected_ticket)
|
||||
var/datum/ticket/T = user.client.selected_ticket
|
||||
selected_ticket = list(
|
||||
"id" = T.id,
|
||||
"name" = T.LinkedReplyName(),
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
"actions" = T.FullMonty(),
|
||||
"log" = T._interactions,
|
||||
)
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.active_tickets)
|
||||
if(user.client.holder || (has_mentor_powers(user.client) && T.level > 0))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.closed_tickets)
|
||||
if(user.client.holder || (has_mentor_powers(user.client) && T.level > 0))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.resolved_tickets)
|
||||
if(user.client.holder || (has_mentor_powers(user.client) && T.level > 0))
|
||||
tickets.Add(list(list(
|
||||
"id" = T.id,
|
||||
"name" = T.initiator_key_name,
|
||||
"state" = get_ticket_state(T.state),
|
||||
"level" = T.level,
|
||||
"handler" = T.handler,
|
||||
"opened_at" = (world.time - T.opened_at),
|
||||
"closed_at" = (world.time - T.closed_at),
|
||||
"opened_at_date" = gameTimestamp(wtime = T.opened_at),
|
||||
"closed_at_date" = gameTimestamp(wtime = T.closed_at),
|
||||
)))
|
||||
data["tickets"] = tickets
|
||||
|
||||
data["selected_ticket"] = selected_ticket
|
||||
|
||||
return data
|
||||
|
||||
/datum/tickets/tgui_act(action, params, datum/tgui/ui)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("legacy")
|
||||
var/choice = tgui_input_list(usr, "Which tickets do you want to list?", "Tickets", list("Active", "Closed", "Resolved"))
|
||||
TicketListLegacy(choice)
|
||||
. = TRUE
|
||||
if("new_ticket")
|
||||
var/list/ckeys
|
||||
for(var/client/C in GLOB.clients)
|
||||
ckeys += C.ckey
|
||||
|
||||
var/ckey = tgui_input_list(usr, "Please select the ckey of the user.", "Select CKEY", ckeys)
|
||||
if(!ckey)
|
||||
return
|
||||
|
||||
var/client/player
|
||||
for(var/client/C in GLOB.clients)
|
||||
if(C.ckey == ckey)
|
||||
player = C
|
||||
|
||||
if(!player)
|
||||
to_chat(usr, "<span class='warning'>Ckey ([ckey]) not online.</span>")
|
||||
return
|
||||
|
||||
var/msg = tgui_input_text(usr, "What should the initial text be?", "New Ticket")
|
||||
if(!msg)
|
||||
to_chat(usr, "<span class='warning'>Ticket message cannot be empty.</span>")
|
||||
return
|
||||
|
||||
var/level = tgui_input_text(usr, "What level is the ticket? 0 = ADMIN, 1 = MENTOR", "Ticket Level")
|
||||
if(level != "0" && level != "1")
|
||||
to_chat(usr, "<span class='warning'>Invalid ticket level: [level].</span>")
|
||||
return
|
||||
|
||||
feedback_add_details("admin_verb","Admincreatedticket") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(player.current_ticket)
|
||||
if(tgui_alert(usr, "The player already has a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No")) != "No")
|
||||
if(player.current_ticket)
|
||||
player.current_ticket.MessageNoRecipient(msg)
|
||||
to_chat(usr, "<span class='adminnotice'>PM to-<b>Admins</b>: [msg]</span>")
|
||||
return
|
||||
else
|
||||
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
|
||||
else
|
||||
player.current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
|
||||
player.current_ticket.Close()
|
||||
|
||||
new /datum/ticket(msg, player, TRUE, level)
|
||||
. = TRUE
|
||||
if("pick_ticket")
|
||||
var/datum/ticket/T = ID2Ticket(params["ticket_id"])
|
||||
usr.client.selected_ticket = T
|
||||
. = TRUE
|
||||
if("retitle_ticket")
|
||||
usr.client.selected_ticket.Retitle()
|
||||
. = TRUE
|
||||
if("reopen_ticket")
|
||||
usr.client.selected_ticket.Reopen()
|
||||
. = TRUE
|
||||
if("undock_ticket")
|
||||
usr.client.selected_ticket.tgui_interact(usr)
|
||||
usr.client.selected_ticket = null
|
||||
. = TRUE
|
||||
|
||||
/datum/tickets/tgui_fallback(payload)
|
||||
if(..())
|
||||
return
|
||||
|
||||
var/choice = tgui_input_list(usr, "Which tickets do you want to list?", "Tickets", list("Active", "Closed", "Resolved"))
|
||||
|
||||
TicketListLegacy(choice)
|
||||
|
||||
//
|
||||
//TICKET DATUM
|
||||
//
|
||||
|
||||
/datum/ticket/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Ticket", "Ticket #[id] - [LinkedReplyName("\ref[src]")]")
|
||||
ui.open()
|
||||
|
||||
/datum/ticket/tgui_state(mob/user)
|
||||
return GLOB.tgui_admin_state
|
||||
|
||||
/datum/ticket/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["id"] = id
|
||||
|
||||
var/ref_src = "\ref[src]"
|
||||
data["title"] = name
|
||||
data["name"] = LinkedReplyName(ref_src)
|
||||
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
data["state"] = "open"
|
||||
// TODO: Mentor tickets cannot be resolved
|
||||
if(AHELP_RESOLVED)
|
||||
data["state"] = "resolved"
|
||||
if(AHELP_CLOSED)
|
||||
data["state"] = "closed"
|
||||
else
|
||||
data["state"] = "unknown"
|
||||
|
||||
data["level"] = level
|
||||
data["handler"] = handler
|
||||
|
||||
data["opened_at"] = (world.time - opened_at)
|
||||
data["closed_at"] = (world.time - closed_at)
|
||||
data["opened_at_date"] = gameTimestamp(wtime = opened_at)
|
||||
data["closed_at_date"] = gameTimestamp(wtime = closed_at)
|
||||
|
||||
data["actions"] = FullMonty(ref_src)
|
||||
|
||||
data["log"] = _interactions
|
||||
|
||||
return data
|
||||
|
||||
/datum/ticket/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("retitle")
|
||||
Retitle()
|
||||
. = TRUE
|
||||
if("reopen")
|
||||
Reopen()
|
||||
. = TRUE
|
||||
if("legacy")
|
||||
TicketPanelLegacy()
|
||||
. = TRUE
|
||||
|
||||
/datum/ticket/tgui_fallback(payload)
|
||||
if(..())
|
||||
return
|
||||
|
||||
TicketPanelLegacy()
|
||||
|
||||
/*
|
||||
/datum/mentor_help/proc/TicketPanelLegacy()
|
||||
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Mentor Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += "<b>State: "
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += "<font color='red'>OPEN</font>"
|
||||
if(AHELP_RESOLVED)
|
||||
dat += "<font color='green'>RESOLVED</font>"
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "</b>[GLOB.TAB][TicketHref("Refresh", ref_src)]"
|
||||
if(state != AHELP_ACTIVE)
|
||||
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
|
||||
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += "<b>Actions:</b> [Context(ref_src)]<br>"
|
||||
else
|
||||
dat += "<b>DISCONNECTED</b>[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br><b>Log:</b><br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
usr << browse(dat.Join(), "window=mhelp[id];size=620x480") */
|
||||
|
||||
/datum/ticket/proc/TicketPanelLegacy()
|
||||
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
|
||||
var/ref_src = "\ref[src]"
|
||||
dat += "<h4>Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
|
||||
dat += "<b>State: "
|
||||
switch(state)
|
||||
if(AHELP_ACTIVE)
|
||||
dat += "<font color='red'>OPEN</font>"
|
||||
if(AHELP_RESOLVED)
|
||||
dat += "<font color='green'>RESOLVED</font>"
|
||||
if(AHELP_CLOSED)
|
||||
dat += "CLOSED"
|
||||
else
|
||||
dat += "UNKNOWN"
|
||||
dat += "</b>[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]"
|
||||
if(state != AHELP_ACTIVE)
|
||||
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
|
||||
dat += "<br><br>Opened at: [gameTimestamp(wtime = opened_at)] (Approx [(world.time - opened_at) / 600] minutes ago)"
|
||||
if(closed_at)
|
||||
dat += "<br>Closed at: [gameTimestamp(wtime = closed_at)] (Approx [(world.time - closed_at) / 600] minutes ago)"
|
||||
dat += "<br><br>"
|
||||
if(initiator)
|
||||
dat += "<b>Actions:</b> [FullMonty(ref_src)]<br>"
|
||||
else
|
||||
dat += "<b>DISCONNECTED</b>[GLOB.TAB][ClosureLinks(ref_src)]<br>"
|
||||
dat += "<br><b>Log:</b><br><br>"
|
||||
for(var/I in _interactions)
|
||||
dat += "[I]<br>"
|
||||
|
||||
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
|
||||
|
||||
/datum/tickets/proc/TicketListLegacy(var/state)
|
||||
var/list/dat = list("<html><head><title>[state] Tickets</title></head>")
|
||||
var/tickets_found = 0
|
||||
|
||||
if(state == "Active")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.active_tickets)
|
||||
dat += "[T.level == 0 ? "Adminhelp" : "Mentorhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
else if(state == "Closed")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.closed_tickets)
|
||||
dat += "[T.level == 0 ? "Adminhelp" : "Mentorhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
else if(state == "Resolved")
|
||||
for(var/datum/ticket/T as anything in GLOB.tickets.resolved_tickets)
|
||||
dat += "[T.level == 0 ? "Adminhelp" : "Mentorhelp"] - [T.TicketHref("#[T.id] - [T.initiator_ckey]: [T.name]")]"
|
||||
tickets_found++
|
||||
|
||||
if(tickets_found == 0)
|
||||
dat += "No [state] tickets found."
|
||||
|
||||
usr << browse(dat.Join(), "window=ahelp-list;size=250x350")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@@ -4,10 +4,10 @@
|
||||
"version": "4.4.1",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"glob": "^8.0.3",
|
||||
"axios": "^0.21.1",
|
||||
"glob": "^7.1.7",
|
||||
"source-map": "^0.7.3",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"ws": "^8.6.0"
|
||||
"ws": "^7.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const State = {
|
||||
'open': 'Open',
|
||||
'resolved': 'Resolved',
|
||||
'unknown': 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const MentorTicketPanel = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
name,
|
||||
state,
|
||||
opened_at,
|
||||
closed_at,
|
||||
opened_at_date,
|
||||
closed_at_date,
|
||||
actions,
|
||||
log,
|
||||
} = data;
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button
|
||||
icon="arrow-up"
|
||||
content="Escalate"
|
||||
onClick={() => act('escalate')}
|
||||
/>{' '}
|
||||
<Button content="Legacy UI" onClick={() => act('legacy')} />
|
||||
</Box>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Mentor Help Ticket">
|
||||
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{State[state]}</LabeledList.Item>
|
||||
{State[state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{opened_at_date} ({Math.round((opened_at / 600) * 10) / 10}{' '}
|
||||
minutes ago.)
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{closed_at_date} ({Math.round((closed_at / 600) * 10) / 10}{' '}
|
||||
minutes ago.){' '}
|
||||
<Button content="Reopen" onClick={() => act('reopen')} />
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div dangerouslySetInnerHTML={{ __html: actions }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log">
|
||||
{Object.keys(log).map((L) => (
|
||||
<div dangerouslySetInnerHTML={{ __html: log[L] }} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
+26
-3
@@ -3,6 +3,22 @@ import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const Level = {
|
||||
0: 'Adminhelp',
|
||||
1: 'Mentorhelp',
|
||||
2: 'GM Request',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'red',
|
||||
1: 'green',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
'example': 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
'open': 'Open',
|
||||
'resolved': 'Resolved',
|
||||
@@ -15,6 +31,8 @@ type Data = {
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
level: number;
|
||||
handler: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
@@ -23,13 +41,15 @@ type Data = {
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const AdminTicketPanel = (props, context) => {
|
||||
export const Ticket = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
name,
|
||||
state,
|
||||
level,
|
||||
handler,
|
||||
opened_at,
|
||||
closed_at,
|
||||
opened_at_date,
|
||||
@@ -49,14 +69,17 @@ export const AdminTicketPanel = (props, context) => {
|
||||
content="Rename Ticket"
|
||||
onClick={() => act('retitle')}
|
||||
/>{' '}
|
||||
<Button content="Legacy UI" onClick={() => act('legacy')} />
|
||||
<Button content="Legacy UI" onClick={() => act('legacy')} />{' '}
|
||||
<Button content={Level[level]} color={LevelColor[level]} />
|
||||
</Box>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Admin Help Ticket">
|
||||
<LabeledList.Item label="Ticket ID">
|
||||
#{id}: <div dangerouslySetInnerHTML={{ __html: name }} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">{Level[level]}</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{State[state]}</LabeledList.Item>
|
||||
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
|
||||
{State[state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{opened_at_date} ({Math.round((opened_at / 600) * 10) / 10}{' '}
|
||||
@@ -0,0 +1,101 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { KEY_ENTER } from '../../common/keycodes';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Divider, Flex, Input, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const Level = {
|
||||
0: 'Adminhelp',
|
||||
1: 'Mentorhelp',
|
||||
2: 'GM Request',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'red',
|
||||
1: 'green',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
'example': 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
'open': 'Open',
|
||||
'resolved': 'Resolved',
|
||||
'closed': 'Closed',
|
||||
'unknown': 'Unknown',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
id: number;
|
||||
level: number;
|
||||
handler: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export const TicketChat = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const [ticketChat, setTicketChat] = useLocalState(context, 'ticketChat', '');
|
||||
const { id, level, handler, log } = data;
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title={'Ticket #' + id}
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button content={Level[level]} color={LevelColor[level]} />
|
||||
</Box>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Assignee">{handler}</LabeledList.Item>
|
||||
<LabeledList.Item label="Log" />
|
||||
</LabeledList>
|
||||
<Divider />
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
<Flex>
|
||||
<Flex.Item grow>
|
||||
<Input
|
||||
fluid
|
||||
placeholder="Enter a message..."
|
||||
value={ticketChat}
|
||||
onInput={(e, value) => setTicketChat(value)}
|
||||
onKeyDown={(event) => {
|
||||
const keyCode = window.event
|
||||
? event.which
|
||||
: event.keyCode;
|
||||
if (keyCode === KEY_ENTER) {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
content="Send"
|
||||
onClick={() => {
|
||||
act('send_msg', { msg: ticketChat });
|
||||
setTicketChat('');
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Divider />
|
||||
<Flex.Item>
|
||||
{Object.keys(log)
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map((L) => (
|
||||
<div dangerouslySetInnerHTML={{ __html: log[L] }} />
|
||||
))}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
/* eslint react/no-danger: "off" */
|
||||
import { BooleanLike } from '../../common/react';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Divider, Dropdown, Flex, Icon, LabeledList, Section, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const Level = {
|
||||
0: 'Admin',
|
||||
1: 'Mentor',
|
||||
2: 'All Levels',
|
||||
};
|
||||
|
||||
const LevelColor = {
|
||||
0: 'red',
|
||||
1: 'green',
|
||||
2: 'pink',
|
||||
};
|
||||
|
||||
const Tag = {
|
||||
'example': 'Example',
|
||||
};
|
||||
|
||||
const State = {
|
||||
'open': 'Open',
|
||||
'resolved': 'Resolved',
|
||||
'closed': 'Closed',
|
||||
'unknown': 'Unknown',
|
||||
'all': 'All States',
|
||||
};
|
||||
|
||||
const StateColor = {
|
||||
'open': 'white',
|
||||
'resolved': 'green',
|
||||
'closed': 'grey',
|
||||
'unknown': 'orange',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
tickets: Ticket[];
|
||||
|
||||
selected_ticket: Ticket;
|
||||
};
|
||||
|
||||
type Ticket = {
|
||||
id: number;
|
||||
title: string;
|
||||
name: string;
|
||||
state: string;
|
||||
level: number;
|
||||
handler: string;
|
||||
opened_at: number;
|
||||
closed_at: number;
|
||||
opened_at_date: string;
|
||||
closed_at_date: string;
|
||||
actions: string;
|
||||
log: string[];
|
||||
|
||||
ref: string;
|
||||
selected: BooleanLike;
|
||||
};
|
||||
|
||||
const getFilteredTickets = (
|
||||
tickets: Ticket[],
|
||||
state: string,
|
||||
level: number
|
||||
): Ticket[] => {
|
||||
let result: Ticket[] = [];
|
||||
|
||||
tickets.forEach((t) => {
|
||||
if (
|
||||
(t.state === state || state === 'all') &&
|
||||
(t.level === level || level === 2)
|
||||
) {
|
||||
result.push(t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const TicketsPanel = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { tickets, selected_ticket } = data;
|
||||
|
||||
const [stateFilter, setStateFilter] = useLocalState(
|
||||
context,
|
||||
'stateFilter',
|
||||
'open'
|
||||
);
|
||||
const [levelFilter, setLevelFilter] = useLocalState(
|
||||
context,
|
||||
'levelFilter',
|
||||
2
|
||||
);
|
||||
|
||||
let filtered_tickets = getFilteredTickets(tickets, stateFilter, levelFilter);
|
||||
return (
|
||||
<Window width={900} height={600}>
|
||||
<Window.Content>
|
||||
<Flex>
|
||||
<Flex.Item shrink>
|
||||
<Section title="Filter">
|
||||
<Dropdown
|
||||
width="100%"
|
||||
maxHeight="160px"
|
||||
options={Object.values(State)}
|
||||
selected={State[stateFilter]}
|
||||
onSelected={(val) =>
|
||||
setStateFilter(
|
||||
Object.keys(State)[Object.values(State).indexOf(val)]
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<Dropdown
|
||||
width="100%"
|
||||
maxHeight="160px"
|
||||
options={Object.values(Level)}
|
||||
selected={Level[levelFilter]}
|
||||
onSelected={(val) =>
|
||||
setLevelFilter(Object.values(Level).indexOf(val))
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
<Section title="Tickets" scrollable>
|
||||
<Tabs vertical>
|
||||
<Tabs.Tab onClick={() => act('new_ticket')}>
|
||||
New Ticket
|
||||
<Icon name="plus" ml={0.5} />
|
||||
</Tabs.Tab>
|
||||
<Divider />
|
||||
{filtered_tickets.map((ticket) => (
|
||||
<Tabs.Tab
|
||||
key={ticket.id}
|
||||
selected={ticket.id === selected_ticket?.id}
|
||||
onClick={() =>
|
||||
act('pick_ticket', { ticket_id: ticket.id })
|
||||
}>
|
||||
<Box inline>
|
||||
<Box>
|
||||
<Button
|
||||
content={Level[ticket.level]}
|
||||
color={LevelColor[ticket.level]}
|
||||
/>{' '}
|
||||
{ticket.name}
|
||||
</Box>
|
||||
<Box fontSize={0.9} textColor={StateColor[ticket.state]}>
|
||||
State: {State[ticket.state]} | Assignee:{' '}
|
||||
{ticket.handler}
|
||||
</Box>
|
||||
</Box>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow>
|
||||
{(selected_ticket && (
|
||||
<Section
|
||||
title={'Ticket #' + selected_ticket.id}
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button
|
||||
icon="arrow-up"
|
||||
content="Undock"
|
||||
onClick={() => act('undock_ticket')}
|
||||
/>{' '}
|
||||
<Button
|
||||
icon="pen"
|
||||
content="Rename Ticket"
|
||||
onClick={() => act('retitle_ticket')}
|
||||
/>{' '}
|
||||
<Button content="Legacy UI" onClick={() => act('legacy')} />{' '}
|
||||
<Button
|
||||
content={Level[selected_ticket.level]}
|
||||
color={LevelColor[selected_ticket.level]}
|
||||
/>
|
||||
</Box>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Ticket ID">
|
||||
#{selected_ticket.id}:{' '}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: selected_ticket.name }}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">
|
||||
{Level[selected_ticket.level]}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="State">
|
||||
{State[selected_ticket.state]}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Assignee">
|
||||
{selected_ticket.handler}
|
||||
</LabeledList.Item>
|
||||
{State[selected_ticket.state] === State.open ? (
|
||||
<LabeledList.Item label="Opened At">
|
||||
{selected_ticket.opened_at_date} (
|
||||
{Math.round((selected_ticket.opened_at / 600) * 10) / 10}{' '}
|
||||
minutes ago.)
|
||||
</LabeledList.Item>
|
||||
) : (
|
||||
<LabeledList.Item label="Closed At">
|
||||
{selected_ticket.closed_at_date} (
|
||||
{Math.round((selected_ticket.closed_at / 600) * 10) / 10}{' '}
|
||||
minutes ago.){' '}
|
||||
<Button
|
||||
content="Reopen"
|
||||
onClick={() => act('reopen_ticket')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Actions">
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: selected_ticket.actions,
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Log">
|
||||
{Object.keys(selected_ticket.log).map((L) => (
|
||||
<Box
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: selected_ticket.log[L],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
)) || (
|
||||
<Section
|
||||
title="No ticket selected"
|
||||
buttons={
|
||||
<Box nowrap>
|
||||
<Button
|
||||
disabled
|
||||
icon="arrow-up"
|
||||
content="Undock"
|
||||
onClick={() => act('undock_ticket')}
|
||||
/>{' '}
|
||||
<Button
|
||||
disabled
|
||||
icon="pen"
|
||||
content="Rename Ticket"
|
||||
onClick={() => act('retitle_ticket')}
|
||||
/>{' '}
|
||||
<Button content="Legacy UI" onClick={() => act('legacy')} />
|
||||
</Box>
|
||||
}>
|
||||
Please select a ticket on the left to view its details.
|
||||
</Section>
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
+15
-38
@@ -2983,13 +2983,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^0.27.2":
|
||||
version: 0.27.2
|
||||
resolution: "axios@npm:0.27.2"
|
||||
"axios@npm:^0.21.1":
|
||||
version: 0.21.4
|
||||
resolution: "axios@npm:0.21.4"
|
||||
dependencies:
|
||||
follow-redirects: ^1.14.9
|
||||
form-data: ^4.0.0
|
||||
checksum: 38cb7540465fe8c4102850c4368053c21683af85c5fdf0ea619f9628abbcb59415d1e22ebc8a6390d2bbc9b58a9806c874f139767389c862ec9b772235f06854
|
||||
follow-redirects: ^1.14.0
|
||||
checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4059,9 +4058,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.4.431":
|
||||
version: 1.4.433
|
||||
resolution: "electron-to-chromium@npm:1.4.433"
|
||||
checksum: 106e3bc2fb4ee5eddd4b141363900d5cd731c7579aa6bebd02509c52d6b598a1684aba1b75791e838dfa54dec0a40ddd17ea01199041ea46310aafb206395e43
|
||||
version: 1.4.434
|
||||
resolution: "electron-to-chromium@npm:1.4.434"
|
||||
checksum: b35ef09571167e65ba867f7d7c3fc3ed7696ccd82313c172fd400e8ada1568a9f310791854ff0b57abbe8869abacae71d32b2f9e9460b2f4ef1eff86661ad075
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4918,7 +4917,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.14.9":
|
||||
"follow-redirects@npm:^1.14.0":
|
||||
version: 1.15.2
|
||||
resolution: "follow-redirects@npm:1.15.2"
|
||||
peerDependenciesMeta:
|
||||
@@ -5162,7 +5161,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0":
|
||||
"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.0":
|
||||
version: 7.2.3
|
||||
resolution: "glob@npm:7.2.3"
|
||||
dependencies:
|
||||
@@ -5176,19 +5175,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^8.0.3":
|
||||
version: 8.1.0
|
||||
resolution: "glob@npm:8.1.0"
|
||||
dependencies:
|
||||
fs.realpath: ^1.0.0
|
||||
inflight: ^1.0.4
|
||||
inherits: 2
|
||||
minimatch: ^5.0.1
|
||||
once: ^1.3.0
|
||||
checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:^11.1.0":
|
||||
version: 11.12.0
|
||||
resolution: "globals@npm:11.12.0"
|
||||
@@ -7073,15 +7059,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^5.0.1":
|
||||
version: 5.1.6
|
||||
resolution: "minimatch@npm:5.1.6"
|
||||
dependencies:
|
||||
brace-expansion: ^2.0.1
|
||||
checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.1":
|
||||
version: 9.0.1
|
||||
resolution: "minimatch@npm:9.0.1"
|
||||
@@ -9344,11 +9321,11 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "tgui-dev-server@workspace:packages/tgui-dev-server"
|
||||
dependencies:
|
||||
axios: ^0.27.2
|
||||
glob: ^8.0.3
|
||||
axios: ^0.21.1
|
||||
glob: ^7.1.7
|
||||
source-map: ^0.7.3
|
||||
stacktrace-parser: ^0.1.10
|
||||
ws: ^8.6.0
|
||||
ws: ^7.5.3
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -10299,7 +10276,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^7.3.1":
|
||||
"ws@npm:^7.3.1, ws@npm:^7.5.3":
|
||||
version: 7.5.9
|
||||
resolution: "ws@npm:7.5.9"
|
||||
peerDependencies:
|
||||
@@ -10314,7 +10291,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.2.3, ws@npm:^8.6.0":
|
||||
"ws@npm:^8.2.3":
|
||||
version: 8.13.0
|
||||
resolution: "ws@npm:8.13.0"
|
||||
peerDependencies:
|
||||
|
||||
+7
-3
@@ -1845,8 +1845,6 @@
|
||||
#include "code\modules\admin\secrets\random_events\trigger_cordical_borer_infestation.dm"
|
||||
#include "code\modules\admin\secrets\random_events\trigger_xenomorph_infestation.dm"
|
||||
#include "code\modules\admin\verbs\admin_ch.dm"
|
||||
#include "code\modules\admin\verbs\adminhelp.dm"
|
||||
#include "code\modules\admin\verbs\adminhelp_vr.dm"
|
||||
#include "code\modules\admin\verbs\adminjump.dm"
|
||||
#include "code\modules\admin\verbs\adminpm.dm"
|
||||
#include "code\modules\admin\verbs\adminsay.dm"
|
||||
@@ -2834,7 +2832,6 @@
|
||||
#include "code\modules\media\mediamanager.dm"
|
||||
#include "code\modules\media\walkpod.dm"
|
||||
#include "code\modules\mentor\mentor.dm"
|
||||
#include "code\modules\mentor\mentorhelp.dm"
|
||||
#include "code\modules\metric\activity.dm"
|
||||
#include "code\modules\metric\count.dm"
|
||||
#include "code\modules\metric\department.dm"
|
||||
@@ -4506,6 +4503,7 @@
|
||||
#include "code\ZAS\Zone.dm"
|
||||
#include "interface\interface.dm"
|
||||
#include "interface\skin.dmf"
|
||||
#include "maps\runtime_station.dm"
|
||||
#include "maps\atoll\atoll_decals.dm"
|
||||
#include "maps\atoll\atoll_objs.dm"
|
||||
#include "maps\atoll\atoll_turfs.dm"
|
||||
@@ -4547,6 +4545,7 @@
|
||||
#include "modular_chomp\code\global.dm"
|
||||
#include "modular_chomp\code\_HELPERS\icons\flatten.dm"
|
||||
#include "modular_chomp\code\_HELPERS\type2type\color.dm"
|
||||
#include "modular_chomp\code\_onclick\hud\alert.dm"
|
||||
#include "modular_chomp\code\ATMOSPHERICS\atmospherics.dm"
|
||||
#include "modular_chomp\code\datums\autolathe\arms.dm"
|
||||
#include "modular_chomp\code\datums\autolathe\engineering_ch.dm"
|
||||
@@ -4733,6 +4732,11 @@
|
||||
#include "modular_chomp\code\modules\resleeving\machines.dm"
|
||||
#include "modular_chomp\code\modules\shieldgen\shield_capacitor.dm"
|
||||
#include "modular_chomp\code\modules\shieldgen\shield_gen.dm"
|
||||
#include "modular_chomp\code\modules\tgui\states\ticket.dm"
|
||||
#include "modular_chomp\code\modules\tickets\procs.dm"
|
||||
#include "modular_chomp\code\modules\tickets\tickets.dm"
|
||||
#include "modular_chomp\code\modules\tickets\tickets_player_ui.dm"
|
||||
#include "modular_chomp\code\modules\tickets\tickets_ui.dm"
|
||||
#include "modular_chomp\code\modules\vore\eating\exportpanel_ch.dm"
|
||||
#include "modular_chomp\code\modules\vore\resizing\resize.dm"
|
||||
#include "modular_chomp\code\modules\xenobio\machinery\monkey_processor.dm"
|
||||
|
||||
Reference in New Issue
Block a user