Initialisation depot
@@ -0,0 +1,423 @@
|
|||||||
|
# Configuration HTTPS pour naval.lan avec Traefik (Kubernetes)
|
||||||
|
|
||||||
|
Ce guide explique comment configurer HTTPS pour votre domaine local `naval.lan` en utilisant Traefik dans Kubernetes sans avertissements de certificat sur les clients Windows et Linux.
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Pour éviter les avertissements de certificat auto-signé, vous devez :
|
||||||
|
1. Créer votre propre Autorité de Certification (CA)
|
||||||
|
2. Générer des certificats SSL signés par votre CA
|
||||||
|
3. Configurer Traefik pour utiliser ces certificats
|
||||||
|
4. Installer le certificat CA sur toutes les machines clientes
|
||||||
|
|
||||||
|
## Partie 1 : Créer votre propre Autorité de Certification
|
||||||
|
|
||||||
|
### 1.1. Générer la clé privée et le certificat CA
|
||||||
|
|
||||||
|
Sur votre serveur ou poste de travail Linux :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer un répertoire pour les certificats
|
||||||
|
mkdir -p ~/certs/naval-ca
|
||||||
|
cd ~/certs/naval-ca
|
||||||
|
|
||||||
|
# Générer la clé privée CA (RSA 4096 bits)
|
||||||
|
openssl genrsa -out ca-key.pem 4096
|
||||||
|
|
||||||
|
# Générer le certificat CA (valide 10 ans)
|
||||||
|
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \
|
||||||
|
-subj "/C=FR/ST=Region/L=Ville/O=Naval Local CA/OU=IT/CN=Naval Local Root CA"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important** : Gardez `ca-key.pem` en sécurité ! C'est votre clé privée CA racine.
|
||||||
|
|
||||||
|
## Partie 2 : Générer le certificat SSL pour naval.lan
|
||||||
|
|
||||||
|
### 2.1. Créer le fichier de configuration OpenSSL
|
||||||
|
|
||||||
|
Créez un fichier nommé `naval-lan.conf` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > naval-lan.conf <<EOF
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
req_extensions = req_ext
|
||||||
|
distinguished_name = dn
|
||||||
|
|
||||||
|
[dn]
|
||||||
|
C = FR
|
||||||
|
ST = Region
|
||||||
|
L = Ville
|
||||||
|
O = Naval Local
|
||||||
|
OU = Département IT
|
||||||
|
CN = *.naval.lan
|
||||||
|
|
||||||
|
[req_ext]
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = naval.lan
|
||||||
|
DNS.2 = *.naval.lan
|
||||||
|
DNS.3 = localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2. Générer la demande de signature de certificat (CSR)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Générer la clé privée pour naval.lan
|
||||||
|
openssl genrsa -out naval-lan-key.pem 2048
|
||||||
|
|
||||||
|
# Générer le CSR
|
||||||
|
openssl req -new -key naval-lan-key.pem -out naval-lan.csr -config naval-lan.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3. Signer le certificat avec votre CA
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Signer le certificat (valide 2 ans)
|
||||||
|
openssl x509 -req -in naval-lan.csr -CA ca-cert.pem -CAkey ca-key.pem \
|
||||||
|
-CAcreateserial -out naval-lan-cert.pem -days 730 \
|
||||||
|
-extensions req_ext -extfile naval-lan.conf
|
||||||
|
|
||||||
|
# Vérifier le certificat
|
||||||
|
openssl x509 -in naval-lan-cert.pem -text -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partie 3 : Configurer Traefik dans Kubernetes
|
||||||
|
|
||||||
|
### 3.1. Créer un Secret Kubernetes avec les certificats
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer un namespace pour Traefik (s'il n'existe pas)
|
||||||
|
kubectl create namespace traefik --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Créer le secret avec vos certificats
|
||||||
|
kubectl create secret tls naval-lan-tls \
|
||||||
|
--cert=naval-lan-cert.pem \
|
||||||
|
--key=naval-lan-key.pem \
|
||||||
|
-n traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Mettre à jour la configuration Traefik
|
||||||
|
|
||||||
|
Créez ou mettez à jour votre fichier de valeurs Helm Traefik (`traefik-values.yaml`) :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# traefik-values.yaml
|
||||||
|
additionalArguments:
|
||||||
|
- "--providers.kubernetescrd"
|
||||||
|
- "--entrypoints.websecure.http.tls=true"
|
||||||
|
- "--entrypoints.web.address=:80"
|
||||||
|
- "--entrypoints.websecure.address=:443"
|
||||||
|
|
||||||
|
ports:
|
||||||
|
web:
|
||||||
|
port: 80
|
||||||
|
exposedPort: 80
|
||||||
|
websecure:
|
||||||
|
port: 443
|
||||||
|
exposedPort: 443
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Monter le certificat TLS
|
||||||
|
volumes:
|
||||||
|
- name: naval-lan-tls
|
||||||
|
mountPath: "/certs"
|
||||||
|
type: secret
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3. Créer l'IngressRoute pour vos services
|
||||||
|
|
||||||
|
Exemple de configuration IngressRoute :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: myapp-ingressroute
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`myapp.naval.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: myapp-service
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: naval-lan-tls
|
||||||
|
---
|
||||||
|
# Optionnel : redirection HTTP vers HTTPS
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: myapp-http-redirect
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: Host(`myapp.naval.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: myapp-service
|
||||||
|
port: 80
|
||||||
|
middlewares:
|
||||||
|
- name: redirect-to-https
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: redirect-to-https
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4. Appliquer la configuration Traefik
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Si vous utilisez Helm
|
||||||
|
helm upgrade --install traefik traefik/traefik \
|
||||||
|
-n traefik \
|
||||||
|
-f traefik-values.yaml
|
||||||
|
|
||||||
|
# Appliquer l'IngressRoute
|
||||||
|
kubectl apply -f ingressroute.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partie 4 : Installer le certificat CA sur les machines clientes
|
||||||
|
|
||||||
|
### 4.1. Clients Linux
|
||||||
|
|
||||||
|
#### Ubuntu/Debian :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier ca-cert.pem vers votre client Linux
|
||||||
|
sudo cp ca-cert.pem /usr/local/share/ca-certificates/naval-ca.crt
|
||||||
|
|
||||||
|
# Mettre à jour les certificats CA
|
||||||
|
sudo update-ca-certificates
|
||||||
|
|
||||||
|
# Vérifier
|
||||||
|
openssl s_client -connect myapp.naval.lan:443 -CAfile /usr/local/share/ca-certificates/naval-ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### RHEL/CentOS/Fedora :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier ca-cert.pem vers votre client Linux
|
||||||
|
sudo cp ca-cert.pem /etc/pki/ca-trust/source/anchors/naval-ca.crt
|
||||||
|
|
||||||
|
# Mettre à jour les certificats CA
|
||||||
|
sudo update-ca-trust
|
||||||
|
|
||||||
|
# Vérifier
|
||||||
|
openssl s_client -connect myapp.naval.lan:443
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pour Firefox (utilise son propre magasin de certificats) :
|
||||||
|
|
||||||
|
1. Ouvrir Firefox
|
||||||
|
2. Aller dans **Paramètres** → **Vie privée et sécurité**
|
||||||
|
3. Descendre jusqu'à **Certificats** → Cliquer sur **Afficher les certificats**
|
||||||
|
4. Aller dans l'onglet **Autorités**
|
||||||
|
5. Cliquer sur **Importer**
|
||||||
|
6. Sélectionner `ca-cert.pem`
|
||||||
|
7. Cocher "Confirmer cette AC pour identifier des sites web"
|
||||||
|
8. Cliquer sur OK
|
||||||
|
|
||||||
|
### 4.2. Clients Windows
|
||||||
|
|
||||||
|
#### Méthode 1 : Utilisation de MMC (Console de gestion Microsoft)
|
||||||
|
|
||||||
|
1. Copier `ca-cert.pem` sur votre machine Windows
|
||||||
|
2. Le renommer en `ca-cert.crt` (optionnel, pour une meilleure reconnaissance)
|
||||||
|
3. Faire un clic droit sur `ca-cert.crt` → **Installer le certificat**
|
||||||
|
4. Choisir **Ordinateur local** (nécessite les droits administrateur)
|
||||||
|
5. Cliquer sur **Suivant**
|
||||||
|
6. Sélectionner **Placer tous les certificats dans le magasin suivant**
|
||||||
|
7. Cliquer sur **Parcourir** → Sélectionner **Autorités de certification racines de confiance**
|
||||||
|
8. Cliquer sur **Suivant** → **Terminer**
|
||||||
|
9. Cliquer sur **Oui** à l'avertissement de sécurité
|
||||||
|
|
||||||
|
#### Méthode 2 : Utilisation de la ligne de commande (PowerShell Administrateur)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Importer le certificat dans le magasin CA racine de confiance
|
||||||
|
Import-Certificate -FilePath "C:\chemin\vers\ca-cert.pem" -CertStoreLocation Cert:\LocalMachine\Root
|
||||||
|
|
||||||
|
# Vérifier
|
||||||
|
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object {$_.Subject -like "*Naval*"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Méthode 3 : Utilisation de certutil (Invite de commandes en Administrateur)
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
certutil -addstore -f "ROOT" ca-cert.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pour Firefox sur Windows :
|
||||||
|
|
||||||
|
Mêmes étapes que Firefox sur Linux ci-dessus.
|
||||||
|
|
||||||
|
### 4.3. Vérifier l'installation
|
||||||
|
|
||||||
|
#### Linux :
|
||||||
|
```bash
|
||||||
|
# Tester avec curl
|
||||||
|
curl -v https://myapp.naval.lan
|
||||||
|
|
||||||
|
# Tester avec openssl
|
||||||
|
openssl s_client -connect myapp.naval.lan:443 -showcerts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows :
|
||||||
|
```powershell
|
||||||
|
# Tester avec PowerShell
|
||||||
|
Invoke-WebRequest -Uri https://myapp.naval.lan
|
||||||
|
|
||||||
|
# Ou utiliser le navigateur
|
||||||
|
# Naviguer vers https://myapp.naval.lan
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partie 5 : Configuration DNS
|
||||||
|
|
||||||
|
Assurez-vous que vos clients peuvent résoudre les domaines `naval.lan` :
|
||||||
|
|
||||||
|
### 5.1. Option 1 : Serveur DNS local (Recommandé)
|
||||||
|
|
||||||
|
Configurez un serveur DNS local (dnsmasq, Pi-hole, ou DNS Windows) avec :
|
||||||
|
```
|
||||||
|
*.naval.lan → [IP Traefik Ingress]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2. Option 2 : Fichier hosts
|
||||||
|
|
||||||
|
#### Linux : `/etc/hosts`
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows : `C:\Windows\System32\drivers\etc\hosts` (en tant qu'Administrateur)
|
||||||
|
```
|
||||||
|
notepad C:\Windows\System32\drivers\etc\hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
Ajouter les entrées :
|
||||||
|
```
|
||||||
|
192.168.1.100 myapp.naval.lan
|
||||||
|
192.168.1.100 dashboard.naval.lan
|
||||||
|
```
|
||||||
|
|
||||||
|
Remplacez `192.168.1.100` par l'IP de votre ingress Traefik.
|
||||||
|
|
||||||
|
## Partie 6 : Renouvellement du certificat
|
||||||
|
|
||||||
|
Vos certificats expireront. Pour les renouveler :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/certs/naval-ca
|
||||||
|
|
||||||
|
# Générer un nouveau CSR (ou réutiliser la clé existante)
|
||||||
|
openssl req -new -key naval-lan-key.pem -out naval-lan-new.csr -config naval-lan.conf
|
||||||
|
|
||||||
|
# Signer avec la CA
|
||||||
|
openssl x509 -req -in naval-lan-new.csr -CA ca-cert.pem -CAkey ca-key.pem \
|
||||||
|
-CAcreateserial -out naval-lan-cert-new.pem -days 730 \
|
||||||
|
-extensions req_ext -extfile naval-lan.conf
|
||||||
|
|
||||||
|
# Mettre à jour le secret Kubernetes
|
||||||
|
kubectl create secret tls naval-lan-tls \
|
||||||
|
--cert=naval-lan-cert-new.pem \
|
||||||
|
--key=naval-lan-key.pem \
|
||||||
|
-n traefik \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Redémarrer les pods Traefik pour recharger le certificat
|
||||||
|
kubectl rollout restart deployment traefik -n traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### Certificat non approuvé après l'installation
|
||||||
|
|
||||||
|
- **Vider le cache du navigateur** : Certains navigateurs mettent en cache la validation des certificats
|
||||||
|
- **Redémarrer le navigateur** : Nécessaire pour Chrome/Edge sur Windows
|
||||||
|
- **Vérifier la chaîne de certificats** : `openssl s_client -connect myapp.naval.lan:443 -showcerts`
|
||||||
|
|
||||||
|
### Erreur "NET::ERR_CERT_AUTHORITY_INVALID"
|
||||||
|
|
||||||
|
- Vérifier que le certificat CA est dans le bon magasin
|
||||||
|
- Sur Windows, assurez-vous qu'il est dans "Autorités de certification racines de confiance", pas "Intermédiaire"
|
||||||
|
- Vérifier que les noms alternatifs du sujet du certificat incluent votre domaine
|
||||||
|
|
||||||
|
### Firefox affiche toujours un avertissement
|
||||||
|
|
||||||
|
- Firefox utilise son propre magasin de certificats sur toutes les plateformes
|
||||||
|
- Vous devez importer le certificat CA directement dans Firefox
|
||||||
|
|
||||||
|
### Certificat expiré
|
||||||
|
|
||||||
|
- Vérifier la validité du certificat : `openssl x509 -in naval-lan-cert.pem -noout -dates`
|
||||||
|
- Suivre les étapes de renouvellement de la Partie 6
|
||||||
|
|
||||||
|
## Considérations de sécurité
|
||||||
|
|
||||||
|
1. **Protéger votre clé privée CA** (`ca-key.pem`) :
|
||||||
|
- Stockez-la en sécurité
|
||||||
|
- Envisagez de la chiffrer avec une phrase secrète
|
||||||
|
- Conservez des sauvegardes dans des emplacements sécurisés
|
||||||
|
|
||||||
|
2. **Période de validité du certificat** :
|
||||||
|
- Ne la rendez pas trop longue (2 ans maximum recommandé)
|
||||||
|
- Configurez des rappels de calendrier pour le renouvellement
|
||||||
|
|
||||||
|
3. **Contrôle d'accès** :
|
||||||
|
- N'installez le certificat CA que sur les machines que vous contrôlez
|
||||||
|
- Ne partagez pas votre clé privée CA
|
||||||
|
|
||||||
|
4. **Isolation réseau** :
|
||||||
|
- Gardez votre domaine `.lan` isolé d'Internet
|
||||||
|
- Utilisez des règles de pare-feu pour empêcher l'accès externe
|
||||||
|
|
||||||
|
## Commandes de référence rapide
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier les détails du certificat
|
||||||
|
openssl x509 -in naval-lan-cert.pem -text -noout
|
||||||
|
|
||||||
|
# Tester la connexion HTTPS
|
||||||
|
curl -v https://myapp.naval.lan
|
||||||
|
|
||||||
|
# Voir la CA installée sur Linux
|
||||||
|
awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt | grep -i naval
|
||||||
|
|
||||||
|
# Voir la CA installée sur Windows (PowerShell)
|
||||||
|
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object {$_.Subject -like "*Naval*"}
|
||||||
|
|
||||||
|
# Obtenir l'IP ingress Traefik
|
||||||
|
kubectl get svc -n traefik traefik -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ressources supplémentaires
|
||||||
|
|
||||||
|
- [Documentation Traefik](https://doc.traefik.io/traefik/)
|
||||||
|
- [Documentation OpenSSL](https://www.openssl.org/docs/)
|
||||||
|
- [Secrets TLS Kubernetes](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Créé** : 9 janvier 2026
|
||||||
|
**Dernière mise à jour** : 9 janvier 2026
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
# Setting Up HTTPS for naval.lan with Traefik (Kubernetes)
|
||||||
|
|
||||||
|
This guide explains how to set up HTTPS for your local domain `naval.lan` using Traefik in Kubernetes without certificate warnings on Windows and Linux clients.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
To avoid self-signed certificate warnings, you need to:
|
||||||
|
1. Create your own Certificate Authority (CA)
|
||||||
|
2. Generate SSL certificates signed by your CA
|
||||||
|
3. Configure Traefik to use these certificates
|
||||||
|
4. Install the CA certificate on all client machines
|
||||||
|
|
||||||
|
## Part 1: Create Your Own Certificate Authority
|
||||||
|
|
||||||
|
### 1.1. Generate CA Private Key and Certificate
|
||||||
|
|
||||||
|
On your Linux server or workstation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a directory for certificates
|
||||||
|
mkdir -p ~/certs/naval-ca
|
||||||
|
cd ~/certs/naval-ca
|
||||||
|
|
||||||
|
# Generate CA private key (4096-bit RSA)
|
||||||
|
openssl genrsa -out ca-key.pem 4096
|
||||||
|
|
||||||
|
# Generate CA certificate (valid for 10 years)
|
||||||
|
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \
|
||||||
|
-subj "/C=US/ST=State/L=City/O=Naval Local CA/OU=IT/CN=Naval Local Root CA"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Keep `ca-key.pem` secure! This is your root CA private key.
|
||||||
|
|
||||||
|
## Part 2: Generate SSL Certificate for naval.lan
|
||||||
|
|
||||||
|
### 2.1. Create OpenSSL Configuration File
|
||||||
|
|
||||||
|
Create a file named `naval-lan.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > naval-lan.conf <<EOF
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
req_extensions = req_ext
|
||||||
|
distinguished_name = dn
|
||||||
|
|
||||||
|
[dn]
|
||||||
|
C = US
|
||||||
|
ST = State
|
||||||
|
L = City
|
||||||
|
O = Naval Local
|
||||||
|
OU = IT Department
|
||||||
|
CN = *.naval.lan
|
||||||
|
|
||||||
|
[req_ext]
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = naval.lan
|
||||||
|
DNS.2 = *.naval.lan
|
||||||
|
DNS.3 = localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2. Generate Certificate Signing Request (CSR)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate private key for naval.lan
|
||||||
|
openssl genrsa -out naval-lan-key.pem 2048
|
||||||
|
|
||||||
|
# Generate CSR
|
||||||
|
openssl req -new -key naval-lan-key.pem -out naval-lan.csr -config naval-lan.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3. Sign the Certificate with Your CA
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sign the certificate (valid for 2 years)
|
||||||
|
openssl x509 -req -in naval-lan.csr -CA ca-cert.pem -CAkey ca-key.pem \
|
||||||
|
-CAcreateserial -out naval-lan-cert.pem -days 730 \
|
||||||
|
-extensions req_ext -extfile naval-lan.conf
|
||||||
|
|
||||||
|
# Verify the certificate
|
||||||
|
openssl x509 -in naval-lan-cert.pem -text -noout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Part 3: Configure Traefik in Kubernetes
|
||||||
|
|
||||||
|
### 3.1. Create Kubernetes Secret with Certificates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a namespace for Traefik (if not exists)
|
||||||
|
kubectl create namespace traefik --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Create secret with your certificates
|
||||||
|
kubectl create secret tls naval-lan-tls \
|
||||||
|
--cert=naval-lan-cert.pem \
|
||||||
|
--key=naval-lan-key.pem \
|
||||||
|
-n traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Update Traefik Configuration
|
||||||
|
|
||||||
|
Create or update your Traefik Helm values file (`traefik-values.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# traefik-values.yaml
|
||||||
|
additionalArguments:
|
||||||
|
- "--providers.kubernetescrd"
|
||||||
|
- "--entrypoints.websecure.http.tls=true"
|
||||||
|
- "--entrypoints.web.address=:80"
|
||||||
|
- "--entrypoints.websecure.address=:443"
|
||||||
|
|
||||||
|
ports:
|
||||||
|
web:
|
||||||
|
port: 80
|
||||||
|
exposedPort: 80
|
||||||
|
websecure:
|
||||||
|
port: 443
|
||||||
|
exposedPort: 443
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Mount the TLS certificate
|
||||||
|
volumes:
|
||||||
|
- name: naval-lan-tls
|
||||||
|
mountPath: "/certs"
|
||||||
|
type: secret
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3. Create IngressRoute for Your Services
|
||||||
|
|
||||||
|
Example IngressRoute configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: myapp-ingressroute
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`myapp.naval.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: myapp-service
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: naval-lan-tls
|
||||||
|
---
|
||||||
|
# Optional: HTTP to HTTPS redirect
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: myapp-http-redirect
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: Host(`myapp.naval.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: myapp-service
|
||||||
|
port: 80
|
||||||
|
middlewares:
|
||||||
|
- name: redirect-to-https
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: redirect-to-https
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4. Apply Traefik Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If using Helm
|
||||||
|
helm upgrade --install traefik traefik/traefik \
|
||||||
|
-n traefik \
|
||||||
|
-f traefik-values.yaml
|
||||||
|
|
||||||
|
# Apply IngressRoute
|
||||||
|
kubectl apply -f ingressroute.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Part 4: Install CA Certificate on Client Machines
|
||||||
|
|
||||||
|
### 4.1. Linux Clients
|
||||||
|
|
||||||
|
#### Ubuntu/Debian:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy ca-cert.pem to your Linux client
|
||||||
|
sudo cp ca-cert.pem /usr/local/share/ca-certificates/naval-ca.crt
|
||||||
|
|
||||||
|
# Update CA certificates
|
||||||
|
sudo update-ca-certificates
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
openssl s_client -connect myapp.naval.lan:443 -CAfile /usr/local/share/ca-certificates/naval-ca.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### RHEL/CentOS/Fedora:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy ca-cert.pem to your Linux client
|
||||||
|
sudo cp ca-cert.pem /etc/pki/ca-trust/source/anchors/naval-ca.crt
|
||||||
|
|
||||||
|
# Update CA certificates
|
||||||
|
sudo update-ca-trust
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
openssl s_client -connect myapp.naval.lan:443
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Firefox (uses its own certificate store):
|
||||||
|
|
||||||
|
1. Open Firefox
|
||||||
|
2. Go to **Settings** → **Privacy & Security**
|
||||||
|
3. Scroll to **Certificates** → Click **View Certificates**
|
||||||
|
4. Go to **Authorities** tab
|
||||||
|
5. Click **Import**
|
||||||
|
6. Select `ca-cert.pem`
|
||||||
|
7. Check "Trust this CA to identify websites"
|
||||||
|
8. Click OK
|
||||||
|
|
||||||
|
### 4.2. Windows Clients
|
||||||
|
|
||||||
|
#### Method 1: Using MMC (Microsoft Management Console)
|
||||||
|
|
||||||
|
1. Copy `ca-cert.pem` to your Windows machine
|
||||||
|
2. Rename it to `ca-cert.crt` (optional, for easier recognition)
|
||||||
|
3. Right-click on `ca-cert.crt` → **Install Certificate**
|
||||||
|
4. Choose **Local Machine** (requires admin rights)
|
||||||
|
5. Click **Next**
|
||||||
|
6. Select **Place all certificates in the following store**
|
||||||
|
7. Click **Browse** → Select **Trusted Root Certification Authorities**
|
||||||
|
8. Click **Next** → **Finish**
|
||||||
|
9. Click **Yes** on the security warning
|
||||||
|
|
||||||
|
#### Method 2: Using Command Line (Admin PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Import certificate to Trusted Root CA store
|
||||||
|
Import-Certificate -FilePath "C:\path\to\ca-cert.pem" -CertStoreLocation Cert:\LocalMachine\Root
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object {$_.Subject -like "*Naval*"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 3: Using certutil (Command Prompt as Admin)
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
certutil -addstore -f "ROOT" ca-cert.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Firefox on Windows:
|
||||||
|
|
||||||
|
Same steps as Linux Firefox above.
|
||||||
|
|
||||||
|
### 4.3. Verify Installation
|
||||||
|
|
||||||
|
#### Linux:
|
||||||
|
```bash
|
||||||
|
# Test with curl
|
||||||
|
curl -v https://myapp.naval.lan
|
||||||
|
|
||||||
|
# Test with openssl
|
||||||
|
openssl s_client -connect myapp.naval.lan:443 -showcerts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows:
|
||||||
|
```powershell
|
||||||
|
# Test with PowerShell
|
||||||
|
Invoke-WebRequest -Uri https://myapp.naval.lan
|
||||||
|
|
||||||
|
# Or use browser
|
||||||
|
# Navigate to https://myapp.naval.lan
|
||||||
|
```
|
||||||
|
|
||||||
|
## Part 5: DNS Configuration
|
||||||
|
|
||||||
|
Ensure your clients can resolve `naval.lan` domains:
|
||||||
|
|
||||||
|
### 5.1. Option 1: Local DNS Server (Recommended)
|
||||||
|
|
||||||
|
Set up a local DNS server (dnsmasq, Pi-hole, or Windows DNS) with:
|
||||||
|
```
|
||||||
|
*.naval.lan → [Traefik Ingress IP]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2. Option 2: Hosts File
|
||||||
|
|
||||||
|
#### Linux: `/etc/hosts`
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows: `C:\Windows\System32\drivers\etc\hosts` (as Administrator)
|
||||||
|
```
|
||||||
|
notepad C:\Windows\System32\drivers\etc\hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
Add entries:
|
||||||
|
```
|
||||||
|
192.168.1.100 myapp.naval.lan
|
||||||
|
192.168.1.100 dashboard.naval.lan
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `192.168.1.100` with your Traefik ingress IP.
|
||||||
|
|
||||||
|
## Part 6: Certificate Renewal
|
||||||
|
|
||||||
|
Your certificates will expire. To renew:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/certs/naval-ca
|
||||||
|
|
||||||
|
# Generate new CSR (or reuse existing key)
|
||||||
|
openssl req -new -key naval-lan-key.pem -out naval-lan-new.csr -config naval-lan.conf
|
||||||
|
|
||||||
|
# Sign with CA
|
||||||
|
openssl x509 -req -in naval-lan-new.csr -CA ca-cert.pem -CAkey ca-key.pem \
|
||||||
|
-CAcreateserial -out naval-lan-cert-new.pem -days 730 \
|
||||||
|
-extensions req_ext -extfile naval-lan.conf
|
||||||
|
|
||||||
|
# Update Kubernetes secret
|
||||||
|
kubectl create secret tls naval-lan-tls \
|
||||||
|
--cert=naval-lan-cert-new.pem \
|
||||||
|
--key=naval-lan-key.pem \
|
||||||
|
-n traefik \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Restart Traefik pods to reload certificate
|
||||||
|
kubectl rollout restart deployment traefik -n traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Certificate not trusted after installation
|
||||||
|
|
||||||
|
- **Clear browser cache**: Some browsers cache certificate validation
|
||||||
|
- **Restart browser**: Required for Chrome/Edge on Windows
|
||||||
|
- **Check certificate chain**: `openssl s_client -connect myapp.naval.lan:443 -showcerts`
|
||||||
|
|
||||||
|
### "NET::ERR_CERT_AUTHORITY_INVALID" error
|
||||||
|
|
||||||
|
- Verify CA certificate is in the correct store
|
||||||
|
- On Windows, ensure it's in "Trusted Root Certification Authorities", not "Intermediate"
|
||||||
|
- Check that the certificate's Subject Alternative Names include your domain
|
||||||
|
|
||||||
|
### Firefox still shows warning
|
||||||
|
|
||||||
|
- Firefox uses its own certificate store on all platforms
|
||||||
|
- Must import CA certificate directly into Firefox
|
||||||
|
|
||||||
|
### Certificate expired
|
||||||
|
|
||||||
|
- Check certificate validity: `openssl x509 -in naval-lan-cert.pem -noout -dates`
|
||||||
|
- Follow renewal steps in Part 6
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Protect your CA private key** (`ca-key.pem`):
|
||||||
|
- Store it securely
|
||||||
|
- Consider encrypting it with a passphrase
|
||||||
|
- Keep backups in secure locations
|
||||||
|
|
||||||
|
2. **Certificate validity period**:
|
||||||
|
- Don't make it too long (2 years max recommended)
|
||||||
|
- Set up calendar reminders for renewal
|
||||||
|
|
||||||
|
3. **Access control**:
|
||||||
|
- Only install the CA certificate on machines you control
|
||||||
|
- Don't share your CA private key
|
||||||
|
|
||||||
|
4. **Network isolation**:
|
||||||
|
- Keep your `.lan` domain isolated from the internet
|
||||||
|
- Use firewall rules to prevent external access
|
||||||
|
|
||||||
|
## Quick Reference Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check certificate details
|
||||||
|
openssl x509 -in naval-lan-cert.pem -text -noout
|
||||||
|
|
||||||
|
# Test HTTPS connection
|
||||||
|
curl -v https://myapp.naval.lan
|
||||||
|
|
||||||
|
# View installed CA on Linux
|
||||||
|
awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt | grep -i naval
|
||||||
|
|
||||||
|
# View installed CA on Windows (PowerShell)
|
||||||
|
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object {$_.Subject -like "*Naval*"}
|
||||||
|
|
||||||
|
# Get Traefik ingress IP
|
||||||
|
kubectl get svc -n traefik traefik -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||||
|
- [OpenSSL Documentation](https://www.openssl.org/docs/)
|
||||||
|
- [Kubernetes TLS Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Created**: January 9, 2026
|
||||||
|
**Last Updated**: January 9, 2026
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
(function(w,d,c,k,a,b,t,e,h,s) {
|
||||||
|
var cs = d.currentScript;
|
||||||
|
if (cs) {
|
||||||
|
var uo = cs.getAttribute('data-ueto');
|
||||||
|
if (uo && w[uo] && typeof w[uo].setUserSignals === 'function') {
|
||||||
|
w[uo].setUserSignals({'ea': c, 'kc': k, 'at': a, 'bi': b, 'pt': t, 'ec': e, 'ah': h, 'sb': s});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(window, document, false, false, true, false, false, true, true, false);
|
||||||
|
After Width: | Height: | Size: 638 B |
|
After Width: | Height: | Size: 554 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
(function(){"use strict";function a(a){const b=new RegExp("(^|;)\\s*"+a+"\\s*=\\s*([^;]*)\\s*(;|$)").exec(window.document.cookie);return b?decodeURIComponent(b[2]):null}function b({key:a,expires:b,domain:c,value:d}){const e=c&&""!==c?";domain="+encodeURIComponent(c):"",f=b&&"number"==typeof b?";expires="+new Date(new Date().getTime()+b).toUTCString():"";document.cookie=a+"="+encodeURIComponent(d)+f+";path=/"+e+";SameSite=None;Secure"}function c(a){return window.localStorage.getItem(a)}function d(a,b){window.localStorage.setItem(a,b)}function e(a){return window.sessionStorage.getItem(a)}function f(a,b){window.sessionStorage.setItem(a,b)}function g(a){window.localStorage.removeItem(a),window.sessionStorage.removeItem(a)}function h(b){const d=e(b);return d?d:a(b)||c(b)}function i({key:a,value:c,expires:e,domain:g}){a&&c&&(b({key:a,value:c,expires:e,domain:g}),d(a,c),f(a,c))}function j(a){try{return null===a?null:JSON.parse(a)}catch{return null}}function k(){const a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".length;let b="";for(let c=0;20>c;c++)b+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random()*a));return b}function l(a){return"function"==typeof a?a:()=>{}}function m(a){return!a?.url||!a?.type}function n(a,b){return a.reduce((a,c)=>{const d=c.hash?c.hash:b;return d?(a[d]||(a[d]=[]),a[d].push(c),a):a},{})}function o(){const a=window[F].find(a=>a.eventType&&"init"===a.eventType);return a||null}function p(a=null){return a?.value?a.value:null}function q(a=null){return a?.dc&&["ams","us","phx","sin","ash","asia"].includes(a.dc)?a.dc:"ams"}function r(a){try{const b=JSON.parse(a),c=Array.isArray(b)?b:[];for(const a of c){const b=m(a);if(!b)if("img"===(a.type+"").toLowerCase()){const b=new Image;b.src=a.url,b.setAttribute("width","1"),b.setAttribute("height","1"),b.setAttribute("scrolling","no"),b.setAttribute("frameBorder","0"),b.setAttribute("style","display:none"),document.body.appendChild(b)}else if("iframe"===(a.type+"").toLowerCase()){const b=document.createElement("iframe");b.onload=()=>{setTimeout(()=>b.parentNode?.removeChild(b),3e4)},b.setAttribute("width","1"),b.setAttribute("height","1"),b.setAttribute("scrolling","no"),b.setAttribute("frameBorder","0"),b.setAttribute("style","display:none"),b.setAttribute("src",a.url),document.body.appendChild(b)}}}catch{}}function s(a){return!!a&&new Date(a)<new Date}function t(a){if(!a)return!1;const b=j(a);return!("string"!=typeof b?.expiryDate)&&s(b.expiryDate)}function u(){return new Date(new Date().getTime()+J).toISOString()}function v(a,b=null){const c=document.referrer?document.referrer:"";return{v:K,sr:c,su:location.href,th:b?b:a}}function w(){const a=[];for(const b of I){const c=h(G+b),d=j(c);d?.eventType&&a.push(d)}return a}function x(a,b){a.forEach(a=>{b.test(a?.eventType+"")&&i({key:G+a.eventType,value:JSON.stringify({...a,expiryDate:u()}),expires:J})})}function y(){const a=w();for(const b of a){if(b&&!b.expiryDate){i({key:G+b.eventType,value:JSON.stringify({...b,expiryDate:u()}),expires:J});continue}if(H.includes(b?.eventType)&&s(b?.expiryDate)){const a=G+b.eventType;g(a)}}const b=h("__rtbh.lid"),c=t(b);if(!b||c)i({key:"__rtbh.lid",value:JSON.stringify({eventType:"lid",id:k(),expiryDate:u()}),expires:J});else{const a=j(b);a?.expiryDate&&i({key:"__rtbh.lid",value:JSON.stringify({...a,expiryDate:u()}),expires:J})}}function z(){for(const a of I){const b=G+a,c=h(`__rtbhouse.${a}`),d=h(b),e=window[F].find(b=>b.eventType===a&&b.id);if(e&&i({key:b,value:JSON.stringify(e),expires:J}),!d&&c&&!c.includes("eventType")){i({key:G+a,value:JSON.stringify({eventType:a,id:c}),expires:J})}}}async function A(a,b,c,d){try{const e=l(c),f=await fetch(a,b),g=f.status;if(200<=g&&300>g)return e(d?await f.json():await f.text(),f)}catch{}}function B(a,b,c){try{const d=n(a,c),e=Object.keys(d),f=w();for(const a of e)b&&b(r,d[a],f,a)}catch{}}function C(a,b,c){if(null===a)return;return function(d,e,f,g=null){const h=e.filter(a=>!b.includes(a.eventType)&&"init"!==a.eventType);return A(`https://${c}.creativecdn.com/tags/v2?type=json`,{method:"POST",mode:"cors",credentials:"include",referrerPolicy:"no-referrer-when-downgrade",headers:{"Content-Type":"application/json"},redirect:"follow",body:JSON.stringify({...v(a,g),tags:[...h,...f]})},d)}}function D(){const a=o(),b=q(a),c=p(a),d=C(c,I,b);return{taggingHash:c,sendTags:d}}function E(a,b){const c=new RegExp(`^(${I.join("|")})$`);return function(...d){try{if(x(d,c),null===a){const{taggingHash:c,sendTags:d}=D();a=c,b=d}return Array.prototype.push.apply(this,d),B(d,b,a),!0}catch{return!1}}}const F="rtbhEvents",G="__rtbh.",H=["uid","sid","aid","eid"],I=[...H,"lid"],J=31536000000,K="v0.2.1";(function(){if(Array.isArray(window[F])||(window[F]=[]),window[F].length&&!window[F].push.prototype)try{const{taggingHash:a,sendTags:b}=D();z(),y(),B(window[F],b,a),window[F].push=E(a,b)}catch{}})()})();
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="29" height="29" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M-9-9h48v48H-9z"/><path d="M14.728 12.607L26.395.939a1.5 1.5 0 1 1 2.122 2.122L16.849 14.728l11.668 11.667a1.5 1.5 0 0 1-2.122 2.122L14.728 16.849 3.06 28.517a1.5 1.5 0 0 1-2.122-2.122l11.668-11.667L.939 3.06A1.5 1.5 0 0 1 3.061.939l11.667 11.668z" fill-opacity=".54" fill="#000" fill-rule="nonzero"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 418 B |
@@ -0,0 +1,2 @@
|
|||||||
|
<html><head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><script src="prompt.js"></script></head><body></body></html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<html><head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><script src="prompt.js"></script></head><body><iframe height="0" width="0" style="display: none; visibility: hidden;" src="a_data_002/sw_iframe.htm"></iframe></body></html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html><head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||||
|
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||||
|
<script src="../prompt.js"></script></head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
'use strict';var m={};class n{constructor(a){this.j=a;this.g={};this.h={};this.i=0;this.id=String(Math.floor(Number.MAX_SAFE_INTEGER*Math.random()))}}function p(a){return a.performance&&a.performance.now()||Date.now()}
|
||||||
|
var q=function(a,c){class d{constructor(b,f,g){this.failureType=b;this.data=f;this.g=g;this.h=new n(p(g))}u(b,f){const g=b.clientId;if(b.type===0){b.isDead=!0;var e=this.h,h=p(this.g);e.g[g]==null&&(e.g[g]=0,e.h[g]=h,e.i++);e.g[g]++;b.stats={targetId:e.id,clientCount:e.i,totalLifeMs:Math.round(h-e.j),heartbeatCount:e.g[g],clientLifeMs:Math.round(h-e.h[g])}}b.failure={failureType:this.failureType,data:this.data};f(b)}}return new d(5,a,c)};/*
|
||||||
|
|
||||||
|
Copyright Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
let r=globalThis.trustedTypes,t;function u(){let a=null;if(!r)return a;try{const c=d=>d;a=r.createPolicy("goog#html",{createHTML:c,createScript:c,createScriptURL:c})}catch(c){}return a};var v=class{constructor(a){this.g=a}toString(){return this.g+""}};function w(a){const c=a;var d;t===void 0&&(t=u());var b=(d=t)?d.createScriptURL(c):c;return new v(b)}function x(a){if(a instanceof v)return a.g;throw Error("");};function y(a,...c){if(c.length===0)return w(a[0]);let d=a[0];for(let b=0;b<c.length;b++)d+=encodeURIComponent(c[b])+a[b+1];return w(d)}function z(a){var c=y`sw.js`,d=x(c).toString();const b=d.split(/[?#]/),f=/[?]/.test(d)?"?"+b[1]:"";return A(b[0],f,/[#]/.test(d)?"#"+(f?b[2]:b[1]):"",a)}
|
||||||
|
function A(a,c,d,b){function f(e,h){e!=null&&(Array.isArray(e)?e.forEach(l=>f(l,h)):(c+=g+encodeURIComponent(h)+"="+encodeURIComponent(e),g="&"))}let g=c.length?"&":"?";b.constructor===Object&&(b=Object.entries(b));Array.isArray(b)?b.forEach(e=>f(e[1],e[0])):b.forEach(f);return w(a+c+d)};const B=/Chrome\/(\d+)/;var D=function(a){const c=a.origin;if(c){var d=a.o?"swe.js":"sw.js",b=a.g?y`/static/service_worker/${a.g}/${d}?origin=${c}`:y`/gtm/static/${d}?origin=${c}`,f=new Map([["origin",c]]);a.h&&f.set("path",a.h);var g=a.l?z(f):b,e=()=>{const k=B.exec(a.window.navigator.userAgent);return k&&Number(k[1])<119},h=a.window.document.location.href;a.g&&(a.l?h=`${a.h}/_/service_worker`:e()||(h="/static/service_worker"));var l={scope:h};a.g&&(l.updateViaCache="all");a.window.navigator.serviceWorker.register(x(g),
|
||||||
|
l).then(()=>{a.window.navigator.serviceWorker.ready.then(k=>{a.i=k.active;C(a)})},k=>{a.j=q(k==null?void 0:k.toString(),a.window);C(a)});a.window.navigator.serviceWorker.addEventListener("message",k=>{a.window.parent.postMessage(k.data,a.origin)})}},C=function(a){const c=a.m.slice();a.m=[];for(const d of c)a.handleEvent(d)};
|
||||||
|
(new class{constructor(a){this.window=a;this.origin="";this.o=this.l=!1;this.h="";this.j=this.i=null;this.m=[];this.g=""}init(){if((f=>{try{return f!==f.top}catch(g){return!0}})(this.window)){var a=new URL(this.window.document.location.href),c=a.searchParams.get("origin");if(c){this.origin=c;this.l=!!a.searchParams.get("1p");this.o=!!a.searchParams.get("e");this.h=a.searchParams.get("path")||"";var d=a.pathname.match(RegExp(".*/service_worker/(\\w+)/"));d&&d.length&&(this.g=d[1]);var b=this.window.document.location.ancestorOrigins;
|
||||||
|
b&&b[0]!==this.origin||(D(this),this.window.addEventListener("message",f=>{this.handleEvent(f)}))}}}handleEvent(a){a.origin===this.origin&&(this.i?this.i.postMessage(a.data):this.j?this.j.u(a.data,c=>{this.window.parent.postMessage(c,this.origin)}):this.m.push(a))}}(window)).init();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!function(t){var n="https://",e="aHR0cHM6Ly9waXBzLnRhYm9vbGEuY29t",r=["YXJjaGl0ZWN0dXJl","cGxhdGZvcm1WZXJzaW9u","dWFGdWxsVmVyc2lvbg=="],a=["YnJhbmRz","bW9iaWxl","bW9kZWw=","cGxhdGZvcm0=","cGxhdGZvcm1WZXJzaW9u","dWFGdWxsVmVyc2lvbg=="],s="dc";function o(){return"currentScript"in t.document&&t.document.currentScript&&"src"in t.document.currentScript&&"URL"in t&&"parse"in t.URL}function i(){var n=null;if(o()){var e=t.URL.parse(t.document.currentScript.src);e&&(n=e.searchParams)}return n||{get:function t(){return null}}}function c(){return{get:function t(){return null}}}function u(t){return t?"&dc=".concat(t):""}try{t.TRC.cdsPips={userId:null,getHttpsPrefix:function t(){return n},doNothing:function t(){return!0},safeGetTfaConfig:function n(e,r,a){return t._tfa&&t._tfa.config&&t._tfa.config.safeGet&&t._tfa.config.safeGet(e,r,a)},safeGetRboxConfig:function t(n){var e;return(TRCImpl?TRCImpl.global:{})[n]},prepareGenericRequest:function n(e,r){var a,s=new(t.XDomainRequest||t.XMLHttpRequest);return s.open(e,r),s.onload=this.doNothing,s.onerror=this.doNothing,s.onprogress=this.doNothing,s},tryToReadUserIdFromLocalStorage:function t(){try{return window.localStorage["taboola global:user-id"]}catch(t){return null}},getUserId:function n(){var e=t.TFASC&&t.TFASC.tfaUserId&&"function"==typeof t.TFASC.tfaUserId.getUserId?t.TFASC.tfaUserId.getUserId():null,r=t.TRC.pageManager&&"function"==typeof t.TRC.pageManager.getUserId?t.TRC.pageManager.getUserId():null,a=TRC.cdsPips.tryToReadUserIdFromLocalStorage();return e||r||a},sendFinalReq:function n(e,r){var a="".concat(this.getHttpsPrefix(),"cds.taboola.com?uid=").concat(this.userId),s;(null!==e[0]&&(a+="&uad=".concat(e[0])),e[1]&&(e[1].platform&&(a+="&ptf=".concat(t.btoa(e[1].platform)),e[1].platformVersion&&(a+="&ptfv=".concat(t.btoa(e[1].platformVersion)))),e[1].model&&(a+="&mdl=".concat(t.btoa(e[1].model))),e[1].uaFullVersion&&(a+="&ufv=".concat(t.btoa(e[1].uaFullVersion))),e[1].brands&&(a+=this.parseFullVersionList(e[1].brands)),"mobile"in e[1]&&(a+="&mbl=".concat(t.btoa(e[1].mobile)))),(a+=u(r))!=="".concat(this.getHttpsPrefix(),"cds.taboola.com?uid=").concat(this.userId))&&this.prepareGenericRequest("GET",a).send()},parseFullVersionList:function n(e){for(var r="",a=0;a<e.length;a++)r+="&bnd=".concat(t.btoa(e[a].brand),"&bndv=").concat(t.btoa(e[a].version));return r},sendSimpleReq:function t(n){var e="".concat(this.getHttpsPrefix(),"cds.taboola.com?uid=").concat(this.userId).concat(u(n)),r;this.prepareGenericRequest("GET",e).send()},fetchUserAgentData:function n(){return new Promise(function(n){try{navigator&&navigator.userAgentData&&navigator.userAgentData.getHighEntropyValues?navigator.userAgentData.getHighEntropyValues(r.map(function(n){return t.atob(n)})).then(function(e){var r={};a.forEach(function(n){var a=t.atob(n);a in e&&(r[a]=e[a])}),Object.keys(r).length>0?n(r):n(null)}):n(null)}catch(t){n(null)}})},sendUadRequest:function n(){var r=this;return new Promise(function(n,a){if(r.safeGetTfaConfig("cds:send-uad-req",!1)||r.safeGetRboxConfig("cds:send-uad")){var s=r.prepareGenericRequest("GET",t.atob(e));s.onreadystatechange=function(){if(4===this.readyState)if(200===this.status){var t=s.responseText;t?n("NULL"!==t?t:null):(__trcWarn("cds: error in pips - status ".concat(this.status,", return null")),n(null))}else __trcWarn("cds: error in pips - status ".concat(this.status,", return null")),n(null)},s.timeout=3e4,s.responseType="text",s.ontimeout=function(){__trcWarn("cds: error in pips - timeout"),n(null)},s.send()}else n(null)})},init:function n(){var e=this,r,a=i().get(s);t.TRC.cdsPips.cdsInitialized||(t.Promise?(t.__trcWarn=t.__trcWarn||function t(){},this.userId=this.getUserId(),this.userId&&Promise.all([this.sendUadRequest(),this.fetchUserAgentData()]).then(function(t){return e.sendFinalReq(t,a)},function(){return e.sendSimpleReq(a)}),t.TRC.cdsPips.cdsInitialized=!0):t.TRC.cdsPips.cdsInitialized=!0)}},window._trcIsUTactive&&(t.TRC.cdsPips.unitestsHelpers={parseQueryParams:i}),t.TRC.cdsPips.init()}catch(t){__trcError("Error running cds",t)}}(window);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!function(i){var t={};function c(n){var e;return(t[n]||(e=t[n]={i:n,l:!1,exports:{}},i[n].call(e.exports,e,e.exports,c),e.l=!0,e)).exports}c.m=i,c.c=t,c.d=function(n,e,i){c.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},c.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"u",{value:!0})},c.t=function(e,n){if(1&n&&(e=c(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.u)return e;var i=Object.create(null);if(c.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)c.d(i,t,function(n){return e[n]}.bind(null,t));return i},c.n=function(n){var e=n&&n.u?function(){return n.default}:function(){return n};return c.d(e,"a",e),e},c.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},c.p="",c(c.s=2)}([function(n,e){var i,t,c={};c.LIST={DERIVED_EPIK:{chance:100,uri:"pin-derived-epik"},SCRAPE_LISTENERS:{chance:100,uri:"pin-scrape-listeners"},SEND_LOGS:{chance:100,uri:"pin-log-errors"},CHECK_CSP:{chance:5,uri:"pin-check-csp"},DEDUPE_AEM_ELIGIBLE_ARRAY:{chance:100,uri:"pin-dedupe-aem-eligible-array"},CHECK_AUTO_UPGRADED:{chance:100,uri:"pin-check-auto-upgraded"},CHECK_DOCUMENT_COOKIE:{chance:0,uri:"pin-check-document-cookie"},SEND_TO_STAGING:{chance:0,uri:"pin-send-to-staging"},CHROME_TRIAL_EPIK_LOCALSTORAGE:{chance:100,uri:"pin-trial-epik-localstorage"},NO_CODE_CAPI_ENABLED:{chance:100,uri:"nocodecapi-enabled"},IAB_IOS_REFERRER_ENABLED:{chance:100,uri:"iab-ios-referrer-enabled"}};for([i,t]of Object.entries(c.LIST))"true"===new URLSearchParams(window.location.search).get(t.uri)&&(c.LIST[i].chance=100);c.isInRampPercentage=function(n){return 100*Math.random()<(n||0)},n.exports=c},function(n,e,i){let t=i(0);var c={},o="unknown";function a(n){n.version=o,100*Math.random()<(t.LIST.SEND_LOGS.chance||0)&&c._(n)}c.setVersion=function(n){o=n},c._=function(n){var e=new window.XMLHttpRequest;e.withCredentials=!1,e.onerror=function(){console.info("Error message failed to send")},e.open("POST","https://ct.pinterest.com/stats/",!1),e.setRequestHeader("Content-Type","application/json"),e.send(JSON.stringify(n))},c.error=function(n,e){var i={messageType:"ERROR",message:n,log:"[".concat(2<arguments.length&&void 0!==arguments[2]?arguments[2]:"Empty","]")};e.hasOwnProperty("stack")?i.log+="[".concat(e.stack,"]"):i.log+="[".concat(e.message,"]"),a(i)},c.info=function(n,e){a({messageType:"INFO",message:n,log:"[".concat(2<arguments.length&&void 0!==arguments[2]?arguments[2]:"Empty","][").concat(e,"]")})},n.exports=c},function(n,e,i){{var t=document,c=i(0);let n=i(1),e=i(3);(i=t.createElement("script")).async=!0,n.setVersion("e258cfd2"),c.isInRampPercentage(c.LIST.SEND_LOGS.chance)&&(i.onerror=function(){n.error("Failed to load ".concat("e258cfd2"),new Error("failed to load main.js"))}),c.isInRampPercentage(c.LIST.CHECK_CSP.chance)&&(document.onsecuritypolicyviolation=function(n){e.sendEventInfo(n)}),i.src="https://s.pinimg.com/ct/lib/main.e258cfd2.js",(c=t.getElementsByTagName("script")[0]).parentNode.insertBefore(i,c)}},function(n,e,i){var t={};let c=i(1);const o=/https?:\/\/s\.pinimg\.com\/ct\/lib\/main\.[0-9a-f]{8}\.js/g;t.sendEventInfo=function(e){if(e&&e.blockedURI&&"https://s.pinimg.com/ct/lib/main.e258cfd2.js"===e.blockedURI){let n="Directive: "+e.effectiveDirective+" Disposition: "+e.disposition+" Blocked URI: "+e.blockedURI;var i;e.originalPolicy?(i=t.v(e.originalPolicy))&&0<i.length?(n+=" main file(s) allowed: "+i.join(" "),c.info("csp violation main file(s) allowed - "+e.disposition,n)):c.info("csp violation no main file allowed - "+e.disposition,n):c.info("csp violation original policy not available",n)}},t.v=function(n){return n?n.match(o):null},n.exports=t}]);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
window.goldlog=(window.goldlog||{});goldlog.Etag="EkCwIfpA7mMCAVvZmpQm0ol4";goldlog.stag=1;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
#J_xiaomi_dialog{padding-top:72px}#J_xiaomi_dialog .J_strong{overflow:visible!important}#J_xiaomi_dialog .J_strong .alime-question-list li{margin-left:0;padding-left:30px;font-size:12px;line-height:20px}#J_xiaomi_dialog .J_strong .alime-strong-header img{display:none;width:72px!important;height:72px;position:absolute;top:-72px;left:50%;transform:translateX(-50%);-ms-transform:translateX(-50%)}#J_xiaomi_dialog .J_weak{overflow:visible!important;position:relative}#J_xiaomi_dialog .J_weak img{width:48px!important;height:48px;position:absolute;top:-48px;left:50%;transform:translateX(-50%);-ms-transform:translateX(-50%)}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.slick-list,.slick-slider,.slick-track{position:relative;display:block}.slick-loading .slick-slide,.slick-loading .slick-track{visibility:hidden}.slick-slider{box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-khtml-user-select:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.slick-list{overflow:hidden;margin:0;padding:0}.slick-list:focus{outline:0}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-list,.slick-slider .slick-track{-ms-transform:translateZ(0);transform:translateZ(0)}.slick-track{top:0;left:0}.slick-track:after,.slick-track:before{display:table;content:""}.slick-track:after{clear:both}.slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.slick-arrow.slick-hidden{display:none}.slick-dots,.slick-next,.slick-prev{position:absolute;display:block;padding:0;z-index:99}.slick-dots li button:before,.slick-next:before,.slick-prev:before{font-family:slick;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.slick-next,.slick-prev{top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;border:none;outline:0;transition:all .4s ease-in-out;overflow:hidden}.slick-next-default,.slick-prev-default{font-size:0;line-height:0;width:30px;height:50px;color:transparent;background:rgba(0,0,0,.25)}.slick-next-default:focus,.slick-next-default:hover,.slick-prev-default:focus,.slick-prev-default:hover{color:transparent;outline:0;background:rgba(0,0,0,.5)}.slick-next-default:before,.slick-prev-default:before{content:" ";position:absolute;top:0;left:0;width:60px;height:50px;background:url(//ae01.alicdn.com/kf/H3a2170950d3848dd85531682a4dc5ef21.png) no-repeat}.slick-prev{left:0}[dir=rtl] .slick-prev{right:0;left:auto}.slick-next-default:before,[dir=rtl] .slick-prev-default:before{left:-30px}.slick-next{right:0}[dir=rtl] .slick-next{right:auto;left:0}.slick-prev-default.slick-outer{left:-40px}[dir=rtl] .slick-prev-default.slick-outer{right:-40px;left:auto}.slick-next-default.slick-outer{right:-40px}[dir=rtl] .slick-next-default.slick-outer{right:auto;left:-40px}.slick-next-default.slick-disabled,.slick-next-default.slick-disabled:hover,.slick-prev-default.slick-disabled,.slick-prev-default.slick-disabled:hover{background:rgba(0,0,0,.13);cursor:not-allowed}.slick-dotted.slick-slider{margin-bottom:30px}.slick-dots{bottom:0;width:100%;margin:0;list-style:none;text-align:center}.slick-dots li{position:relative;display:inline-block;margin:0 5px;padding:0}.slick-dots li,.slick-dots li button{width:20px;height:20px;cursor:pointer}.slick-dots li button{font-size:0;line-height:0;display:block;padding:5px;color:transparent;border:0;outline:0;background:0 0}.slick-dots li button:focus,.slick-dots li button:hover{outline:0}.slick-dots li button:before{content:" ";position:absolute;top:0;left:0;width:10px;height:10px;border-radius:50%;text-align:center;background:#fff}.slick-dots li.slick-active button:before{opacity:1;background:#ff4747}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 SideeX committers
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const originalPrompt = window.prompt
|
||||||
|
let nextPromptResult = false
|
||||||
|
let recordedPrompt = null
|
||||||
|
|
||||||
|
const originalConfirmation = window.confirm
|
||||||
|
let nextConfirmationResult = false
|
||||||
|
let recordedConfirmation = null
|
||||||
|
|
||||||
|
const originalAlert = window.alert
|
||||||
|
let recordedAlert = null
|
||||||
|
|
||||||
|
function getFrameLocation() {
|
||||||
|
let frameLocation = ''
|
||||||
|
let currentWindow = window
|
||||||
|
let currentParentWindow
|
||||||
|
while (currentWindow !== window.top) {
|
||||||
|
currentParentWindow = currentWindow.parent
|
||||||
|
for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
|
||||||
|
if (currentParentWindow.frames[idx] === currentWindow) {
|
||||||
|
frameLocation = ':' + idx + frameLocation
|
||||||
|
currentWindow = currentParentWindow
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameLocation = 'root' + frameLocation
|
||||||
|
return frameLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
//before record prompt
|
||||||
|
|
||||||
|
// Not a top window
|
||||||
|
if (window !== window.top) {
|
||||||
|
window.prompt = function(text, defaultText) {
|
||||||
|
if (document.body.hasAttribute('SideeXPlayingFlag')) {
|
||||||
|
return window.top.prompt(text, defaultText)
|
||||||
|
} else {
|
||||||
|
let result = originalPrompt(text, defaultText)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'prompt',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.confirm = function(text) {
|
||||||
|
if (document.body.hasAttribute('SideeXPlayingFlag')) {
|
||||||
|
return window.top.confirm(text)
|
||||||
|
} else {
|
||||||
|
let result = originalConfirmation(text)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'confirm',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.alert = function(text) {
|
||||||
|
if (document.body.hasAttribute('SideeXPlayingFlag')) {
|
||||||
|
recordedAlert = text
|
||||||
|
// Response directly
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'alert',
|
||||||
|
value: recordedAlert,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
let result = originalAlert(text)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'alert',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// top window
|
||||||
|
|
||||||
|
window.prompt = function(text, defaultText) {
|
||||||
|
recordedPrompt = text
|
||||||
|
if (document.body.hasAttribute('setPrompt')) {
|
||||||
|
document.body.removeAttribute('setPrompt')
|
||||||
|
return nextPromptResult
|
||||||
|
} else {
|
||||||
|
let result = originalPrompt(text, defaultText)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'prompt',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.confirm = function(text) {
|
||||||
|
recordedConfirmation = text
|
||||||
|
if (document.body.hasAttribute('setConfirm')) {
|
||||||
|
document.body.removeAttribute('setConfirm')
|
||||||
|
return nextConfirmationResult
|
||||||
|
} else {
|
||||||
|
let result = originalConfirmation(text)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'confirm',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.alert = function(text) {
|
||||||
|
recordedAlert = text
|
||||||
|
if (document.body.hasAttribute('SideeXPlayingFlag')) {
|
||||||
|
// Response directly
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'alert',
|
||||||
|
value: recordedAlert,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
let result = originalAlert(text)
|
||||||
|
let frameLocation = getFrameLocation()
|
||||||
|
window.top.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
recordedType: 'alert',
|
||||||
|
recordedMessage: text,
|
||||||
|
recordedResult: result,
|
||||||
|
frameLocation: frameLocation,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//play window methods
|
||||||
|
if (window == window.top) {
|
||||||
|
window.addEventListener('message', handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handler(event) {
|
||||||
|
if (
|
||||||
|
event.source == window &&
|
||||||
|
event.data &&
|
||||||
|
event.data.direction == 'from-content-script'
|
||||||
|
) {
|
||||||
|
if (event.data.detach) {
|
||||||
|
window.removeEventListener('message', handler)
|
||||||
|
window.prompt = originalPrompt
|
||||||
|
window.confirm = originalConfirmation
|
||||||
|
window.alert = originalAlert
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let result = undefined
|
||||||
|
switch (event.data.command) {
|
||||||
|
case 'setNextPromptResult':
|
||||||
|
nextPromptResult = event.data.target
|
||||||
|
document.body.setAttribute('setPrompt', true)
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'prompt',
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'getPromptMessage':
|
||||||
|
result = recordedPrompt
|
||||||
|
recordedPrompt = null
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'prompt',
|
||||||
|
value: result,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'setNextConfirmationResult':
|
||||||
|
nextConfirmationResult = event.data.target
|
||||||
|
document.body.setAttribute('setConfirm', true)
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'confirm',
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'getConfirmationMessage':
|
||||||
|
result = recordedConfirmation
|
||||||
|
recordedConfirmation = null
|
||||||
|
try {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'confirm',
|
||||||
|
value: result,
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
} catch (e) {} // eslint-disable-line no-empty
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'setNextAlertResult':
|
||||||
|
document.body.setAttribute('setAlert', true)
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
direction: 'from-page-script',
|
||||||
|
response: 'alert',
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
(window["webpackJsonp-ae-fe/cosmos-1"]=window["webpackJsonp-ae-fe/cosmos-1"]||[]).push([[38],{1245:function(e,r,n){"use strict";n.r(r);var t=n(36),o=n(7);r.default=function(){var e,r,n=function(e){return e.replace(/</g,"<").replace(/"/g,""")},i=n(window.location.search),a=function(){if(!document.referrer)return"";try{return new URL(document.referrer).host}catch(e){console.error(e)}return""}();if(/isdl=y/.test(i)||/aff_short_key=/.test(i)||/(google|bing|yahoo|yandex|baidu|naver|msn|rambler)/.test(null==a?void 0:a.toLowerCase())){var c,d=window.location.href,s=encodeURIComponent(n(d.substring(0,Math.min(1e3,d.indexOf("?"))))),l=[];a&&l.push("__referer__="+a),window.dmtrack_pageid&&l.push("__page_id__="+window.dmtrack_pageid),s&&l.push("current_url="+s),e="//s.click.aliexpress.com/direct_landing.htm"+(c=i?i+"&":"?")+l.join("&"),(r=document.createElement("img")).src=e,r.alt="affiliate",r.style.display="none",document.body.appendChild(r),r.addEventListener("load",(function(){document.body.removeChild(r)})),function(e){t.a.request({api:"mtop.aliexpress.traffic.web.direct.landing.click.report",v:"1.0",type:"GET",data:{browserId:o.a.get("xman_us_f","acs_rt"),currentUrl:e}})}("//s.click.aliexpress.com/direct_landing.htm"+c+l.join("&"))}}}}]);
|
||||||
|
After Width: | Height: | Size: 43 B |
@@ -0,0 +1 @@
|
|||||||
|
(window["webpackJsonp-ae-fe/cosmos-1"]=window["webpackJsonp-ae-fe/cosmos-1"]||[]).push([[43],{1259:function(e,t,n){"use strict";n.r(t);var r=n(170),o=n(4),i=n.n(o),a=n(17),c=n.n(a),u=n(38),l=n.n(u),s=n(81),f=n.n(s),d=n(83),m=n.n(d),v=n(506);function p(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function b(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?p(Object(n),!0).forEach((function(t){m()(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):p(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}var y=function(e,t){return l.a.createElement(v.a,b(b({},e),{},{ref:t}))},w=Object(u.forwardRef)(y),O=(n(299),n(624),n(169)),j=n(625),h=n.n(j),g=n(172),S=n(108),A=["data","className","columnNum","onVisible","autoSize"];var C=function(e){var t=e.data,n=e.className,r=e.columnNum,o=e.onVisible,a=(e.autoSize,c()(e,A)),s=Object(u.useRef)({}),d=Object(u.useContext)(S.a),m=d.infinite,v=d.scene,p=Object(u.useState)(""),b=p[0],y=p[1],j=null==t?void 0:t.some((function(e){return"pc_new_card"===e.itemCardType||"app_us_local_card"===e.itemCardType})),C=Object(g.a)(r||6,j),E=C.column,_=C.ref;Object(u.useEffect)((function(){if(_.current){var e=_.current.querySelector(".slick-slide");e&&setTimeout((function(){y((null==e?void 0:e.offsetWidth)<=269?h.a.miniCardContainerBtm:"")}))}}),[t,E]);var T=function(e){s.current[e.productId]||null==o||o(e,{isLoadMore:e.index===t.length-1}),s.current[e.productId]=!0};return l.a.createElement("div",{ref:_,"data-spm":v},(null==t?void 0:t.length)>0&&l.a.createElement(w,{className:f()(h.a.container,n,j?h.a.containerBtm:"",b),infinite:void 0===m||m,lazyLoad:"ondemand",slidesToScroll:E,slidesToShow:E},null==t?void 0:t.filter((function(e){return"rmdproductV3"===e.itemType||"productV3"===e.itemType})).map((function(e,t){return l.a.createElement("div",{key:e.productId,className:h.a.cardContainer},l.a.createElement(O.a,i()({className:f()(h.a.card,"slider-card-item"),show:!0,openHover:!e.rainbow,slider:j,data:e,index:t,onVisible:T},a)))}))))},E=Object(r.a)(C);t.default=E},172:function(e,t,n){"use strict";var r=n(5),o=n(38),i=function(){var e;return!r.isSSR&&"function"==typeof(null===(e=window)||void 0===e?void 0:e.requestAnimationFrame)};var a=function(e){var t,n=!0;return Object(o.useCallback)((function(){for(var r=arguments.length,o=new Array(r),a=0;a<r;a++)o[a]=arguments[a];var c=function(e,n){return i()?(window.cancelAnimationFrame(t),window.requestAnimationFrame(e)):setTimeout(e,n)};n&&(n=!1,t=c((function(){n=!0,e.apply(void 0,o)}),66))}),[])};function c(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(n)return(n=n.call(e)).next.bind(n);if(Array.isArray(e)||(n=function(e,t){if(e){if("string"==typeof e)return u(e,t);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(e,t):void 0}}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0;return function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}var l=r.isSSR?o.useEffect:o.useLayoutEffect;t.a=function(e,t){void 0===t&&(t=!1);var n=Object(o.useRef)(null),r=Object(o.useState)(e),i=r[0],u=r[1],s=a((function(e){var t,n=null==e||null===(t=e.contentRect)||void 0===t?void 0:t.width;n&&u(n>1150?6:5)}));return l((function(){if(t){if(!n.current)return null;if(!window.ResizeObserver)return null;var e=new ResizeObserver((function(e){for(var t,n=c(e);!(t=n()).done;){var r=t.value;s(r)}}));return e.observe(n.current),function(){return e.disconnect()}}return function(){}})),{ref:n,column:i}}},299:function(e,t,n){},624:function(e,t,n){},625:function(e,t,n){e.exports={containerBtm:"_1mKg7",container:"_2vJwD",cardContainer:"_3D2WI",card:"_3uAF4",miniCardContainerBtm:"_1XWjs"}}}]);
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html><head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var JSON;
|
||||||
|
return (
|
||||||
|
JSON || (JSON = {}),
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
function f(e) {
|
||||||
|
return e < 10 ? '0' + e : e;
|
||||||
|
}
|
||||||
|
function quote(e) {
|
||||||
|
return (
|
||||||
|
(escapable.lastIndex = 0),
|
||||||
|
escapable.test(e)
|
||||||
|
? '"' +
|
||||||
|
e.replace(escapable, function (e) {
|
||||||
|
var t = meta[e];
|
||||||
|
return typeof t == 'string'
|
||||||
|
? t
|
||||||
|
: '\\u' + ('0000' + e.charCodeAt(0).toString(16)).slice(-4);
|
||||||
|
}) +
|
||||||
|
'"'
|
||||||
|
: '"' + e + '"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function str(e, t) {
|
||||||
|
var n,
|
||||||
|
r,
|
||||||
|
i,
|
||||||
|
s,
|
||||||
|
o = gap,
|
||||||
|
u,
|
||||||
|
a = t[e];
|
||||||
|
a && typeof a == 'object' && typeof a.toJSON == 'function' && (a = a.toJSON(e)),
|
||||||
|
typeof rep == 'function' && (a = rep.call(t, e, a));
|
||||||
|
switch (typeof a) {
|
||||||
|
case 'string':
|
||||||
|
return quote(a);
|
||||||
|
case 'number':
|
||||||
|
return isFinite(a) ? String(a) : 'null';
|
||||||
|
case 'boolean':
|
||||||
|
case 'null':
|
||||||
|
return String(a);
|
||||||
|
case 'object':
|
||||||
|
if (!a) return 'null';
|
||||||
|
(gap += indent), (u = []);
|
||||||
|
if (Object.prototype.toString.apply(a) === '[object Array]') {
|
||||||
|
s = a.length;
|
||||||
|
for (n = 0; n < s; n += 1) u[n] = str(n, a) || 'null';
|
||||||
|
return (
|
||||||
|
(i =
|
||||||
|
u.length === 0
|
||||||
|
? '[]'
|
||||||
|
: gap
|
||||||
|
? '[\n' + gap + u.join(',\n' + gap) + '\n' + o + ']'
|
||||||
|
: '[' + u.join(',') + ']'),
|
||||||
|
(gap = o),
|
||||||
|
i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (rep && typeof rep == 'object') {
|
||||||
|
s = rep.length;
|
||||||
|
for (n = 0; n < s; n += 1)
|
||||||
|
typeof rep[n] == 'string' &&
|
||||||
|
((r = rep[n]),
|
||||||
|
(i = str(r, a)),
|
||||||
|
i && u.push(quote(r) + (gap ? ': ' : ':') + i));
|
||||||
|
} else
|
||||||
|
for (r in a)
|
||||||
|
Object.prototype.hasOwnProperty.call(a, r) &&
|
||||||
|
((i = str(r, a)), i && u.push(quote(r) + (gap ? ': ' : ':') + i));
|
||||||
|
return (
|
||||||
|
(i =
|
||||||
|
u.length === 0
|
||||||
|
? '{}'
|
||||||
|
: gap
|
||||||
|
? '{\n' + gap + u.join(',\n' + gap) + '\n' + o + '}'
|
||||||
|
: '{' + u.join(',') + '}'),
|
||||||
|
(gap = o),
|
||||||
|
i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeof Date.prototype.toJSON != 'function' &&
|
||||||
|
((Date.prototype.toJSON = function (e) {
|
||||||
|
return isFinite(this.valueOf())
|
||||||
|
? this.getUTCFullYear() +
|
||||||
|
'-' +
|
||||||
|
f(this.getUTCMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
f(this.getUTCDate()) +
|
||||||
|
'T' +
|
||||||
|
f(this.getUTCHours()) +
|
||||||
|
':' +
|
||||||
|
f(this.getUTCMinutes()) +
|
||||||
|
':' +
|
||||||
|
f(this.getUTCSeconds()) +
|
||||||
|
'Z'
|
||||||
|
: null;
|
||||||
|
}),
|
||||||
|
(String.prototype.toJSON =
|
||||||
|
Number.prototype.toJSON =
|
||||||
|
Boolean.prototype.toJSON =
|
||||||
|
function (e) {
|
||||||
|
return this.valueOf();
|
||||||
|
}));
|
||||||
|
var cx =
|
||||||
|
/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||||
|
escapable =
|
||||||
|
/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||||
|
gap,
|
||||||
|
indent,
|
||||||
|
meta = {
|
||||||
|
'\b': '\\b',
|
||||||
|
'\t': '\\t',
|
||||||
|
'\n': '\\n',
|
||||||
|
'\f': '\\f',
|
||||||
|
'\r': '\\r',
|
||||||
|
'"': '\\"',
|
||||||
|
'\\': '\\\\',
|
||||||
|
},
|
||||||
|
rep;
|
||||||
|
typeof JSON.stringify != 'function' &&
|
||||||
|
(JSON.stringify = function (e, t, n) {
|
||||||
|
var r;
|
||||||
|
(gap = ''), (indent = '');
|
||||||
|
if (typeof n == 'number') for (r = 0; r < n; r += 1) indent += ' ';
|
||||||
|
else typeof n == 'string' && (indent = n);
|
||||||
|
rep = t;
|
||||||
|
if (
|
||||||
|
!t ||
|
||||||
|
typeof t == 'function' ||
|
||||||
|
(typeof t == 'object' && typeof t.length == 'number')
|
||||||
|
)
|
||||||
|
return str('', { '': e });
|
||||||
|
throw new Error('JSON.stringify');
|
||||||
|
}),
|
||||||
|
typeof JSON.parse != 'function' &&
|
||||||
|
(JSON.parse = function (text, reviver) {
|
||||||
|
function walk(e, t) {
|
||||||
|
var n,
|
||||||
|
r,
|
||||||
|
i = e[t];
|
||||||
|
if (i && typeof i == 'object')
|
||||||
|
for (n in i)
|
||||||
|
Object.prototype.hasOwnProperty.call(i, n) &&
|
||||||
|
((r = walk(i, n)), r !== undefined ? (i[n] = r) : delete i[n]);
|
||||||
|
return reviver.call(e, t, i);
|
||||||
|
}
|
||||||
|
var j;
|
||||||
|
(text = String(text)),
|
||||||
|
(cx.lastIndex = 0),
|
||||||
|
cx.test(text) &&
|
||||||
|
(text = text.replace(cx, function (e) {
|
||||||
|
return '\\u' + ('0000' + e.charCodeAt(0).toString(16)).slice(-4);
|
||||||
|
}));
|
||||||
|
if (
|
||||||
|
/^[\],:{}\s]*$/.test(
|
||||||
|
text
|
||||||
|
.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
||||||
|
.replace(
|
||||||
|
/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
|
||||||
|
']',
|
||||||
|
)
|
||||||
|
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
(j = eval('(' + text + ')')),
|
||||||
|
typeof reviver == 'function' ? walk({ '': j }, '') : j
|
||||||
|
);
|
||||||
|
throw new SyntaxError('JSON.parse');
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
(this.JSON = JSON)
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function a() {
|
||||||
|
try {
|
||||||
|
return i in n && n[i];
|
||||||
|
} catch (e) {
|
||||||
|
return !1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function f() {
|
||||||
|
try {
|
||||||
|
return s in n && n[s] && n[s][n.location.hostname];
|
||||||
|
} catch (e) {
|
||||||
|
return !1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var e = {},
|
||||||
|
n = window,
|
||||||
|
r = n.document,
|
||||||
|
i = 'localStorage',
|
||||||
|
s = 'globalStorage',
|
||||||
|
o = '__storejs__',
|
||||||
|
u;
|
||||||
|
(e.disabled = !1),
|
||||||
|
(e.set = function (e, t) {}),
|
||||||
|
(e.get = function (e) {}),
|
||||||
|
(e.remove = function (e) {}),
|
||||||
|
(e.clear = function () {}),
|
||||||
|
(e.transact = function (t, n, r) {
|
||||||
|
var i = e.get(t);
|
||||||
|
r == null && ((r = n), (n = null)),
|
||||||
|
typeof i == 'undefined' && (i = n || {}),
|
||||||
|
r(i),
|
||||||
|
e.set(t, i);
|
||||||
|
}),
|
||||||
|
(e.getAll = function () {}),
|
||||||
|
(e.serialize = function (e) {
|
||||||
|
return JSON.stringify(e);
|
||||||
|
}),
|
||||||
|
(e.deserialize = function (e) {
|
||||||
|
if (typeof e != 'string') return undefined;
|
||||||
|
try {
|
||||||
|
return JSON.parse(e);
|
||||||
|
} catch (t) {
|
||||||
|
return e || undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (a())
|
||||||
|
(u = n[i]),
|
||||||
|
(e.set = function (t, n) {
|
||||||
|
return n === undefined ? e.remove(t) : (u.setItem(t, e.serialize(n)), n);
|
||||||
|
}),
|
||||||
|
(e.get = function (t) {
|
||||||
|
return e.deserialize(u.getItem(t));
|
||||||
|
}),
|
||||||
|
(e.remove = function (e) {
|
||||||
|
u.removeItem(e);
|
||||||
|
}),
|
||||||
|
(e.clear = function () {
|
||||||
|
u.clear();
|
||||||
|
}),
|
||||||
|
(e.getAll = function () {
|
||||||
|
var t = {};
|
||||||
|
for (var n = 0; n < u.length; ++n) {
|
||||||
|
var r = u.key(n);
|
||||||
|
t[r] = e.get(r);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
else if (f())
|
||||||
|
(u = n[s][n.location.hostname]),
|
||||||
|
(e.set = function (t, n) {
|
||||||
|
return n === undefined ? e.remove(t) : ((u[t] = e.serialize(n)), n);
|
||||||
|
}),
|
||||||
|
(e.get = function (t) {
|
||||||
|
return e.deserialize(u[t] && u[t].value);
|
||||||
|
}),
|
||||||
|
(e.remove = function (e) {
|
||||||
|
delete u[e];
|
||||||
|
}),
|
||||||
|
(e.clear = function () {
|
||||||
|
for (var e in u) delete u[e];
|
||||||
|
}),
|
||||||
|
(e.getAll = function () {
|
||||||
|
var t = {};
|
||||||
|
for (var n = 0; n < u.length; ++n) {
|
||||||
|
var r = u.key(n);
|
||||||
|
t[r] = e.get(r);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
else if (r.documentElement.addBehavior) {
|
||||||
|
var l, c;
|
||||||
|
try {
|
||||||
|
(c = new ActiveXObject('htmlfile')),
|
||||||
|
c.open(),
|
||||||
|
c.write(
|
||||||
|
'<sc' + 'ript>document.w=window</scr' + 'ipt><iframe src="/favicon.ico"></frame>',
|
||||||
|
),
|
||||||
|
c.close(),
|
||||||
|
(l = c.w.frames[0].document),
|
||||||
|
(u = l.createElement('div'));
|
||||||
|
} catch (h) {
|
||||||
|
(u = r.createElement('div')), (l = r.body);
|
||||||
|
}
|
||||||
|
function p(t) {
|
||||||
|
return function () {
|
||||||
|
var n = Array.prototype.slice.call(arguments, 0);
|
||||||
|
n.unshift(u), l.appendChild(u), u.addBehavior('#default#userData'), u.load(i);
|
||||||
|
var r = t.apply(e, n);
|
||||||
|
return l.removeChild(u), r;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var d = new RegExp('[!"#$%&\'()*+,/\\\\:;<=>?@[\\]^`{|}~]', 'g');
|
||||||
|
function v(e) {
|
||||||
|
return e.replace(d, '___');
|
||||||
|
}
|
||||||
|
(e.set = p(function (t, n, r) {
|
||||||
|
return (
|
||||||
|
(n = v(n)),
|
||||||
|
r === undefined ? e.remove(n) : (t.setAttribute(n, e.serialize(r)), t.save(i), r)
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
(e.get = p(function (t, n) {
|
||||||
|
return (n = v(n)), e.deserialize(t.getAttribute(n));
|
||||||
|
})),
|
||||||
|
(e.remove = p(function (e, t) {
|
||||||
|
(t = v(t)), e.removeAttribute(t), e.save(i);
|
||||||
|
})),
|
||||||
|
(e.clear = p(function (e) {
|
||||||
|
var t = e.XMLDocument.documentElement.attributes;
|
||||||
|
e.load(i);
|
||||||
|
for (var n = 0, r; (r = t[n]); n++) e.removeAttribute(r.name);
|
||||||
|
e.save(i);
|
||||||
|
})),
|
||||||
|
(e.getAll = p(function (t) {
|
||||||
|
var n = t.XMLDocument.documentElement.attributes;
|
||||||
|
t.load(i);
|
||||||
|
var r = {};
|
||||||
|
for (var s = 0, o; (o = n[s]); ++s) r[o] = e.get(o);
|
||||||
|
return r;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
e.set(o, o), e.get(o) != o && (e.disabled = !0), e.remove(o);
|
||||||
|
} catch (h) {
|
||||||
|
e.disabled = !0;
|
||||||
|
}
|
||||||
|
(e.enabled = !e.disabled),
|
||||||
|
typeof t != 'undefined' && typeof t != 'function'
|
||||||
|
? (t.exports = e)
|
||||||
|
: typeof define == 'function' && define.amd
|
||||||
|
? define(e)
|
||||||
|
: (this.store = e);
|
||||||
|
})();
|
||||||
|
|
||||||
|
var Messenger = (function () {
|
||||||
|
function n(e, t) {
|
||||||
|
var n = '';
|
||||||
|
arguments.length < 2
|
||||||
|
? (n = 'target error - target and name are both required')
|
||||||
|
: typeof e != 'object'
|
||||||
|
? (n = 'target error - target itself must be window object')
|
||||||
|
: typeof t != 'string' && (n = 'target error - target name must be string type');
|
||||||
|
if (n) throw new Error(n);
|
||||||
|
(this.target = e), (this.name = t);
|
||||||
|
}
|
||||||
|
function r(t, n) {
|
||||||
|
(this.targets = {}),
|
||||||
|
(this.name = t),
|
||||||
|
(this.listenFunc = []),
|
||||||
|
(e = n || e),
|
||||||
|
this.initListen();
|
||||||
|
}
|
||||||
|
var e = 'icbu-messenger',
|
||||||
|
t = 'postMessage' in window;
|
||||||
|
return (
|
||||||
|
t
|
||||||
|
? (n.prototype.send = function (t) {
|
||||||
|
this.target.postMessage(e + t, '*');
|
||||||
|
})
|
||||||
|
: (n.prototype.send = function (t) {
|
||||||
|
var n = window.navigator[e + this.name];
|
||||||
|
if (typeof n != 'function')
|
||||||
|
throw new Error('target callback function is not defined');
|
||||||
|
n(e + t, window);
|
||||||
|
}),
|
||||||
|
(r.prototype.addTarget = function (e, t) {
|
||||||
|
var r = new n(e, t);
|
||||||
|
this.targets[t] = r;
|
||||||
|
}),
|
||||||
|
(r.prototype.initListen = function () {
|
||||||
|
var n = this,
|
||||||
|
r = function (t) {
|
||||||
|
typeof t == 'object' && t.data && (t = t.data),
|
||||||
|
(t = t.slice ? t.slice(e.length) : t);
|
||||||
|
for (var r = 0; r < n.listenFunc.length; r++) n.listenFunc[r](t);
|
||||||
|
};
|
||||||
|
t
|
||||||
|
? 'addEventListener' in document
|
||||||
|
? window.addEventListener('message', r, !1)
|
||||||
|
: 'attachEvent' in document && window.attachEvent('onmessage', r)
|
||||||
|
: (window.navigator[e + this.name] = r);
|
||||||
|
}),
|
||||||
|
(r.prototype.listen = r.prototype.on =
|
||||||
|
function (e) {
|
||||||
|
this.listenFunc.push(e);
|
||||||
|
}),
|
||||||
|
(r.prototype.clear = function () {
|
||||||
|
this.listenFunc = [];
|
||||||
|
}),
|
||||||
|
(r.prototype.send = function (e) {
|
||||||
|
var t = this.targets,
|
||||||
|
n;
|
||||||
|
for (n in t) t.hasOwnProperty(n) && t[n].send(e);
|
||||||
|
}),
|
||||||
|
r
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var messenger = new Messenger('proxy', 'store-proxy');
|
||||||
|
messenger.addTarget(window.parent, 'parent');
|
||||||
|
|
||||||
|
var spe2c = '-_-';
|
||||||
|
var spe2p = 'T_T';
|
||||||
|
messenger.listen(function (msg) {
|
||||||
|
if (!msg.split) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tmp = msg.split(spe2c);
|
||||||
|
|
||||||
|
var op = tmp[0];
|
||||||
|
var key, value;
|
||||||
|
|
||||||
|
if (op === 'set') {
|
||||||
|
key = tmp[1];
|
||||||
|
value = tmp.splice(2, tmp.length - 2).join(spe2c);
|
||||||
|
} else {
|
||||||
|
key = tmp[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var rst, err;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (op) {
|
||||||
|
case 'set':
|
||||||
|
store.set(key, value);
|
||||||
|
break;
|
||||||
|
case 'get':
|
||||||
|
rst = store.get(key);
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
store.remove(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
err = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
messenger.targets['parent'].send([msg, err, rst].join(spe2p));
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script src="prompt.js"></script></head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
# AliExpress Order Parser
|
||||||
|
|
||||||
|
This project extracts order information from an AliExpress HTML page and stores it in a MariaDB database.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Parses AliExpress order HTML page
|
||||||
|
- Extracts order information:
|
||||||
|
- Order date (converted from French format to US format YYYY-MM-DD)
|
||||||
|
- Order number (16-digit identifier)
|
||||||
|
- Order detail URL
|
||||||
|
- Item description
|
||||||
|
- Item price (in EUR)
|
||||||
|
- Item quantity
|
||||||
|
- Item image URL
|
||||||
|
- Order total (in EUR)
|
||||||
|
- Creates MariaDB table with proper structure
|
||||||
|
- Inserts extracted data into database
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.7+
|
||||||
|
- MariaDB or MySQL server
|
||||||
|
- Python packages (see requirements.txt)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Install Python dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Setup MariaDB Database
|
||||||
|
|
||||||
|
Option A: Using SQL file
|
||||||
|
```bash
|
||||||
|
mysql -u root -p < create_database.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Option B: Using MySQL Workbench or phpMyAdmin
|
||||||
|
- Open the `create_database.sql` file
|
||||||
|
- Execute the SQL commands
|
||||||
|
|
||||||
|
### 3. Configure Database Connection
|
||||||
|
|
||||||
|
Edit `parse_orders.py` and update the database configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': 'localhost',
|
||||||
|
'user': 'your_username', # Change this
|
||||||
|
'password': 'your_password', # Change this
|
||||||
|
'database': 'aliexpress',
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Run the parser
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python parse_orders.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
1. Parse the `Commandes.htm` file
|
||||||
|
2. Extract all order information
|
||||||
|
3. Create the database table if it doesn't exist
|
||||||
|
4. Insert all extracted orders into the database
|
||||||
|
|
||||||
|
## Database Structure
|
||||||
|
|
||||||
|
### Table: `items`
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INT | Auto-increment primary key |
|
||||||
|
| orderDate | DATE | Order date (YYYY-MM-DD) |
|
||||||
|
| orderNumber | VARCHAR(20) | 16-digit order number |
|
||||||
|
| orderURL | VARCHAR(500) | URL to order detail page |
|
||||||
|
| itemDesc | TEXT | Item description |
|
||||||
|
| itemPrice | DECIMAL(10,2) | Item unit price in EUR |
|
||||||
|
| itemQuantity | INT | Quantity ordered |
|
||||||
|
| itemImageURL | VARCHAR(500) | URL to item image |
|
||||||
|
| orderTotal | DECIMAL(10,2) | Total order price in EUR |
|
||||||
|
| created_at | TIMESTAMP | Record creation timestamp |
|
||||||
|
| updated_at | TIMESTAMP | Record update timestamp |
|
||||||
|
|
||||||
|
## Example Queries
|
||||||
|
|
||||||
|
### View all orders
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items ORDER BY orderDate DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### View orders by date range
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items
|
||||||
|
WHERE orderDate BETWEEN '2025-12-01' AND '2026-01-31';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get specific order details
|
||||||
|
```sql
|
||||||
|
SELECT * FROM items
|
||||||
|
WHERE orderNumber = '3066436169351201';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Calculate total spending
|
||||||
|
```sql
|
||||||
|
SELECT SUM(orderTotal) as total_spent
|
||||||
|
FROM items;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Count orders by month
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
DATE_FORMAT(orderDate, '%Y-%m') as month,
|
||||||
|
COUNT(DISTINCT orderNumber) as order_count,
|
||||||
|
SUM(orderTotal) as monthly_total
|
||||||
|
FROM items
|
||||||
|
GROUP BY month
|
||||||
|
ORDER BY month DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get items with price above 10 EUR
|
||||||
|
```sql
|
||||||
|
SELECT orderNumber, itemDesc, itemPrice, itemQuantity
|
||||||
|
FROM items
|
||||||
|
WHERE itemPrice > 10.00
|
||||||
|
ORDER BY itemPrice DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Extraction Details
|
||||||
|
|
||||||
|
### Date Conversion
|
||||||
|
French dates like "3 janv. 2026" are converted to US format "2026-01-03"
|
||||||
|
|
||||||
|
Supported French month abbreviations:
|
||||||
|
- janv. → 01, févr. → 02, mars → 03, avr. → 04
|
||||||
|
- mai → 05, juin → 06, juil. → 07, août → 08
|
||||||
|
- sept. → 09, oct. → 10, nov. → 11, déc. → 12
|
||||||
|
|
||||||
|
### Price Conversion
|
||||||
|
French prices like "1,29€" are converted to decimal 1.29
|
||||||
|
|
||||||
|
### Quantity Extraction
|
||||||
|
Quantity strings like "x1", "x2" are converted to integers 1, 2
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Database Connection Error
|
||||||
|
- Verify MariaDB is running
|
||||||
|
- Check username and password in DB_CONFIG
|
||||||
|
- Ensure database 'aliexpress' exists
|
||||||
|
|
||||||
|
### Parsing Error
|
||||||
|
- Verify the HTML file path is correct
|
||||||
|
- Check that Commandes.htm is in the correct location
|
||||||
|
- Ensure the HTML structure matches the expected format
|
||||||
|
|
||||||
|
### Character Encoding Issues
|
||||||
|
- The script uses UTF-8 encoding for both file reading and database
|
||||||
|
- Ensure your MariaDB database uses utf8mb4 charset
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `parse_orders.py` - Main Python script to parse HTML and insert into database
|
||||||
|
- `create_database.sql` - SQL script to create database and table
|
||||||
|
- `requirements.txt` - Python package dependencies
|
||||||
|
- `Commandes.htm` - Source HTML file (from AliExpress)
|
||||||
|
- `README.md` - This file
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is for personal use.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Add itemImage column to items table
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mysql.connector
|
||||||
|
|
||||||
|
# Database configuration
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 31175,
|
||||||
|
'user': 'root',
|
||||||
|
'password': 'vLuH6WhOTMm5O9CarrAX4S5F',
|
||||||
|
'database': 'aliexpress',
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def add_column():
|
||||||
|
"""Add itemImage column to items table if it doesn't exist"""
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Check if column already exists
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = 'aliexpress'
|
||||||
|
AND TABLE_NAME = 'items'
|
||||||
|
AND COLUMN_NAME = 'itemImage'
|
||||||
|
""")
|
||||||
|
|
||||||
|
exists = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
print("Column 'itemImage' already exists")
|
||||||
|
else:
|
||||||
|
# Add the column
|
||||||
|
cursor.execute("""
|
||||||
|
ALTER TABLE items
|
||||||
|
ADD COLUMN itemImage VARCHAR(255)
|
||||||
|
AFTER itemImageURL
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
print("Column 'itemImage' added successfully")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
except mysql.connector.Error as err:
|
||||||
|
print(f"Database error: {err}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
add_column()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
-- Create database for AliExpress orders
|
||||||
|
CREATE DATABASE IF NOT EXISTS aliexpress
|
||||||
|
CHARACTER SET utf8mb4
|
||||||
|
COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE aliexpress;
|
||||||
|
|
||||||
|
-- Create items table
|
||||||
|
CREATE TABLE IF NOT EXISTS items (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
orderDate DATE COMMENT 'Order date in US format (YYYY-MM-DD)',
|
||||||
|
orderNumber VARCHAR(20) COMMENT 'Order number (16 digits)',
|
||||||
|
orderURL VARCHAR(500) COMMENT 'URL to order detail page',
|
||||||
|
itemDesc TEXT COMMENT 'Item description',
|
||||||
|
itemPrice DECIMAL(10, 2) COMMENT 'Item price in EUR',
|
||||||
|
itemQuantity INT COMMENT 'Quantity ordered',
|
||||||
|
itemImageURL VARCHAR(500) COMMENT 'URL to item image',
|
||||||
|
orderTotal DECIMAL(10, 2) COMMENT 'Total order price in EUR',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation timestamp',
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update timestamp',
|
||||||
|
|
||||||
|
INDEX idx_orderNumber (orderNumber),
|
||||||
|
INDEX idx_orderDate (orderDate),
|
||||||
|
INDEX idx_created_at (created_at)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='AliExpress order items extracted from HTML';
|
||||||
|
|
||||||
|
-- Display table structure
|
||||||
|
DESCRIBE items;
|
||||||
|
|
||||||
|
-- Example queries to use after data import:
|
||||||
|
|
||||||
|
-- Select all orders
|
||||||
|
-- SELECT * FROM items ORDER BY orderDate DESC;
|
||||||
|
|
||||||
|
-- Select orders by date range
|
||||||
|
-- SELECT * FROM items WHERE orderDate BETWEEN '2025-12-01' AND '2026-01-31';
|
||||||
|
|
||||||
|
-- Select orders by order number
|
||||||
|
-- SELECT * FROM items WHERE orderNumber = '3066436169351201';
|
||||||
|
|
||||||
|
-- Calculate total spending
|
||||||
|
-- SELECT SUM(orderTotal) as total_spent FROM items;
|
||||||
|
|
||||||
|
-- Count orders by month
|
||||||
|
-- SELECT DATE_FORMAT(orderDate, '%Y-%m') as month, COUNT(DISTINCT orderNumber) as order_count
|
||||||
|
-- FROM items
|
||||||
|
-- GROUP BY month
|
||||||
|
-- ORDER BY month DESC;
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Download images from itemImageURL stored in the database
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import mysql.connector
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Database configuration
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 31175,
|
||||||
|
'user': 'root',
|
||||||
|
'password': 'vLuH6WhOTMm5O9CarrAX4S5F',
|
||||||
|
'database': 'aliexpress',
|
||||||
|
'charset': 'utf8mb4'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Image folder
|
||||||
|
IMG_FOLDER = 'img'
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_folder_exists():
|
||||||
|
"""Create the img folder if it doesn't exist"""
|
||||||
|
Path(IMG_FOLDER).mkdir(exist_ok=True)
|
||||||
|
print(f"Image folder '{IMG_FOLDER}' ready")
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_urls_from_db():
|
||||||
|
"""Retrieve all image URLs and their IDs from the database"""
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get image URLs with their IDs (not distinct, as we need to update each row)
|
||||||
|
query = """
|
||||||
|
SELECT id, itemImageURL, orderNumber
|
||||||
|
FROM items
|
||||||
|
WHERE itemImageURL IS NOT NULL AND itemImageURL != ''
|
||||||
|
AND (itemImage IS NULL OR itemImage = '')
|
||||||
|
ORDER BY orderNumber
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
results = cursor.fetchall()
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except mysql.connector.Error as err:
|
||||||
|
print(f"Database error: {err}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def generate_filename(url, item_id, order_number):
|
||||||
|
"""Generate a unique filename based on URL, item ID and order number"""
|
||||||
|
# Extract file extension from URL
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
path = parsed_url.path
|
||||||
|
ext = os.path.splitext(path)[1]
|
||||||
|
|
||||||
|
# If no extension found, try to extract from query params or use .jpg as default
|
||||||
|
if not ext or ext == '':
|
||||||
|
if '.jpg' in url.lower():
|
||||||
|
ext = '.jpg'
|
||||||
|
elif '.png' in url.lower():
|
||||||
|
ext = '.png'
|
||||||
|
elif '.avif' in url.lower():
|
||||||
|
ext = '.avif'
|
||||||
|
elif '.webp' in url.lower():
|
||||||
|
ext = '.webp'
|
||||||
|
else:
|
||||||
|
ext = '.jpg' # Default
|
||||||
|
|
||||||
|
# Create a hash of the URL for uniqueness
|
||||||
|
url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
|
||||||
|
|
||||||
|
# Combine item ID, order number and hash for filename
|
||||||
|
filename = f"{item_id}_{order_number}_{url_hash}{ext}"
|
||||||
|
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def download_image(url, filepath):
|
||||||
|
"""Download an image from URL and save to filepath"""
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers, timeout=30, stream=True)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Write image to file
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f" Error downloading: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_item_image_path(item_id, relative_path):
|
||||||
|
"""Update the itemImage field in the database"""
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
update_query = """
|
||||||
|
UPDATE items
|
||||||
|
SET itemImage = %s
|
||||||
|
WHERE id = %s
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(update_query, (relative_path, item_id))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except mysql.connector.Error as err:
|
||||||
|
print(f" Database error: {err}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to download all images"""
|
||||||
|
print("Starting image download process...")
|
||||||
|
|
||||||
|
# Ensure img folder exists
|
||||||
|
ensure_folder_exists()
|
||||||
|
|
||||||
|
# Get image URLs from database
|
||||||
|
print("\nFetching image URLs from database...")
|
||||||
|
image_data = get_image_urls_from_db()
|
||||||
|
|
||||||
|
if not image_data:
|
||||||
|
print("No image URLs found in database")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Found {len(image_data)} image(s) to download")
|
||||||
|
|
||||||
|
# Download each image
|
||||||
|
downloaded = 0
|
||||||
|
skipped = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for idx, (item_id, url, order_number) in enumerate(image_data, 1):
|
||||||
|
filename = generate_filename(url, item_id, order_number or 'unknown')
|
||||||
|
filepath = os.path.join(IMG_FOLDER, filename)
|
||||||
|
relative_path = os.path.join(IMG_FOLDER, filename)
|
||||||
|
|
||||||
|
# Check if file already exists
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
print(f"[{idx}/{len(image_data)}] Skipped (already exists): {filename}")
|
||||||
|
# Still update the database if not already set
|
||||||
|
update_item_image_path(item_id, relative_path)
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"[{idx}/{len(image_data)}] Downloading: {filename}")
|
||||||
|
|
||||||
|
if download_image(url, filepath):
|
||||||
|
downloaded += 1
|
||||||
|
print(f" ✓ Saved to {filepath}")
|
||||||
|
# Update database with local image path
|
||||||
|
if update_item_image_path(item_id, relative_path):
|
||||||
|
print(f" ✓ Updated database")
|
||||||
|
else:
|
||||||
|
print(f" ✗ Failed to update database")
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("Download Summary:")
|
||||||
|
print(f" Downloaded: {downloaded}")
|
||||||
|
print(f" Skipped (already exists): {skipped}")
|
||||||
|
print(f" Failed: {failed}")
|
||||||
|
print(f" Total: {len(image_data)}")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 7.0 KiB |