feat: surface stack versions in app footer
This commit is contained in:
parent
beea5f67f9
commit
d8119da65b
13 changed files with 480 additions and 49 deletions
|
|
@ -23,4 +23,10 @@ export { KRAIKEN_ABI, STAKE_ABI, ABIS } from './abis.js';
|
|||
// Backward compatible aliases
|
||||
export { KRAIKEN_ABI as KraikenAbi, STAKE_ABI as StakeAbi } from './abis.js';
|
||||
|
||||
export { KRAIKEN_LIB_VERSION, COMPATIBLE_CONTRACT_VERSIONS, isCompatibleVersion, getVersionMismatchError } from './version.js';
|
||||
export {
|
||||
KRAIKEN_LIB_VERSION,
|
||||
COMPATIBLE_CONTRACT_VERSIONS,
|
||||
STACK_META_ID,
|
||||
isCompatibleVersion,
|
||||
getVersionMismatchError,
|
||||
} from './version.js';
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
*/
|
||||
export const KRAIKEN_LIB_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Singleton ID used for stack metadata rows across services.
|
||||
*/
|
||||
export const STACK_META_ID = 'stack-meta';
|
||||
|
||||
/**
|
||||
* List of Kraiken contract versions this library is compatible with.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type Meta {
|
|||
}
|
||||
|
||||
type Query {
|
||||
stackMeta(id: String!): stackMeta
|
||||
stackMetas(where: stackMetaFilter, orderBy: String, orderDirection: String, before: String, after: String, limit: Int): stackMetaPage!
|
||||
stats(id: String!): stats
|
||||
statss(where: statsFilter, orderBy: String, orderDirection: String, before: String, after: String, limit: Int): statsPage!
|
||||
positions(id: String!): positions
|
||||
|
|
@ -24,6 +26,69 @@ type Query {
|
|||
_meta: Meta
|
||||
}
|
||||
|
||||
type stackMeta {
|
||||
id: String!
|
||||
contractVersion: Int!
|
||||
ponderVersion: String!
|
||||
kraikenLibVersion: Int!
|
||||
updatedAt: BigInt!
|
||||
}
|
||||
|
||||
type stackMetaPage {
|
||||
items: [stackMeta!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
input stackMetaFilter {
|
||||
AND: [stackMetaFilter]
|
||||
OR: [stackMetaFilter]
|
||||
id: String
|
||||
id_not: String
|
||||
id_in: [String]
|
||||
id_not_in: [String]
|
||||
id_contains: String
|
||||
id_not_contains: String
|
||||
id_starts_with: String
|
||||
id_ends_with: String
|
||||
id_not_starts_with: String
|
||||
id_not_ends_with: String
|
||||
contractVersion: Int
|
||||
contractVersion_not: Int
|
||||
contractVersion_in: [Int]
|
||||
contractVersion_not_in: [Int]
|
||||
contractVersion_gt: Int
|
||||
contractVersion_lt: Int
|
||||
contractVersion_gte: Int
|
||||
contractVersion_lte: Int
|
||||
ponderVersion: String
|
||||
ponderVersion_not: String
|
||||
ponderVersion_in: [String]
|
||||
ponderVersion_not_in: [String]
|
||||
ponderVersion_contains: String
|
||||
ponderVersion_not_contains: String
|
||||
ponderVersion_starts_with: String
|
||||
ponderVersion_ends_with: String
|
||||
ponderVersion_not_starts_with: String
|
||||
ponderVersion_not_ends_with: String
|
||||
kraikenLibVersion: Int
|
||||
kraikenLibVersion_not: Int
|
||||
kraikenLibVersion_in: [Int]
|
||||
kraikenLibVersion_not_in: [Int]
|
||||
kraikenLibVersion_gt: Int
|
||||
kraikenLibVersion_lt: Int
|
||||
kraikenLibVersion_gte: Int
|
||||
kraikenLibVersion_lte: Int
|
||||
updatedAt: BigInt
|
||||
updatedAt_not: BigInt
|
||||
updatedAt_in: [BigInt]
|
||||
updatedAt_not_in: [BigInt]
|
||||
updatedAt_gt: BigInt
|
||||
updatedAt_lt: BigInt
|
||||
updatedAt_gte: BigInt
|
||||
updatedAt_lte: BigInt
|
||||
}
|
||||
|
||||
type stats {
|
||||
id: String!
|
||||
kraikenTotalSupply: BigInt!
|
||||
|
|
|
|||
|
|
@ -4,6 +4,26 @@ import { TAX_RATE_OPTIONS } from 'kraiken-lib/taxRates';
|
|||
export const HOURS_IN_RING_BUFFER = 168; // 7 days * 24 hours
|
||||
const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
|
||||
|
||||
export const stackMeta = onchainTable('stackMeta', t => ({
|
||||
id: t.text().primaryKey(),
|
||||
contractVersion: t
|
||||
.integer()
|
||||
.notNull()
|
||||
.$default(() => 0),
|
||||
ponderVersion: t
|
||||
.text()
|
||||
.notNull()
|
||||
.$default(() => 'unknown'),
|
||||
kraikenLibVersion: t
|
||||
.integer()
|
||||
.notNull()
|
||||
.$default(() => 0),
|
||||
updatedAt: t
|
||||
.bigint()
|
||||
.notNull()
|
||||
.$default(() => 0n),
|
||||
}));
|
||||
|
||||
// Global protocol stats - singleton with id "0x01"
|
||||
export const stats = onchainTable('stats', t => ({
|
||||
id: t.text().primaryKey(), // Always "0x01"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import { isCompatibleVersion, getVersionMismatchError, KRAIKEN_LIB_VERSION } from 'kraiken-lib/version';
|
||||
import { createRequire } from 'node:module';
|
||||
import { isCompatibleVersion, getVersionMismatchError, KRAIKEN_LIB_VERSION, STACK_META_ID } from 'kraiken-lib/version';
|
||||
import { stackMeta } from 'ponder:schema';
|
||||
import type { Context } from 'ponder:registry';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { version: PONDER_APP_VERSION } = require('../../package.json') as { version: string };
|
||||
|
||||
/**
|
||||
* Validates that the deployed Kraiken contract version is compatible
|
||||
* with this indexer's kraiken-lib version.
|
||||
|
|
@ -25,6 +30,24 @@ export async function validateContractVersion(context: Context): Promise<void> {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const timestamp = BigInt(Math.floor(Date.now() / 1000));
|
||||
const metaPayload = {
|
||||
contractVersion: versionNumber,
|
||||
ponderVersion: PONDER_APP_VERSION ?? 'dev',
|
||||
kraikenLibVersion: KRAIKEN_LIB_VERSION,
|
||||
updatedAt: timestamp,
|
||||
};
|
||||
|
||||
const existingMeta = await context.db.find(stackMeta, { id: STACK_META_ID });
|
||||
if (existingMeta) {
|
||||
await context.db.update(stackMeta, { id: STACK_META_ID }).set(metaPayload);
|
||||
} else {
|
||||
await context.db.insert(stackMeta).values({
|
||||
id: STACK_META_ID,
|
||||
...metaPayload,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`✓ Contract version validated: v${versionNumber} (kraiken-lib v${KRAIKEN_LIB_VERSION})`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to read contract VERSION:', error);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,17 @@ test.describe('Acquire & Stake', () => {
|
|||
await expect(walletDisplay).toBeVisible({ timeout: 15_000 });
|
||||
console.log('[TEST] Wallet connected successfully!');
|
||||
|
||||
console.log('[TEST] Verifying stack version footer...');
|
||||
const versionFooter = page.getByTestId('stack-version-footer');
|
||||
await expect(versionFooter).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByTestId('stack-version-contracts')).not.toHaveText(/Loading|—/i);
|
||||
await expect(page.getByTestId('stack-version-ponder')).not.toHaveText(/Loading|—/i);
|
||||
await expect(page.getByTestId('stack-version-kraiken-lib')).toHaveText(/^v\d+/i);
|
||||
await expect(page.getByTestId('stack-version-web-app')).toHaveText(/^v/i);
|
||||
const warningBanner = page.getByTestId('stack-version-warning');
|
||||
await expect(warningBanner).toHaveCount(0);
|
||||
console.log('[TEST] Stack version footer verified.');
|
||||
|
||||
console.log('[TEST] Navigating to cheats page...');
|
||||
await page.goto(`${STACK_WEBAPP_URL}/app/#/cheats`);
|
||||
await expect(page.getByRole('heading', { name: 'Cheat Console' })).toBeVisible();
|
||||
|
|
|
|||
2
web-app/env.d.ts
vendored
2
web-app/env.d.ts
vendored
|
|
@ -8,4 +8,6 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
export {};
|
||||
|
|
|
|||
124
web-app/src/components/layouts/StackVersionFooter.vue
Normal file
124
web-app/src/components/layouts/StackVersionFooter.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<footer class="stack-version-footer" data-testid="stack-version-footer">
|
||||
<div class="stack-version-footer__items">
|
||||
<div class="stack-version-footer__item" v-for="item in footerItems" :key="item.key">
|
||||
<span class="stack-version-footer__label">{{ item.label }}</span>
|
||||
<span class="stack-version-footer__value" :class="valueClass(item.key)" :data-testid="`stack-version-${item.key}`">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="footerMessage" class="stack-version-footer__warning" data-testid="stack-version-warning">
|
||||
{{ footerMessage }}
|
||||
</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { DEFAULT_CHAIN_ID } from '@/config';
|
||||
import { resolveGraphqlEndpoint } from '@/utils/graphqlRetry';
|
||||
import { useVersionCheckOnMount } from '@/composables/useVersionCheck';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
const endpointHint = ref<string | null>(null);
|
||||
let endpoint: string | undefined;
|
||||
try {
|
||||
endpoint = resolveGraphqlEndpoint(DEFAULT_CHAIN_ID);
|
||||
logger.info('Stack version footer resolved GraphQL endpoint', endpoint);
|
||||
} catch (error) {
|
||||
endpoint = undefined;
|
||||
endpointHint.value = 'GraphQL endpoint unavailable – ensure the stack is running.';
|
||||
logger.error('Failed to resolve GraphQL endpoint for stack version footer', error);
|
||||
}
|
||||
|
||||
const { versionStatus, stackVersions, isChecking, hasChecked } = useVersionCheckOnMount(endpoint);
|
||||
|
||||
const footerItems = computed(() => {
|
||||
const loading = isChecking.value && !hasChecked.value;
|
||||
const loadingText = loading ? 'Loading…' : '—';
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'contracts',
|
||||
label: 'Contracts',
|
||||
value: stackVersions.value.contractVersion !== null ? `v${stackVersions.value.contractVersion}` : loadingText,
|
||||
},
|
||||
{
|
||||
key: 'ponder',
|
||||
label: 'Ponder',
|
||||
value: stackVersions.value.ponderVersion ? `v${stackVersions.value.ponderVersion}` : loadingText,
|
||||
},
|
||||
{
|
||||
key: 'kraiken-lib',
|
||||
label: 'kraiken-lib',
|
||||
value: `v${stackVersions.value.kraikenLibVersion}`,
|
||||
},
|
||||
{
|
||||
key: 'web-app',
|
||||
label: 'Web App',
|
||||
value: `v${stackVersions.value.webAppVersion}`,
|
||||
},
|
||||
] as const;
|
||||
});
|
||||
|
||||
const footerMessage = computed(() => {
|
||||
if (!versionStatus.value.isValid) {
|
||||
return versionStatus.value.error ?? 'Stack version mismatch detected.';
|
||||
}
|
||||
return endpointHint.value;
|
||||
});
|
||||
|
||||
function valueClass(key: (typeof footerItems.value)[number]['key']) {
|
||||
if (key === 'kraiken-lib') {
|
||||
return versionStatus.value.isValid ? null : 'stack-version-footer__value--warning';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.stack-version-footer
|
||||
padding: 12px 24px
|
||||
background: rgba(7, 17, 27, 0.92)
|
||||
color: #cbd5f5
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08)
|
||||
font-size: 12px
|
||||
letter-spacing: 0.04em
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 8px
|
||||
align-items: center
|
||||
width: 100%
|
||||
|
||||
.stack-version-footer__items
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: center
|
||||
gap: 16px
|
||||
|
||||
.stack-version-footer__item
|
||||
display: flex
|
||||
gap: 6px
|
||||
align-items: baseline
|
||||
|
||||
.stack-version-footer__label
|
||||
text-transform: uppercase
|
||||
font-weight: 600
|
||||
font-size: 11px
|
||||
color: #94a3b8
|
||||
|
||||
.stack-version-footer__value
|
||||
font-family: 'Fira Code', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace
|
||||
font-weight: 600
|
||||
color: #e2e8f0
|
||||
|
||||
.stack-version-footer__value--warning
|
||||
color: #f97316
|
||||
|
||||
.stack-version-footer__warning
|
||||
margin: 0
|
||||
color: #f97316
|
||||
font-size: 11px
|
||||
text-align: center
|
||||
</style>
|
||||
|
|
@ -1,57 +1,162 @@
|
|||
import { ref, onMounted } from 'vue';
|
||||
import { KRAIKEN_LIB_VERSION } from 'kraiken-lib/version';
|
||||
import { DEFAULT_CHAIN_ID } from '@/config';
|
||||
import { resolveGraphqlEndpoint } from '@/utils/graphqlRetry';
|
||||
import { KRAIKEN_LIB_VERSION, STACK_META_ID } from 'kraiken-lib/version';
|
||||
|
||||
const WEB_APP_VERSION = __APP_VERSION__;
|
||||
|
||||
type GraphqlError = { message?: string | null };
|
||||
type GraphqlResponse<T> = {
|
||||
data?: T;
|
||||
errors?: GraphqlError[];
|
||||
};
|
||||
|
||||
interface StackMetaRecord {
|
||||
contractVersion?: number | null;
|
||||
ponderVersion?: string | null;
|
||||
kraikenLibVersion?: number | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
interface StackMetaResult {
|
||||
stackMeta?: StackMetaRecord | null;
|
||||
}
|
||||
|
||||
export interface StackVersions {
|
||||
contractVersion: number | null;
|
||||
ponderVersion: string | null;
|
||||
indexerLibVersion: number | null;
|
||||
kraikenLibVersion: number;
|
||||
webAppVersion: string;
|
||||
updatedAt: bigint | null;
|
||||
}
|
||||
|
||||
export interface VersionStatus {
|
||||
isValid: boolean;
|
||||
error?: string;
|
||||
contractVersion?: number;
|
||||
indexerVersion?: number;
|
||||
indexerLibVersion?: number;
|
||||
ponderVersion?: string | null;
|
||||
libVersion: number;
|
||||
webAppVersion: string;
|
||||
}
|
||||
|
||||
const STACK_META_QUERY = `
|
||||
query StackMeta($id: String!) {
|
||||
stackMeta(id: $id) {
|
||||
contractVersion
|
||||
ponderVersion
|
||||
kraikenLibVersion
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const versionStatus = ref<VersionStatus>({
|
||||
isValid: true,
|
||||
libVersion: KRAIKEN_LIB_VERSION,
|
||||
webAppVersion: WEB_APP_VERSION,
|
||||
});
|
||||
|
||||
const stackVersions = ref<StackVersions>({
|
||||
contractVersion: null,
|
||||
ponderVersion: null,
|
||||
indexerLibVersion: null,
|
||||
kraikenLibVersion: KRAIKEN_LIB_VERSION,
|
||||
webAppVersion: WEB_APP_VERSION,
|
||||
updatedAt: null,
|
||||
});
|
||||
|
||||
const isChecking = ref(false);
|
||||
const hasChecked = ref(false);
|
||||
|
||||
/**
|
||||
* Validates version compatibility between contract, indexer (Ponder), and frontend (kraiken-lib).
|
||||
*
|
||||
* Queries Ponder GraphQL for the contract version it indexed, then compares:
|
||||
* 1. Frontend lib version vs Ponder's lib version (should match exactly)
|
||||
* 2. Contract version vs compatible versions list (should be in COMPATIBLE_CONTRACT_VERSIONS)
|
||||
*/
|
||||
function formatError(error: unknown): string {
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
if (typeof error === 'string' && error.length > 0) {
|
||||
return error;
|
||||
}
|
||||
return 'Failed to load stack version metadata';
|
||||
}
|
||||
|
||||
function sanitizeNumber(value: unknown): number | null {
|
||||
const parsed = typeof value === 'number' ? value : Number(value ?? Number.NaN);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
export function useVersionCheck() {
|
||||
async function checkVersions(_graphqlUrl: string) {
|
||||
if (isChecking.value || hasChecked.value) return versionStatus.value;
|
||||
async function checkVersions(graphqlEndpointOverride?: string) {
|
||||
if (isChecking.value) return versionStatus.value;
|
||||
|
||||
isChecking.value = true;
|
||||
|
||||
try {
|
||||
// For now, we don't have contract version in Ponder stats yet
|
||||
// This is a placeholder for when we add it to the schema
|
||||
// Just validate that kraiken-lib is loaded correctly
|
||||
const endpoint = resolveGraphqlEndpoint(DEFAULT_CHAIN_ID, graphqlEndpointOverride);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: STACK_META_QUERY,
|
||||
variables: { id: STACK_META_ID },
|
||||
}),
|
||||
});
|
||||
|
||||
versionStatus.value = {
|
||||
isValid: true,
|
||||
libVersion: KRAIKEN_LIB_VERSION,
|
||||
if (!response.ok) {
|
||||
throw new Error(`GraphQL stackMeta request failed (HTTP ${response.status})`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as GraphqlResponse<StackMetaResult>;
|
||||
|
||||
const apiErrors = payload.errors?.filter(err => err?.message);
|
||||
if (apiErrors && apiErrors.length > 0) {
|
||||
throw new Error(apiErrors.map(err => err.message).join(', '));
|
||||
}
|
||||
|
||||
const meta = payload.data?.stackMeta;
|
||||
if (!meta) {
|
||||
throw new Error('Stack metadata unavailable (stackMeta query returned null)');
|
||||
}
|
||||
|
||||
const contractVersion = sanitizeNumber(meta.contractVersion);
|
||||
const ponderVersion = meta.ponderVersion ?? null;
|
||||
const indexerLibVersion = sanitizeNumber(meta.kraikenLibVersion);
|
||||
const updatedAt = meta.updatedAt ? BigInt(meta.updatedAt) : null;
|
||||
|
||||
const libMismatch = indexerLibVersion !== null && indexerLibVersion !== KRAIKEN_LIB_VERSION;
|
||||
|
||||
stackVersions.value = {
|
||||
contractVersion,
|
||||
ponderVersion,
|
||||
indexerLibVersion,
|
||||
kraikenLibVersion: KRAIKEN_LIB_VERSION,
|
||||
webAppVersion: WEB_APP_VERSION,
|
||||
updatedAt,
|
||||
};
|
||||
|
||||
// TODO: Implement actual version check against Ponder GraphQL
|
||||
// console.log(`✓ Frontend version check passed: v${KRAIKEN_LIB_VERSION}`);
|
||||
hasChecked.value = true;
|
||||
versionStatus.value = {
|
||||
isValid: !libMismatch,
|
||||
error: libMismatch
|
||||
? `Stack mismatch: Ponder reports kraiken-lib v${indexerLibVersion} (frontend expects v${KRAIKEN_LIB_VERSION}). Restart the stack to rebuild containers.`
|
||||
: undefined,
|
||||
contractVersion: contractVersion ?? undefined,
|
||||
indexerLibVersion: indexerLibVersion ?? undefined,
|
||||
ponderVersion,
|
||||
libVersion: KRAIKEN_LIB_VERSION,
|
||||
webAppVersion: WEB_APP_VERSION,
|
||||
};
|
||||
} catch (error) {
|
||||
// TODO: Add proper error logging
|
||||
// console.error('Version check failed:', error);
|
||||
versionStatus.value = {
|
||||
isValid: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check version compatibility',
|
||||
error: formatError(error),
|
||||
contractVersion: stackVersions.value.contractVersion ?? undefined,
|
||||
indexerLibVersion: stackVersions.value.indexerLibVersion ?? undefined,
|
||||
ponderVersion: stackVersions.value.ponderVersion,
|
||||
libVersion: KRAIKEN_LIB_VERSION,
|
||||
webAppVersion: WEB_APP_VERSION,
|
||||
};
|
||||
} finally {
|
||||
hasChecked.value = true;
|
||||
isChecking.value = false;
|
||||
}
|
||||
|
||||
|
|
@ -60,18 +165,15 @@ export function useVersionCheck() {
|
|||
|
||||
return {
|
||||
versionStatus,
|
||||
stackVersions,
|
||||
checkVersions,
|
||||
isChecking,
|
||||
hasChecked,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue composable that automatically checks versions on mount.
|
||||
* Shows a warning banner if versions are incompatible.
|
||||
*/
|
||||
export function useVersionCheckOnMount(graphqlUrl: string) {
|
||||
const { versionStatus, checkVersions, isChecking } = useVersionCheck();
|
||||
export function useVersionCheckOnMount(graphqlUrl?: string) {
|
||||
const { versionStatus, stackVersions, checkVersions, isChecking, hasChecked } = useVersionCheck();
|
||||
|
||||
onMounted(async () => {
|
||||
await checkVersions(graphqlUrl);
|
||||
|
|
@ -79,6 +181,9 @@ export function useVersionCheckOnMount(graphqlUrl: string) {
|
|||
|
||||
return {
|
||||
versionStatus,
|
||||
stackVersions,
|
||||
isChecking,
|
||||
hasChecked,
|
||||
checkVersions,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,22 @@
|
|||
<template>
|
||||
<div class="default-layout">
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
<StackVersionFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StackVersionFooter from '@/components/layouts/StackVersionFooter.vue';
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.default-layout
|
||||
min-height: 100vh
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.default-layout > main
|
||||
flex: 1 1 auto
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@
|
|||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
<StackVersionFooter class="navbar-layout__footer" />
|
||||
</div>
|
||||
<!-- <footer>
|
||||
<f-footer :dark="darkTheme"></f-footer>
|
||||
</footer> -->
|
||||
<div class="mobile-navigation-bar" v-if="isMobile">
|
||||
<div class="mobile-navigation-tab" @click="router.push('/')">
|
||||
<div class="mobile-navigation-tab__icon">
|
||||
|
|
@ -33,6 +31,7 @@
|
|||
import NavbarHeader from '@/components/layouts/NavbarHeader.vue';
|
||||
import SlideoutPanel from '@/components/layouts/SlideoutPanel.vue';
|
||||
import ConnectWallet from '@/components/layouts/ConnectWallet.vue';
|
||||
import StackVersionFooter from '@/components/layouts/StackVersionFooter.vue';
|
||||
import { useMobile } from '@/composables/useMobile';
|
||||
import IconHome from '@/components/icons/IconHome.vue';
|
||||
import IconDocs from '@/components/icons/IconDocs.vue';
|
||||
|
|
@ -54,6 +53,16 @@ function openDocs() {
|
|||
<style lang="sass">
|
||||
.navbar-layout
|
||||
padding-top: 100px
|
||||
padding-bottom: 120px
|
||||
min-height: 100vh
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.navbar-layout > main
|
||||
flex: 1 1 auto
|
||||
|
||||
.navbar-layout__footer
|
||||
margin-top: auto
|
||||
|
||||
.mobile-navigation-bar
|
||||
height: 72px
|
||||
|
|
|
|||
|
|
@ -1,20 +1,59 @@
|
|||
export function info(text: string, data: unknown = null) {
|
||||
if (data) {
|
||||
// console.log(`%c ${text}`, 'color: #17a2b8', data);
|
||||
} else {
|
||||
// console.log(`%c ${text}`, 'color: #17a2b8');
|
||||
type LogLevel = 'info' | 'error' | 'contract';
|
||||
|
||||
interface LogEntry {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
data?: unknown;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
const MAX_BUFFER = 50;
|
||||
const buffer: LogEntry[] = [];
|
||||
|
||||
function emit(entry: LogEntry) {
|
||||
buffer.push(entry);
|
||||
if (buffer.length > MAX_BUFFER) {
|
||||
buffer.shift();
|
||||
}
|
||||
|
||||
if (isBrowser && import.meta.env.DEV) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('kraiken:log', {
|
||||
detail: entry,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function contract(text: string, data: unknown = null) {
|
||||
if (data) {
|
||||
// console.log(`%c ${text}`, 'color: #8732a8', data);
|
||||
} else {
|
||||
// console.log(`%c ${text}`, 'color: #8732a8');
|
||||
function buildEntry(level: LogLevel, message: string, data?: unknown): LogEntry {
|
||||
return {
|
||||
level,
|
||||
message,
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function info(message: string, data?: unknown) {
|
||||
emit(buildEntry('info', message, data));
|
||||
}
|
||||
|
||||
export function contract(message: string, data?: unknown) {
|
||||
emit(buildEntry('contract', message, data));
|
||||
}
|
||||
|
||||
export function error(message: string, data?: unknown) {
|
||||
emit(buildEntry('error', message, data));
|
||||
}
|
||||
|
||||
export function getBufferedLogs(): LogEntry[] {
|
||||
return [...buffer];
|
||||
}
|
||||
|
||||
export default {
|
||||
info,
|
||||
contract,
|
||||
error,
|
||||
getBufferedLogs,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import { fileURLToPath, URL } from 'node:url'
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import packageJson from './package.json' assert { type: 'json' }
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(() => {
|
||||
const localRpcProxyTarget = process.env.VITE_LOCAL_RPC_PROXY_TARGET
|
||||
const localGraphqlProxyTarget = process.env.VITE_LOCAL_GRAPHQL_PROXY_TARGET ?? 'http://127.0.0.1:42069'
|
||||
const localTxnProxyTarget = process.env.VITE_LOCAL_TXN_PROXY_TARGET ?? 'http://127.0.0.1:43069'
|
||||
const appVersion = (packageJson as { version?: string }).version ?? 'dev'
|
||||
|
||||
return {
|
||||
// base: "/HarbergPublic/",
|
||||
|
|
@ -16,6 +18,9 @@ export default defineConfig(() => {
|
|||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(appVersion),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue