First Init
Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
@@ -0,0 +1,5 @@
|
||||
Contact: mailto:security@workroot.in
|
||||
Expires: 2027-01-01T00:00:00.000Z
|
||||
Preferred-Languages: en
|
||||
Canonical: https://workroot.in/.well-known/security.txt
|
||||
Policy: https://workroot.in/security-policy
|
||||
|
After Width: | Height: | Size: 495 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||
<rect width="32" height="32" rx="6" fill="#0891b2"/>
|
||||
<path d="M8 12L14 20L8 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 12L18 20L24 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="16" cy="16" r="2" fill="#f59e0b"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#3B82F6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#8B5CF6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1200" height="630" fill="url(#grad1)"/>
|
||||
<circle cx="900" cy="150" r="200" fill="rgba(255,255,255,0.1)"/>
|
||||
<circle cx="300" cy="500" r="150" fill="rgba(255,255,255,0.1)"/>
|
||||
<text x="600" y="280" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">AI Transforming</text>
|
||||
<text x="600" y="360" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">Business</text>
|
||||
<text x="600" y="430" font-family="Arial, sans-serif" font-size="28" fill="rgba(255,255,255,0.9)" text-anchor="middle">Artificial Intelligence & Machine Learning</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 997 B |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
||||
<defs>
|
||||
<linearGradient id="grad2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#FF5D01;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FF1639;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1200" height="630" fill="url(#grad2)"/>
|
||||
<circle cx="200" cy="150" r="100" fill="rgba(255,255,255,0.1)"/>
|
||||
<circle cx="1000" cy="480" r="150" fill="rgba(255,255,255,0.1)"/>
|
||||
<text x="600" y="280" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">Getting Started</text>
|
||||
<text x="600" y="360" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">with Astro</text>
|
||||
<text x="600" y="430" font-family="Arial, sans-serif" font-size="28" fill="rgba(255,255,255,0.9)" text-anchor="middle">Modern Web Framework for Fast Websites</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 992 B |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
||||
<defs>
|
||||
<linearGradient id="grad3" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0EA5E9;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#06B6D4;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1200" height="630" fill="url(#grad3)"/>
|
||||
<circle cx="800" cy="200" r="180" fill="rgba(255,255,255,0.1)"/>
|
||||
<circle cx="400" cy="450" r="120" fill="rgba(255,255,255,0.1)"/>
|
||||
<text x="600" y="280" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">Cloud Migration</text>
|
||||
<text x="600" y="360" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">Guide</text>
|
||||
<text x="600" y="430" font-family="Arial, sans-serif" font-size="28" fill="rgba(255,255,255,0.9)" text-anchor="middle">Best Practices for Moving to the Cloud</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 986 B |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "WorkRoot IT Solutions",
|
||||
"short_name": "WorkRoot",
|
||||
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#0f172a",
|
||||
"theme_color": "#0891b2",
|
||||
"orientation": "any",
|
||||
"scope": "/",
|
||||
"lang": "en-US",
|
||||
"categories": ["business", "productivity"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/og-image.jpg",
|
||||
"sizes": "1200x630",
|
||||
"type": "image/jpeg",
|
||||
"label": "WorkRoot IT Solutions Homepage"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Contact Us",
|
||||
"short_name": "Contact",
|
||||
"description": "Get in touch with our team",
|
||||
"url": "/contact",
|
||||
"icons": [{ "src": "/apple-touch-icon.png", "sizes": "180x180" }]
|
||||
},
|
||||
{
|
||||
"name": "Our Services",
|
||||
"short_name": "Services",
|
||||
"description": "Explore our IT services",
|
||||
"url": "/services",
|
||||
"icons": [{ "src": "/apple-touch-icon.png", "sizes": "180x180" }]
|
||||
}
|
||||
],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,82 @@
|
||||
# Robots.txt for WorkRoot IT Solutions
|
||||
# https://workroot.in
|
||||
|
||||
# Default rules for all bots
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Disallow admin and build directories
|
||||
Disallow: /admin/
|
||||
Disallow: /private/
|
||||
Disallow: /_astro/
|
||||
|
||||
# Allow access to CSS and JS files for proper rendering
|
||||
Allow: /*.css$
|
||||
Allow: /*.js$
|
||||
Allow: /*.webp$
|
||||
Allow: /*.jpg$
|
||||
Allow: /*.png$
|
||||
Allow: /*.svg$
|
||||
|
||||
# Crawl-delay for polite crawling
|
||||
Crawl-delay: 1
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: https://workroot.in/sitemap.xml
|
||||
|
||||
# Traditional Search Engines
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Googlebot-Image
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Slurp
|
||||
Allow: /
|
||||
|
||||
User-agent: DuckDuckBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Baiduspider
|
||||
Allow: /
|
||||
|
||||
User-agent: YandexBot
|
||||
Allow: /
|
||||
|
||||
# Google AdSense Bot
|
||||
User-agent: Mediapartners-Google
|
||||
Allow: /
|
||||
|
||||
# AI Search Engines & LLM Crawlers (GEO Optimization)
|
||||
# OpenAI/ChatGPT
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
# Anthropic/Claude
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
# Perplexity AI
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
# Google Gemini/Bard
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
|
||||
# Common Crawl (used by many AI models)
|
||||
User-agent: CCBot
|
||||
Allow: /
|
||||
|
||||
# Meta AI
|
||||
User-agent: FacebookBot
|
||||
Allow: /
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://workroot.in/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/about/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/services/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/portfolio/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/blog/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/contact/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/privacy/</loc>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://workroot.in/terms/</loc>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* WorkRoot IT Solutions - Service Worker
|
||||
* Implements:
|
||||
* - Cache-first strategy for static assets
|
||||
* - Stale-while-revalidate for HTML pages
|
||||
* - Offline fallback page
|
||||
* - Background sync for form submissions
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = 'v1';
|
||||
const STATIC_CACHE = `workroot-static-${CACHE_VERSION}`;
|
||||
const PAGE_CACHE = `workroot-pages-${CACHE_VERSION}`;
|
||||
const SYNC_QUEUE = 'workroot-sync-queue';
|
||||
|
||||
// Static assets to pre-cache on install
|
||||
const PRECACHE_ASSETS = [
|
||||
'/',
|
||||
'/offline',
|
||||
'/manifest.json',
|
||||
'/favicon.svg',
|
||||
'/apple-touch-icon.png',
|
||||
];
|
||||
|
||||
// Page routes to cache with stale-while-revalidate
|
||||
const PAGE_ROUTES = [
|
||||
'/',
|
||||
'/about',
|
||||
'/services',
|
||||
'/portfolio',
|
||||
'/contact',
|
||||
'/blog',
|
||||
'/privacy',
|
||||
'/terms',
|
||||
];
|
||||
|
||||
// ─── Install Event ────────────────────────────────────────────────────────────
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE).then((cache) => {
|
||||
return cache.addAll(PRECACHE_ASSETS);
|
||||
}).then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Activate Event ───────────────────────────────────────────────────────────
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
// Delete old caches
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames
|
||||
.filter((name) => name.startsWith('workroot-') && name !== STATIC_CACHE && name !== PAGE_CACHE)
|
||||
.map((name) => caches.delete(name))
|
||||
);
|
||||
}),
|
||||
// Take control immediately
|
||||
self.clients.claim(),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Fetch Event ──────────────────────────────────────────────────────────────
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Only handle same-origin requests
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
// Skip non-GET requests (POST etc. handled via background sync)
|
||||
if (request.method !== 'GET') return;
|
||||
|
||||
// Skip API routes — always go to network
|
||||
if (url.pathname.startsWith('/api/')) return;
|
||||
|
||||
// Static assets: cache-first
|
||||
if (isStaticAsset(url.pathname)) {
|
||||
event.respondWith(cacheFirst(request, STATIC_CACHE));
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML pages: stale-while-revalidate with offline fallback
|
||||
if (request.headers.get('Accept')?.includes('text/html')) {
|
||||
event.respondWith(staleWhileRevalidate(request));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Background Sync Event ───────────────────────────────────────────────────
|
||||
|
||||
self.addEventListener('sync', (event) => {
|
||||
if (event.tag === 'form-sync') {
|
||||
event.waitUntil(processSyncQueue());
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Push Event (for future notifications) ──────────────────────────────────
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
if (!event.data) return;
|
||||
|
||||
const data = event.data.json();
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title || 'WorkRoot', {
|
||||
body: data.body || '',
|
||||
icon: '/apple-touch-icon.png',
|
||||
badge: '/favicon.svg',
|
||||
data: data.url ? { url: data.url } : undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
if (event.notification.data?.url) {
|
||||
event.waitUntil(clients.openWindow(event.notification.data.url));
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Strategy Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
function isStaticAsset(pathname) {
|
||||
return (
|
||||
pathname.startsWith('/_assets/') ||
|
||||
/\.(js|css|woff2?|ttf|otf|eot|png|jpg|jpeg|gif|webp|avif|svg|ico)$/i.test(pathname)
|
||||
);
|
||||
}
|
||||
|
||||
async function cacheFirst(request, cacheName) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const cached = await cache.match(request);
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch {
|
||||
return new Response('Asset unavailable offline', { status: 503 });
|
||||
}
|
||||
}
|
||||
|
||||
async function staleWhileRevalidate(request) {
|
||||
const cache = await caches.open(PAGE_CACHE);
|
||||
const cached = await cache.match(request);
|
||||
|
||||
const fetchPromise = fetch(request)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
if (cached) {
|
||||
// Serve cached immediately, update in background
|
||||
fetchPromise.catch(() => {});
|
||||
return cached;
|
||||
}
|
||||
|
||||
// No cache — await network
|
||||
const response = await fetchPromise;
|
||||
if (response) return response;
|
||||
|
||||
// Network failed and no cache — serve offline fallback
|
||||
const offlineFallback = await cache.match('/offline') || await caches.match('/offline');
|
||||
if (offlineFallback) return offlineFallback;
|
||||
|
||||
return new Response(
|
||||
'<html><body><h1>You are offline</h1><p>Please check your connection.</p></body></html>',
|
||||
{ status: 503, headers: { 'Content-Type': 'text/html' } }
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Background Sync Queue ────────────────────────────────────────────────────
|
||||
|
||||
async function processSyncQueue() {
|
||||
const db = await openDB();
|
||||
const queue = await getAllFromDB(db, SYNC_QUEUE);
|
||||
|
||||
for (const item of queue) {
|
||||
try {
|
||||
const response = await fetch(item.url, {
|
||||
method: item.method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(item.body),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await deleteFromDB(db, SYNC_QUEUE, item.id);
|
||||
// Notify any open clients of success
|
||||
const clients = await self.clients.matchAll();
|
||||
clients.forEach((client) => {
|
||||
client.postMessage({ type: 'SYNC_SUCCESS', id: item.id, formType: item.formType });
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Leave in queue for next sync attempt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── IndexedDB Helpers ────────────────────────────────────────────────────────
|
||||
|
||||
function openDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('workroot-pwa', 1);
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains(SYNC_QUEUE)) {
|
||||
const store = db.createObjectStore(SYNC_QUEUE, { keyPath: 'id', autoIncrement: true });
|
||||
store.createIndex('timestamp', 'timestamp', { unique: false });
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => resolve(event.target.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
function getAllFromDB(db, storeName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
const request = store.getAll();
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteFromDB(db, storeName, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
const request = store.delete(id);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||