Skip to content

Commit

Permalink
ITArmy statistics by APIKey. Removed winter theme. Minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
opengs committed Jan 19, 2024
1 parent 1b7e1ef commit be25f49
Show file tree
Hide file tree
Showing 14 changed files with 583 additions and 204 deletions.
57 changes: 57 additions & 0 deletions lib/itarmy/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fetch, { RequestInit } from 'electron-fetch'

const BASE_URL = 'https://cossackguard.pp.ua/api'
const BASE_REQUEST_OPTIONS: RequestInit = {
timeout: 10000, // 10 seconds
size: 1024 * 1024 * 10, // 10MB
}

export interface UserStats {
login: string
totalTraffic: number
createdDate: Date
}

export interface GetUserStatsRequest {
apiKey: string
}
export interface GetUserStatsResponse {
success: boolean
error: string
errorType: 'OK' | 'BAD_STATUS_CODE' | 'ERR_FROM_BACKEND' | 'REQUEST_FAILED'
data: UserStats
}

export async function getUserStats(params: GetUserStatsRequest): Promise<GetUserStatsResponse> {
try {
const statsResponse = await fetch(`${BASE_URL}/user/get-user-stats?apiKey=${encodeURI(params.apiKey)}`, BASE_REQUEST_OPTIONS)
if (statsResponse.status !== 200) {
return {
success: false,
errorType: 'BAD_STATUS_CODE',
error: `Bad status code: ${statsResponse.status}. Message: ${await statsResponse.text()}`,
data: undefined as unknown as UserStats
}
}

const responseJSON = await statsResponse.json() as GetUserStatsResponse
if (!responseJSON.success) {
return {
...responseJSON,
errorType: 'ERR_FROM_BACKEND',
}
}

if (responseJSON.data?.createdDate) {
responseJSON.data.createdDate = new Date(responseJSON.data?.createdDate)
}
return responseJSON
} catch (err) {
return {
success: false,
errorType: 'REQUEST_FAILED',
error: String(err),
data: undefined as unknown as UserStats
}
}
}
40 changes: 40 additions & 0 deletions lib/itarmy/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { UserStats, getUserStats, GetUserStatsResponse } from './api'

const USERSTATS_CACHE_UPDATE_INTERVAL = 1000 * 30 // 30 seconds

export class ItArmyClient {
protected userStatsCache?: UserStats
protected userStatsCacheTimestamp?: Date
protected userStatsCacheAPIKeyUsed?: string

async getUserStats(apiKey: string): Promise<GetUserStatsResponse> {
// Prevent requests with empty API key so the API will not be spammed
if (apiKey === '') {
return {
success: false,
error: 'API key is empty',
errorType: 'REQUEST_FAILED',
data: undefined as unknown as UserStats
}
}

if (this.userStatsCache && this.userStatsCacheTimestamp && apiKey === this.userStatsCacheAPIKeyUsed && (Date.now() - this.userStatsCacheTimestamp.getTime()) < USERSTATS_CACHE_UPDATE_INTERVAL) {
return {
success: true,
error: '',
errorType: 'OK',
data: this.userStatsCache
}
}

const response = await getUserStats({ apiKey })

if (response.success) {
this.userStatsCache = response.data
this.userStatsCacheTimestamp = new Date()
this.userStatsCacheAPIKeyUsed = apiKey
}

return response
}
}
21 changes: 19 additions & 2 deletions src-electron/electron-preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const settingsAPI = {
},
async setAPIKey (data: SettingsData['itarmy']['apiKey']): Promise<void> {
return await ipcRenderer.invoke('settings:itarmy:apiKey', data)
}
},
},
bootstrap: {
async setStep (data: SettingsData['bootstrap']['step']): Promise<void> {
Expand Down Expand Up @@ -271,4 +271,21 @@ const activenessAPI = {
}
}

contextBridge.exposeInMainWorld('activenessAPI', activenessAPI)
contextBridge.exposeInMainWorld('activenessAPI', activenessAPI)


import { GetUserStatsResponse as GetITArmyUserStatsResponse } from '../lib/itarmy/api'

declare global {
interface Window {
itArmyAPI: typeof itArmyAPI
}
}

const itArmyAPI = {
async getStats (): Promise<GetITArmyUserStatsResponse> {
return await ipcRenderer.invoke('itarmy:getStats')
},
}

contextBridge.exposeInMainWorld('itArmyAPI', itArmyAPI)
2 changes: 2 additions & 0 deletions src-electron/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Settings, handleSettings } from './settings'
import { handleDevelopers } from './developers'
import { handleTray } from './tray'
import { handleActiveness } from './activeness'
import { handleItArmy } from './itarmy'
import { BrowserWindow } from 'electron'

export function handle (mainWindow: BrowserWindow) {
Expand All @@ -28,4 +29,5 @@ export function handle (mainWindow: BrowserWindow) {
handleSettings(settings)
handleDevelopers()
handleActiveness(settings)
handleItArmy(settings)
}
15 changes: 15 additions & 0 deletions src-electron/handlers/itarmy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Settings } from "./settings";
import { ItArmyClient } from '../../lib/itarmy/client'
import { ipcMain } from "electron";

export function handleItArmy(settings: Settings) {
const client = new ItArmyClient()


ipcMain.handle('itarmy:getStats', async () => {
const settingsData = await settings.getData()
const apiKey = settingsData.itarmy.apiKey

return await client.getUserStats(apiKey)
})
}
5 changes: 3 additions & 2 deletions src/i18n/en-US/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,13 @@ export default {

dashboard: {
statistics: "Attack Power Statistics",
bytes: "Sent / Traffic",
bytes: "Sent / Traffic / Total",
bytesHint: "Statistics of send traffic may be not accurate. It depends on the module and the way it works. Total statistics is always precise and shows aggregated information from all the running tools.",
moduleStatus: "Module Status",
updates: "KIT Version",
latest: "Current",
activeness: {
score: "Activeness Score",
score: "Activeness Score (BETA)",
}
},

Expand Down
5 changes: 3 additions & 2 deletions src/i18n/ua-UA/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,13 @@ export default {

dashboard: {
statistics: "Статистика сили атаки",
bytes: "Відправлено / Трафік",
bytes: "Відправлено / Трафік / Загалом",
bytesHint: "Статистика відправленого трафіку може бути не точна для деяких модулів так як вони не мають компатибільності з кітом. Загальна статистика завжди є точною але вимагає прописаного IT Army ID для роботи. Загальна статистика відображає дані зі всіх ваших підключених тулів.",
moduleStatus: "Статус модуля",
updates: "Версія KIT",
latest: "Поточна",
activeness: {
score: "Очки Activeness",
score: "Бали Activeness (BETA)",
}
},

Expand Down
13 changes: 10 additions & 3 deletions src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
@click="toggleLeftDrawer"
/>

<q-toolbar-title> </q-toolbar-title>
<q-toolbar-title class="text-right q-pr-xl text-subtitle2">
<ShortStatisticsComponent />
</q-toolbar-title>

<div>
v{{ version }}
<!--
<q-btn flat class="q-pa-sm q-ma-xs" @click="showMurkaDialog = true">
<q-avatar
style="outline: 2px solid #555"
Expand All @@ -30,6 +33,7 @@
</q-avatar>
<q-tooltip> Slamy's (developer) cat </q-tooltip>
</q-btn>
-->

<q-dialog v-model="showMurkaDialog">
<MurkaDialog />
Expand All @@ -47,7 +51,8 @@
($q.dark.isActive ? ' text-grey-1 ' : ' text-grey-9 ')
"
>
IT Army Kit 🎅
IT Army Kit
<!--🎅-->
</q-item-label>

<div class="row" style="border-top: solid 1px #aaa">
Expand Down Expand Up @@ -79,7 +84,7 @@
</q-drawer>

<q-page-container>
<SnowEffectComponent />
<!-- <SnowEffectComponent /> -->
<router-view />
</q-page-container>
</q-layout>
Expand All @@ -96,6 +101,8 @@ import MatrixCanvas from "./MatrixCanvas.vue";
import SnowEffectComponent from "./snowEffect/SnowEffectComponent.vue";
import MurkaDialog from "./snowEffect/MurkaDialog.vue";
import ShortStatisticsComponent from "./ShortStatisticsComponent.vue";
const pages = [
{
name: "dashboard",
Expand Down
111 changes: 111 additions & 0 deletions src/layouts/ShortStatisticsComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<template>
{{ userName + " | " + selectedModule }} |
<span
:class="
moduleState == 'RUNNING'
? 'text-positive'
: moduleState == 'ERROR'
? 'text-negative'
: ''
"
>{{ moduleState }}</span
>
| {{ moduleTraffic }} | {{ moduleTotalBytesSend }}
</template>

<script setup lang="ts">
import { ModuleExecutionStatisticsEventData } from "app/lib/module/module";
import { ExecutionLogEntry } from "app/src-electron/handlers/engine";
import { IpcRendererEvent } from "electron/renderer";
import { onBeforeMount, onMounted, onUnmounted, ref } from "vue";
const userName = ref("");
const selectedModule = ref("");
const moduleState = ref("");
const moduleTraffic = ref("");
const moduleTotalBytesSend = ref("");
function humanBytesString(bytes: number, dp = 1) {
const thresh = 1000;
if (Math.abs(bytes) < thresh) {
return bytes + " B";
}
const units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
let u = -1;
const r = 10 ** dp;
do {
bytes /= thresh;
++u;
} while (
Math.round(Math.abs(bytes) * r) / r >= thresh &&
u < units.length - 1
);
return bytes.toFixed(dp) + " " + units[u];
}
async function loadInitialState() {
const executionEngineState = await window.executionEngineAPI.getState();
selectedModule.value = executionEngineState.moduleToRun || "";
moduleState.value = executionEngineState.run ? "RUNNING" : "IDLE";
let bitrate = 0;
if (executionEngineState.statistics.length > 0) {
bitrate =
executionEngineState.statistics[
executionEngineState.statistics.length - 1
].currentSendBitrate;
}
moduleTraffic.value = humanBytesString(bitrate) + "/s";
const response = await window.itArmyAPI.getStats();
if (response.success) {
moduleTotalBytesSend.value = humanBytesString(response.data.totalTraffic);
}
}
function onExecutionLog(_e: IpcRendererEvent, data: ExecutionLogEntry) {
selectedModule.value = data.moduleName;
if (data.type === "STARTED") {
moduleState.value = "RUNNING";
} else if (data.type === "STOPPED") {
moduleState.value = "IDLE";
} else if (data.type === "ERROR") {
moduleState.value = "ERROR";
}
}
function onStatisticsUpdate(
_e: IpcRendererEvent,
data: ModuleExecutionStatisticsEventData
) {
moduleTraffic.value = humanBytesString(data.currentSendBitrate) + "/s";
}
async function updateITArmyData() {
const response = await window.itArmyAPI.getStats();
if (response.success) {
moduleTotalBytesSend.value = humanBytesString(response.data.totalTraffic);
userName.value = response.data.login;
}
}
let updateStatisticsInterval = null as any;
onMounted(async () => {
window.executionEngineAPI.listenForStatistics(onStatisticsUpdate);
window.executionEngineAPI.listenForExecutionLog(onExecutionLog);
await loadInitialState();
updateStatisticsInterval = setInterval(updateITArmyData, 5000);
});
onUnmounted(() => {
window.executionEngineAPI.stopListeningForExecutionLog(onExecutionLog);
window.executionEngineAPI.stopListeningForStatistics(onStatisticsUpdate);
if (updateStatisticsInterval) {
clearInterval(updateStatisticsInterval);
}
});
</script>
1 change: 1 addition & 0 deletions src/pages/SettingsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
v-model="itArmyAPIKey"
@update:model-value="setItArmyAPIKey"
debounce="500"
type="password"
/>
</q-card-section>

Expand Down
Loading

0 comments on commit be25f49

Please sign in to comment.