diff --git a/mythic-docker/app/__init__.py b/mythic-docker/app/__init__.py index c4531c5ff..7fc9bd615 100755 --- a/mythic-docker/app/__init__.py +++ b/mythic-docker/app/__init__.py @@ -14,6 +14,7 @@ mythic_admin_password = str(settings.get("ADMIN_PASSWORD", "mythic_password")) default_operation_name = str(settings.get("DEFAULT_OPERATION_NAME", "Operation Chimera")) nginx_port = str(settings.get("NGINX_PORT", 7443)) +nginx_host = str(settings.get("NGINX_HOST", "127.0.0.1")) listen_port = str(settings.get("SERVER_PORT", 17443)) allowed_ip_blocks = settings.get("ALLOWED_IP_BLOCKS", "0.0.0.0/0").split(",") server_header = settings.get("SERVER_HEADER", "nginx 1.2") @@ -40,6 +41,7 @@ "No MYTHIC_JWT_SECRET environment variable found") sys.exit(1) redis_port = int(settings.get("REDIS_PORT", 6379)) +redis_host = settings.get("REDIS_HOST", "127.0.0.1") # -------------------------------------------- # -------------------------------------------- # IP to bind to for the server, 0.0.0.0 means all local IPv4 addresses @@ -151,7 +153,7 @@ def format(self, record): mythic = Sanic(__name__, strict_slashes=False, log_config=mythic_logging) -mythic.config["SERVER_IP_ADDRESS"] = "127.0.0.1" +mythic.config["SERVER_IP_ADDRESS"] = nginx_host mythic.config["SERVER_PORT"] = nginx_port mythic.config["DB_HOST"] = db_host mythic.config["DB_PORT"] = db_port diff --git a/mythic-docker/app/api/payloadtype_api.py b/mythic-docker/app/api/payloadtype_api.py index 5d38a1834..7e74481e8 100755 --- a/mythic-docker/app/api/payloadtype_api.py +++ b/mythic-docker/app/api/payloadtype_api.py @@ -478,9 +478,17 @@ async def import_payload_type_func(ptype, operator, rabbitmqName): return {"status": "success", "new": new_payload, **payload_type.to_json()} except Exception as e: logger.exception("exception on importing payload type {}".format(payload_type.ptype)) + asyncio.create_task( + send_all_operations_message( + message=f"{rabbitmqName}'s sync with Mythic failed:\n" + str(e), + level="warning", source="payload_type_import")) return {"status": "error", "error": str(e)} except Exception as e: logger.exception("failed to import a payload type: " + str(e)) + asyncio.create_task( + send_all_operations_message( + message=f"{rabbitmqName}'s sync with Mythic failed:\n" + str(e), + level="warning", source="payload_type_import")) return {"status": "error", "error": str(e)} diff --git a/mythic-docker/app/api/rabbitmq_api.py b/mythic-docker/app/api/rabbitmq_api.py index e35d8cbd7..9610bf4d5 100755 --- a/mythic-docker/app/api/rabbitmq_api.py +++ b/mythic-docker/app/api/rabbitmq_api.py @@ -645,6 +645,8 @@ async def create_tasking(self, task: MythicTask) -> MythicTask: from app.api.task_api import add_all_payload_info payload_info = await add_all_payload_info(payload) + if payload_info["status"] == "error": + return payload_info payload_json["commands"] = payload_info["commands"] payload_json["c2info"] = payload_info["c2info"] payload_json["build_parameters"] = payload_info["build_parameters"] @@ -920,6 +922,8 @@ async def handle_automated_payload_creation_response(task, rsp, data, host): from app.api.task_api import add_all_payload_info payload_info = await add_all_payload_info(payload) + if payload_info["status"] == "error": + return payload_info payload_info = {**payload_info, **payload.to_json()} return {"status": "success", "response": payload_info} else: diff --git a/mythic-docker/app/api/response_api.py b/mythic-docker/app/api/response_api.py index 7768dcb72..24f73e355 100755 --- a/mythic-docker/app/api/response_api.py +++ b/mythic-docker/app/api/response_api.py @@ -562,33 +562,39 @@ async def post_agent_response(agent_message, callback): rabbit_message["task"]["callback"] = task.callback.to_json() # get the information for the callback's associated payload payload_info = await add_all_payload_info(task.callback.registered_payload) - rabbit_message["task"]["callback"]["build_parameters"] = payload_info[ - "build_parameters" - ] - rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"] - tags = await app.db_objects.execute( - db_model.tasktag_query.where(db_model.TaskTag.task == task)) - rabbit_message["task"]["tags"] = [t.tag for t in tags] - rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None - rabbit_message["response"] = parsed_response["process_response"] - if app.debugging_enabled: - await send_all_operations_message( - message=f"Sending message to {task.callback.registered_payload.payload_type.ptype}'s container for processing of a 'process_response' message:\n{str(parsed_response['process_container'])}", - level="info", source="debug", operation=task.callback.operation) - status = await send_pt_rabbitmq_message(payload_type=task.callback.registered_payload.payload_type.ptype, - command="process_container", - username="", - reference_id=task.id, - message_body=js.dumps(rabbit_message)) - if status["status"] == "error" and "type" in status: - logger.error("response_api.py: sending process_response message: " + status["error"]) - await app.db_objects.create(Response, task=task, - response="Container not running, failed to process process_response data, saving here") - await app.db_objects.create(Response, task=task, response=parsed_response["process_response"]) - task.callback.registered_payload.payload_type.container_count = 0 - await app.db_objects.update(task.callback.registered_payload.payload_type) - if status["status"] == "error": - logger.error("response_api.py: sending process_response message: " + status["error"]) + if payload_info["status"] == "error": + asyncio.create_task( + send_all_operations_message( + message=f"Failed to process post_response message for task {task.id}:\n{payload_info['error']}", + level="warning", source=f"task_response_{task.id}")) + else: + rabbit_message["task"]["callback"]["build_parameters"] = payload_info[ + "build_parameters" + ] + rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"] + tags = await app.db_objects.execute( + db_model.tasktag_query.where(db_model.TaskTag.task == task)) + rabbit_message["task"]["tags"] = [t.tag for t in tags] + rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None + rabbit_message["response"] = parsed_response["process_response"] + if app.debugging_enabled: + await send_all_operations_message( + message=f"Sending message to {task.callback.registered_payload.payload_type.ptype}'s container for processing of a 'process_response' message:\n{str(parsed_response['process_container'])}", + level="info", source="debug", operation=task.callback.operation) + status = await send_pt_rabbitmq_message(payload_type=task.callback.registered_payload.payload_type.ptype, + command="process_container", + username="", + reference_id=task.id, + message_body=js.dumps(rabbit_message)) + if status["status"] == "error" and "type" in status: + logger.error("response_api.py: sending process_response message: " + status["error"]) + await app.db_objects.create(Response, task=task, + response="Container not running, failed to process process_response data, saving here") + await app.db_objects.create(Response, task=task, response=parsed_response["process_response"]) + task.callback.registered_payload.payload_type.container_count = 0 + await app.db_objects.update(task.callback.registered_payload.payload_type) + elif status["status"] == "error": + logger.error("response_api.py: sending process_response message: " + status["error"]) except Exception as pc: logger.error("response_api.py: " + str(sys.exc_info()[-1].tb_lineno) + str(pc)) if app.debugging_enabled: diff --git a/mythic-docker/app/api/task_api.py b/mythic-docker/app/api/task_api.py index 04a1c8c9e..adc6c18e9 100755 --- a/mythic-docker/app/api/task_api.py +++ b/mythic-docker/app/api/task_api.py @@ -1017,12 +1017,14 @@ async def issue_dynamic_parameter_call(command: str, parameter_name: str, payloa rabbitmq_message = callback.to_json() # get the information for the callback's associated payload payload_info = await add_all_payload_info(callback.registered_payload) + if payload_info["status"] == "error": + return {"status": "error", "error": payload_info["error"]} rabbitmq_message["build_parameters"] = payload_info[ "build_parameters" ] rabbitmq_message["c2info"] = payload_info["c2info"] except Exception as e: - return {"status": "error", "error": "Failed to get callback and payload information"}, False + return {"status": "error", "error": "Failed to get callback and payload information"} status, successfully_sent = await payload_rpc.call(message={ "action": parameter_name, "command": command, @@ -1238,6 +1240,8 @@ async def submit_task_to_container(task, username, params: str = None): rabbit_message["task"]["callback"] = task.callback.to_json() # get the information for the callback's associated payload payload_info = await add_all_payload_info(task.callback.registered_payload) + if payload_info["status"] == "error": + return payload_info rabbit_message["task"]["callback"]["build_parameters"] = payload_info[ "build_parameters" ] @@ -1280,6 +1284,8 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user rabbit_message["task"]["callback"] = task.callback.to_json() # get the information for the callback's associated payload payload_info = await add_all_payload_info(task.callback.registered_payload) + if payload_info["status"] == "error": + return payload_info rabbit_message["task"]["callback"]["build_parameters"] = payload_info[ "build_parameters" ] @@ -1308,54 +1314,64 @@ async def submit_task_callback_to_container(task: Task, function_name: str, user async def add_all_payload_info(payload): - rabbit_message = {} - if payload.uuid in cached_payload_info: - rabbit_message["build_parameters"] = cached_payload_info[payload.uuid][ - "build_parameters" - ] - rabbit_message["commands"] = cached_payload_info[payload.uuid]["commands"] - rabbit_message["c2info"] = cached_payload_info[payload.uuid]["c2info"] - else: - cached_payload_info[payload.uuid] = {} - build_parameters = {} - build_params = await app.db_objects.execute( - db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload) - ) - for bp in build_params: - build_parameters[bp.build_parameter.name] = bp.parameter - rabbit_message["build_parameters"] = build_parameters - # cache it for later - cached_payload_info[payload.uuid]["build_parameters"] = build_parameters - c2_profile_parameters = [] - payloadc2profiles = await app.db_objects.execute( - db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload) - ) - for pc2p in payloadc2profiles: - # for each profile, we need to get all of the parameters and supplied values for just that profile - param_dict = {} - c2_param_instances = await app.db_objects.execute( - db_model.c2profileparametersinstance_query.where( - (C2ProfileParametersInstance.payload == payload) - & (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile) - ) + rabbit_message = {"status": "success"} + try: + if payload.uuid in cached_payload_info: + rabbit_message["build_parameters"] = cached_payload_info[payload.uuid][ + "build_parameters" + ] + rabbit_message["commands"] = cached_payload_info[payload.uuid]["commands"] + rabbit_message["c2info"] = cached_payload_info[payload.uuid]["c2info"] + else: + cached_payload_info[payload.uuid] = {} + build_parameters = {} + build_params = await app.db_objects.execute( + db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload) ) - # save all the variables off to a dictionary for easy looping - for instance in c2_param_instances: - param = instance.c2_profile_parameters - param_dict[param.name] = instance.value - - c2_profile_parameters.append( - {"parameters": param_dict, **pc2p.c2_profile.to_json()} + for bp in build_params: + build_parameters[bp.build_parameter.name] = bp.parameter + rabbit_message["build_parameters"] = build_parameters + # cache it for later + cached_payload_info[payload.uuid]["build_parameters"] = build_parameters + c2_profile_parameters = [] + payloadc2profiles = await app.db_objects.execute( + db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload) ) - rabbit_message["c2info"] = c2_profile_parameters - cached_payload_info[payload.uuid]["c2info"] = c2_profile_parameters - stamped_commands = await app.db_objects.execute(db_model.payloadcommand_query.where( - db_model.PayloadCommand.payload == payload - )) - commands = [c.command.cmd for c in stamped_commands] - rabbit_message["commands"] = commands - cached_payload_info[payload.uuid]["commands"] = commands - return rabbit_message + for pc2p in payloadc2profiles: + # for each profile, we need to get all of the parameters and supplied values for just that profile + param_dict = {} + c2_param_instances = await app.db_objects.execute( + db_model.c2profileparametersinstance_query.where( + (C2ProfileParametersInstance.payload == payload) + & (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile) + ) + ) + # save all the variables off to a dictionary for easy looping + for instance in c2_param_instances: + param = instance.c2_profile_parameters + param_dict[param.name] = instance.value + + c2_profile_parameters.append( + {"parameters": param_dict, **pc2p.c2_profile.to_json()} + ) + rabbit_message["c2info"] = c2_profile_parameters + cached_payload_info[payload.uuid]["c2info"] = c2_profile_parameters + stamped_commands = await app.db_objects.execute(db_model.payloadcommand_query.where( + db_model.PayloadCommand.payload == payload + )) + commands = [c.command.cmd for c in stamped_commands] + rabbit_message["commands"] = commands + cached_payload_info[payload.uuid]["commands"] = commands + return rabbit_message + except Exception as e: + rabbit_message["status"] = "error" + rabbit_message["error"] = str(e) + from app.api.operation_api import send_all_operations_message + asyncio.create_task( + send_all_operations_message( + message=f"Failed to fetch Payload info for {payload.uuid}:\n{str(e)}", + level="warning")) + return rabbit_message async def add_command_attack_to_task(task, command): diff --git a/mythic-docker/app/routes/routes.py b/mythic-docker/app/routes/routes.py index b279748be..6d96ff374 100755 --- a/mythic-docker/app/routes/routes.py +++ b/mythic-docker/app/routes/routes.py @@ -40,6 +40,7 @@ import app.database_models.model as db_model from sanic.log import logger from uuid import uuid4 +import asyncio env = Environment(loader=PackageLoader("app", "templates"), autoescape=True) @@ -64,8 +65,7 @@ async def respect_pivot(my_links, request): server_port = host_field[1] updated_links["server_ip"] = server_ip updated_links["server_port"] = server_port - updated_links["login"] = "{}://{}/login".format(request.scheme, request.host) - updated_links["register"] = "{}://{}/register".format(request.scheme, request.host) + updated_links["login"] = "/login" return updated_links @@ -369,59 +369,66 @@ async def setup_initial_info(sanic, loop): async def initial_setup(): # create mythic_admin import multiprocessing - max_worker_connection = int(400 / (multiprocessing.cpu_count() + 1)) - app.websocket_pool = await asyncpg.create_pool(mythic.config["DB_POOL_ASYNCPG_CONNECT_STRING"], - max_size=max_worker_connection) - # redis automatically creates a pool behind the scenes - app.redis_pool = redis.Redis(host="127.0.0.1", port=app.redis_port, db=0) - # clear the database on start - keys = app.redis_pool.keys("*") - for k in keys: - app.redis_pool.delete(k) - operators = await app.db_objects.execute(Operator.select()) - if len(operators) != 0: - logger.info("Users already exist, aborting initial install") - return - salt = str(uuid4()) - password = await crypto.hash_SHA512(salt + mythic_admin_password) try: - admin, created = await app.db_objects.get_or_create( - Operator, username=mythic_admin_user, password=password, admin=True, active=True, salt=salt + max_worker_connection = int(400 / (multiprocessing.cpu_count() + 1)) + app.websocket_pool = await asyncpg.create_pool(mythic.config["DB_POOL_ASYNCPG_CONNECT_STRING"], + max_size=max_worker_connection) + # redis automatically creates a pool behind the scenes + app.redis_pool = redis.Redis(host=app.redis_host, port=app.redis_port, db=0) + # clear the database on start + keys = app.redis_pool.keys("*") + for k in keys: + app.redis_pool.delete(k) + operators = await app.db_objects.execute(Operator.select()) + if len(operators) != 0: + logger.info("Users already exist, aborting initial install") + return + salt = str(uuid4()) + password = await crypto.hash_SHA512(salt + mythic_admin_password) + try: + admin, created = await app.db_objects.get_or_create( + Operator, username=mythic_admin_user, password=password, admin=True, active=True, salt=salt + ) + except Exception as e: + print(e) + return + logger.info("Created Admin") + # create default operation + operation, created = await app.db_objects.get_or_create( + Operation, + name=default_operation_name, + admin=admin, + complete=False, ) - except Exception as e: - print(e) - return - logger.info("Created Admin") - # create default operation - operation, created = await app.db_objects.get_or_create( - Operation, - name=default_operation_name, - admin=admin, - complete=False, - ) - logger.info("Created Operation") - await app.db_objects.get_or_create( - OperatorOperation, operator=admin, operation=operation - ) - admin.current_operation = operation - await app.db_objects.update(admin) - logger.info("Registered Admin with the default operation") - logger.info("Started parsing ATT&CK data...") - file = open("./app/default_files/other_info/attack.json", "r") - attack = js.load(file) # this is a lot of data and might take a hot second to load - for obj in attack["techniques"]: - await app.db_objects.create(ATTACK, **obj) - file.close() - logger.info("Created all ATT&CK entries") - file = open("./app/default_files/other_info/artifacts.json", "r") - artifacts_file = js.load(file) - for artifact in artifacts_file["artifacts"]: + logger.info("Created Operation") await app.db_objects.get_or_create( - Artifact, name=artifact["name"], description=artifact["description"] + OperatorOperation, operator=admin, operation=operation ) - file.close() - logger.info("Created all base artifacts") - logger.info("Successfully finished initial setup") + admin.current_operation = operation + await app.db_objects.update(admin) + logger.info("Registered Admin with the default operation") + logger.info("Started parsing ATT&CK data...") + file = open("./app/default_files/other_info/attack.json", "r") + attack = js.load(file) # this is a lot of data and might take a hot second to load + for obj in attack["techniques"]: + await app.db_objects.create(ATTACK, **obj) + file.close() + logger.info("Created all ATT&CK entries") + file = open("./app/default_files/other_info/artifacts.json", "r") + artifacts_file = js.load(file) + for artifact in artifacts_file["artifacts"]: + await app.db_objects.get_or_create( + Artifact, name=artifact["name"], description=artifact["description"] + ) + file.close() + logger.info("Created all base artifacts") + logger.info("Successfully finished initial setup") + except Exception as e: + from app.api.operation_api import send_all_operations_message + asyncio.create_task( + send_all_operations_message( + message=f"Worker failed to initialize:\n {str(e)}", + level="warning")) # /static serves out static images and files @@ -440,5 +447,4 @@ async def initial_setup(): links["index"] = mythic.url_for("index") links["login"] = links["WEB_BASE"] + "/login" links["logout"] = mythic.url_for("logout") -links["register"] = links["WEB_BASE"] + "/register" links["settings"] = mythic.url_for("settings") diff --git a/mythic-react-docker/mythic/src/components/App.js b/mythic-react-docker/mythic/src/components/App.js index 31a624d84..0483a8424 100644 --- a/mythic-react-docker/mythic/src/components/App.js +++ b/mythic-react-docker/mythic/src/components/App.js @@ -61,7 +61,7 @@ export function App(props) { const theme = React.useMemo( () => createMuiTheme({ palette: { primary: { - main: "#617AB1" + main: "#7f93c0" }, secondary: { main: "#a791c3" @@ -86,6 +86,10 @@ export function App(props) { primary: themeMode === 'dark' ? '#fff' : '#000', secondary: themeMode === 'dark' ? 'rgba(255, 255, 255, 0.7)': 'rgba(0, 0, 0, 0.54)' }, + textBackgroundColor: themeMode === 'dark' ? '#74828b' : '#d9dbdc', + textBackgroundColorMythic: themeMode === 'dark' ? '#436b9f': '#aadcf5', + textBackgroundColorSuccess: themeMode === 'dark' ? '#09a21a' : '#70e373', + textBackgroundColorError: themeMode === 'dark' ? '#9f1616' : '#f19da3', graphGroup: themeMode === 'dark' ? '#394c5d' : "#d3d7e8" }, folderColor: '#f1d592', @@ -111,7 +115,7 @@ export function App(props) { }}>
- {me.loggedIn && me.user !== undefined && me.user !== null ? () : (null)} + {me.loggedIn && me.user !== undefined && me.user !== null ? () : (null)}
diff --git a/mythic-react-docker/mythic/src/components/EventFeedNotifications.js b/mythic-react-docker/mythic/src/components/EventFeedNotifications.js index 78a3c6fd9..8853c99b7 100644 --- a/mythic-react-docker/mythic/src/components/EventFeedNotifications.js +++ b/mythic-react-docker/mythic/src/components/EventFeedNotifications.js @@ -39,7 +39,7 @@ export function EventFeedNotifications(props) { return; } if(data.operationeventlog[0].operator ){ - const message = data.operationeventlog[0].operator.username + ":" + data.operationaleventlog[0].message; + const message = data.operationeventlog[0].operator.username + ":" + data.operationeventlog[0].message; snackActions.toast(message, data.operationeventlog[0].level, { autoHideDuration: 3000}); }else if(!data.operationeventlog[0].operator){ snackActions.toast(data.operationeventlog[0].message, data.operationeventlog[0].level, {autoHideDuration: 3000}); diff --git a/mythic-react-docker/mythic/src/components/MythicComponents/MythicDialog.js b/mythic-react-docker/mythic/src/components/MythicComponents/MythicDialog.js index 5afee9c19..69b67caea 100644 --- a/mythic-react-docker/mythic/src/components/MythicComponents/MythicDialog.js +++ b/mythic-react-docker/mythic/src/components/MythicComponents/MythicDialog.js @@ -56,7 +56,7 @@ export function MythicModifyStringDialog(props) { {props.title} - +
); } diff --git a/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeed.js b/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeed.js index d54d1f53a..270381fee 100644 --- a/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeed.js +++ b/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeed.js @@ -131,6 +131,7 @@ export function EventFeed(props){ }, fetchPolicy: "network-only", onCompleted: (data) => { + snackActions.dismiss(); if(data.operationeventlog.length === 0){ snackActions.info("No more events"); return; @@ -143,6 +144,7 @@ export function EventFeed(props){ }, [...operationeventlog]); setOffset(offset + EVENT_QUERY_SIZE); setOperationEventLog(newEvents); + snackActions.success("Successfully fetched more events"); } }); const [getSurroundingEventQuery] = useLazyQuery(GET_Surrounding_Events, { @@ -151,13 +153,22 @@ export function EventFeed(props){ }, fetchPolicy: "network-only", onCompleted: (data) => { + snackActions.dismiss(); + let foundNew = false; const newEvents = data.operationeventlog.reduce( (prev, cur) => { if(prev.find(({ id }) => id === cur.id)){ return [...prev]; } + foundNew = true; return [...prev, cur]; }, [...operationeventlog]); setOperationEventLog(newEvents); + if(foundNew){ + snackActions.success("Successfully fetched surrounding events"); + }else{ + snackActions.info("No additional surrounding events"); + } + } }); const [getNextError] = useLazyQuery(GET_Event_Feed_Next_Error, { @@ -166,6 +177,7 @@ export function EventFeed(props){ }, fetchPolicy: "network-only", onCompleted: (data) => { + snackActions.dismiss(); if(data.operationeventlog.length === 0){ snackActions.info("No more events"); return; @@ -177,6 +189,7 @@ export function EventFeed(props){ return [...prev, cur]; }, [...operationeventlog]); setOperationEventLog(newEvents); + snackActions.success("Successfully fetched more errors"); } }); const [newOperationEventLog] = useMutation(Create_Operational_Event_Log); @@ -185,6 +198,7 @@ export function EventFeed(props){ const removedMessage = data.update_operationeventlog.returning[0]; const newMessages = operationeventlog.filter(op => (op.id !== removedMessage.id)); setOperationEventLog(newMessages); + snackActions.success("Successfully deleted event log"); } }); const [updateResolution] = useMutation(Update_Resolution, { @@ -212,6 +226,7 @@ export function EventFeed(props){ } }); const onUpdateDeleted = ({id}) => { + snackActions.info("Deleting event log..."); updateDeleted({variables: {id}}); } const onSubmitMessage = ({level, message}) => { @@ -224,12 +239,15 @@ export function EventFeed(props){ updateLevel({variables: {id}}) } const loadMore = () => { + snackActions.info("Loading more events..."); getMoreTasking({variables: {operation_id: me.user.current_operation_id, offset: offset, eventQuerySize: EVENT_QUERY_SIZE}}) } const loadNextError = () => { + snackActions.info("Loading more errors..."); getNextError({variables: {operation_id: me.user.current_operation_id}}) } const getSurroundingEvents = ({id}) => { + snackActions.info("Loading surrounding events..."); getSurroundingEventQuery({variables: {lower_id: id - SURROUNDING_EVENTS, upper_id: id + SURROUNDING_EVENTS, operation_id: me.user.current_operation_id}}) } return ( diff --git a/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeedTableEvents.js b/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeedTableEvents.js index 3f074c3ed..2f1f8cc4d 100644 --- a/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeedTableEvents.js +++ b/mythic-react-docker/mythic/src/components/pages/EventFeed/EventFeedTableEvents.js @@ -27,13 +27,13 @@ export function EventFeedTableEvents(props){ const me = useReactiveVar(meState); return ( - + {props.operator ? props.operator.username[0] : "M"} - {toLocalTime(props.timestamp, me.user.view_utc_time)} } secondary={ -
{props.message}
+
{props.message}
} - style={{overflowX: "auto"}} + style={{}} />