Skip to content

Commit 7cbadac

Browse files
committed
feat: minimal manifest implementation
1 parent 6867312 commit 7cbadac

9 files changed

Lines changed: 125 additions & 9 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\File;
7+
8+
class GenerateManifest extends Command {
9+
protected $signature = 'app:manifest';
10+
11+
protected $description = 'Generate the current Git version and commit hash to a manifest';
12+
13+
public function handle() {
14+
$version = trim(shell_exec('git describe --tags --abbrev=0')) ?: 'unversioned';
15+
$commit = trim(shell_exec('git rev-parse --short HEAD')) ?: 'unknown';
16+
17+
$data = json_encode([
18+
'version' => $version,
19+
'commit' => $commit,
20+
], JSON_PRETTY_PRINT);
21+
22+
File::put(storage_path('app/manifest.json'), $data);
23+
24+
$this->info("App manifest generated: $version ($commit)");
25+
}
26+
}

app/Support/AppManifest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Support;
4+
5+
use Illuminate\Support\Facades\File;
6+
7+
class AppManifest {
8+
public static function info(): array {
9+
$path = storage_path('app/manifest.json');
10+
11+
if (! File::exists($path)) {
12+
return ['version' => 'unknown', 'commit' => null];
13+
}
14+
15+
$data = json_decode(File::get($path), true);
16+
17+
return [
18+
'version' => $data['version'] ?? 'unknown',
19+
'commit' => $data['commit'] ?? null,
20+
];
21+
}
22+
23+
public static function string(): string {
24+
$info = self::info();
25+
26+
return "{$info['version']} (#{$info['commit']})";
27+
}
28+
}

resources/js/service/queries.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { CategoryResource, FolderResource, TaskResource, UserResource } from '@/types/resources';
2-
import type { PulseResponse, TaskStatsResponse } from '@/types/types.ts';
2+
import type { AppManifest, PulseResponse, TaskStatsResponse } from '@/types/types.ts';
33
import type { Ref } from 'vue';
44

5-
import { getSiteAnalytics, getPulse, getUsers, getTasks, getTaskStats, getActiveSessions } from '@/service/siteAPI.ts';
5+
import { getSiteAnalytics, getPulse, getUsers, getTasks, getTaskStats, getActiveSessions, getManifest } from '@/service/siteAPI.ts';
66
import { useAuthStore } from '@/stores/AuthStore';
77
import { storeToRefs } from 'pinia';
88
import { useQuery } from '@tanstack/vue-query';
@@ -115,6 +115,16 @@ export const useGetActiveSessions = () => {
115115
});
116116
};
117117

118+
export const useGetManifest = () => {
119+
return useQuery<{ data: AppManifest }>({
120+
queryKey: ['manifest'],
121+
queryFn: async () => {
122+
const { data: response } = await getManifest();
123+
return response;
124+
},
125+
});
126+
};
127+
118128
// export const useGetChatRoomMessagesQuery = (chat_id: number | null) => {
119129
// return useInfiniteQuery({
120130
// queryKey: ['chatroom-messages', chat_id],

resources/js/service/siteAPI.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ export function deleteSubTask(taskId: number) {
7777
export function deleteUser(userId: number) {
7878
return API.delete(`/users/${userId}`);
7979
}
80+
81+
export function getManifest() {
82+
return API.get('/manifest');
83+
}

resources/js/stores/AppStore.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { type ContextMenu as ContextMenuType, type ContextMenuItem, type Broadcaster } from '@/types/types';
2-
import { nextTick, ref, useTemplateRef } from 'vue';
1+
import { type ContextMenu as ContextMenuType, type ContextMenuItem, type Broadcaster, type AppManifest } from '@/types/types';
2+
import { nextTick, ref, useTemplateRef, watch } from 'vue';
3+
import { useGetManifest } from '@/service/queries';
34
import { defineStore } from 'pinia';
45
import { EchoConfig } from '@/echo.ts';
56

@@ -25,6 +26,9 @@ export const useAppStore = defineStore('App', () => {
2526

2627
const contextMenu = useTemplateRef<InstanceType<typeof ContextMenu> | null>('contextMenu');
2728

29+
const { data: rawAppManifest } = useGetManifest();
30+
const appManifest = ref<AppManifest>({ version: 'Unversioned', commit: null });
31+
2832
function toggleDarkMode() {
2933
let rootHTML = document.querySelector('html');
3034

@@ -114,6 +118,10 @@ export const useAppStore = defineStore('App', () => {
114118
}
115119
};
116120

121+
watch(rawAppManifest, (v: AppManifest) => {
122+
appManifest.value = v ?? { version: 'Unversioned', commit: 'unknown' };
123+
});
124+
117125
return {
118126
initDarkMode,
119127
toggleDarkMode,
@@ -137,7 +145,8 @@ export const useAppStore = defineStore('App', () => {
137145
setContextMenu,
138146
createEcho,
139147
disconnectEcho,
140-
ws,
141148
isAutoPlay,
149+
ws,
150+
appManifest,
142151
};
143152
});

resources/js/types/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,8 @@ export interface DropdownMenuItem {
297297
action?: () => void;
298298
shortcut?: string;
299299
}
300+
301+
export interface AppManifest {
302+
version: string;
303+
commit: string | null;
304+
}

resources/js/views/DashboardView.vue

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import type { TaskStatsResponse } from '@/types/types';
2+
import type { AppManifest, TaskStatsResponse } from '@/types/types';
33
44
import { computed, onMounted, ref, watch, type Component, type Ref } from 'vue';
55
import { useDashboardStore } from '@/stores/DashboardStore';
@@ -17,6 +17,8 @@ import SidebarCard from '@/components/cards/SidebarCard.vue';
1717
import LayoutBase from '@/layouts/LayoutBase.vue';
1818
1919
import ProiconsLibrary from '~icons/proicons/library';
20+
import ProiconsServer from '~icons/proicons/server';
21+
import ProiconsGithub from '~icons/proicons/github';
2022
import ProiconsGraph from '~icons/proicons/graph';
2123
import CircumServer from '~icons/circum/server';
2224
import LucideUsers from '~icons/lucide/users';
@@ -27,7 +29,7 @@ const { stateTaskStats, stateTotalLibrariesSize, stateLibraryId, stateActiveSess
2729
stateLibraryId: Ref<number>;
2830
stateActiveSessions: Ref<number>;
2931
};
30-
const { pageTitle, selectedSideBar } = storeToRefs(useAppStore());
32+
const { pageTitle, selectedSideBar, appManifest } = storeToRefs(useAppStore()) as unknown as { pageTitle: Ref<any>; selectedSideBar: Ref<any>; appManifest: Ref<AppManifest> };
3133
const { cycleSideBar } = useAppStore();
3234
const { userData } = storeToRefs(useAuthStore());
3335
@@ -123,7 +125,7 @@ watch(
123125
<h2 id="sidebar-title" class="text-2xl h-8 w-full capitalize dark:text-white">{{ selectedSideBar }}</h2>
124126
<hr class="" />
125127
</div>
126-
<section class="flex flex-col gap-2">
128+
<section class="flex flex-col gap-2 flex-1">
127129
<SidebarCard
128130
v-for="(tab, index) in dashboardTabs.filter((tab) => !tab.disabled)"
129131
:key="index"
@@ -143,7 +145,7 @@ watch(
143145
:aria-disabled="tab.disabled"
144146
>
145147
<template #header>
146-
<h3 class="w-full flex-1" :title="tab.title ?? tab.name">{{ tab.title ?? tab.name }}</h3>
148+
<h3 class="w-full flex-1 text-gray-900 dark:text-white" :title="tab.title ?? tab.name">{{ tab.title ?? tab.name }}</h3>
147149
<component v-if="tab.icon" :is="tab.icon" class="ml-auto w-6 h-6" />
148150
</template>
149151
<template #body>
@@ -156,6 +158,29 @@ watch(
156158
</h4>
157159
</template>
158160
</SidebarCard>
161+
162+
<SidebarCard
163+
:to="`${appManifest?.commit ? `https://github.com/aminnausin/mediaServer/commit/${appManifest?.commit}` : ''}`"
164+
:class="`
165+
items-center justify-between text-sm
166+
capitalize overflow-hidden bg-white hover:bg-primary-800
167+
ring-inset ring-purple-600 hover:ring-purple-600/50 hover:ring-[0.125rem]
168+
aria-disabled:cursor-not-allowed aria-disabled:hover:ring-neutral-200 aria-disabled:hover:dark:ring-neutral-700 aria-disabled:opacity-60
169+
`"
170+
@click=""
171+
:aria-disabled="false"
172+
>
173+
<template #header>
174+
<h3 class="text-gray-900 dark:text-white" :title="'Source Code'">#{{ appManifest.commit }}</h3>
175+
<ProiconsGithub class="ml-auto w-6 h-6" />
176+
</template>
177+
<template #body>
178+
<h4 title="Description" class="text-neutral-500 w-full text-wrap truncate sm:text-nowrap flex-1">MediaServer</h4>
179+
<h4 v-if="appManifest.commit" title="Information" class="truncate text-nowrap sm:text-right text-neutral-500 w-fit">
180+
{{ appManifest.version ?? 'V0.1.15b' }}
181+
</h4>
182+
</template>
183+
</SidebarCard>
159184
</section>
160185
</div>
161186
</template>

routes/api.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\Http\Controllers\Api\V1\UserController;
1717
use App\Http\Controllers\Api\V1\VideoController;
1818
use App\Http\Controllers\DirectoryController;
19+
use App\Support\AppManifest;
1920
use Illuminate\Http\Request;
2021
use Illuminate\Support\Facades\Route;
2122
use Robertogallea\PulseApi\Http\Controllers\DashboardController;
@@ -66,6 +67,10 @@
6667

6768
// public
6869

70+
Route::get('/manifest', function () {
71+
return response()->json(AppManifest::info());
72+
});
73+
6974
Route::post('/login', [AuthController::class, 'login']);
7075
Route::post('/register', [AuthController::class, 'register']);
7176
Route::patch('/videos/watch/{video}', [VideoController::class, 'watch']);

storage/app/manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"version": "beta-088f1c9",
3+
"commit": "f5a21892"
4+
}

0 commit comments

Comments
 (0)