<scriptsetuplang="ts">import{useResizeObserver}from"@vueuse/core";importLoaderOverlayfrom"components/LoaderOverlay.vue";importPlotlyfrom"plotly.js-dist-min";import{buildQueryStringFromState,getHoneypot,useChartStore,}from"src/store";import{activityChartMargin,activityDataLimit,getCalculatedColor,gridcolor,hoverlabel,labelFontSize,modebar,spikecolor,tickfontcolor,}from"utils/chart";import{formatDateTimeForAPI}from"utils/formatting";import{URL}from"utils/utils";import{onMounted,ref,useTemplateRef,watch}from"vue";defineProps<{chartData?:any;loading?:boolean;connectionStatus?:string;}>();constchartStore=useChartStore();constchartContainer=useTemplateRef<HTMLDivElement>("chartContainer");constloading=ref(false);constactivityData=ref<any[]>([]);asyncfunctionfetchActivity(){loading.value=true;try{constqueryString=buildQueryStringFromState(chartStore.state);constparams=newURLSearchParams(queryString);// Always send the time filter. If set manually, use it; otherwise, use the zoom range.
if(!params.get("time_start")&&chartStore.state.x_range?.[0]){params.set("time_start",formatDateTimeForAPI(chartStore.state.x_range[0]),);}if(!params.get("time_end")&&chartStore.state.x_range?.[1]){params.set("time_end",formatDateTimeForAPI(chartStore.state.x_range[1]));}// We don't need limit for activity chart usually, or we want a large one
params.set("limit",activityDataLimit.toString());constresponse=awaitfetch(`${URL()}/api/stats/activity-over-time?${params.toString()}`,);if(!response.ok)thrownewError("Failed to fetch activity");activityData.value=awaitresponse.json();drawChart();}catch(err){console.error(err);}finally{loading.value=false;}}functiondrawChart(){if(!chartContainer.value)return;if(activityData.value.length===0){Plotly.purge(chartContainer.value);return;}// Group data by type
constgroups:Record<string,{x:Date[];y:number[]}>={};activityData.value.forEach((d:any)=>{if(!groups[d.type])groups[d.type]={x:[],y:[]};constgroup=groups[d.type]!;group.x.push(newDate(d.time));group.y.push(d.count);});consttraces:Plotly.Data[]=Object.entries(groups).map(([type,data],index)=>({x:data.x,y:data.y,name:getHoneypot(type)?.label||type,type:"scatter"asconst,mode:"lines"asconst,line:{shape:"linear"asconst,width:2,color:getCalculatedColor(index,Object.keys(groups).length),},hoverlabel:{bgcolor:"#141210",font:{color:"#ccc",size:12},},}),);constlayout:Partial<Plotly.Layout>={xaxis:{title:{text:"Time",font:{size:labelFontSize,color:tickfontcolor},},type:"date"asconst,gridcolor:gridcolor,range:chartStore.state.x_range&&chartStore.state.x_range.length===2?[...chartStore.state.x_range]:undefined,tickfont:{color:tickfontcolor,size:labelFontSize},showspikes:true,spikemode:"across",spikecolor:spikecolor,spikethickness:-2,},yaxis:{title:{text:"Events per Minute",font:{size:labelFontSize,color:tickfontcolor},},gridcolor:gridcolor,zerolinecolor:gridcolor,tickfont:{color:tickfontcolor,size:labelFontSize},},margin:activityChartMargin,paper_bgcolor:"transparent",plot_bgcolor:"transparent",font:{color:tickfontcolor},showlegend:true,legend:{orientation:"h"asconst,y:-0.2,font:{size:labelFontSize},},hovermode:"x unified"asconst,hoverlabel:hoverlabel,modebar:modebar,};constconfig:any={responsive:true,displaylogo:false,modeBarButtonsToRemove:["lasso2d","select2d"],};Plotly.react(chartContainer.value,traces,layout,config);constplotDiv=chartContainer.valueasany;plotDiv.removeAllListeners?.("plotly_relayout");plotDiv.on("plotly_relayout",(event:any)=>{if(event["xaxis.range[0]"]&&event["xaxis.range[1]"]){conststart=event["xaxis.range[0]"].toString();constend=event["xaxis.range[1]"].toString();chartStore.state.x_range=[start,end];if(chartStore.state.sync_time_with_chart){chartStore.state.time_start=start;chartStore.state.time_end=end;}}if(event["xaxis.autorange"]){chartStore.state.x_range=[];if(chartStore.state.sync_time_with_chart){chartStore.state.time_start="";chartStore.state.time_end="";}}});}onMounted(()=>{fetchActivity();});watch([()=>buildQueryStringFromState(chartStore.state),()=>chartStore.state.x_range,],()=>{fetchActivity();},{deep:true},);useResizeObserver(chartContainer,()=>{if(chartContainer.value)Plotly.Plots.resize(chartContainer.value);});</script><template><divclass="flex h-full flex-col gap-4 pt-2"><divclass="chart-container"><LoaderOverlayv-if="loading"/><divref="chartContainer"class="h-full w-full rounded-lg"></div></div></div></template>