[MIRROR] Check Access List Mapper Verb (#12647)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2026-04-08 18:27:17 +02:00
committed by GitHub
co-authored by Will Kashargul
parent 646a9a1030
commit 74f6f2f08d
10 changed files with 336 additions and 33 deletions
+81
View File
@@ -0,0 +1,81 @@
/*
Tgui panel for admins editing the access list of various machines.
*/
/datum/access_viewer
var/datum/weakref/focused_obj
/datum/access_viewer/Destroy(force)
focused_obj = null
. = ..()
/datum/access_viewer/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AccessViewer", "Access Viewer")
ui.open()
/datum/access_viewer/tgui_state(mob/user)
return ADMIN_STATE(R_DEBUG)
/datum/access_viewer/tgui_act(action, params, datum/tgui/ui)
if(..() || !check_rights_for(ui.user.client, R_DEBUG))
return FALSE
var/obj/machinery/req_thing = focused_obj?.resolve()
if(!req_thing)
return FALSE
switch(action)
if("req_all")
var/set_id = text2num(params["set_id"])
if(!set_id)
return FALSE
if(!req_thing.req_access)
req_thing.req_access = list()
if(set_id in req_thing.req_access)
req_thing.req_access -= set_id
else
req_thing.req_access += set_id
return TRUE
if("req_one")
var/set_id = text2num(params["set_id"])
if(!set_id)
return FALSE
if(!req_thing.req_one_access)
req_thing.req_one_access = list()
if(set_id in req_thing.req_one_access)
req_thing.req_one_access -= set_id
else
req_thing.req_one_access += set_id
return TRUE
/datum/access_viewer/tgui_static_data(mob/user)
var/list/data = list()
var/list/access_list = list()
for(var/datum/access/dat as anything in subtypesof(/datum/access))
access_list += list(
list(
"id" = dat.id,
"name" = dat.desc,
"region" = dat.region,
"access_type" = dat.access_type,
)
)
data["access_list"] = access_list
return data
/datum/access_viewer/tgui_data(mob/user)
var/list/data = list()
// Check if the object still exists
var/obj/machinery/req_thing = focused_obj?.resolve()
if(req_thing)
data["name"] = req_thing.name
data["coords"] = "[req_thing.x].[req_thing.y].[req_thing.z]"
data["req_access"] = req_thing.req_access ? req_thing.req_access : list()
data["req_one_access"] = req_thing.req_one_access ? req_thing.req_one_access : list()
return data
/datum/access_viewer/proc/set_access_focus(obj/machinery/req_thing)
focused_obj = WEAKREF(req_thing)
+2
View File
@@ -35,6 +35,8 @@ GLOBAL_PROTECT(href_token)
var/datum/spawn_menu/spawn_menu
var/datum/spawnpanel/spawn_panel
var/datum/access_viewer/access_view_menu
/// A lazylist of tagged datums, for quick reference with the View Tags verb
var/list/tagged_datums
+9
View File
@@ -5,3 +5,12 @@ ADMIN_VERB(spawn_panel, R_SPAWN, "Spawn Panel", "Spawn Panel (TGUI).", ADMIN_CAT
user.holder.spawn_panel = panel
panel.tgui_interact(user.mob)
feedback_add_details("admin_verb","SP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
ADMIN_VERB_AND_CONTEXT_MENU(machine_access_check, R_DEBUG, "Check Access List", "Read and edit the access list of a machine.", ADMIN_CATEGORY_GAME, obj/machinery/req_thing in world)
var/datum/access_viewer/panel = user.holder.access_view_menu
if(!panel)
panel = new()
user.holder.access_view_menu = panel
panel.set_access_focus(req_thing)
panel.tgui_interact(user.mob)
feedback_add_details("admin_verb","SR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+31
View File
@@ -17,3 +17,34 @@
// continue
TEST_FAIL(log_entry)
/* Should probably be done as a linter thing instead
/// Checks all machines for legal access numbers
/datum/unit_test/all_access_id_must_have_existing_datums
/datum/unit_test/all_access_id_must_have_existing_datums/Run()
var/failed = FALSE
var/list/access_datums = SSaccess.get_all_access_datums_by_id()
for(var/obj/machinery/thing in world)
failed += validate_list(thing.req_access, thing, "req_access")
failed += validate_list(thing.req_one_access, thing, "req_one_access")
if(failed)
TEST_FAIL("Machinery had an illegal access id.")
/datum/unit_test/proc/validate_list(var/list/access_list, var/obj/machinery/thing, name_list)
if(!access_list)
return FALSE // null is legal
if(!islist(access_list))
TEST_NOTICE(src, "Access - [thing] ([thing.x].[thing.y].[thing.z]) had a [name_list] that was not a list or null.")
return TRUE // Was something other than null or a list... illegal
var/failed = FALSE
for(var/access in access_list)
if(!SSaccess.get_access_by_id(access))
TEST_NOTICE(src, "Access - [thing] ([thing.x].[thing.y].[thing.z]) had a [name_list] with a non-existant id [access].")
failed = TRUE // has a non-existant id, illegal
return failed
*/
+33 -33
View File
@@ -37,8 +37,8 @@ export function setClientTheme(name) {
'rpanewindow.background-color': 'none',
'rpanewindow.text-color': '#000000',
'mainvsplit.background-color': 'none',
'info.tab-background-color': 'none',
'info.tab-text-color': '#000000',
//'info.tab-background-color': 'none',
//'info.tab-text-color': '#000000',
'discord.background-color': 'none',
'discord.text-color': '#000000',
'mapb.background-color': 'none',
@@ -57,47 +57,47 @@ export function setClientTheme(name) {
return Byond.winset({
// Main windows
'infowindow.background-color': 'none',
'infowindow.text-color': '#000000',
'info.background-color': 'none',
'info.text-color': '#000000',
//'infowindow.background-color': 'none',
//'infowindow.text-color': '#000000',
//'info.background-color': 'none',
//'info.text-color': '#000000',
'browseroutput.background-color': 'none',
'browseroutput.text-color': '#000000',
'outputwindow.background-color': 'none',
'outputwindow.text-color': '#000000',
'mainwindow.background-color': 'none',
'split.background-color': 'none',
//'split.background-color': 'none',
// Buttons
'changelog.background-color': 'none',
'changelog.text-color': '#000000',
'rules.background-color': 'none',
'rules.text-color': '#000000',
'wiki.background-color': 'none',
'wiki.text-color': '#000000',
'forum.background-color': 'none',
'forum.text-color': '#000000',
//'rules.background-color': 'none',
//'rules.text-color': '#000000',
//'wiki.background-color': 'none',
//'wiki.text-color': '#000000',
//'forum.background-color': 'none',
//'forum.text-color': '#000000',
'github.background-color': 'none',
'github.text-color': '#000000',
'report-issue.background-color': 'none',
'report-issue.text-color': '#000000',
//'report-issue.background-color': 'none',
//'report-issue.text-color': '#000000',
// Status and verb tabs
'output.background-color': 'none',
'output.text-color': '#000000',
'statwindow.background-color': 'none',
'statwindow.text-color': '#000000',
'stat.background-color': '#FFFFFF',
'stat.tab-background-color': 'none',
'stat.text-color': '#000000',
'stat.tab-text-color': '#000000',
'stat.prefix-color': '#000000',
'stat.suffix-color': '#000000',
//'stat.background-color': '#FFFFFF',
//'stat.tab-background-color': 'none',
//'stat.text-color': '#000000',
//'stat.tab-text-color': '#000000',
//'stat.prefix-color': '#000000',
//'stat.suffix-color': '#000000',
// Say, OOC, me Buttons etc.
'saybutton.background-color': 'none',
'saybutton.text-color': '#000000',
'oocbutton.background-color': 'none',
'oocbutton.text-color': '#000000',
'mebutton.background-color': 'none',
'mebutton.text-color': '#000000',
//'oocbutton.background-color': 'none',
//'oocbutton.text-color': '#000000',
//'mebutton.background-color': 'none',
//'mebutton.text-color': '#000000',
'asset_cache_browser.background-color': 'none',
'asset_cache_browser.text-color': '#000000',
'tooltip.background-color': 'none',
@@ -114,8 +114,8 @@ export function setClientTheme(name) {
'rpanewindow.background-color': COLOR_DARK_BG_DARKER,
'rpanewindow.text-color': COLOR_DARK_TEXT,
'mainvsplit.background-color': COLOR_DARK_BG,
'info.tab-background-color': COLOR_DARK_BG,
'info.tab-text-color': COLOR_DARK_TEXT,
//'info.tab-background-color': COLOR_DARK_BG,
//'info.tab-text-color': COLOR_DARK_TEXT,
'mapb.background-color': '#494949',
'mapb.text-color': COLOR_DARK_TEXT,
'discord.background-color': '#494949',
@@ -136,8 +136,8 @@ export function setClientTheme(name) {
// Main windows
'infowindow.background-color': COLOR_DARK_BG,
'infowindow.text-color': COLOR_DARK_TEXT,
'info.background-color': COLOR_DARK_BG,
'info.text-color': COLOR_DARK_TEXT,
//'info.background-color': COLOR_DARK_BG,
//'info.text-color': COLOR_DARK_TEXT,
'browseroutput.background-color': COLOR_DARK_BG,
'browseroutput.text-color': COLOR_DARK_TEXT,
'outputwindow.background-color': COLOR_DARK_BG,
@@ -147,16 +147,16 @@ export function setClientTheme(name) {
// Buttons
'changelog.background-color': '#494949',
'changelog.text-color': COLOR_DARK_TEXT,
'rules.background-color': '#494949',
'rules.text-color': COLOR_DARK_TEXT,
//'rules.background-color': '#494949',
//'rules.text-color': COLOR_DARK_TEXT,
'wiki.background-color': '#494949',
'wiki.text-color': COLOR_DARK_TEXT,
'forum.background-color': '#494949',
'forum.text-color': COLOR_DARK_TEXT,
'github.background-color': '#3a3a3a',
'github.text-color': COLOR_DARK_TEXT,
'report-issue.background-color': '#492020',
'report-issue.text-color': COLOR_DARK_TEXT,
//'report-issue.background-color': '#492020',
//'report-issue.text-color': COLOR_DARK_TEXT,
// Status and verb tabs
'output.background-color': COLOR_DARK_BG_DARKER,
'output.text-color': COLOR_DARK_TEXT,
@@ -0,0 +1,29 @@
import { Box, Tooltip } from 'tgui-core/components';
import { ACCESS_FLAGS } from './constants';
export const AccessTypeDisplay = (props: { type: number }) => {
const { type } = props;
const names = ACCESS_FLAGS.filter((flag) => type & flag.bit).map(
(f) => f.label,
);
const allFlags = ACCESS_FLAGS.reduce((acc, f) => acc | f.bit, 0);
const label =
names.length === 0
? 'None'
: type === allFlags
? 'All'
: names.length > 1
? 'Multiple'
: names[0];
const tooltip =
names.length > 1 || type === allFlags ? names.join(', ') : undefined;
return (
<Tooltip content={tooltip}>
<Box>{label}</Box>
</Tooltip>
);
};
@@ -0,0 +1,26 @@
export enum region_ids {
None = -1,
All = 0,
Security,
Medbay,
Research,
Engineering,
Command,
General,
Supply,
}
enum access_types {
None = 0,
Centcom = 1,
Station = 2,
Syndicate = 4,
Private = 8,
}
export const ACCESS_FLAGS = [
{ bit: access_types.Centcom, label: 'Centcom' },
{ bit: access_types.Station, label: 'Station' },
{ bit: access_types.Syndicate, label: 'Syndicate' },
{ bit: access_types.Private, label: 'Private' },
];
@@ -0,0 +1,106 @@
import { Fragment, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import { Button, Divider, Input, Section, Table } from 'tgui-core/components';
import { createSearch } from 'tgui-core/string';
import { AccessTypeDisplay } from './AccessTypeDisplay';
import { region_ids } from './constants';
import type { Access, Data } from './types';
export const AccessViewer = (props) => {
const { act, data } = useBackend<Data>();
const { access_list, name, coords, req_access, req_one_access } = data;
const [searchText, setSearchText] = useState('');
const access_regions: Record<number, Access[]> = {
[region_ids.None]: [],
[region_ids.Security]: [],
[region_ids.Medbay]: [],
[region_ids.Research]: [],
[region_ids.Engineering]: [],
[region_ids.Command]: [],
[region_ids.General]: [],
[region_ids.Supply]: [],
};
const search = createSearch<Access>(searchText.toLowerCase(), (entry) =>
`${entry.id}: ${entry.name}`.toLowerCase(),
);
access_list.forEach((entry) => {
entry.has_req = req_access?.includes(entry.id);
entry.has_req_one = req_one_access?.includes(entry.id);
if (search(entry)) {
access_regions[entry.region].push(entry);
}
});
return (
<Window width={620} height={540}>
<Window.Content>
<Section
fill
title={`${name}'s access at ${coords}`}
scrollable
buttons={
<Input
width="200px"
value={searchText}
onChange={setSearchText}
placeholder="Search for access..."
/>
}
>
<Table>
{Object.keys(access_regions).map((key, index) => (
<Fragment key={key}>
{index > 0 && (
<Table.Row>
<Table.Cell colSpan={4}>
<Divider />
</Table.Cell>
</Table.Row>
)}
<Table.Row header>
<Table.Cell colSpan={4}>{region_ids[key]}</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell colSpan={4}>
<Divider />
</Table.Cell>
</Table.Row>
{access_regions[key].map((entry: Access) => (
<Table.Row key={entry.id}>
<Table.Cell
collapsing
>{`${entry.id}: ${entry.name}`}</Table.Cell>
<Table.Cell collapsing>
<Button.Checkbox
checked={entry.has_req}
onClick={() => act('req_all', { set_id: entry.id })}
>
Requires
</Button.Checkbox>
</Table.Cell>
<Table.Cell collapsing>
<Button.Checkbox
checked={entry.has_req_one}
onClick={() => act('req_one', { set_id: entry.id })}
>
Have Any
</Button.Checkbox>
</Table.Cell>
<Table.Cell collapsing color="label">
<AccessTypeDisplay type={entry.access_type} />
</Table.Cell>
</Table.Row>
))}
</Fragment>
))}
</Table>
</Section>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,18 @@
import type { BooleanLike } from 'tgui-core/react';
export type Data = {
access_list: Access[];
name?: string;
coords?: string;
req_access?: number[];
req_one_access?: number[];
};
export type Access = {
name: string;
id: number;
region: number;
access_type: number;
has_req: BooleanLike;
has_req_one: BooleanLike;
};
+1
View File
@@ -2269,6 +2269,7 @@
#include "code\game\turfs\unsimulated\walls.dm"
#include "code\js\byjax.dm"
#include "code\matrices\color_matrix.dm"
#include "code\modules\admin\access_viewer.dm"
#include "code\modules\admin\admin.dm"
#include "code\modules\admin\admin_attack_log.dm"
#include "code\modules\admin\admin_investigate.dm"