You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
14 KiB
578 lines
14 KiB
<template>
|
|
<div class="status-view">
|
|
<div class="view-header">
|
|
<h1 class="page-title">📊 Status Matrix</h1>
|
|
<p class="page-subtitle">Comprehensive system status and diagnostics</p>
|
|
</div>
|
|
|
|
<!-- Status Matrix Grid -->
|
|
<div class="status-matrix">
|
|
<div class="matrix-header">
|
|
<h2>Runtime Capabilities</h2>
|
|
<div class="matrix-actions">
|
|
<button
|
|
class="action-button refresh"
|
|
@click="refreshStatus"
|
|
:disabled="isRefreshing"
|
|
>
|
|
🔄 {{ isRefreshing ? 'Refreshing...' : 'Refresh' }}
|
|
</button>
|
|
<button
|
|
class="action-button export"
|
|
@click="exportDiagnostics"
|
|
>
|
|
📋 Copy Diagnostics
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="matrix-grid">
|
|
<StatusCard
|
|
v-for="item in statusItems"
|
|
:key="item.key"
|
|
:title="item.title"
|
|
:status="item.status"
|
|
:value="item.value"
|
|
:description="item.description"
|
|
:action="item.action"
|
|
@action-click="handleAction"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Diagnostics Section -->
|
|
<div class="diagnostics-section">
|
|
<h2>System Diagnostics</h2>
|
|
<div class="diagnostics-content">
|
|
<div class="diagnostics-info">
|
|
<div class="info-item">
|
|
<span class="label">App Version:</span>
|
|
<span class="value">{{ diagnostics.appVersion }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Platform:</span>
|
|
<span class="value">{{ diagnostics.platform }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">API Level:</span>
|
|
<span class="value">{{ diagnostics.apiLevel }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Timezone:</span>
|
|
<span class="value">{{ diagnostics.timezone }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="label">Last Updated:</span>
|
|
<span class="value">{{ diagnostics.lastUpdated }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="diagnostics-json">
|
|
<h3>Raw Diagnostics (JSON)</h3>
|
|
<pre class="json-output">{{ diagnosticsJson }}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Display -->
|
|
<div v-if="errorMessage" class="error-section">
|
|
<h2>⚠️ Error Information</h2>
|
|
<div class="error-content">
|
|
<p>{{ errorMessage }}</p>
|
|
<button class="action-button" @click="clearError">Clear Error</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Vue, Component, toNative } from 'vue-facing-decorator'
|
|
import StatusCard from '../components/cards/StatusCard.vue'
|
|
import { createTypedPlugin, type PermissionStatus, type NotificationStatus, type ExactAlarmStatus } from '../lib/typed-plugin'
|
|
import { collectDiagnostics, copyDiagnosticsToClipboard, type ComprehensiveDiagnostics } from '../lib/diagnostics-export'
|
|
|
|
interface StatusItem {
|
|
key: string
|
|
title: string
|
|
status: 'success' | 'warning' | 'error' | 'info'
|
|
value: string
|
|
description: string
|
|
action?: {
|
|
label: string
|
|
method: string
|
|
}
|
|
}
|
|
|
|
// Use ComprehensiveDiagnostics from diagnostics-export
|
|
type Diagnostics = ComprehensiveDiagnostics
|
|
|
|
@Component({
|
|
components: {
|
|
StatusCard
|
|
}
|
|
})
|
|
class StatusView extends Vue {
|
|
isRefreshing = false
|
|
errorMessage = ''
|
|
diagnostics: Diagnostics = {
|
|
appVersion: '1.0.0',
|
|
platform: 'Android',
|
|
apiLevel: 'Unknown',
|
|
timezone: 'Unknown',
|
|
lastUpdated: 'Never',
|
|
postNotificationsGranted: false,
|
|
exactAlarmGranted: false,
|
|
channelEnabled: false,
|
|
batteryOptimizationsIgnored: false,
|
|
canScheduleNow: false,
|
|
lastError: undefined,
|
|
capabilities: {
|
|
notificationStatus: { isEnabled: false, isScheduled: false, pending: false },
|
|
permissions: { notifications: 'denied', notificationsEnabled: false },
|
|
exactAlarmStatus: { enabled: false, supported: false },
|
|
userAgent: '',
|
|
language: '',
|
|
platform: '',
|
|
cookieEnabled: false,
|
|
onLine: false,
|
|
hardwareConcurrency: 0,
|
|
maxTouchPoints: 0,
|
|
timestamp: 0,
|
|
timezoneOffset: 0,
|
|
pluginAvailable: false,
|
|
errorContext: { hasError: false, errorMessage: null, lastCheckTime: '' },
|
|
performanceMetrics: { loadTime: 0 }
|
|
},
|
|
systemInfo: {
|
|
screenResolution: '0x0',
|
|
colorDepth: 0,
|
|
pixelRatio: 1,
|
|
viewportSize: '0x0',
|
|
devicePixelRatio: 1
|
|
},
|
|
networkInfo: {
|
|
connectionType: 'unknown'
|
|
},
|
|
storageInfo: {
|
|
localStorageAvailable: false,
|
|
sessionStorageAvailable: false,
|
|
indexedDBAvailable: false,
|
|
webSQLAvailable: false
|
|
}
|
|
}
|
|
|
|
get statusItems(): StatusItem[] {
|
|
return [
|
|
{
|
|
key: 'postNotifications',
|
|
title: 'Post Notifications',
|
|
status: this.diagnostics.postNotificationsGranted ? 'success' : 'error',
|
|
value: this.diagnostics.postNotificationsGranted ? 'Granted' : 'Not Granted',
|
|
description: 'Permission to display notifications',
|
|
action: !this.diagnostics.postNotificationsGranted ? {
|
|
label: 'Request Permission',
|
|
method: 'requestPermissions'
|
|
} : undefined
|
|
},
|
|
{
|
|
key: 'exactAlarm',
|
|
title: 'Exact Alarm',
|
|
status: this.diagnostics.exactAlarmGranted ? 'success' : 'warning',
|
|
value: this.diagnostics.exactAlarmGranted ? 'Granted' : 'Not Granted',
|
|
description: 'Permission for precise alarm scheduling',
|
|
action: !this.diagnostics.exactAlarmGranted ? {
|
|
label: 'Open Settings',
|
|
method: 'openExactAlarmSettings'
|
|
} : undefined
|
|
},
|
|
{
|
|
key: 'channelEnabled',
|
|
title: 'Notification Channel',
|
|
status: this.diagnostics.channelEnabled ? 'success' : 'error',
|
|
value: this.diagnostics.channelEnabled ? 'Enabled' : 'Disabled',
|
|
description: 'Notification channel status',
|
|
action: !this.diagnostics.channelEnabled ? {
|
|
label: 'Open Channel Settings',
|
|
method: 'openChannelSettings'
|
|
} : undefined
|
|
},
|
|
{
|
|
key: 'batteryOptimizations',
|
|
title: 'Battery Optimizations',
|
|
status: this.diagnostics.batteryOptimizationsIgnored ? 'success' : 'warning',
|
|
value: this.diagnostics.batteryOptimizationsIgnored ? 'Ignored' : 'Not Ignored',
|
|
description: 'Battery optimization exemption',
|
|
action: !this.diagnostics.batteryOptimizationsIgnored ? {
|
|
label: 'Request Exemption',
|
|
method: 'requestBatteryOptimizationExemption'
|
|
} : undefined
|
|
},
|
|
{
|
|
key: 'canSchedule',
|
|
title: 'Can Schedule Now',
|
|
status: this.diagnostics.canScheduleNow ? 'success' : 'error',
|
|
value: this.diagnostics.canScheduleNow ? 'Yes' : 'No',
|
|
description: 'Ready to schedule notifications',
|
|
action: !this.diagnostics.canScheduleNow ? {
|
|
label: 'Check Prerequisites',
|
|
method: 'checkPrerequisites'
|
|
} : undefined
|
|
}
|
|
]
|
|
}
|
|
|
|
get diagnosticsJson(): string {
|
|
return JSON.stringify(this.diagnostics, null, 2)
|
|
}
|
|
|
|
async mounted() {
|
|
await this.refreshStatus()
|
|
}
|
|
|
|
async refreshStatus() {
|
|
this.isRefreshing = true
|
|
this.errorMessage = ''
|
|
|
|
try {
|
|
console.log('🔄 Refreshing status matrix...')
|
|
|
|
// Create typed plugin instance
|
|
const typedPlugin = await createTypedPlugin()
|
|
|
|
if (!typedPlugin) {
|
|
throw new Error('DailyNotification plugin not available')
|
|
}
|
|
|
|
// Collect comprehensive status with type safety
|
|
const [notificationStatus, permissions, exactAlarmStatus] = await Promise.all([
|
|
typedPlugin.getNotificationStatus(),
|
|
typedPlugin.checkPermissions(),
|
|
typedPlugin.getExactAlarmStatus()
|
|
])
|
|
|
|
// Collect comprehensive diagnostics
|
|
this.diagnostics = await collectDiagnostics(
|
|
notificationStatus,
|
|
permissions,
|
|
exactAlarmStatus,
|
|
!!typedPlugin
|
|
)
|
|
|
|
console.log('✅ Status matrix refreshed successfully')
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to refresh status:', error)
|
|
this.errorMessage = `Failed to refresh status: ${error.message}`
|
|
} finally {
|
|
this.isRefreshing = false
|
|
}
|
|
}
|
|
|
|
async handleAction(action: { label: string; method: string }) {
|
|
try {
|
|
console.log(`🔧 Executing action: ${action.method}`)
|
|
|
|
const typedPlugin = await createTypedPlugin()
|
|
|
|
if (!typedPlugin) {
|
|
throw new Error('Plugin not available')
|
|
}
|
|
|
|
switch (action.method) {
|
|
case 'requestPermissions':
|
|
await typedPlugin.requestPermissions()
|
|
break
|
|
case 'openExactAlarmSettings':
|
|
await typedPlugin.openExactAlarmSettings()
|
|
break
|
|
case 'openChannelSettings':
|
|
await typedPlugin.openChannelSettings()
|
|
break
|
|
case 'requestBatteryOptimizationExemption':
|
|
await typedPlugin.requestBatteryOptimizationExemption()
|
|
break
|
|
case 'checkPrerequisites':
|
|
await this.refreshStatus()
|
|
break
|
|
default:
|
|
console.warn(`Unknown action method: ${action.method}`)
|
|
}
|
|
|
|
// Refresh status after action
|
|
await this.refreshStatus()
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Action ${action.method} failed:`, error)
|
|
this.errorMessage = `Action failed: ${error.message}`
|
|
}
|
|
}
|
|
|
|
async exportDiagnostics() {
|
|
try {
|
|
await copyDiagnosticsToClipboard(this.diagnostics, 'json')
|
|
console.log('📋 Comprehensive diagnostics copied to clipboard')
|
|
|
|
// Show success feedback
|
|
const button = document.querySelector('.action-button.export') as HTMLButtonElement
|
|
const originalText = button.textContent
|
|
button.textContent = '✅ Copied!'
|
|
setTimeout(() => {
|
|
button.textContent = originalText
|
|
}, 2000)
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to copy diagnostics:', error)
|
|
this.errorMessage = `Failed to copy diagnostics: ${error.message}`
|
|
}
|
|
}
|
|
|
|
clearError() {
|
|
this.errorMessage = ''
|
|
}
|
|
}
|
|
|
|
export default toNative(StatusView)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.status-view {
|
|
padding: 20px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.view-header {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.page-title {
|
|
margin: 0 0 8px 0;
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: white;
|
|
}
|
|
|
|
.page-subtitle {
|
|
margin: 0;
|
|
font-size: 16px;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
/* Status Matrix */
|
|
.status-matrix {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
margin-bottom: 24px;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.matrix-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.matrix-header h2 {
|
|
margin: 0;
|
|
color: white;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.matrix-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.action-button {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.action-button:hover:not(:disabled) {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.action-button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
.action-button.refresh {
|
|
background: rgba(59, 130, 246, 0.3);
|
|
border-color: rgba(59, 130, 246, 0.5);
|
|
}
|
|
|
|
.action-button.export {
|
|
background: rgba(34, 197, 94, 0.3);
|
|
border-color: rgba(34, 197, 94, 0.5);
|
|
}
|
|
|
|
.matrix-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
/* Diagnostics Section */
|
|
.diagnostics-section {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
margin-bottom: 24px;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.diagnostics-section h2 {
|
|
margin: 0 0 20px 0;
|
|
color: white;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.diagnostics-content {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
.diagnostics-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.info-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-item .label {
|
|
color: rgba(255, 255, 255, 0.8);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.info-item .value {
|
|
color: white;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.diagnostics-json h3 {
|
|
margin: 0 0 12px 0;
|
|
color: white;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.json-output {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
color: #e5e7eb;
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
overflow-x: auto;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Error Section */
|
|
.error-section {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.error-section h2 {
|
|
margin: 0 0 16px 0;
|
|
color: #fca5a5;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.error-content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.error-content p {
|
|
margin: 0;
|
|
color: #fca5a5;
|
|
flex: 1;
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
.status-view {
|
|
padding: 16px;
|
|
}
|
|
|
|
.matrix-header {
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.matrix-actions {
|
|
justify-content: center;
|
|
}
|
|
|
|
.diagnostics-content {
|
|
grid-template-columns: 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.error-content {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.matrix-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.page-title {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.matrix-actions {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.action-button {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style>
|
|
|