Compare commits
3 Commits
master
...
develop-up
| Author | SHA1 | Date | |
|---|---|---|---|
| d473b0f59e | |||
| 051fc27dac | |||
| 3be483cff0 |
@@ -2,24 +2,28 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: stream.ui-config
|
name: stream-ui-config
|
||||||
namespace: stream-production
|
namespace: stream-production
|
||||||
labels:
|
labels:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
data:
|
data:
|
||||||
STREAM_API_GRPC_ADDR: "stream.api-svc:9000"
|
STREAM_API_GRPC_ADDR: "stream.api-svc:9000"
|
||||||
GOOGLE_AUTH_FINALIZE_PATH: "/auth/google/finalize"
|
GOOGLE_AUTH_FINALIZE_PATH: "/auth/google/finalize"
|
||||||
|
STREAM_INTERNAL_AUTH_MARKER: "stream_maker_123xxx"
|
||||||
|
STREAM_UI_JWT_SECRET: "xxx_stream_maker_123_xxx"
|
||||||
|
STREAM_UI_REDIS_URL: "redis://:pass123@47.84.62.226:6379/3"
|
||||||
|
FRONTEND_BASE_URL: "https://hlstiktok.com"
|
||||||
---
|
---
|
||||||
kind: Service
|
kind: Service
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
metadata:
|
metadata:
|
||||||
name: stream.ui-svc
|
name: stream-ui-svc
|
||||||
namespace: stream-production
|
namespace: stream-production
|
||||||
labels:
|
labels:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 80
|
port: 80
|
||||||
@@ -29,51 +33,55 @@ spec:
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: stream.ui-dep
|
name: stream-ui-dep
|
||||||
namespace: stream-production
|
namespace: stream-production
|
||||||
labels:
|
labels:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: stream.ui
|
app: stream-ui
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
# imagePullSecrets:
|
||||||
- name: registry-production-secret
|
# - name: registry-production-secret
|
||||||
containers:
|
containers:
|
||||||
- name: stream.ui
|
- name: stream-ui
|
||||||
image: registry.awing.vn/stream-production/stream.ui:$BUILD_NUMBER
|
image: registry.awing.vn/stream-production/stream-ui:$BUILD_NUMBER
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
env:
|
env:
|
||||||
- name: STREAM_API_GRPC_ADDR
|
- name: STREAM_API_GRPC_ADDR
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: stream.ui-config
|
name: stream-ui-config
|
||||||
key: STREAM_API_GRPC_ADDR
|
key: STREAM_API_GRPC_ADDR
|
||||||
- name: GOOGLE_AUTH_FINALIZE_PATH
|
- name: GOOGLE_AUTH_FINALIZE_PATH
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: stream.ui-config
|
name: stream-ui-config
|
||||||
key: GOOGLE_AUTH_FINALIZE_PATH
|
key: GOOGLE_AUTH_FINALIZE_PATH
|
||||||
- name: STREAM_INTERNAL_AUTH_MARKER
|
- name: STREAM_INTERNAL_AUTH_MARKER
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
configMapKeyRef:
|
||||||
name: stream.ui-secret
|
name: stream-ui-config
|
||||||
key: STREAM_INTERNAL_AUTH_MARKER
|
key: STREAM_INTERNAL_AUTH_MARKER
|
||||||
- name: STREAM_UI_JWT_SECRET
|
- name: STREAM_UI_JWT_SECRET
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
configMapKeyRef:
|
||||||
name: stream.ui-secret
|
name: stream-ui-config
|
||||||
key: STREAM_UI_JWT_SECRET
|
key: STREAM_UI_JWT_SECRET
|
||||||
- name: STREAM_UI_REDIS_URL
|
- name: STREAM_UI_REDIS_URL
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
configMapKeyRef:
|
||||||
name: stream.ui-secret
|
name: stream-ui-config
|
||||||
key: STREAM_UI_REDIS_URL
|
key: STREAM_UI_REDIS_URL
|
||||||
|
- name: FRONTEND_BASE_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: stream-ui-config
|
||||||
|
key: FRONTEND_BASE_URL
|
||||||
|
|||||||
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -38,7 +38,6 @@ declare module 'vue' {
|
|||||||
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
||||||
CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
||||||
copy: typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
|
||||||
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
@@ -132,7 +131,6 @@ declare global {
|
|||||||
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default']
|
||||||
const ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
const ClientOnly: typeof import('./src/components/ClientOnly.tsx')['default']
|
||||||
const CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
const CoinsIcon: typeof import('./src/components/icons/CoinsIcon.vue')['default']
|
||||||
const copy: typeof import('./src/components/icons/UserIcon copy.vue')['default']
|
|
||||||
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||||
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
const CreditCardIcon: typeof import('./src/components/icons/CreditCardIcon.vue')['default']
|
||||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||||
|
|||||||
21
curl.text
Normal file
21
curl.text
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
curl --request POST \
|
||||||
|
--url https://api.github.com/repos/lethdat09/builder/dispatches \
|
||||||
|
--header 'accept: */*' \
|
||||||
|
--header 'authorization: Bearer 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' \
|
||||||
|
--header 'content-type: application/json' \
|
||||||
|
--header 'user-agent: Thunder Client (https://www.thunderclient.io)' \
|
||||||
|
--data '{
|
||||||
|
"event_type": "trigger_build",
|
||||||
|
"client_payload": {
|
||||||
|
"gitUrl": "https://git.inet.io.vn/stream/stream.api.git",
|
||||||
|
"branch": "develop-refactor",
|
||||||
|
"imageName": "stream123/stream.api",
|
||||||
|
"dockerfilePath": "Dockerfile",
|
||||||
|
"kubeConfigYamlPath": ".deploy/stream.api-production.yaml",
|
||||||
|
"kubeConfig": "YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gY2x1c3RlcjoKICAgIGNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VKa2VrTkRRVkl5WjBGM1NVSkJaMGxDUVVSQlMwSm5aM0ZvYTJwUFVGRlJSRUZxUVdwTlUwVjNTSGRaUkZaUlVVUkVRbWh5VFROTmRHTXlWbmtLWkcxV2VVeFhUbWhSUkVVelRucFJOVTVFU1RCT2VrMTNTR2hqVGsxcVdYZE5lazE0VFVSWmVrNUVUWHBYYUdOT1RYcFpkMDE2U1RSTlJGbDZUa1JOZWdwWGFrRnFUVk5GZDBoM1dVUldVVkZFUkVKb2NrMHpUWFJqTWxaNVpHMVdlVXhYVG1oUlJFVXpUbnBSTlU1RVNUQk9lazEzVjFSQlZFSm5ZM0ZvYTJwUENsQlJTVUpDWjJkeGFHdHFUMUJSVFVKQ2QwNURRVUZUWm5sUVRHaE5kVEJvZFVNelpFb3JlbFZHV0ZVNVYyMHdLM1YxVUhSUFVVTjRSMFZSYkV4ak9Wa0tZV3RsYm1kc1JFZDRTRGs1UjBKcFRFOHlka1pZTm5oalZYcFdka040T0U0NFpqWm9NREpFZVZJNFJGTnZNRWwzVVVSQlQwSm5UbFpJVVRoQ1FXWTRSUXBDUVUxRFFYRlJkMFIzV1VSV1VqQlVRVkZJTDBKQlZYZEJkMFZDTDNwQlpFSm5UbFpJVVRSRlJtZFJWVWhuUTFGWFVVNHlLM1p4TlZKWmRFcFdVVEJNQ2toa00xVlhkMGwzUTJkWlNVdHZXa2w2YWpCRlFYZEpSRk5CUVhkU1VVbG5SbXBDU1hoMFFUVXdRMmwyZFdoVVUzbFZRalpqYjBSU2FWWjBWVVYzUVZrS2VYWjZXRGxHUm5CcVl6aERTVkZFUzBGVFNrZFBaRUZXUW01TmJsRTNWa3BpVVVkWldFRlJSMjlwTmpCRlpuZzVZMUprWTA5UVJWQTFkejA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLCiAgICBzZXJ2ZXI6IGh0dHBzOi8vNDIuOTYuMTUuMTA5OjY0NDMKICBuYW1lOiBkZWZhdWx0CmNvbnRleHRzOgotIGNvbnRleHQ6CiAgICBjbHVzdGVyOiBkZWZhdWx0CiAgICB1c2VyOiBkZWZhdWx0CiAgbmFtZTogZGVmYXVsdApjdXJyZW50LWNvbnRleHQ6IGRlZmF1bHQKa2luZDogQ29uZmlnCnVzZXJzOgotIG5hbWU6IGRlZmF1bHQKICB1c2VyOgogICAgY2xpZW50LWNlcnRpZmljYXRlLWRhdGE6IExTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVUpyUkVORFFWUmxaMEYzU1VKQlowbEpabG95WW10NGJVRTFTRFIzUTJkWlNVdHZXa2w2YWpCRlFYZEpkMGw2UldoTlFqaEhRVEZWUlVGM2Qxa0tZWHBPZWt4WFRuTmhWMVoxWkVNeGFsbFZRWGhPZW1Nd1QxUlJlVTVFWTNwTlFqUllSRlJKTWsxRVRYcE5WRUV5VFhwUmVrMHhiMWhFVkVrelRVUk5lZ3BOVkVFeVRYcFJlazB4YjNkTlJFVllUVUpWUjBFeFZVVkRhRTFQWXpOc2VtUkhWblJQYlRGb1l6TlNiR051VFhoR1ZFRlVRbWRPVmtKQlRWUkVTRTQxQ21NelVteGlWSEJvV2tjeGNHSnFRbHBOUWsxSFFubHhSMU5OTkRsQlowVkhRME54UjFOTk5EbEJkMFZJUVRCSlFVSkZjVE0wUWl0U05tdEVXVzlQY213S1dTdDFiMFpTTjBOdFJUTTVVRk54U1hNeGFYWllWak5aVjBoUmF6bHdSVlpZUm5GWVpYQnZXVmg0TW5KM1pVbFlTVEZTY1dGSU9TdHJPVGw0WkM5c1FRb3ZZamxRWm14UGFsTkVRa2ROUVRSSFFURlZaRVIzUlVJdmQxRkZRWGRKUm05RVFWUkNaMDVXU0ZOVlJVUkVRVXRDWjJkeVFtZEZSa0pSWTBSQmFrRm1Da0puVGxaSVUwMUZSMFJCVjJkQ1V6azFRa0pRWWxWT2MwaFljeXR6WXpoTmNWaHJZWEF3VVhscFJFRkxRbWRuY1docmFrOVFVVkZFUVdkT1NFRkVRa1VLUVdsQ1RXZFJVVGRaY0c5WlMwcDNiMFIyVTBNMlMwVnhaM0VyTkZWTkt6Vkxja2hVV0d0UVFuRTBVazFrUVVsblF6SmhPV0owZDNwdGMwUkZZVFpKVWdwNmRucFpOUzlLUjBKRVZrOUNkM28wV0ZNNU0xaFVkR2h0UW5jOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwS0xTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVUpsUkVORFFWSXlaMEYzU1VKQlowbENRVVJCUzBKblozRm9hMnBQVUZGUlJFRnFRV3BOVTBWM1NIZFpSRlpSVVVSRVFtaHlUVE5OZEZreWVIQUtXbGMxTUV4WFRtaFJSRVV6VG5wUk5VNUVTVEJPZWsxM1NHaGpUazFxV1hkTmVrMTRUVVJaZWs1RVRYcFhhR05PVFhwWmQwMTZTVFJOUkZsNlRrUk5lZ3BYYWtGcVRWTkZkMGgzV1VSV1VWRkVSRUpvY2swelRYUlpNbmh3V2xjMU1FeFhUbWhSUkVVelRucFJOVTVFU1RCT2VrMTNWMVJCVkVKblkzRm9hMnBQQ2xCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlNabTFRYTBaT1pYTnNhV05aZFhSUGJrVmtVbmN2S3pCVE0yVkxkSGNyU0ZwbmJIcFVRazF3WVdrS2RYQjFXWFJuVmpad2IwdG9kSGhUYVhFdk5rWktRa0owZWtoSlNsSjRUMlp0V1RnemVtaENVbE5oUlZOdk1FbDNVVVJCVDBKblRsWklVVGhDUVdZNFJRcENRVTFEUVhGUmQwUjNXVVJXVWpCVVFWRklMMEpCVlhkQmQwVkNMM3BCWkVKblRsWklVVFJGUm1kUlZYWmxVVkZVTWpGRVlrSXhOMUJ5U0ZCRVMydzFDa2R4WkVWTmIyZDNRMmRaU1V0dldrbDZhakJGUVhkSlJGTlJRWGRTWjBsb1FVb3pNVVJWTUhSaFRHVnNWVFJpUVcxUlRYSnJNMEpvT0doSWNuUTNhamtLYkRka1p6YzFhelJ5Vlc1MVFXbEZRWGhsVDFCaFVVUTBTWHBNYzBwVmRITkpOWGRWUzBoUFZWTnFWblE1U20xVWMwSTRTVnB0TTBOM1lXODlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsKICAgIGNsaWVudC1rZXktZGF0YTogTFMwdExTMUNSVWRKVGlCRlF5QlFVa2xXUVZSRklFdEZXUzB0TFMwdENrMUlZME5CVVVWRlNVTk9hVlp2VG1KVGRHZEJWSEJzT1ZSTlpWbHlOMHBUWkVoRk5qZElWMWxrWkZOc05UTmFSbFpUZEhodlFXOUhRME54UjFOTk5Ea0tRWGRGU0c5VlVVUlJaMEZGVTNKbVowZzFTSEZSVG1sbk5uVldhalkyWjFaSWMwdFpWR1l3T1V0dmFYcFhTemxrV0dSb1dXUkRWREpyVWxaalYzQmtOZ3B0YUdobVNHRjJRalJvWTJwV1IzQnZaak0yVkRNelJqTXJWVVE1ZGpBNUsxVjNQVDBLTFMwdExTMUZUa1FnUlVNZ1VGSkpWa0ZVUlNCTFJWa3RMUzB0TFFvPQo=",
|
||||||
|
"quay_username": "lethdat",
|
||||||
|
"quay_token": "htK3xi1/mQdOSQyBxbGVr9Hhpm/ywzNGawjk29lNHZcRXRdec7kc1v9LRE6X1ATE",
|
||||||
|
"telegram_chat_id": "-4891576755",
|
||||||
|
"tele_token": "8230541188:AAGNu6-2iBaFu2JkvORtXM9c6dUZQdQdqYU"
|
||||||
|
}
|
||||||
|
}'
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun x --bun vite",
|
"dev": "bun x --bun vite",
|
||||||
"build": "bun x --bun vite build",
|
"build": "bun x --bun vite build && bun build dist/server/index.js --target=bun --outfile dist/index.js",
|
||||||
"preview": "bun x --bun vite preview"
|
"preview": "bun x --bun vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import Upload from "@/routes/upload/Upload.vue";
|
import Upload from "@/routes/upload/Upload.vue";
|
||||||
import DashboardNav from "./DashboardNav.vue";
|
import DashboardNav from "./DashboardNav.vue";
|
||||||
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
|
import GlobalUploadIndicator from "./GlobalUploadIndicator.vue";
|
||||||
import PopupAdsRuntime from "./PopupAdsRuntime.vue";
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -23,6 +22,5 @@ import PopupAdsRuntime from "./PopupAdsRuntime.vue";
|
|||||||
</div>
|
</div>
|
||||||
<GlobalUploadIndicator />
|
<GlobalUploadIndicator />
|
||||||
<Upload />
|
<Upload />
|
||||||
<PopupAdsRuntime />
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const unreadCount = computed(() => notificationStore.unreadCount.value);
|
|||||||
const mutableNotifications = computed(() => notificationStore.notifications.value.slice(0, 8));
|
const mutableNotifications = computed(() => notificationStore.notifications.value.slice(0, 8));
|
||||||
|
|
||||||
const toggle = (event?: Event) => {
|
const toggle = (event?: Event) => {
|
||||||
console.log(event);
|
|
||||||
visible.value = !visible.value;
|
visible.value = !visible.value;
|
||||||
if (visible.value && !notificationStore.loaded.value) {
|
if (visible.value && !notificationStore.loaded.value) {
|
||||||
void notificationStore.fetchNotifications();
|
void notificationStore.fetchNotifications();
|
||||||
|
|||||||
@@ -47,9 +47,3 @@ const props = defineProps<Props>();
|
|||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.empty-state {
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { registerRpcRoutes } from './server/routes/rpc';
|
|||||||
import { registerSSRRoutes } from './server/routes/ssr';
|
import { registerSSRRoutes } from './server/routes/ssr';
|
||||||
import { registerWellKnownRoutes } from './server/routes/wellKnown';
|
import { registerWellKnownRoutes } from './server/routes/wellKnown';
|
||||||
import { setupServices } from './server/services/grpcClient';
|
import { setupServices } from './server/services/grpcClient';
|
||||||
|
import { serveStatic } from 'hono/bun';
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
// Global middlewares
|
// Global middlewares
|
||||||
setupMiddlewares(app);
|
setupMiddlewares(app);
|
||||||
@@ -14,6 +15,9 @@ setupServices(app);
|
|||||||
registerWellKnownRoutes(app);
|
registerWellKnownRoutes(app);
|
||||||
registerAuthRoutes(app);
|
registerAuthRoutes(app);
|
||||||
registerRpcRoutes(app);
|
registerRpcRoutes(app);
|
||||||
|
if (!import.meta.env.DEV) {
|
||||||
|
app.use(serveStatic({ root: './dist/client' }))
|
||||||
|
}
|
||||||
registerSSRRoutes(app);
|
registerSSRRoutes(app);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import I18NextHttpBackend, { HttpBackendOptions } from "i18next-http-backend";
|
import I18NextHttpBackend, { HttpBackendOptions } from "i18next-http-backend";
|
||||||
|
|
||||||
const backendOptions: HttpBackendOptions = {
|
const backendOptions: HttpBackendOptions = {
|
||||||
loadPath: 'http://localhost:5173/locales/{{lng}}/{{ns}}.json',
|
loadPath: `${process.env.FRONTEND_BASE_URL || 'http://localhost:3000'}/locales/{{lng}}/{{ns}}.json`,
|
||||||
request: (_options, url, _payload, callback) => {
|
|
||||||
fetch(url)
|
request: async (_options, url, _payload, callback) => {
|
||||||
.then((res) =>
|
try {
|
||||||
res.json().then((r) => {
|
const res = await fetch(url);
|
||||||
callback(null, {
|
|
||||||
data: JSON.stringify(r),
|
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
|
||||||
status: 200,
|
|
||||||
})
|
const data = await res.json();
|
||||||
})
|
|
||||||
)
|
callback(null, { data, status: 200 });
|
||||||
.catch(() => {
|
} catch (error) {
|
||||||
callback(null, {
|
console.error("Lỗi fetch file ngôn ngữ i18n:", error);
|
||||||
status: 500,
|
callback(error as any, { status: 500, data: '' });
|
||||||
data: '',
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
export const createI18nInstance = (lng: string) => {
|
|
||||||
console.log('Initializing i18n with language:', lng);
|
|
||||||
const i18n = i18next.createInstance();
|
|
||||||
|
|
||||||
i18n
|
export const createI18nInstance = async (lng: string) => {
|
||||||
|
const i18n = i18next.createInstance();
|
||||||
|
|
||||||
|
await i18n
|
||||||
.use(I18NextHttpBackend)
|
.use(I18NextHttpBackend)
|
||||||
.init({
|
.init({
|
||||||
lng,
|
lng,
|
||||||
@@ -37,6 +36,8 @@ i18n
|
|||||||
},
|
},
|
||||||
backend: backendOptions,
|
backend: backendOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
return i18n;
|
return i18n;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createI18nInstance;
|
export default createI18nInstance;
|
||||||
@@ -81,18 +81,22 @@ export const formatDate = (
|
|||||||
dateOnly: boolean = false
|
dateOnly: boolean = false
|
||||||
) => {
|
) => {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
const locale =
|
|
||||||
typeof document !== "undefined"
|
const date = new Date(dateString);
|
||||||
? document.documentElement.lang === "vi"
|
if (Number.isNaN(date.getTime())) return dateString;
|
||||||
? "vi-VN"
|
|
||||||
: "en-US"
|
const year = date.getUTCFullYear();
|
||||||
: "en-US";
|
const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
|
||||||
return new Date(dateString).toLocaleDateString(locale, {
|
const day = `${date.getUTCDate()}`.padStart(2, "0");
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
if (dateOnly) {
|
||||||
year: "numeric",
|
return `${year}-${month}-${day}`;
|
||||||
...(dateOnly ? {} : { hour: "2-digit", minute: "2-digit" }),
|
}
|
||||||
});
|
|
||||||
|
const hours = `${date.getUTCHours()}`.padStart(2, "0");
|
||||||
|
const minutes = `${date.getUTCMinutes()}`.padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes} UTC`;
|
||||||
};
|
};
|
||||||
type Status = "success" | "failed" | "pending" | string;
|
type Status = "success" | "failed" | "pending" | string;
|
||||||
export const getStatusSeverity = (status: Status = "") => {
|
export const getStatusSeverity = (status: Status = "") => {
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export async function createApp(lng: string = 'en') {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.use(I18NextVue, { i18next: createI18nInstance(lng) });
|
const i18next = await createI18nInstance(lng);
|
||||||
|
app.use(I18NextVue, { i18next });
|
||||||
app.use(PiniaColada, {
|
app.use(PiniaColada, {
|
||||||
pinia,
|
pinia,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListTemplatesResponse = Awaited<ReturnType<typeof rpcClient.listAdminAdTemplates>>;
|
type ListTemplatesResponse = Awaited<ReturnType<typeof rpcClient.listAdminAdTemplates>>;
|
||||||
type AdminAdTemplateRow = NonNullable<ListTemplatesResponse["templates"]>[number];
|
type AdminAdTemplateRow = NonNullable<ListTemplatesResponse["templates"]>[number];
|
||||||
@@ -238,11 +239,7 @@ const nextPage = async () => {
|
|||||||
await loadTemplates();
|
await loadTemplates();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = computed<ColumnDef<AdminAdTemplateRow>[]>(() => [
|
const columns = computed<ColumnDef<AdminAdTemplateRow>[]>(() => [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListAgentsResponse = Awaited<ReturnType<typeof rpcClient.listAdminAgents>>;
|
type ListAgentsResponse = Awaited<ReturnType<typeof rpcClient.listAdminAgents>>;
|
||||||
type AdminAgentRow = NonNullable<ListAgentsResponse["agents"]>[number];
|
type AdminAgentRow = NonNullable<ListAgentsResponse["agents"]>[number];
|
||||||
@@ -128,11 +129,7 @@ const submitUpdate = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatCpu = (value?: number) => `${Number(value ?? 0).toFixed(1)}%`;
|
const formatCpu = (value?: number) => `${Number(value ?? 0).toFixed(1)}%`;
|
||||||
const formatRam = (value?: number) => `${Number(value ?? 0).toFixed(1)} MB`;
|
const formatRam = (value?: number) => `${Number(value ?? 0).toFixed(1)} MB`;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListJobsResponse = Awaited<ReturnType<typeof rpcClient.listAdminJobs>>;
|
type ListJobsResponse = Awaited<ReturnType<typeof rpcClient.listAdminJobs>>;
|
||||||
type AdminJobRow = NonNullable<ListJobsResponse["jobs"]>[number];
|
type AdminJobRow = NonNullable<ListJobsResponse["jobs"]>[number];
|
||||||
@@ -85,7 +86,7 @@ const selectedMeta = computed(() => {
|
|||||||
{ label: "Priority", value: String(selectedRow.value.priority ?? 0) },
|
{ label: "Priority", value: String(selectedRow.value.priority ?? 0) },
|
||||||
{ label: "Progress", value: formatProgress(selectedRow.value.progress) },
|
{ label: "Progress", value: formatProgress(selectedRow.value.progress) },
|
||||||
{ label: "Owner", value: selectedRow.value.userId || "—" },
|
{ label: "Owner", value: selectedRow.value.userId || "—" },
|
||||||
{ label: "Updated", value: formatDate(selectedRow.value.updatedAt) },
|
{ label: "Updated", value: formatAdminDate(selectedRow.value.updatedAt) },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -180,6 +181,10 @@ const openDetailDialog = async (row: AdminJobRow) => {
|
|||||||
actionError.value = null;
|
actionError.value = null;
|
||||||
selectedLogs.value = "Loading logs...";
|
selectedLogs.value = "Loading logs...";
|
||||||
detailOpen.value = true;
|
detailOpen.value = true;
|
||||||
|
if (!row.id) {
|
||||||
|
selectedLogs.value = "No logs available.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await loadSelectedLogs(row.id);
|
await loadSelectedLogs(row.id);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -192,6 +197,11 @@ const openLogsDialog = async (row: AdminJobRow) => {
|
|||||||
actionError.value = null;
|
actionError.value = null;
|
||||||
selectedLogs.value = "Loading logs...";
|
selectedLogs.value = "Loading logs...";
|
||||||
logsOpen.value = true;
|
logsOpen.value = true;
|
||||||
|
if (!row.id) {
|
||||||
|
selectedLogs.value = "";
|
||||||
|
actionError.value = "Failed to load job logs";
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await loadSelectedLogs(row.id);
|
await loadSelectedLogs(row.id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -266,11 +276,7 @@ const submitRetry = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string): string => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatProgress = (value?: number) => `${Number(value ?? 0).toFixed(2)}%`;
|
const formatProgress = (value?: number) => `${Number(value ?? 0).toFixed(2)}%`;
|
||||||
|
|
||||||
@@ -343,7 +349,7 @@ const columns = computed<ColumnDef<AdminJobRow>[]>(() => [
|
|||||||
id: "updated",
|
id: "updated",
|
||||||
header: "Updated",
|
header: "Updated",
|
||||||
accessorFn: row => row.updatedAt || "",
|
accessorFn: row => row.updatedAt || "",
|
||||||
cell: ({ row }) => h("span", { class: "text-foreground/60" }, formatDate(row.original.updatedAt)),
|
cell: ({ row }) => h("span", { class: "text-foreground/60" }, formatAdminDate(row.original.updatedAt)),
|
||||||
meta: {
|
meta: {
|
||||||
headerClass: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-foreground/50",
|
headerClass: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-foreground/50",
|
||||||
cellClass: "px-4 py-3",
|
cellClass: "px-4 py-3",
|
||||||
@@ -393,8 +399,11 @@ useAdminRuntimeMqtt(({ topic, payload }) => {
|
|||||||
if (selectedRow.value?.id === payload.job_id && typeof payload.line === "string") {
|
if (selectedRow.value?.id === payload.job_id && typeof payload.line === "string") {
|
||||||
const nextLine = payload.line.endsWith("\n") ? payload.line : `${payload.line}\n`;
|
const nextLine = payload.line.endsWith("\n") ? payload.line : `${payload.line}\n`;
|
||||||
selectedLogs.value = `${selectedLogs.value === "Loading logs..." || selectedLogs.value === "No logs available." ? "" : selectedLogs.value}${nextLine}`;
|
selectedLogs.value = `${selectedLogs.value === "Loading logs..." || selectedLogs.value === "No logs available." ? "" : selectedLogs.value}${nextLine}`;
|
||||||
selectedRow.value.progress = payload.progress ?? selectedRow.value.progress;
|
const selected = selectedRow.value;
|
||||||
selectedRow.value.updatedAt = new Date().toISOString();
|
if (selected) {
|
||||||
|
selected.progress = payload.progress ?? selected.progress;
|
||||||
|
selected.updatedAt = new Date().toISOString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListPaymentsResponse = Awaited<ReturnType<typeof rpcClient.listAdminPayments>>;
|
type ListPaymentsResponse = Awaited<ReturnType<typeof rpcClient.listAdminPayments>>;
|
||||||
type AdminPaymentRow = NonNullable<ListPaymentsResponse["payments"]>[number];
|
type AdminPaymentRow = NonNullable<ListPaymentsResponse["payments"]>[number];
|
||||||
@@ -201,11 +202,7 @@ const nextPage = async () => {
|
|||||||
await loadPayments();
|
await loadPayments();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatMoney = (amount?: number, currency?: string) => `${amount ?? 0} ${currency || "USD"}`;
|
const formatMoney = (amount?: number, currency?: string) => `${amount ?? 0} ${currency || "USD"}`;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListConfigsResponse = Awaited<ReturnType<typeof rpcClient.listAdminPlayerConfigs>>;
|
type ListConfigsResponse = Awaited<ReturnType<typeof rpcClient.listAdminPlayerConfigs>>;
|
||||||
type AdminPlayerConfigRow = NonNullable<ListConfigsResponse["configs"]>[number];
|
type AdminPlayerConfigRow = NonNullable<ListConfigsResponse["configs"]>[number];
|
||||||
@@ -287,11 +288,7 @@ const nextPage = async () => {
|
|||||||
await loadConfigs();
|
await loadConfigs();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
useAdminPageHeader(() => ({
|
useAdminPageHeader(() => ({
|
||||||
eyebrow: 'Playback',
|
eyebrow: 'Playback',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
|||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import AdminUserFormFields from "./components/AdminUserFormFields.vue";
|
import AdminUserFormFields from "./components/AdminUserFormFields.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListUsersResponse = Awaited<ReturnType<typeof rpcClient.listAdminUsers>>;
|
type ListUsersResponse = Awaited<ReturnType<typeof rpcClient.listAdminUsers>>;
|
||||||
type AdminUserRow = NonNullable<ListUsersResponse["users"]>[number];
|
type AdminUserRow = NonNullable<ListUsersResponse["users"]>[number];
|
||||||
@@ -343,11 +344,7 @@ const nextPage = async () => {
|
|||||||
await loadUsers();
|
await loadUsers();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const roleBadgeClass = (role?: string) => {
|
const roleBadgeClass = (role?: string) => {
|
||||||
const normalized = String(role || "USER").toUpperCase();
|
const normalized = String(role || "USER").toUpperCase();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import AdminMetricCard from "./components/AdminMetricCard.vue";
|
|||||||
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
import AdminPlaceholderTable from "./components/AdminPlaceholderTable.vue";
|
||||||
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
import AdminSectionShell from "./components/AdminSectionShell.vue";
|
||||||
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
import { useAdminPageHeader } from "./components/useAdminPageHeader";
|
||||||
|
import { formatDate } from "@/lib/utils";
|
||||||
|
|
||||||
type ListVideosResponse = Awaited<ReturnType<typeof rpcClient.listAdminVideos>>;
|
type ListVideosResponse = Awaited<ReturnType<typeof rpcClient.listAdminVideos>>;
|
||||||
type AdminVideoRow = NonNullable<ListVideosResponse["videos"]>[number];
|
type AdminVideoRow = NonNullable<ListVideosResponse["videos"]>[number];
|
||||||
@@ -257,11 +258,7 @@ const nextPage = async () => {
|
|||||||
await loadVideos();
|
await loadVideos();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string) => {
|
const formatAdminDate = (value?: string) => formatDate(value || "") || "—";
|
||||||
if (!value) return "—";
|
|
||||||
const date = new Date(value);
|
|
||||||
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatBytes = (value?: number) => {
|
const formatBytes = (value?: number) => {
|
||||||
const bytes = Number(value || 0);
|
const bytes = Number(value || 0);
|
||||||
|
|||||||
@@ -36,10 +36,3 @@ const props = withDefaults(defineProps<{
|
|||||||
<slot />
|
<slot />
|
||||||
</SettingsSectionCard>
|
</SettingsSectionCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.admin-section-card {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -58,16 +58,3 @@ function resolveBodyRowClass(row: Row<TData>) {
|
|||||||
</BaseTable>
|
</BaseTable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.admin-primer-table :deep(th) {
|
|
||||||
padding: 0.5rem 0.75rem !important;
|
|
||||||
font-size: 0.75rem !important;
|
|
||||||
line-height: 1rem !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-primer-table :deep(td) {
|
|
||||||
padding: 0.625rem 0.75rem !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { RedisClient } from "bun";
|
import { RedisClient } from "bun";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
|
import { serveStatic } from 'hono/bun'
|
||||||
import { contextStorage } from "hono/context-storage";
|
import { contextStorage } from "hono/context-storage";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
import { languageDetector } from "hono/language";
|
import { languageDetector } from "hono/language";
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export default defineConfig((env) => {
|
|||||||
// console.log("env:", env, import.meta.env);
|
// console.log("env:", env, import.meta.env);
|
||||||
return {
|
return {
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0'
|
host: '0.0.0.0',
|
||||||
|
port: 3000
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
unocss(),
|
unocss(),
|
||||||
|
|||||||
Reference in New Issue
Block a user