diff --git a/frontend/app/public/images/combatlog-fleet-card.jpg b/frontend/app/public/images/combatlog-fleet-card.jpg
new file mode 100644
index 00000000..db7c7566
Binary files /dev/null and b/frontend/app/public/images/combatlog-fleet-card.jpg differ
diff --git a/frontend/app/src/components/blocks/CombatLogAnalysisComponent.astro b/frontend/app/src/components/blocks/CombatLogAnalysisComponent.astro
index a390830a..97aa3edd 100644
--- a/frontend/app/src/components/blocks/CombatLogAnalysisComponent.astro
+++ b/frontend/app/src/components/blocks/CombatLogAnalysisComponent.astro
@@ -18,6 +18,7 @@ const damage_out = combat_log_analysis?.damage_out
const timeline_time = timeline.map(datetime => datetime.split(' ')[1])
import { format_date_time } from '@helpers/date'
+import { get_item_icon } from '@helpers/eve_image_server'
import Flexblock from '@components/compositions/Flexblock.astro'
import Grid from '@components/compositions/Grid.astro'
@@ -26,19 +27,63 @@ import TextGroup from '@components/blocks/TextGroup.astro'
import ComponentBlock from '@components/blocks/ComponentBlock.astro'
import DamageBadge from '@components/blocks/DamageBadge.astro';
import CombatLogChart from '@components/blocks/CombatLogChart.astro';
+import Badge from '@components/blocks/Badge.astro';
---
-
- {format_date_time(lang, combat_log_analysis?.start)}
- {format_date_time(lang, combat_log_analysis?.end)}
- {combat_log_analysis?.logged_events.toLocaleString()}
-
+
+
+ {format_date_time(lang, combat_log_analysis?.start)}
+ {format_date_time(lang, combat_log_analysis?.end)}
+ {combat_log_analysis?.logged_events.toLocaleString()}
+ {combat_log_analysis?.character_name}
+ {combat_log_analysis?.fitting &&
+
+
+
+ }
+ {(combat_log_analysis?.fleet_id ?? 0 > 0) &&
+ {combat_log_analysis.fleet_id}
+ }
+
+
+ {(combat_log_analysis?.max_to || combat_log_analysis?.max_from) &&
+
+ {combat_log_analysis?.max_to &&
+
+
+ {combat_log_analysis?.max_to.damage} - {combat_log_analysis?.max_to.weapon} - {combat_log_analysis?.max_to.outcome}
+ To {combat_log_analysis?.max_to.entity}
+
+
+ }
+ {combat_log_analysis?.max_from &&
+
+
+ {combat_log_analysis?.max_from.damage} - {combat_log_analysis?.max_from.weapon} - {combat_log_analysis?.max_from.outcome}
+ From {combat_log_analysis?.max_from.entity}
+
+
+ }
+
+ }
+
{(combat_log_analysis?.weapons?.length ?? 0) > 0 &&
diff --git a/frontend/app/src/components/blocks/CombatLogChart.astro b/frontend/app/src/components/blocks/CombatLogChart.astro
index 818fbdc4..0e298bcf 100644
--- a/frontend/app/src/components/blocks/CombatLogChart.astro
+++ b/frontend/app/src/components/blocks/CombatLogChart.astro
@@ -26,8 +26,8 @@ import MultiRangeInput from '@components/blocks/MultiRangeInput.astro';
min: 0,
max: 0,
timeline_time: ${JSON.stringify(timeline_time)},
- damage_in: ${JSON.stringify(damage_in)},
- damage_out: ${JSON.stringify(damage_out)},
+ damage_in: ${JSON.stringify(damage_in.map(damage => damage/10))},
+ damage_out: ${JSON.stringify(damage_out.map(damage => damage/10))},
init() {
const ctx = document.getElementById("damage-log-chart").getContext("2d")
@@ -41,7 +41,7 @@ import MultiRangeInput from '@components/blocks/MultiRangeInput.astro';
labels: this.timeline_time,
datasets: [
{
- label: "${t('damage_taken')}",
+ label: "${t('dps_taken')}",
data: this.damage_in,
pointStyle: 'rect',
backgroundColor: '#b53620',
@@ -53,7 +53,7 @@ import MultiRangeInput from '@components/blocks/MultiRangeInput.astro';
}
},
{
- label: "${t('damage_done')}",
+ label: "${t('dps_done')}",
data: this.damage_out,
pointStyle: 'rect',
backgroundColor: '#198754',
@@ -86,7 +86,7 @@ import MultiRangeInput from '@components/blocks/MultiRangeInput.astro';
beginAtZero: true,
title: {
display: true,
- text: "${t('damage')}"
+ text: "${t('damage_per_second')}"
},
grid: {
display: false
diff --git a/frontend/app/src/components/blocks/DamageBadge.astro b/frontend/app/src/components/blocks/DamageBadge.astro
index 1b1ffbb8..d21eda93 100644
--- a/frontend/app/src/components/blocks/DamageBadge.astro
+++ b/frontend/app/src/components/blocks/DamageBadge.astro
@@ -31,7 +31,7 @@ import Square from '@components/blocks/Square.astro'
x-init="tippy($el, tippy_options)"
data-tippy-content={t('damage_to_enemy')}
/>
- {damage.total_to.toLocaleString()} ({damage.dps_to.toLocaleString()} dps) - {damage.volleys_to.toLocaleString()} {t('volleys')}
+ {damage.total_to.toLocaleString()} ({damage.dps_to.toLocaleString()} {t('per_volley')}) - {damage.volleys_to.toLocaleString()} {t('volleys')}
}
{damage.volleys_from > 0 &&
@@ -41,7 +41,7 @@ import Square from '@components/blocks/Square.astro'
x-init="tippy($el, tippy_options)"
data-tippy-content={t('damage_from_enemy')}
/>
- {damage.total_from.toLocaleString()} ({damage.dps_from.toLocaleString()} dps) - {damage.volleys_from.toLocaleString()} {t('volleys')}
+ {damage.total_from.toLocaleString()} ({damage.dps_from.toLocaleString()} {t('per_volley')}) - {damage.volleys_from.toLocaleString()} {t('volleys')}
}
diff --git a/frontend/app/src/components/blocks/FittingCombatLogItem.astro b/frontend/app/src/components/blocks/FittingCombatLogItem.astro
new file mode 100644
index 00000000..b3e8347e
--- /dev/null
+++ b/frontend/app/src/components/blocks/FittingCombatLogItem.astro
@@ -0,0 +1,83 @@
+---
+import { i18n } from '@helpers/i18n'
+const { t, lang, translatePath } = i18n(Astro.url)
+
+import type { SavedCombatLog, Fitting } from '@dtypes/api.minmatar.org'
+
+interface Props {
+ log: SavedCombatLog;
+ fitting: Fitting | undefined;
+}
+
+const {
+ log,
+ fitting,
+} = Astro.props
+
+const CAPSULE_TYPE_ID = 670
+
+import { format_date_short } from '@helpers/date'
+
+import Flexblock from "@components/compositions/Flexblock.astro";
+import Wrapper from "@components/compositions/Wrapper.astro";
+
+import ComponentBlock from '@components/blocks/ComponentBlock.astro';
+import ItemPicture from "@components/blocks/ItemPicture.astro";
+import ClipboardButton from "@components/blocks/ClipboardButton.astro";
+---
+
+
+
+
+
+
+ {fitting?.name ?? t('unknown_fitting_name')}
+ {format_date_short(lang, log.uploaded_at)}
+
+
+ {fitting &&
+
+ {translatePath(`${Astro.url}/${log.id}`)}
+
+ }
+
+
+
+
\ No newline at end of file
diff --git a/frontend/app/src/components/blocks/FleetCombatLogItem.astro b/frontend/app/src/components/blocks/FleetCombatLogItem.astro
new file mode 100644
index 00000000..25952578
--- /dev/null
+++ b/frontend/app/src/components/blocks/FleetCombatLogItem.astro
@@ -0,0 +1,98 @@
+---
+import { i18n } from '@helpers/i18n'
+const { t, lang, translatePath } = i18n(Astro.url)
+
+import type { SavedCombatLog } from '@dtypes/api.minmatar.org'
+
+interface Props {
+ log: SavedCombatLog;
+}
+
+const {
+ log,
+} = Astro.props
+
+import { format_date_short } from '@helpers/date'
+
+import Flexblock from "@components/compositions/Flexblock.astro";
+import Wrapper from "@components/compositions/Wrapper.astro";
+
+import ComponentBlock from '@components/blocks/ComponentBlock.astro';
+import ClipboardButton from "@components/blocks/ClipboardButton.astro";
+import FleetEvEIcon from '@components/icons/FleetEvEIcon.astro';
+---
+
+
+
+
+
+
+
+ {t('fleet')} {log.fleet_id}
+ {format_date_short(lang, log.uploaded_at)}
+
+
+
+ {translatePath(`${Astro.url}/${log.id}`)}
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/app/src/components/blocks/FleetCombatLogListItem.astro b/frontend/app/src/components/blocks/FleetCombatLogListItem.astro
new file mode 100644
index 00000000..0c60fea3
--- /dev/null
+++ b/frontend/app/src/components/blocks/FleetCombatLogListItem.astro
@@ -0,0 +1,34 @@
+---
+import { i18n } from '@helpers/i18n'
+const { lang, t, translatePath } = i18n(Astro.url)
+
+import type { FleetCombatLog } from '@dtypes/layout_components'
+import { format_date_time } from '@helpers/date'
+
+interface Props {
+ log: FleetCombatLog;
+}
+
+const {
+ log,
+} = Astro.props
+
+import PilotBadge from '@components/blocks/PilotBadge.astro';
+import StylessButton from './StylessButton.astro';
+---
+
+
+
+ {format_date_time(lang, log.uploaded_at)}
+
+
+
+
\ No newline at end of file
diff --git a/frontend/app/src/components/blocks/FleetDetails.astro b/frontend/app/src/components/blocks/FleetDetails.astro
index ba50dd55..6bfed9ef 100644
--- a/frontend/app/src/components/blocks/FleetDetails.astro
+++ b/frontend/app/src/components/blocks/FleetDetails.astro
@@ -2,7 +2,7 @@
import { i18n } from '@helpers/i18n'
const { lang, t, translatePath } = i18n(Astro.url)
-import type { FleetUI, FleetCompositionUI, FleetRadarUI, SRPUI } from '@dtypes/layout_components'
+import type { FleetUI, FleetCompositionUI, FleetRadarUI, SRPUI, FleetCombatLog } from '@dtypes/layout_components'
interface Props {
fleet: FleetUI;
@@ -12,6 +12,7 @@ interface Props {
fleet_composition?: FleetCompositionUI[];
fleet_radar?: FleetRadarUI[];
fleet_srps?: SRPUI[];
+ saved_logs?: FleetCombatLog[];
}
const {
@@ -22,6 +23,7 @@ const {
fleet_composition,
fleet_radar,
fleet_srps,
+ saved_logs = [],
} = Astro.props
import { query_string } from '@helpers/string';
@@ -36,6 +38,7 @@ import TextBox from '@components/layout/TextBox.astro';
import Flexblock from '@components/compositions/Flexblock.astro';
import FlexInline from '@components/compositions/FlexInline.astro';
+import Grid from '@components/compositions/Grid.astro';
import FleetCompositionBlock from '@components/blocks/FleetCompositionBlock.astro';
import Button from '@components/blocks/Button.astro';
@@ -45,6 +48,7 @@ import ComponentBlock from '@components/blocks/ComponentBlock.astro';
import PilotBadge from '@components/blocks/PilotBadge.astro';
import FleetStatus from '@components/blocks/FleetStatus.astro';
import SRPTable from '@components/blocks/SRPTable.astro';
+import FleetCombatLogListItem from '@components/blocks/FleetCombatLogListItem.astro';
const eve_time = new Date(fleet.start_time);
const eve_time_text = eve_time.toLocaleDateString(lang, JSON.parse(import.meta.env.DATETIME_FORMAT))
@@ -197,4 +201,20 @@ const eve_time_text = eve_time.toLocaleDateString(lang, JSON.parse(import.meta.e
+
+ {saved_logs?.length > 0 &&
+
+
+
+ {t('fleet_logs')}
+ {saved_logs.length} {saved_logs.length !== 1 ? t('logs_recorded') : t('log_recorded')}
+
+
+ {saved_logs.map(log =>
+
+ )}
+
+
+
+ }
\ No newline at end of file
diff --git a/frontend/app/src/components/blocks/FleetListItem.astro b/frontend/app/src/components/blocks/FleetListItem.astro
index 1cd9260a..003c4b7e 100644
--- a/frontend/app/src/components/blocks/FleetListItem.astro
+++ b/frontend/app/src/components/blocks/FleetListItem.astro
@@ -42,7 +42,6 @@ import TextGroup from '@components/blocks/TextGroup.astro';
import MagnifierIcon from '@components/icons/buttons/MagnifierIcon.astro';
import StratopIcon from '@components/icons/StratopIcon.astro';
import NonStrategicIcon from '@components/icons/NonStrategicIcon.astro';
-import CasualIcon from '@components/icons/CasualIcon.astro';
import TrainingIcon from '@components/icons/TrainingIcon.astro';
import FlexInline from '@components/compositions/FlexInline.astro';
diff --git a/frontend/app/src/components/partials/HeadScripts.astro b/frontend/app/src/components/partials/HeadScripts.astro
index 1737ca98..59a99ed4 100644
--- a/frontend/app/src/components/partials/HeadScripts.astro
+++ b/frontend/app/src/components/partials/HeadScripts.astro
@@ -37,6 +37,6 @@
-
+
\ No newline at end of file
diff --git a/frontend/app/src/helpers/api.minmatar.org/combatlog.ts b/frontend/app/src/helpers/api.minmatar.org/combatlog.ts
index 941ff714..27484cc3 100644
--- a/frontend/app/src/helpers/api.minmatar.org/combatlog.ts
+++ b/frontend/app/src/helpers/api.minmatar.org/combatlog.ts
@@ -1,8 +1,79 @@
-import type { CombatLog } from '@dtypes/api.minmatar.org'
+import type { CombatLog, SavedCombatLog, SavedLogsRequest } from '@dtypes/api.minmatar.org'
import { get_error_message, query_string } from '@helpers/string'
const API_ENDPOINT = `${import.meta.env.API_URL}/api/combatlog`
+export async function get_saved_logs(access_token:string, saved_logs_request:SavedLogsRequest) {
+ const headers = {
+ 'Content-Type': 'text/plain',
+ 'Authorization': `Bearer ${access_token}`
+ }
+
+ const { user_id, fleet_id } = saved_logs_request
+
+ const query_params = {
+ ...(user_id && { user_id }),
+ ...(fleet_id && { fleet_id }),
+ };
+
+ const query = query_string(query_params)
+
+ const ENDPOINT = `${API_ENDPOINT}${query ? `?${query}` : '/'}`
+
+ console.log(`Requesting GET: ${ENDPOINT}`)
+
+ try {
+ const response = await fetch(ENDPOINT, {
+ headers: headers,
+ method: 'GET'
+ })
+
+ // console.log(response)
+
+ if (!response.ok) {
+ throw new Error(get_error_message(
+ response.status,
+ `GET ${ENDPOINT}`
+ ))
+ }
+
+ return await response.json() as SavedCombatLog[];
+ } catch (error) {
+ throw new Error(`Error analizing log: ${error.message}`);
+ }
+}
+
+export async function get_log_by_id(access_token:string, log_id:number) {
+ const headers = {
+ 'Content-Type': 'text/plain',
+ 'Authorization': `Bearer ${access_token}`
+ }
+
+ const ENDPOINT = `${API_ENDPOINT}/${log_id}`
+
+ console.log(`Requesting GET: ${ENDPOINT}`)
+
+ try {
+ const response = await fetch(ENDPOINT, {
+ headers: headers,
+ method: 'GET'
+ })
+
+ // console.log(response)
+
+ if (!response.ok) {
+ throw new Error(get_error_message(
+ response.status,
+ `GET ${ENDPOINT}`
+ ))
+ }
+
+ return await response.json() as CombatLog;
+ } catch (error) {
+ throw new Error(`Error fetching log: ${error.message}`);
+ }
+}
+
export async function analize_log(combatlog:string) {
const headers = {
'Content-Type': 'text/plain'
@@ -34,11 +105,15 @@ export async function analize_log(combatlog:string) {
}
}
-export async function analize_zipped_log(combatlog:Uint8Array, fitting_id?:number, fleet_id?:number) {
+export async function analize_zipped_log(combatlog:Uint8Array, access_token?:string, fitting_id?:number, fleet_id?:number) {
const headers = {
- 'Content-Type': 'application/gzip',
+ 'Content-Type': 'application/gzip'
}
+ if (access_token)
+ headers['Authorization'] = `Bearer ${access_token}`
+ console.log(headers)
+
const optional_attributes = {
...((fitting_id !== undefined && fitting_id > 0) && { "fitting_id": fitting_id }),
...((fleet_id !== undefined && fleet_id > 0) && { "fleet_id": fleet_id }),
diff --git a/frontend/app/src/helpers/fetching/combatlog.ts b/frontend/app/src/helpers/fetching/combatlog.ts
index 7502e362..89e0227d 100644
--- a/frontend/app/src/helpers/fetching/combatlog.ts
+++ b/frontend/app/src/helpers/fetching/combatlog.ts
@@ -1,10 +1,78 @@
-import type { CombatLogAnalysis } from '@dtypes/layout_components'
-import { analize_log, analize_zipped_log } from '@helpers/api.minmatar.org/combatlog'
+import { useTranslations } from '@i18n/utils';
+
+const t = useTranslations('en');
+
+import type { CombatLogAnalysis, CombatLogMaxUI, FleetCombatLog } from '@dtypes/layout_components'
+import { analize_log, analize_zipped_log, get_log_by_id, get_saved_logs } from '@helpers/api.minmatar.org/combatlog'
import { generate_timeline } from '@helpers/date'
import { parse_damage_from_logs } from '@helpers/eve'
+import { get_fitting_by_id } from '@helpers/api.minmatar.org/ships'
+import { unique_values } from '@helpers/array'
+import { get_users_character } from '@helpers/fetching/characters'
+
+export async function fetch_combatlog_analysis(combatlog:string | Uint8Array, gzipped:boolean, access_token?:string, fitting_id?:number, fleet_id?:number) {
+ const analysis = gzipped ? await analize_zipped_log(combatlog as Uint8Array, access_token, fitting_id, fleet_id) : await analize_log(combatlog as string)
+
+ const start_time = analysis.times[0].name
+ const end_time = analysis.times[analysis.times.length - 1].name
+
+ const timeline = generate_timeline(start_time, end_time)
+ const damage_in:number[] = []
+ const damage_out:number[] = []
+
+ const damage_time_in = {}
+ const damage_time_out = {}
+ analysis.times.forEach(tick => {
+ damage_time_in[tick.name] = tick.damage_from
+ damage_time_out[tick.name] = tick.damage_to
+ })
+
+ timeline.forEach(tick => {
+ damage_in.push(damage_time_in[tick] ?? 0)
+ damage_out.push(damage_time_out[tick] ?? 0)
+ })
+
+ const enemies = await parse_damage_from_logs(analysis.enemies)
+ const weapons = await parse_damage_from_logs(analysis.weapons)
+
+ const max_from = analysis?.max_from ? {
+ damage: analysis?.max_from.damage,
+ entity: analysis?.max_from.entity,
+ outcome: analysis?.max_from.outcome,
+ weapon: analysis?.max_from.weapon,
+ } as CombatLogMaxUI : null
+
+ const max_to = analysis?.max_to ? {
+ damage: analysis?.max_to.damage,
+ entity: analysis?.max_to.entity,
+ outcome: analysis?.max_to.outcome,
+ weapon: analysis?.max_to.weapon,
+ } as CombatLogMaxUI : null
+
+ const fitting = analysis?.fitting_id > 0 ? await get_fitting_by_id(analysis?.fitting_id) : null
+ const fleet = analysis?.fleet_id > 0 ? analysis?.fleet_id : null
+
+ return {
+ logged_events: analysis.logged_events,
+ damage_done: analysis.damage_done,
+ damage_taken: analysis.damage_taken,
+ start: new Date(analysis.start),
+ end: new Date(analysis.end),
+ damage_in: damage_in,
+ damage_out: damage_out,
+ timeline: timeline,
+ enemies: enemies,
+ weapons: weapons,
+ character_name: analysis.character_name,
+ ...(fitting && { fitting }),
+ ...(fleet_id && { fleet }),
+ ...(max_from && { max_from }),
+ ...(max_to && { max_to }),
+ } as CombatLogAnalysis
+}
-export async function fetch_combatlog_analysis(combatlog:string | Uint8Array, gzipped:boolean, fitting_id?:number, fleet_id?:number) {
- const analysis = gzipped ? await analize_zipped_log(combatlog as Uint8Array, fitting_id, fleet_id) : await analize_log(combatlog as string)
+export async function fetch_combatlog_by_id(access_token:string, log_id:number) {
+ const analysis = await get_log_by_id(access_token, log_id)
const start_time = analysis.times[0].name
const end_time = analysis.times[analysis.times.length - 1].name
@@ -28,6 +96,23 @@ export async function fetch_combatlog_analysis(combatlog:string | Uint8Array, gz
const enemies = await parse_damage_from_logs(analysis.enemies)
const weapons = await parse_damage_from_logs(analysis.weapons)
+ const max_from = analysis?.max_from ? {
+ damage: analysis?.max_from.damage,
+ entity: analysis?.max_from.entity,
+ outcome: analysis?.max_from.outcome,
+ weapon: analysis?.max_from.weapon,
+ } as CombatLogMaxUI : null
+
+ const max_to = analysis?.max_to ? {
+ damage: analysis?.max_to.damage,
+ entity: analysis?.max_to.entity,
+ outcome: analysis?.max_to.outcome,
+ weapon: analysis?.max_to.weapon,
+ } as CombatLogMaxUI : null
+
+ const fitting = analysis?.fitting_id > 0 ? await get_fitting_by_id(analysis?.fitting_id) : null
+ const fleet_id = analysis?.fleet_id > 0 ? analysis?.fleet_id : null
+
return {
logged_events: analysis.logged_events,
damage_done: analysis.damage_done,
@@ -39,5 +124,38 @@ export async function fetch_combatlog_analysis(combatlog:string | Uint8Array, gz
timeline: timeline,
enemies: enemies,
weapons: weapons,
+ character_name: analysis.character_name,
+ ...(fitting && { fitting }),
+ ...(fleet_id && { fleet_id }),
+ ...(max_from && { max_from }),
+ ...(max_to && { max_to }),
} as CombatLogAnalysis
+}
+
+export async function get_fleet_combatlogs(access_token:string, fleet_id:number) {
+ const fleet_logs = await get_saved_logs(access_token as string, { fleet_id: fleet_id })
+ const not_null_applications = fleet_logs.filter(fleet_log => fleet_log)
+ const user_ids = unique_values(not_null_applications.map(log => log.user_id))
+ const loggers = user_ids.length > 0 ? await get_users_character(user_ids) : []
+
+ return fleet_logs.map(log => {
+ const logger = loggers.find(log => log.user_id === log.user_id)
+
+ return {
+ id: log.id,
+ uploaded_at: log.uploaded_at,
+ user_id: log.user_id,
+ logger: logger !== undefined ? {
+ character_id: logger.character_id,
+ character_name: logger.character_name,
+ corporation: {
+ id: logger.corporation_id,
+ name: logger.corporation_name,
+ }
+ } : {
+ character_id: 0,
+ character_name: t('unknown_character')
+ }
+ } as FleetCombatLog
+ })
}
\ No newline at end of file
diff --git a/frontend/app/src/helpers/fetching/posts.ts b/frontend/app/src/helpers/fetching/posts.ts
index 403fea30..bffc3820 100644
--- a/frontend/app/src/helpers/fetching/posts.ts
+++ b/frontend/app/src/helpers/fetching/posts.ts
@@ -3,7 +3,7 @@ import type { PostListUI, PostUI, Posts } from '@dtypes/layout_components'
import type { PostRequest } from '@dtypes/api.minmatar.org'
import { get_posts, get_post, get_posts_tags } from '@helpers/api.minmatar.org/posts'
import { get_users_character, get_user_character } from '@helpers/fetching/characters'
-import { paginate, unique_values } from '@helpers/array'
+import { unique_values } from '@helpers/array'
const t = useTranslations('en');
diff --git a/frontend/app/src/i18n/ui.ts b/frontend/app/src/i18n/ui.ts
index a97abaff..e9ffdc95 100644
--- a/frontend/app/src/i18n/ui.ts
+++ b/frontend/app/src/i18n/ui.ts
@@ -1173,6 +1173,24 @@ export const ui = {
'share_post': 'Share post',
'post_url_copied': 'Post URL copied!',
'view_station_market_contracts': 'Contract service available on this station',
+ 'damage_per_second': 'Damage per second',
+ 'dps_taken': 'DPS taken',
+ 'dps_done': 'DPS done',
+ 'per_volley': 'per volley',
+ 'character_name': 'Character name',
+ 'max_damage_inflicted': 'Max damage inflicted',
+ 'max_damage_received': 'Max damage received',
+ 'fitting_logs': 'Fitting logs',
+ 'fleet_logs': 'Fleet logs',
+ 'unknown_fitting_name': 'Unknown fitting name',
+ 'share': 'Share',
+ 'get_combatlog_selects_error': 'An error occurred while fetching fleet and fitting options.',
+ 'get_combatlog_error': 'An error occurred while fetching the combatlog.',
+ 'invalid_combatlog': 'The Combat Log is not valid',
+ 'uploaded_at': 'Uploaded at',
+ 'combat_logs': 'Combat Logs',
+ 'logs_recorded': 'logs recorded',
+ 'log_recorded': 'log recorded',
'fetch_srp_error': 'An error ocurred fetching SRPs',
'copy_name': 'Copy name',
'copy_amount': 'Copy amount',
diff --git a/frontend/app/src/pages/fleets/history/[fleet_id].astro b/frontend/app/src/pages/fleets/history/[fleet_id].astro
index 6cdae050..864b9eb5 100644
--- a/frontend/app/src/pages/fleets/history/[fleet_id].astro
+++ b/frontend/app/src/pages/fleets/history/[fleet_id].astro
@@ -28,16 +28,18 @@ const fleet_id = parseInt(Astro.params.fleet_id ?? '0')
if (isNaN(fleet_id))
return HTTP_404_Not_Found()
-import type { FleetUI, SRPUI } from '@dtypes/layout_components'
+import type { FleetUI, SRPUI, FleetCombatLog } from '@dtypes/layout_components'
import type { EveCharacterProfile } from '@dtypes/api.minmatar.org'
import { fetch_fleet_by_id } from '@helpers/fetching/fleets'
import { fetch_fleet_srps } from '@helpers/fetching/srp'
import { get_user_character } from '@helpers/fetching/characters'
+import { get_fleet_combatlogs } from '@helpers/fetching/combatlog'
let fetch_fleets_error:string | false = false
let fleet:FleetUI | null = null
let fleet_srps:SRPUI[] | null = []
let user_character:EveCharacterProfile | null = null
+let saved_logs:FleetCombatLog[] = []
try {
fleet = await fetch_fleet_by_id(auth_token as string, fleet_id)
@@ -51,6 +53,12 @@ try {
fetch_fleets_error = prod_error_messages() ? t('fetch_fleets_error') : error.message
}
+try {
+ saved_logs = user && !fetch_fleets_error ? await get_fleet_combatlogs(auth_token as string, fleet_id) : []
+} catch (error) {
+ console.log(error)
+}
+
const can_remove_fleet = is_superuser ||
user_permissions.includes('fleets.delete_evefleet') ||
user_character?.character_id === fleet?.fleet_commander_id
@@ -123,6 +131,7 @@ const page_title = `${t('fleet')} ${fleet_id}`;
history={true}
can_remove_fleet={can_remove_fleet}
fleet_srps={fleet_srps}
+ saved_logs={saved_logs}
/>
}
diff --git a/frontend/app/src/pages/intel/combatlog.astro b/frontend/app/src/pages/intel/combatlog.astro
index b04f5f68..a5a09e3b 100644
--- a/frontend/app/src/pages/intel/combatlog.astro
+++ b/frontend/app/src/pages/intel/combatlog.astro
@@ -1,14 +1,23 @@
---
import { i18n } from '@helpers/i18n'
-const { t } = i18n(Astro.url)
+const { t, translatePath } = i18n(Astro.url)
import { prod_error_messages } from '@helpers/env'
+import { HTTP_404_Not_Found } from '@helpers/http_responses'
+
+import type { User } from '@dtypes/jwt'
+import * as jose from 'jose'
+
+const auth_token = Astro.cookies.has('auth_token') ? (Astro.cookies.get('auth_token')?.value as string) : false
+const user:User | false = auth_token ? jose.decodeJwt(auth_token) as User : false
import type { SelectOptions } from '@dtypes/layout_components'
-import type { Fitting } from '@dtypes/api.minmatar.org'
+import type { Fitting, SavedCombatLog } from '@dtypes/api.minmatar.org'
import { get_fittings } from '@helpers/api.minmatar.org/ships'
import { get_fleets } from '@helpers/api.minmatar.org/fleets'
+import { get_saved_logs } from '@helpers/api.minmatar.org/combatlog'
+let saved_logs:SavedCombatLog[] = []
let fittings:Fitting[] = []
let fitting_options:SelectOptions[] = []
let combatlog_selects_error:string | false = false
@@ -33,6 +42,14 @@ try {
})
} catch (error) {
combatlog_selects_error = prod_error_messages() ? t('get_fittings_error') : error.message
+ console.log(combatlog_selects_error)
+}
+
+try {
+ saved_logs = user ? await get_saved_logs(auth_token as string, { user_id: user.user_id }) : []
+} catch (error) {
+ combatlog_selects_error = prod_error_messages() ? t('get_fittings_error') : error.message
+ console.log(combatlog_selects_error)
}
import Viewport from '@layouts/Viewport.astro';
@@ -41,11 +58,15 @@ import PageWide from '@components/page/PageWide.astro';
import PageTitle from '@components/page/PageTitle.astro';
import Flexblock from '@components/compositions/Flexblock.astro';
+import BlockList from '@components/compositions/BlockList.astro';
import FlexInline from '@components/compositions/FlexInline.astro';
import FixedFluid from '@components/compositions/FixedFluid.astro';
+import Grid from '@components/compositions/Grid.astro';
import AnalizeCombatLogButton from '@components/blocks/AnalizeCombatLogButton.astro';
import CollapsableButton from '@components/blocks/CollapsableButton.astro';
+import FittingCombatLogItem from '@components/blocks/FittingCombatLogItem.astro';
+import FleetCombatLogItem from '@components/blocks/FleetCombatLogItem.astro';
import TutorialIcon from '@components/icons/TutorialIcon.astro';
@@ -92,42 +113,69 @@ const page_description = t('intel.combatlog.leading_text');
-
- {t('paste_combatlog_hint')}
-
-
-
+ {saved_logs.length > 0 &&
+
-
- {t('paste_combatlog_tutorial')}
-
-
-
-
-
+
{t('paste_combatlog_hint')}
+
+
+
+
+ {t('paste_combatlog_tutorial')}
+
+
+
+
+
+
+
+ }
\ No newline at end of file
diff --git a/frontend/app/src/pages/intel/combatlog/[log_id].astro b/frontend/app/src/pages/intel/combatlog/[log_id].astro
new file mode 100644
index 00000000..05f33531
--- /dev/null
+++ b/frontend/app/src/pages/intel/combatlog/[log_id].astro
@@ -0,0 +1,100 @@
+---
+import { i18n } from '@helpers/i18n'
+const { t, translatePath } = i18n(Astro.url)
+import { HTTP_404_Not_Found } from '@helpers/http_responses'
+
+import type { User } from '@dtypes/jwt'
+import * as jose from 'jose'
+
+const auth_token = Astro.cookies.has('auth_token') ? (Astro.cookies.get('auth_token')?.value as string) : false
+const user:User | false = auth_token ? jose.decodeJwt(auth_token) as User : false
+
+if (!auth_token || !user)
+ return HTTP_404_Not_Found()
+
+import { prod_error_messages } from '@helpers/env'
+
+import { fetch_combatlog_by_id } from '@helpers/fetching/combatlog'
+import type { CombatLogAnalysis } from '@dtypes/layout_components'
+
+let get_combatlog_error:string | false = false
+let combat_log_analysis:CombatLogAnalysis | null = null
+
+const log_id = parseInt(Astro?.params?.log_id ?? '0')
+
+try {
+ if (!(log_id > 0))
+ throw new Error(t('invalid_combatlog'))
+
+ combat_log_analysis = await fetch_combatlog_by_id(auth_token, log_id)
+} catch (error) {
+ get_combatlog_error = prod_error_messages() ? t('get_combatlog_error') : error.message
+ console.log(get_combatlog_error)
+}
+
+const COMBAT_LOG_PARTIAL_URL = translatePath(`/partials/combatlog_component?log_id=${log_id}`)
+
+import Viewport from '@layouts/Viewport.astro';
+
+import PageWide from '@components/page/PageWide.astro';
+import PageTitle from '@components/page/PageTitle.astro';
+
+import Flexblock from '@components/compositions/Flexblock.astro';
+import FlexInline from '@components/compositions/FlexInline.astro';
+
+import CombatLogAnalysisComponent from '@components/blocks/CombatLogAnalysisComponent.astro'
+import Button from '@components/blocks/Button.astro'
+import ErrorRefetch from '@components/blocks/ErrorRefetch.astro';
+
+const page_title = `${t('combatlog')} ${log_id}`;
+---
+
+
+
+
+
+
+
+ {page_title}
+
+
+
+
+
+
+
+
+
+ {log_id === 0 ?
+
{t('invalid_combatlog')}
:
+ get_combatlog_error ?
+
+ :
+
+ }
+
+
+
\ No newline at end of file
diff --git a/frontend/app/src/pages/partials/combatlog_analysis_component.astro b/frontend/app/src/pages/partials/combatlog_analysis_component.astro
index 9faa283a..494b2fcb 100644
--- a/frontend/app/src/pages/partials/combatlog_analysis_component.astro
+++ b/frontend/app/src/pages/partials/combatlog_analysis_component.astro
@@ -1,6 +1,13 @@
---
import { i18n } from '@helpers/i18n'
const { t } = i18n(Astro.url)
+import { HTTP_404_Not_Found } from '@helpers/http_responses'
+
+import type { User } from '@dtypes/jwt'
+import * as jose from 'jose'
+
+const auth_token = Astro.cookies.has('auth_token') ? (Astro.cookies.get('auth_token')?.value as string) : false
+const user:User | false = auth_token ? jose.decodeJwt(auth_token) as User : false
import { prod_error_messages } from '@helpers/env'
import { b64_to_Uint8Array } from '@helpers/string'
@@ -19,7 +26,9 @@ if (Astro.request.method === "POST") {
const base64_gziped_combatlog = form_data.get("gziped_combatlog")?.valueOf()
const gziped_combatlog = b64_to_Uint8Array(base64_gziped_combatlog as string)
- combat_log_analysis = await fetch_combatlog_analysis(gziped_combatlog, true, fitting_id, fleet_id)
+ combat_log_analysis = auth_token ?
+ await fetch_combatlog_analysis(gziped_combatlog, true, auth_token, fitting_id, fleet_id) :
+ await fetch_combatlog_analysis(gziped_combatlog, true)
} catch (error) {
combatlog_analysis_error = prod_error_messages() ? t('combatlog_analysis_error') : error.message
}
diff --git a/frontend/app/src/pages/partials/combatlog_component.astro b/frontend/app/src/pages/partials/combatlog_component.astro
new file mode 100644
index 00000000..60cdabd9
--- /dev/null
+++ b/frontend/app/src/pages/partials/combatlog_component.astro
@@ -0,0 +1,53 @@
+---
+import { i18n } from '@helpers/i18n'
+const { t, translatePath } = i18n(Astro.url)
+import { HTTP_404_Not_Found } from '@helpers/http_responses'
+
+import type { User } from '@dtypes/jwt'
+import * as jose from 'jose'
+
+const auth_token = Astro.cookies.has('auth_token') ? (Astro.cookies.get('auth_token')?.value as string) : false
+const user:User | false = auth_token ? jose.decodeJwt(auth_token) as User : false
+
+if (!auth_token || !user)
+ return HTTP_404_Not_Found()
+
+import { prod_error_messages } from '@helpers/env'
+
+import { fetch_combatlog_by_id } from '@helpers/fetching/combatlog'
+import type { CombatLogAnalysis } from '@dtypes/layout_components'
+
+let get_combatlog_error:string | false = false
+let combat_log_analysis:CombatLogAnalysis | null = null
+
+const log_id = parseInt(Astro.url.searchParams.get('log_id') ?? '0')
+
+try {
+ if (!(log_id > 0))
+ throw new Error(t('invalid_combatlog'))
+
+ combat_log_analysis = await fetch_combatlog_by_id(auth_token, log_id)
+} catch (error) {
+ get_combatlog_error = prod_error_messages() ? t('get_combatlog_error') : error.message
+ console.log(get_combatlog_error)
+}
+
+const COMBAT_LOG_PARTIAL_URL = translatePath(`/partials/combatlog_component?log_id=${log_id}`)
+
+const delay = parseInt(Astro.url.searchParams.get('delay') ?? '0')
+
+import CombatLogAnalysisComponent from '@components/blocks/CombatLogAnalysisComponent.astro'
+import ErrorRefetch from '@components/blocks/ErrorRefetch.astro';
+---
+
+{get_combatlog_error ?
+
+ :
+
+}
\ No newline at end of file
diff --git a/frontend/app/src/pages/partials/fleet_detail_component.astro b/frontend/app/src/pages/partials/fleet_detail_component.astro
index 90f3b38f..46d01bfd 100644
--- a/frontend/app/src/pages/partials/fleet_detail_component.astro
+++ b/frontend/app/src/pages/partials/fleet_detail_component.astro
@@ -23,13 +23,14 @@ const fleet_id = parseInt(Astro.url.searchParams.get('fleet_id') as string)
if (isNaN(fleet_id))
return HTTP_404_Not_Found()
-import type { FleetUI, FleetCompositionUI, FleetRadarUI, SRPUI } from '@dtypes/layout_components'
+import type { FleetUI, FleetCompositionUI, FleetRadarUI, SRPUI, FleetCombatLog } from '@dtypes/layout_components'
import type { EveCharacterProfile } from '@dtypes/api.minmatar.org'
import { fetch_fleet_by_id, group_members_by_ship, group_members_by_location } from '@helpers/fetching/fleets'
import { get_fleet_members } from '@helpers/api.minmatar.org/fleets'
import { get_user_character } from '@helpers/fetching/characters'
import { fetch_fleet_srps } from '@helpers/fetching/srp'
import { get_system_id } from '@helpers/sde/map'
+import { get_fleet_combatlogs } from '@helpers/fetching/combatlog'
let fetch_fleets_error:string | false = false
let fleet:FleetUI | null = null
@@ -38,6 +39,7 @@ let fleet_composition:FleetCompositionUI[] = []
let fleet_radar:FleetRadarUI[] = []
let fleet_srps:SRPUI[] | null = []
let is_fleet_commander = false
+let saved_logs:FleetCombatLog[] = []
const upcoming = JSON.parse(Astro.url.searchParams.get('upcoming') as string)
@@ -65,6 +67,12 @@ try {
fetch_fleets_error = prod_error_messages() ? t('fetch_fleets_error') : error.message
}
+try {
+ saved_logs = user && !fetch_fleets_error ? await get_fleet_combatlogs(auth_token as string, fleet_id) : []
+} catch (error) {
+ console.log(error)
+}
+
const FLEET_DETAIL_PARTIAL_URL = translatePath(`/partials/fleet_detail_component?fleet_id=${fleet_id}&upcoming=${JSON.stringify(upcoming)}`)
const delay = parseInt(Astro.url.searchParams.get('delay') ?? '0')
@@ -94,5 +102,6 @@ import FleetDetails from '@components/blocks/FleetDetails.astro';
fleet_radar={fleet_radar}
history={!upcoming}
fleet_srps={fleet_srps}
+ saved_logs={saved_logs}
/>
}
\ No newline at end of file
diff --git a/frontend/app/src/styles/_global.scss b/frontend/app/src/styles/_global.scss
index c4b86e0c..92da5527 100644
--- a/frontend/app/src/styles/_global.scss
+++ b/frontend/app/src/styles/_global.scss
@@ -182,6 +182,10 @@ ul {
color: var(--fleet-red);
}
+.text-green {
+ color: var(--green);
+}
+
.text-status-1 {
color: var(--security-status-1)
}
diff --git a/frontend/app/src/styles/_typography.scss b/frontend/app/src/styles/_typography.scss
index 4df44f79..7f3342f6 100644
--- a/frontend/app/src/styles/_typography.scss
+++ b/frontend/app/src/styles/_typography.scss
@@ -45,4 +45,8 @@ img {
textarea.uppercase:focus {
text-transform: none !important;
+}
+
+b {
+ font-weight: 600;
}
\ No newline at end of file
diff --git a/frontend/app/src/types/api.minmatar.org.ts b/frontend/app/src/types/api.minmatar.org.ts
index 5d1052c4..faaef3fe 100644
--- a/frontend/app/src/types/api.minmatar.org.ts
+++ b/frontend/app/src/types/api.minmatar.org.ts
@@ -377,8 +377,37 @@ export interface CombatLog {
times: CombatLogItem[];
start: Date;
end: Date;
+ db_id: number;
+ user_id: number;
+ fitting_id: number;
+ fleet_id: number;
+ character_name: string;
+ max_from: CombatLogMax;
+ max_to: CombatLogMax;
+}
+
+export interface SavedCombatLog {
+ id: number;
+ uploaded_at: Date;
+ user_id: number;
+ fleet_id: number;
+ fitting_id: number;
}
+export interface CombatLogMax {
+ event_time: string;
+ damage: number;
+ direction: DamageDirection;
+ entity: string;
+ weapon: string;
+ outcome: string;
+ location: string;
+ text: string;
+}
+
+export const damage_direction = [ 'from', 'to' ] as const
+export type DamageDirection = typeof damage_direction[number]
+
export const combatlog_item_category = [ 'Weapon', 'Enemy', 'TimeBucket' ] as const
export type CombatlogItemCategory = typeof combatlog_item_category[number]
@@ -397,6 +426,11 @@ export interface CombatLogItem {
last: Date;
}
+export interface SavedLogsRequest {
+ user_id?: number;
+ fleet_id?: number;
+}
+
export interface Contract {
expectation_id: number;
title: string;
diff --git a/frontend/app/src/types/layout_components.ts b/frontend/app/src/types/layout_components.ts
index 00fcd500..ef57b7b7 100644
--- a/frontend/app/src/types/layout_components.ts
+++ b/frontend/app/src/types/layout_components.ts
@@ -883,6 +883,25 @@ export interface CombatLogAnalysis {
timeline: string[];
damage_in: number[];
damage_out: number[];
+ character_name: string;
+ fitting?: Fitting;
+ fleet_id?: number;
+ max_from?: CombatLogMaxUI;
+ max_to?: CombatLogMaxUI;
+}
+
+export interface CombatLogMaxUI {
+ damage: number;
+ entity: string;
+ weapon: string;
+ outcome: string;
+}
+
+export interface FleetCombatLog {
+ id: number;
+ uploaded_at: Date;
+ user_id: number;
+ logger: CharacterBasic;
}
export interface Damage {