Initial import: shopdb-flask multi-site asset platform

Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin
framework so sister GE Aerospace sites can adopt it. Product version
0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests.

Key entry points:
- docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook
  (own-site and subpath deployment methods)
- docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify
- docs/IMPORT-API.md - schema-agnostic legacy data import contract
- scripts/site_imports/wjf/ - West Jefferson reference import loader
- docs/adr/ - twelve accepted architecture decision records
This commit is contained in:
2026-07-14 07:58:51 -04:00
commit a9d0dfff4a
599 changed files with 88557 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import AppLayout from '../views/AppLayout.vue'
import SettingsLayout from '../views/settings/SettingsLayout.vue'
import { setupComplete, setupSkipped, isSetupLoaded, refreshSetupState } from '../composables/setupState'
import { loadEnabledPlugins, isPluginEnabled } from '../composables/enabledPlugins'
import { useToast } from '../composables/toast'
// Auto-discover all route modules from routes/ directory
const routeModules = import.meta.glob('./routes/*.js', { eager: true })
const rawChildren = Object.values(routeModules).flatMap(m => m.default)
// Gather the settings pages (spread across plugin route files) and nest them
// under a single two-pane shell so the grouped rail stays put while the right
// pane swaps. Slides is a sidebar page that happens to live at /settings/slides;
// keep it full-width (not inside the settings rail).
const SETTINGS_STANDALONE = new Set(['settings/slides'])
const settingsChildren = []
const otherChildren = []
for (const route of rawChildren) {
const path = route.path
if (path === 'settings') {
// Old index becomes the shell's default child (keeps name 'settings').
settingsChildren.unshift({ ...route, path: '' })
} else if (typeof path === 'string' && path.startsWith('settings/') && !SETTINGS_STANDALONE.has(path)) {
settingsChildren.push({ ...route, path: path.replace(/^settings\//, '') })
} else {
otherChildren.push(route)
}
}
const appChildren = [
...otherChildren,
{
path: 'settings',
component: SettingsLayout,
meta: { requiresAuth: true, requiresAdmin: true },
children: settingsChildren,
},
]
const routes = [
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue'),
meta: { guest: true }
},
// Forced/self-service password change (standalone, authenticated)
{
path: '/change-password',
name: 'change-password',
component: () => import('../views/ChangePassword.vue'),
meta: { requiresAuth: true }
},
// First-run setup wizard (standalone, admin-only)
{
path: '/setup',
name: 'setup',
component: () => import('../views/SetupWizard.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
// Standalone full-screen dashboards (no sidebar, no auth required)
{
path: '/shopfloor',
name: 'shopfloor',
component: () => import('../views/ShopfloorDashboard.vue')
},
{
path: '/tv',
name: 'tv',
component: () => import('../views/TVDashboard.vue'),
meta: { plugin: 'slides' }
},
// Print pages (standalone, no sidebar/header)
{
path: '/print/machine-badge/:id',
name: 'print-machine-badge',
component: () => import('../views/print/MachineBadge.vue')
},
// Shared asset label/code generator for any asset type (public, like the
// other /print/* routes). assettype = machine|computer|printer|
// network_device|measuring_tool; id = the asset's plugin id.
{
path: '/print/asset-label/:assettype/:id',
name: 'print-asset-label',
component: () => import('../views/print/AssetLabel.vue')
},
// Batch label sheets (ULINE 6-up / mini 72-up) for one asset type.
{
path: '/print/asset-label-batch/:assettype',
name: 'print-asset-label-batch',
component: () => import('../views/print/AssetLabelBatch.vue')
},
{
path: '/print/printer-qr',
name: 'print-printer-qr-batch',
component: () => import('../views/print/PrinterQRBatch.vue'),
meta: { plugin: 'printers' }
},
{
path: '/print/printer-qr/:id',
name: 'print-printer-qr-single',
component: () => import('../views/print/PrinterQRSingle.vue'),
meta: { plugin: 'printers' }
},
{
path: '/print/usb-labels',
name: 'print-usb-labels',
component: () => import('../views/print/USBLabelBatch.vue'),
meta: { plugin: 'usb' }
},
{
path: '/',
component: AppLayout,
children: appChildren
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
// Navigation guard
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
// Remember where they were headed so login can send them back there.
return next({ path: '/login', query: { redirect: to.fullPath } })
}
if (to.meta.requiresAdmin && !authStore.isAdmin) {
return next('/')
}
if (to.meta.guest && authStore.isAuthenticated) {
return next('/')
}
// Forced password change: an admin-set temporary password must be replaced
// before the user reaches the rest of the app. Let them log out.
if (authStore.isAuthenticated && authStore.mustChangePassword
&& to.path !== '/change-password' && to.path !== '/login') {
return next('/change-password')
}
// Plugin gating: a disabled backend plugin's frontend routes are dead ends.
// The enabled list is fetched once and cached; fail-open on error so a blip
// cannot brick navigation. Works unauthenticated (endpoint is jwt-optional).
if (to.meta.plugin) {
await loadEnabledPlugins()
if (!isPluginEnabled(to.meta.plugin)) {
useToast().info(`The ${to.meta.plugin} feature is not enabled.`)
return next('/')
}
}
// First-run: steer a fresh admin into the setup wizard (once, until finished).
if (authStore.isAuthenticated && authStore.isAdmin && to.path !== '/setup') {
if (!isSetupLoaded()) {
await refreshSetupState()
}
if (!setupComplete.value && !setupSkipped.value) {
return next('/setup')
}
}
next()
})
export default router

View File

@@ -0,0 +1,27 @@
/**
* Applications routes (core feature)
*/
export default [
{
path: 'applications',
name: 'applications',
component: () => import('../../views/applications/ApplicationsList.vue')
},
{
path: 'applications/new',
name: 'application-new',
component: () => import('../../views/applications/ApplicationForm.vue'),
meta: { requiresAuth: true }
},
{
path: 'applications/:id',
name: 'application-detail',
component: () => import('../../views/applications/ApplicationDetail.vue')
},
{
path: 'applications/:id/edit',
name: 'application-edit',
component: () => import('../../views/applications/ApplicationForm.vue'),
meta: { requiresAuth: true }
}
]

View File

@@ -0,0 +1,48 @@
/**
* Computers plugin routes
*/
export default [
{
path: 'pcs',
name: 'pcs',
component: () => import('../../views/pcs/PCsList.vue'),
meta: { plugin: 'computers' }
},
{
path: 'pcs/new',
name: 'pc-new',
component: () => import('../../views/pcs/PCForm.vue'),
meta: { requiresAuth: true, plugin: 'computers' }
},
{
path: 'pcs/:id',
name: 'pc-detail',
component: () => import('../../views/pcs/PCDetail.vue'),
meta: { plugin: 'computers' }
},
{
path: 'pcs/:id/edit',
name: 'pc-edit',
component: () => import('../../views/pcs/PCForm.vue'),
meta: { requiresAuth: true, plugin: 'computers' }
},
// Computer-specific settings
{
path: 'settings/pctypes',
name: 'pctypes',
component: () => import('../../views/settings/PCTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'computers' }
},
{
path: 'settings/operatingsystems',
name: 'operatingsystems',
component: () => import('../../views/settings/OperatingSystemsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'computers' }
},
{
path: 'settings/accessprotocols',
name: 'access-protocols',
component: () => import('../../views/settings/AccessProtocolsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'computers' }
}
]

View File

@@ -0,0 +1,285 @@
/**
* Core routes - Dashboard, Search, Map, Reports, Settings, Employees
*/
export default [
{
path: '',
name: 'dashboard',
component: () => import('../../views/Dashboard.vue')
},
{
path: 'search',
name: 'search',
component: () => import('../../views/SearchResults.vue')
},
{
path: 'map',
name: 'map',
component: () => import('../../views/MapView.vue')
},
{
path: 'map/editor',
name: 'map-editor',
component: () => import('../../views/MapEditor.vue'),
meta: { requiresAuth: true }
},
{
path: 'reports',
name: 'reports',
component: () => import('../../views/reports/ReportsIndex.vue')
},
{
path: 'reports/pc-relationships',
name: 'report-pc-relationships',
component: () => import('../../views/reports/PCRelationshipsReport.vue'),
meta: { plugin: 'computers' }
},
{
path: 'reports/toner',
name: 'toner-report',
component: () => import('../../views/reports/TonerReport.vue'),
meta: { plugin: 'printers' }
},
{
path: 'employees/:sso',
name: 'employee-detail',
component: () => import('../../views/employees/EmployeeDetail.vue'),
meta: { plugin: 'employees' }
},
// Settings
{
path: 'settings',
name: 'settings',
component: () => import('../../views/settings/SettingsIndex.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/vendors',
name: 'vendors',
component: () => import('../../views/vendors/VendorsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/modeltypes',
name: 'modeltypes',
component: () => import('../../views/settings/ModelTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/locations',
name: 'locations',
component: () => import('../../views/settings/LocationsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/statuses',
name: 'statuses',
component: () => import('../../views/settings/StatusesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/models',
name: 'models',
component: () => import('../../views/settings/ModelsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/businessunits',
name: 'businessunits',
component: () => import('../../views/settings/BusinessUnitsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/supportteams',
name: 'supportteams',
component: () => import('../../views/settings/SupportTeamsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/dashboarddefaults',
name: 'dashboarddefaults',
component: () => import('../../views/settings/DashboardDefaultsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/site',
name: 'site-settings',
component: () => import('../../views/settings/SiteSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/slides',
name: 'slide-manager',
component: () => import('../../views/settings/SlideManager.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'slides' }
},
{
path: 'settings/machinetypes',
name: 'machinetypes',
component: () => import('../../views/settings/MachineTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/networktypes',
name: 'network-types',
component: () => import('../../views/settings/NetworkTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/printertypes',
name: 'printer-types',
component: () => import('../../views/settings/PrinterTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/relationshiptypes',
name: 'relationship-types',
component: () => import('../../views/settings/RelationshipTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/locationtypes',
name: 'location-types',
component: () => import('../../views/settings/LocationTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/assettypes',
name: 'asset-types',
component: () => import('../../views/settings/AssetTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/customfields',
name: 'custom-fields',
component: () => import('../../views/settings/CustomFieldsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/employeedirectory',
name: 'employee-directory',
component: () => import('../../views/settings/EmployeeDirectory.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
// The old monolithic System Settings tab page is split into one routed page
// per section (below). This redirect keeps old bookmarks working: bare
// /settings/system and every /settings/system?tab=<key> land on the right page.
{
path: 'settings/system',
name: 'system-settings',
redirect: to => {
const tabRedirects = {
integrations: '/settings/servicenow',
branding: '/settings/branding',
printing: '/settings/printing',
email: '/settings/email',
audit: '/settings/audit',
auth: '/settings/authentication',
identifiers: '/settings/assetidentifiers',
search: '/settings/globalsearch',
map: '/settings/floormap',
pctype: '/settings/pctypemapping'
}
// vue-router carries the original ?tab= query onto the target; harmless
// since the new pages ignore it, and the path is what routes the view.
return tabRedirects[to.query.tab] || '/settings/servicenow'
},
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/servicenow',
name: 'servicenow-settings',
component: () => import('../../views/settings/ServiceNowSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/zabbix',
name: 'zabbix-settings',
component: () => import('../../views/settings/ZabbixSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/dellwarranty',
name: 'dell-warranty-settings',
component: () => import('../../views/settings/DellWarrantySettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/pctypemapping',
name: 'pctype-mapping-settings',
component: () => import('../../views/settings/PCTypeMappingSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/branding',
name: 'branding-settings',
component: () => import('../../views/settings/BrandingSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/printing',
name: 'printing-settings',
component: () => import('../../views/settings/PrintingSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/email',
name: 'email-settings',
component: () => import('../../views/settings/EmailSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/audit',
name: 'audit-settings',
component: () => import('../../views/settings/AuditSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/authentication',
name: 'authentication-settings',
component: () => import('../../views/settings/AuthenticationSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/apitokens',
name: 'apitokens-settings',
component: () => import('../../views/settings/ApiTokensList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/assetidentifiers',
name: 'asset-identifiers-settings',
component: () => import('../../views/settings/AssetIdentifiersSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/globalsearch',
name: 'global-search-settings',
component: () => import('../../views/settings/GlobalSearchSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/floormap',
name: 'floor-map-settings',
component: () => import('../../views/settings/FloorMapSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/plugins',
name: 'plugins',
component: () => import('../../views/settings/PluginsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/auditlogs',
name: 'audit-logs',
component: () => import('../../views/settings/AuditLogs.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: 'settings/users',
name: 'users',
component: () => import('../../views/settings/UsersList.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
}
]

View File

@@ -0,0 +1,30 @@
/**
* GE-Enforce plugin routes.
*
* A top-level section (not under /settings) - the manifest editor + fleet
* reports are a large operational surface, so they get their own full-width
* shell with tabs. meta.plugin = 'geenforce' so the ADR-009 guard hides the
* section when the plugin is disabled. Admin-only.
*/
export default [
{
path: 'geenforce',
component: () => import('../../views/geenforce/GeEnforceLayout.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'geenforce' },
children: [
{ path: '', redirect: '/geenforce/manifests' },
{
path: 'manifests',
name: 'geenforce-manifests',
component: () => import('../../views/geenforce/ManifestEditor.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'geenforce' }
},
{
path: 'reports',
name: 'geenforce-reports',
component: () => import('../../views/geenforce/EnforcementReports.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'geenforce' }
}
]
}
]

View File

@@ -0,0 +1,29 @@
/**
* Knowledge Base plugin routes
*/
export default [
{
path: 'knowledgebase',
name: 'knowledgebase',
component: () => import('../../views/knowledgebase/KnowledgeBaseList.vue'),
meta: { plugin: 'knowledgebase' }
},
{
path: 'knowledgebase/new',
name: 'knowledgebase-new',
component: () => import('../../views/knowledgebase/KnowledgeBaseForm.vue'),
meta: { requiresAuth: true, plugin: 'knowledgebase' }
},
{
path: 'knowledgebase/:id',
name: 'knowledgebase-detail',
component: () => import('../../views/knowledgebase/KnowledgeBaseDetail.vue'),
meta: { plugin: 'knowledgebase' }
},
{
path: 'knowledgebase/:id/edit',
name: 'knowledgebase-edit',
component: () => import('../../views/knowledgebase/KnowledgeBaseForm.vue'),
meta: { requiresAuth: true, plugin: 'knowledgebase' }
}
]

View File

@@ -0,0 +1,29 @@
/**
* Machines plugin routes
*/
export default [
{
path: 'machines',
name: 'machines',
component: () => import('../../views/machines/MachinesList.vue'),
meta: { plugin: 'machines' }
},
{
path: 'machines/new',
name: 'machine-new',
component: () => import('../../views/machines/MachineForm.vue'),
meta: { requiresAuth: true, plugin: 'machines' }
},
{
path: 'machines/:id',
name: 'machine-detail',
component: () => import('../../views/machines/MachineDetail.vue'),
meta: { plugin: 'machines' }
},
{
path: 'machines/:id/edit',
name: 'machine-edit',
component: () => import('../../views/machines/MachineForm.vue'),
meta: { requiresAuth: true, plugin: 'machines' }
}
]

View File

@@ -0,0 +1,54 @@
/**
* Measuring-tools plugin routes.
*
* Every route carries meta.plugin = 'measuringtools' so the ADR-009 router
* guard redirects to the dashboard when the backend plugin is disabled. List
* and detail are public (no requiresAuth, matching the other asset types); the
* form requires auth; the settings subtype page requires admin.
*/
export default [
{
path: 'measuringtools',
name: 'measuringtools',
component: () => import('../../views/measuringtools/MeasuringToolsList.vue'),
meta: { plugin: 'measuringtools' }
},
{
path: 'measuringtools/new',
name: 'measuringtool-new',
component: () => import('../../views/measuringtools/MeasuringToolForm.vue'),
meta: { requiresAuth: true, plugin: 'measuringtools' }
},
{
// Resolve a tool from its core asset id (search rows / cross-links carry
// assetid, not the extension id). Shares the detail component.
path: 'measuringtools/by-asset/:assetid',
name: 'measuringtool-by-asset',
component: () => import('../../views/measuringtools/MeasuringToolDetail.vue'),
meta: { plugin: 'measuringtools' }
},
{
path: 'measuringtools/:id',
name: 'measuringtool-detail',
component: () => import('../../views/measuringtools/MeasuringToolDetail.vue'),
meta: { plugin: 'measuringtools' }
},
{
path: 'measuringtools/:id/edit',
name: 'measuringtool-edit',
component: () => import('../../views/measuringtools/MeasuringToolForm.vue'),
meta: { requiresAuth: true, plugin: 'measuringtools' }
},
{
path: 'reports/calibration',
name: 'calibration-report',
component: () => import('../../views/reports/CalibrationReport.vue'),
meta: { plugin: 'measuringtools' }
},
{
path: 'settings/measuringtooltypes',
name: 'measuringtooltypes',
component: () => import('../../views/settings/MeasuringToolTypesList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'measuringtools' }
}
]

View File

@@ -0,0 +1,53 @@
/**
* Network plugin routes
*/
export default [
{
path: 'network',
name: 'network',
component: () => import('../../views/network/NetworkHub.vue'),
meta: { plugin: 'network' }
},
{
path: 'network/new',
name: 'network-new',
component: () => import('../../views/network/NetworkDeviceForm.vue'),
meta: { requiresAuth: true, plugin: 'network' }
},
{
path: 'network/:id',
name: 'network-detail',
component: () => import('../../views/network/NetworkDeviceDetail.vue'),
meta: { plugin: 'network' }
},
{
path: 'network/:id/edit',
name: 'network-edit',
component: () => import('../../views/network/NetworkDeviceForm.vue'),
meta: { requiresAuth: true, plugin: 'network' }
},
{
// Legacy path -> the Networks tab of the hub.
path: 'networks',
redirect: { path: '/network', query: { tab: 'networks' } }
},
{
path: 'networks/:id',
name: 'network-subnet-detail',
component: () => import('../../views/network/SubnetDetail.vue'),
meta: { plugin: 'network' }
},
// Network-specific settings
{
path: 'settings/vlans',
name: 'vlans',
component: () => import('../../views/settings/VLANsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'network' }
},
{
path: 'settings/subnets',
name: 'subnets',
component: () => import('../../views/settings/SubnetsList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'network' }
}
]

View File

@@ -0,0 +1,41 @@
/**
* Notifications plugin routes
*/
export default [
{
path: 'notifications',
name: 'notifications',
component: () => import('../../views/notifications/NotificationsList.vue'),
meta: { plugin: 'notifications' }
},
{
path: 'notifications/new',
name: 'notification-new',
component: () => import('../../views/notifications/NotificationForm.vue'),
meta: { requiresAuth: true, plugin: 'notifications' }
},
{
path: 'settings/notificationtypes',
name: 'notification-types',
component: () => import('../../views/notifications/NotificationTypesList.vue'),
meta: { requiresAuth: true, plugin: 'notifications' }
},
{
path: 'notifications/:id',
name: 'notification-detail',
component: () => import('../../views/notifications/NotificationForm.vue'),
meta: { plugin: 'notifications' }
},
{
path: 'notifications/:id/edit',
name: 'notification-edit',
component: () => import('../../views/notifications/NotificationForm.vue'),
meta: { requiresAuth: true, plugin: 'notifications' }
},
{
path: 'calendar',
name: 'calendar',
component: () => import('../../views/CalendarView.vue'),
meta: { plugin: 'notifications' }
}
]

View File

@@ -0,0 +1,42 @@
/**
* Printers plugin routes
*/
export default [
{
path: 'printers',
name: 'printers',
component: () => import('../../views/printers/PrintersList.vue'),
meta: { plugin: 'printers' }
},
{
path: 'printers/new',
name: 'printer-new',
component: () => import('../../views/printers/PrinterForm.vue'),
meta: { requiresAuth: true, plugin: 'printers' }
},
{
path: 'printers/:id',
name: 'printer-detail',
component: () => import('../../views/printers/PrinterDetail.vue'),
meta: { plugin: 'printers' }
},
{
path: 'printers/:id/edit',
name: 'printer-edit',
component: () => import('../../views/printers/PrinterForm.vue'),
meta: { requiresAuth: true, plugin: 'printers' }
},
// printer-specific settings
{
path: 'settings/modelsupplies',
name: 'model-supplies',
component: () => import('../../views/settings/ModelSuppliesList.vue'),
meta: { requiresAuth: true, plugin: 'printers' }
},
{
path: 'settings/printerdrivers',
name: 'printer-drivers',
component: () => import('../../views/settings/PrinterDriversList.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' }
}
]

View File

@@ -0,0 +1,29 @@
/**
* USB plugin routes
*/
export default [
{
path: 'usb',
name: 'usb',
component: () => import('../../views/usb/USBList.vue'),
meta: { plugin: 'usb' }
},
{
path: 'usb/new',
name: 'usb-new',
component: () => import('../../views/usb/USBForm.vue'),
meta: { requiresAuth: true, plugin: 'usb' }
},
{
path: 'usb/:id',
name: 'usb-detail',
component: () => import('../../views/usb/USBDetail.vue'),
meta: { plugin: 'usb' }
},
{
path: 'usb/:id/edit',
name: 'usb-edit',
component: () => import('../../views/usb/USBForm.vue'),
meta: { requiresAuth: true, plugin: 'usb' }
}
]

View File

@@ -0,0 +1,17 @@
/**
* Warranty plugin routes
*/
export default [
{
path: 'warranties',
name: 'warranties',
component: () => import('../../views/warranty/WarrantiesList.vue'),
meta: { plugin: 'warranty' }
},
{
path: 'reports/warranty',
name: 'warranty-report',
component: () => import('../../views/reports/WarrantyReport.vue'),
meta: { plugin: 'warranty' }
}
]