mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-13 16:13:19 +01:00
Ports in game bug reporting from CM (#30376)
* adds the files doesn't even compile yet WIP * compiles now. axed some stuff we don't need * Update skin.dmf * It technically works * Adds in the stylesheet as well * makes approval work * Update tgui.bundle.js * changes url to para master * Makes apostrophes work * removes token read line * moves bug report verb to OOC.dm and changes its name to "Report a Bug" * Update bug_report.dm * Update tgui.bundle.js * Adds API token var to config. * adds a per ckey cooldown of 5 minutes for bug reports * tgui rebuild * rebuild tgui * Update tgui.bundle.js * review changes * better timeout check * changes example config to include the github token * Update tgui.bundle.js * tgui rebuild * add message when bug report list is empty and changes some warnings to info type messages * move round id and byond builds to additional details * moves initial user ref to UID. Fixes commit hash stuff adds TM list * Update tgui.bundle.js
This commit is contained in:
committed by
GitHub
parent
a9c3b6d242
commit
1abb121eb7
@@ -14,6 +14,8 @@
|
||||
var/api_host = null
|
||||
/// Internal API key
|
||||
var/api_key = null
|
||||
/// Github API token
|
||||
var/github_api_token = null
|
||||
/// List of IP addresses which bypass world topic rate limiting
|
||||
var/list/topic_ip_ratelimit_bypass = list()
|
||||
/// Server instance ID
|
||||
@@ -45,6 +47,7 @@
|
||||
CONFIG_LOAD_STR(shutdown_shell_command, data["shutdown_shell_command"])
|
||||
CONFIG_LOAD_STR(api_host, data["api_host"])
|
||||
CONFIG_LOAD_STR(api_key, data["api_key"])
|
||||
CONFIG_LOAD_STR(github_api_token, data["github_api_token"])
|
||||
|
||||
CONFIG_LOAD_LIST(topic_ip_ratelimit_bypass, data["topic_ip_ratelimit_bypass"])
|
||||
|
||||
@@ -54,6 +57,7 @@
|
||||
CONFIG_LOAD_STR(override_map, data["override_map"])
|
||||
CONFIG_LOAD_STR(ytdlp_url, data["ytdlp_url"])
|
||||
|
||||
|
||||
// Load region overrides
|
||||
if(islist(data["regional_servers"]))
|
||||
region_map.Cut()
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
GLOBAL_LIST_EMPTY(bug_reports)
|
||||
GLOBAL_LIST_EMPTY(bug_report_time)
|
||||
|
||||
// Datum for handling bug reports
|
||||
#define STATUS_SUCCESS 201
|
||||
|
||||
/datum/tgui_bug_report_form
|
||||
/// contains all the body text for the bug report.
|
||||
var/list/bug_report_data = null
|
||||
|
||||
/// client of the bug report author, needed to create the ticket
|
||||
var/initial_user_uid = null
|
||||
// ckey of the author
|
||||
var/initial_key = null // just incase they leave after creating the bug report
|
||||
|
||||
/// client of the admin/dev who is accessing the report, we don't want multiple people unknowingly making changes at the same time.
|
||||
var/client/approving_user = null
|
||||
|
||||
/// value to determine if the bug report is submitted and awaiting admin/dev approval, used for state purposes in tgui.
|
||||
var/awaiting_approval = FALSE
|
||||
|
||||
/// for garbage collection purposes.
|
||||
var/selected_confirm = FALSE
|
||||
|
||||
/// byond version of the user, so we still have the byond version if the user logs out
|
||||
var/user_byond_version
|
||||
|
||||
/// Current Server commit
|
||||
var/local_commit
|
||||
|
||||
/// Current test merges formatted for the bug report
|
||||
var/test_merges
|
||||
|
||||
/datum/tgui_bug_report_form/New(mob/user)
|
||||
local_commit = GLOB.revision_info.commit_hash
|
||||
initial_user_uid = user.client.UID()
|
||||
initial_key = user.client.key
|
||||
user_byond_version = "[user.client.byond_version].[user.client.byond_build]"
|
||||
if(length(GLOB.revision_info.origin_commit))
|
||||
local_commit = GLOB.revision_info.origin_commit
|
||||
for(var/datum/tgs_revision_information/test_merge/tm in GLOB.revision_info.testmerges)
|
||||
test_merges += "#[tm.number] at [tm.head_commit]\n"
|
||||
|
||||
|
||||
/datum/tgui_bug_report_form/proc/external_link_prompt(client/user)
|
||||
tgui_alert(user, "Unable to create a bug report at this time, please create the issue directly through our GitHub repository instead")
|
||||
var/url = "https://github.com/ParadiseSS13/Paradise"
|
||||
|
||||
if(tgui_alert(user, "This will open the GitHub in your browser. Are you sure?", "Confirm", list("Yes", "No")) == "Yes")
|
||||
user << link(url)
|
||||
|
||||
/datum/tgui_bug_report_form/ui_state()
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/tgui_bug_report_form/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "BugReportForm")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_bug_report_form/ui_close(mob/user)
|
||||
. = ..()
|
||||
var/client/initial_user = locateUID(initial_user_uid)
|
||||
if(!approving_user && user.client == initial_user && !selected_confirm) // user closes the ui without selecting confirm or approve.
|
||||
qdel(src)
|
||||
return
|
||||
approving_user = null
|
||||
selected_confirm = FALSE
|
||||
|
||||
/datum/tgui_bug_report_form/Destroy()
|
||||
GLOB.bug_reports -= src
|
||||
return ..()
|
||||
|
||||
/datum/tgui_bug_report_form/proc/sanitize_payload(list/params)
|
||||
for(var/param in params)
|
||||
params[param] = html_decode(sanitize(params[param], list("\t"=" ","�"=" ","<"=" ",">"=" ","&"=" ")))
|
||||
|
||||
return params
|
||||
|
||||
// whether or not an admin/dev can access the record at a given time.
|
||||
/datum/tgui_bug_report_form/proc/assign_approver(mob/user)
|
||||
if(!initial_key)
|
||||
to_chat(user, "<span class='warning'>Unable to identify the author of the bug report.</span>")
|
||||
return FALSE
|
||||
if(approving_user)
|
||||
if(user.client == approving_user)
|
||||
to_chat(user, "<span class='warning'>This bug report review is already opened and accessed by you.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='warning'>Another staff member is currently accessing this report, please wait for them to finish before making any changes.</span>")
|
||||
return FALSE
|
||||
if(!check_rights(R_VIEWRUNTIMES|R_ADMIN|R_DEBUG, user = user))
|
||||
message_admins("[user.ckey] has attempted to review [initial_key]'s bug report titled [bug_report_data["title"]] without proper authorization at [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")].")
|
||||
return FALSE
|
||||
|
||||
approving_user = user.client
|
||||
return TRUE
|
||||
|
||||
// returns the body payload
|
||||
/datum/tgui_bug_report_form/proc/create_form()
|
||||
var/desc = {"
|
||||
## What did you expect to happen?
|
||||
[bug_report_data["expected_behavior"]]
|
||||
|
||||
## What happened instead?
|
||||
[bug_report_data["description"]]
|
||||
|
||||
## Why is this bad/What are the consequences?
|
||||
[bug_report_data["consequences"]]
|
||||
|
||||
## Steps to reproduce the issue:
|
||||
[bug_report_data["steps"]]
|
||||
|
||||
## Attached logs
|
||||
```
|
||||
[bug_report_data["log"] ? bug_report_data["log"] : "N/A"]
|
||||
```
|
||||
|
||||
## Additional details
|
||||
- Author: [initial_key]
|
||||
- Approved By: [approving_user]
|
||||
- Round ID: [GLOB.round_id ? GLOB.round_id : "N/A"]
|
||||
- Client BYOND Version: [user_byond_version]
|
||||
- Server BYOND Version: [world.byond_version].[world.byond_build]
|
||||
- Server commit: [local_commit]
|
||||
- Active Test Merges: [test_merges ? test_merges : "None"]
|
||||
- Note: [bug_report_data["approver_note"] ? bug_report_data["approver_note"] : "None"]
|
||||
"}
|
||||
|
||||
return desc
|
||||
|
||||
// the real deal, we are sending the request through the api.
|
||||
/datum/tgui_bug_report_form/proc/send_request(payload_body, client/user)
|
||||
// for any future changes see https://docs.github.com/en/rest/issues/issues
|
||||
var/repo_name = "Paradise"
|
||||
var/org = "ParadiseSS13"
|
||||
var/token = GLOB.configuration.system.github_api_token
|
||||
|
||||
if(token == null)
|
||||
tgui_alert(user, "The configuration is not set for the external API.", "Issue not reported!")
|
||||
external_link_prompt(user)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/url = "https://api.github.com/repos/[org]/[repo_name]/issues"
|
||||
var/list/headers = list()
|
||||
headers["Authorization"] = "Bearer [token]"
|
||||
headers["Content-Type"] = "text/markdown; charset=utf-8"
|
||||
headers["Accept"] = "application/vnd.github+json"
|
||||
|
||||
var/datum/http_request/request = new()
|
||||
var/list/payload = list(
|
||||
"title" = bug_report_data["title"],
|
||||
"body" = payload_body,
|
||||
"labels" = list("Bug")
|
||||
)
|
||||
|
||||
request.prepare(RUSTLIBS_HTTP_METHOD_POST, url, json_encode(payload), headers)
|
||||
request.begin_async()
|
||||
var/start_time = world.time
|
||||
UNTIL(request.is_complete() || (world.time > start_time + 5 SECONDS))
|
||||
if(!request.is_complete() && world.time > start_time + 5 SECONDS)
|
||||
CRASH("bug report HTML request hit timeout limit of 5 seconds");
|
||||
|
||||
var/datum/http_response/response = request.into_response()
|
||||
if(response.errored || response.status_code != STATUS_SUCCESS)
|
||||
message_admins("<span class='adminnotice'>The GitHub API has failed to create the bug report titled [bug_report_data["title"]] approved by [approving_user], status code:[response.status_code]. Please paste this error code into the development channel on discord.</span>")
|
||||
external_link_prompt(user)
|
||||
else
|
||||
var/client/initial_user = locateUID(initial_user_uid)
|
||||
message_admins("[user.ckey] has approved a bug report from [initial_key] titled [bug_report_data["title"]] at [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")].")
|
||||
to_chat(initial_user, "<span class='notice'>An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!</span>")
|
||||
qdel(src)// approved and submitted, we no longer need the datum.
|
||||
|
||||
// proc that creates a ticket for an admin to approve or deny a bug report request
|
||||
/datum/tgui_bug_report_form/proc/bug_report_request()
|
||||
var/client/initial_user = locateUID(initial_user_uid)
|
||||
if(initial_user)
|
||||
to_chat(initial_user, "<span class='notice'>Your bug report has been submitted, thank you!</span>")
|
||||
GLOB.bug_reports += src
|
||||
|
||||
var/general_message = "[initial_key] has created a bug report which is now pending approval. The report can be viewed using \"View Bug Reports\" in the debug tab. </span>"
|
||||
message_admins(general_message)
|
||||
|
||||
/datum/tgui_bug_report_form/ui_act(action, list/params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
var/mob/user = ui.user
|
||||
switch(action)
|
||||
if("confirm")
|
||||
if(selected_confirm) // prevent someone from spamming the approve button
|
||||
to_chat(user, "<span class='warning'>You have already approved this submission, please wait a moment for the API to process your submission.</span>")
|
||||
return
|
||||
bug_report_data = sanitize_payload(params)
|
||||
selected_confirm = TRUE
|
||||
// bug report request is now waiting for admin approval
|
||||
if(!awaiting_approval)
|
||||
bug_report_request()
|
||||
GLOB.bug_report_time[user.ckey] = world.time
|
||||
awaiting_approval = TRUE
|
||||
else // otherwise it's been approved
|
||||
var/payload_body = create_form()
|
||||
send_request(payload_body, user.client)
|
||||
if("cancel")
|
||||
if(awaiting_approval) // admin has chosen to reject the bug report
|
||||
reject(user.client)
|
||||
qdel(src)
|
||||
ui.close()
|
||||
. = TRUE
|
||||
|
||||
/datum/tgui_bug_report_form/ui_data(mob/user)
|
||||
. = list()
|
||||
.["report_details"] = bug_report_data // only filled out once the user as submitted the form
|
||||
.["awaiting_approval"] = awaiting_approval
|
||||
|
||||
/datum/tgui_bug_report_form/proc/reject(client/user)
|
||||
message_admins("[user.ckey] has rejected a bug report from [initial_key] titled [bug_report_data["title"]] at [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")].")
|
||||
var/client/initial_user = locateUID(initial_user_uid)
|
||||
if(initial_user)
|
||||
to_chat(initial_user_uid, "<span class = 'warning'>A staff member has rejected your bug report, this can happen for several reasons. They will most likely get back to you shortly regarding your issue.</span>")
|
||||
|
||||
#undef STATUS_SUCCESS
|
||||
@@ -1,4 +1,5 @@
|
||||
#define DEFAULT_PLAYER_OOC_COLOUR "#075FE5" // Can't initial() a global so we store the default in a macro instead
|
||||
#define BUG_REPORT_CD (5 MINUTES)
|
||||
GLOBAL_VAR_INIT(normal_ooc_colour, DEFAULT_PLAYER_OOC_COLOUR)
|
||||
|
||||
GLOBAL_VAR_INIT(member_ooc_colour, "#035417")
|
||||
@@ -330,4 +331,25 @@ GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00")
|
||||
popup.set_content(output.Join(""))
|
||||
popup.open()
|
||||
|
||||
/client/verb/submitbug()
|
||||
set name = "Report a Bug"
|
||||
set desc = "Submit a bug report."
|
||||
set category = "OOC"
|
||||
set hidden = TRUE
|
||||
if(!usr?.client)
|
||||
return
|
||||
|
||||
if(GLOB.bug_report_time[usr.ckey] && world.time < (GLOB.bug_report_time[usr.client] + BUG_REPORT_CD))
|
||||
var/cd_total_time = GLOB.bug_report_time[usr.ckey] + BUG_REPORT_CD - world.time
|
||||
var/cd_minutes = round(cd_total_time / (1 MINUTES))
|
||||
var/cd_seconds = round((cd_total_time - cd_minutes MINUTES) / (1 SECONDS))
|
||||
tgui_alert(usr, "You must wait another [cd_minutes]:[cd_seconds < 10 ? "0" : ""][cd_seconds] minute[cd_minutes < 2 ? "" : "s"] before submitting another bug report", "Bug Report Rate Limit")
|
||||
return
|
||||
|
||||
var/datum/tgui_bug_report_form/report = new(usr)
|
||||
|
||||
report.ui_interact(usr)
|
||||
return
|
||||
|
||||
#undef DEFAULT_PLAYER_OOC_COLOUR
|
||||
#undef BUG_REPORT_CD
|
||||
|
||||
@@ -187,6 +187,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
|
||||
/client/proc/debug_bloom,
|
||||
/client/proc/cmd_mass_screenshot,
|
||||
/client/proc/allow_browser_inspect,
|
||||
/client/proc/view_bug_reports,
|
||||
))
|
||||
GLOBAL_LIST_INIT(admin_verbs_possess, list(
|
||||
/proc/possess,
|
||||
|
||||
@@ -667,7 +667,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
|
||||
dresscode = custom_names[selected_name]
|
||||
if(isnull(dresscode))
|
||||
return
|
||||
|
||||
|
||||
if(dresscode == "Recover destroyed body...")
|
||||
dresscode = input("Select body to rebuild", "Robust quick dress shop") as null|anything in potential_minds
|
||||
|
||||
@@ -923,3 +923,22 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
|
||||
CHECK_TICK
|
||||
|
||||
log_and_message_admins_no_usr("The world has been decontaminated of [counter] radiation components.")
|
||||
|
||||
/client/proc/view_bug_reports()
|
||||
set name = "View Bug Reports"
|
||||
set desc = "Select a bug report to view"
|
||||
set category = "Debug"
|
||||
if(!check_rights(R_DEBUG|R_VIEWRUNTIMES|R_ADMIN))
|
||||
return
|
||||
if(!length(GLOB.bug_reports))
|
||||
to_chat(usr, "<span class='warning'>There are no bug reports to view</span>")
|
||||
return
|
||||
var/list/bug_report_selection = list()
|
||||
for(var/datum/tgui_bug_report_form/report in GLOB.bug_reports)
|
||||
bug_report_selection["[report.initial_key] - [report.bug_report_data["title"]]"] = report
|
||||
var/datum/tgui_bug_report_form/form = bug_report_selection[tgui_input_list(usr, "Select a report to view:", "Bug Reports", bug_report_selection)]
|
||||
if(!form.assign_approver(usr))
|
||||
return
|
||||
form.ui_interact(usr)
|
||||
return
|
||||
|
||||
|
||||
@@ -764,6 +764,8 @@ shutdown_on_reboot = false
|
||||
#api_host = "http://127.0.0.1:8080"
|
||||
# Access key for the internal API.
|
||||
#api_key = "your_secret_here"
|
||||
# Github API token for in game bug reporting
|
||||
#github_api_token = "your_secret_here"
|
||||
# List of IP addresses to be ignored by the world/Topic rate limiting. Useful if you have other services
|
||||
topic_ip_ratelimit_bypass = ["127.0.0.1"]
|
||||
# Turn this to true if you are running a production server
|
||||
|
||||
@@ -404,6 +404,7 @@
|
||||
#include "code\datums\atom_hud.dm"
|
||||
#include "code\datums\beam.dm"
|
||||
#include "code\datums\browser.dm"
|
||||
#include "code\datums\bug_report.dm"
|
||||
#include "code\datums\callback.dm"
|
||||
#include "code\datums\card_deck_table_tracker.dm"
|
||||
#include "code\datums\chat_payload.dm"
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Flex, Section } from 'tgui-core/components';
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
interface FormTypes {
|
||||
awaiting_approval: BooleanLike;
|
||||
report_details: FormDetails;
|
||||
}
|
||||
|
||||
// all the information necessary to pass into the github api
|
||||
type FormDetails = {
|
||||
steps: string;
|
||||
title: string;
|
||||
description: string;
|
||||
expected_behavior: string;
|
||||
consequences: string;
|
||||
approver_note: string;
|
||||
log: string;
|
||||
};
|
||||
|
||||
const InputTitle = (props) => {
|
||||
return (
|
||||
<h2>
|
||||
{props.children}
|
||||
{props.required && <span className="input-title-required">{' *'}</span>}
|
||||
</h2>
|
||||
);
|
||||
};
|
||||
|
||||
export const BugReportForm = (props) => {
|
||||
const { act, data } = useBackend<FormTypes>();
|
||||
const { awaiting_approval, report_details } = data;
|
||||
const [checkBox, setCheckbox] = useState(false);
|
||||
|
||||
const [title, setTitle] = useState(report_details?.title || '');
|
||||
const [steps, setSteps] = useState(report_details?.steps || '');
|
||||
const [description, setDescription] = useState(report_details?.description || '');
|
||||
const [expected_behavior, setExpectedBehavior] = useState(report_details?.expected_behavior || '');
|
||||
const [consequences, setConsequences] = useState(report_details?.consequences || '');
|
||||
const [approver_note, setApproverNote] = useState(report_details?.approver_note || '');
|
||||
const [log, setLog] = useState(report_details?.log || '');
|
||||
|
||||
const submit = () => {
|
||||
if (!title || !description || !expected_behavior || !steps || !consequences) {
|
||||
alert('Please fill out all required fields!');
|
||||
return;
|
||||
}
|
||||
const updatedReportDetails = {
|
||||
title,
|
||||
expected_behavior,
|
||||
description,
|
||||
consequences,
|
||||
steps,
|
||||
log,
|
||||
approver_note,
|
||||
};
|
||||
act('confirm', updatedReportDetails);
|
||||
};
|
||||
|
||||
return (
|
||||
<Window title={'Bug Report Form'} width={700} height={awaiting_approval ? 1000 : 700}>
|
||||
<Window.Content>
|
||||
<Section fill scrollable>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item className="text-center">
|
||||
<a
|
||||
href="https://github.com/ParadiseSS13/Paradise/issues"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="link"
|
||||
>
|
||||
GitHub Repository
|
||||
</a>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<h2 className="tip">{'TIP: please be as descriptive as possible, it really does help tremendously'}</h2>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<InputTitle required>{'Title'}</InputTitle>
|
||||
<input width="100%" className="textarea" value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle required>{'What did you expect to happen?'}</InputTitle>
|
||||
{'Give a description of what you expected to happen'}
|
||||
<textarea
|
||||
rows={3}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={expected_behavior}
|
||||
onChange={(e) => setExpectedBehavior(e.target.value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle required>{'What happened instead?'}</InputTitle>
|
||||
{'Give a description of the bug: '}
|
||||
<textarea
|
||||
rows={3}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle required>{'Why is this bad/What are the consequences?'}</InputTitle>
|
||||
{'Issues caused by the bug:'}
|
||||
<textarea
|
||||
rows={3}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={consequences}
|
||||
onChange={(e) => setConsequences(e.target.value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle required>{'How do we reproduce this bug?'}</InputTitle>
|
||||
{'Give a list of steps to reproduce this issue, the more details the better'}
|
||||
<textarea
|
||||
rows={4}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={steps}
|
||||
onChange={(e) => setSteps(e.target.value)}
|
||||
placeholder={'1) Step 1\n2) Step 2\n3) Step 3\n'}
|
||||
/>
|
||||
</Flex.Item>
|
||||
{!!awaiting_approval && (
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle>{'Additional Note'}</InputTitle>
|
||||
{"Any additional notes to submit with the author's bug report "}
|
||||
<textarea
|
||||
rows={4}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={approver_note}
|
||||
onChange={(e) => setApproverNote(e.target.value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
)}
|
||||
<Flex.Item my={2}>
|
||||
<InputTitle>{'Logs'}</InputTitle>
|
||||
{'Please paste any relevant logs here'}
|
||||
<textarea
|
||||
rows={4}
|
||||
className="textarea"
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = 'auto';
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
value={log}
|
||||
onChange={(e) => setLog(e.target.value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<Flex className="flex-center">
|
||||
<Flex.Item mx={1}>
|
||||
<div className="button-cancel" onClick={() => act('cancel')}>
|
||||
{awaiting_approval ? 'Reject' : 'Cancel'}
|
||||
</div>
|
||||
</Flex.Item>
|
||||
<Flex.Item mx={1}>
|
||||
<div className="button-submit" onClick={submit}>
|
||||
{awaiting_approval ? 'Approve' : 'Submit'}
|
||||
</div>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
@use '../base.scss';
|
||||
|
||||
.textarea {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
border: solid 1px hsl(212, 42%, 59%);
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: hsl(212, 42%, 59%);
|
||||
}
|
||||
|
||||
.input-title-required {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.button-default {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid hsl(212, 42%, 59%);
|
||||
background-color: hsl(0, 0%, 16%);
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button-default:hover {
|
||||
background-color: hsl(0, 0%, 23%);
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-submit {
|
||||
background-color: hsl(120, 100%, 25%);
|
||||
color: hsl(0, 0%, 100%);
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button-cancel {
|
||||
background-color: hsl(0, 100%, 50%);
|
||||
color: hsl(0, 0%, 100%);
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
@include meta.load-css('./interfaces/AIControllerDebugger.scss');
|
||||
@include meta.load-css('./interfaces/AlertModal.scss');
|
||||
@include meta.load-css('./interfaces/BrigCells.scss');
|
||||
@include meta.load-css('./interfaces/BugReportForm.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
@include meta.load-css('./interfaces/ColorPicker.scss');
|
||||
@include meta.load-css('./interfaces/Contractor.scss');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user