fix: web.config inheritInChildApplications, form error messages, docs infra
- web.config: aggiunto inheritInChildApplications=false per evitare che rewrite rules e httpErrors si propaghino alle Virtual Applications figlie - contatti.astro: errori HTTP 400 mostrano il messaggio specifico dell'API invece del messaggio generico - docs/INFRA.md: documentazione infrastruttura IIS e procedura di deploy Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
docs/INFRA.md
Normal file
90
docs/INFRA.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Infrastruttura — SmartRootsSite
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Sito istituzionale statico generato con **Astro 4**, deployato su **IIS / Windows Server 2016** presso Hetzner.
|
||||||
|
|
||||||
|
- URL: https://www.smart-roots.net/
|
||||||
|
- Cartella sul server: `C:\inetpub\wwwroot\smartroots-site\`
|
||||||
|
- Backend API: https://api.smart-roots.net/ (progetto SmartRootsServices)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Struttura IIS — Site `smart-roots`
|
||||||
|
|
||||||
|
Il Site IIS `smart-roots` ha come Physical Path `C:\inetpub\wwwroot\smartroots-site\`.
|
||||||
|
Sotto di esso convivono due tipi di nodi:
|
||||||
|
|
||||||
|
### Virtual Applications (webapp con proprio App Pool)
|
||||||
|
Sopravvivono automaticamente al cambio di Physical Path del Site.
|
||||||
|
|
||||||
|
`/investirehub`, `/mantis`, `/motoregrafico`, `/olympiadocs`, `/reports`, `/services`,
|
||||||
|
`/smartreports`, `/smartwiki`, `/testblazor`, `/tradingview`, `/wiki`, `/zertimporter`
|
||||||
|
|
||||||
|
### Virtual Directories (contenuto statico, nessun App Pool)
|
||||||
|
Vanno ricreate manualmente se si cambia la Physical Path del Site.
|
||||||
|
Puntano tutte a sottocartelle di `C:\inetpub\wwwroot\smart-roots.net\`.
|
||||||
|
|
||||||
|
| Alias | Physical path |
|
||||||
|
|---|---|
|
||||||
|
| `.well-known` | `...\smart-roots.net\.well-known\` — ⚠️ necessario per rinnovo SSL |
|
||||||
|
| `allegati` | `...\smart-roots.net\allegati\` |
|
||||||
|
| `Images` | `...\smart-roots.net\Images\` |
|
||||||
|
| `Pdf` | `...\smart-roots.net\Pdf\` |
|
||||||
|
| `PricerApi` | `...\smart-roots.net\PricerApi\` |
|
||||||
|
| `PricerApp` | `...\smart-roots.net\PricerApp\` |
|
||||||
|
| `PricerApp2` | `...\smart-roots.net\PricerApp2\` |
|
||||||
|
| `video` | `...\smart-roots.net\video\` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy (da ripetere ad ogni aggiornamento)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run build
|
||||||
|
Copy-Item -Path "dist\*" -Destination "C:\inetpub\wwwroot\smartroots-site\" -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
Nessuna modifica IIS necessaria per i deploy ordinari.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## web.config — nota critica
|
||||||
|
|
||||||
|
Il file `public/web.config` (copiato da Astro in `dist/` durante la build) contiene:
|
||||||
|
|
||||||
|
1. **Redirect HTTP→HTTPS**
|
||||||
|
2. **Astro static routing** — fallback catch-all che riscrive URL senza file fisico a `/{path}/index.html`
|
||||||
|
3. **httpErrors 404** — serve `/index.html` per qualsiasi 404
|
||||||
|
|
||||||
|
L'intera sezione `<system.webServer>` è avvolta in:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<location path="." inheritInChildApplications="false">
|
||||||
|
```
|
||||||
|
|
||||||
|
**Questo wrapper è fondamentale.** Senza di esso, le regole di rewrite e il gestore 404
|
||||||
|
vengono ereditati dalle Virtual Applications figlie, che ricevono traffico riscritto
|
||||||
|
o risposte sostituite con la homepage Astro invece dei loro contenuti.
|
||||||
|
|
||||||
|
Sintomo senza il wrapper: endpoint dinamici delle webapp (es. `/smartreports/api/report?p=...`)
|
||||||
|
restituiscono la homepage Astro invece del contenuto atteso.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pagine
|
||||||
|
|
||||||
|
| URL | File |
|
||||||
|
|---|---|
|
||||||
|
| `/` | `src/pages/index.astro` |
|
||||||
|
| `/contatti` | `src/pages/contatti.astro` |
|
||||||
|
| `/privacy` | `src/pages/privacy.astro` |
|
||||||
|
| `/termini` | `src/pages/termini.astro` |
|
||||||
|
|
||||||
|
## API usate dal frontend
|
||||||
|
|
||||||
|
| Endpoint | Metodo | Scopo |
|
||||||
|
|---|---|---|
|
||||||
|
| `https://api.smart-roots.net/api/contatti` | POST | Invio form contatti |
|
||||||
|
|
||||||
|
Il controller è in `SmartRootsServices/SRServices/Controllers/SmartRootsSite/ContattiController.cs`.
|
||||||
@@ -4,60 +4,62 @@
|
|||||||
Da copiare nella cartella /dist dopo il build: astro build
|
Da copiare nella cartella /dist dopo il build: astro build
|
||||||
-->
|
-->
|
||||||
<configuration>
|
<configuration>
|
||||||
<system.webServer>
|
<location path="." inheritInChildApplications="false">
|
||||||
|
<system.webServer>
|
||||||
|
|
||||||
<!-- Routing: se il file non esiste fisicamente, serve index.html della directory -->
|
<!-- Routing: se il file non esiste fisicamente, serve index.html della directory -->
|
||||||
<rewrite>
|
<rewrite>
|
||||||
<rules>
|
<rules>
|
||||||
<!-- Forza HTTPS -->
|
<!-- Forza HTTPS -->
|
||||||
<rule name="HTTP to HTTPS" stopProcessing="true">
|
<rule name="HTTP to HTTPS" stopProcessing="true">
|
||||||
<match url="(.*)" />
|
<match url="(.*)" />
|
||||||
<conditions>
|
<conditions>
|
||||||
<add input="{HTTPS}" pattern="^OFF$" />
|
<add input="{HTTPS}" pattern="^OFF$" />
|
||||||
</conditions>
|
</conditions>
|
||||||
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
|
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
|
||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
<!-- SPA/Static fallback: cartelle Astro con index.html -->
|
<!-- SPA/Static fallback: cartelle Astro con index.html -->
|
||||||
<rule name="Astro static routing" stopProcessing="true">
|
<rule name="Astro static routing" stopProcessing="true">
|
||||||
<match url="(.*)" />
|
<match url="(.*)" />
|
||||||
<conditions logicalGrouping="MatchAll">
|
<conditions logicalGrouping="MatchAll">
|
||||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||||
</conditions>
|
</conditions>
|
||||||
<action type="Rewrite" url="/{R:1}/index.html" />
|
<action type="Rewrite" url="/{R:1}/index.html" />
|
||||||
</rule>
|
</rule>
|
||||||
</rules>
|
</rules>
|
||||||
</rewrite>
|
</rewrite>
|
||||||
|
|
||||||
<!-- MIME types aggiuntivi necessari per Astro -->
|
<!-- MIME types aggiuntivi necessari per Astro -->
|
||||||
<staticContent>
|
<staticContent>
|
||||||
<remove fileExtension=".webmanifest" />
|
<remove fileExtension=".webmanifest" />
|
||||||
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
|
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
|
||||||
<remove fileExtension=".woff2" />
|
<remove fileExtension=".woff2" />
|
||||||
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
|
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
|
||||||
<remove fileExtension=".woff" />
|
<remove fileExtension=".woff" />
|
||||||
<mimeMap fileExtension=".woff" mimeType="font/woff" />
|
<mimeMap fileExtension=".woff" mimeType="font/woff" />
|
||||||
</staticContent>
|
</staticContent>
|
||||||
|
|
||||||
<!-- Cache control per asset statici -->
|
<!-- Cache control per asset statici -->
|
||||||
<httpProtocol>
|
<httpProtocol>
|
||||||
<customHeaders>
|
<customHeaders>
|
||||||
<add name="X-Content-Type-Options" value="nosniff" />
|
<add name="X-Content-Type-Options" value="nosniff" />
|
||||||
<add name="X-Frame-Options" value="SAMEORIGIN" />
|
<add name="X-Frame-Options" value="SAMEORIGIN" />
|
||||||
<add name="X-XSS-Protection" value="1; mode=block" />
|
<add name="X-XSS-Protection" value="1; mode=block" />
|
||||||
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
|
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
|
||||||
</customHeaders>
|
</customHeaders>
|
||||||
</httpProtocol>
|
</httpProtocol>
|
||||||
|
|
||||||
<!-- Compressione gzip -->
|
<!-- Compressione gzip -->
|
||||||
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
|
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
|
||||||
|
|
||||||
<!-- Errori personalizzati -->
|
<!-- Errori personalizzati -->
|
||||||
<httpErrors errorMode="Custom" existingResponse="Replace">
|
<httpErrors errorMode="Custom" existingResponse="Replace">
|
||||||
<remove statusCode="404" />
|
<remove statusCode="404" />
|
||||||
<error statusCode="404" path="/index.html" responseMode="ExecuteURL" />
|
<error statusCode="404" path="/index.html" responseMode="ExecuteURL" />
|
||||||
</httpErrors>
|
</httpErrors>
|
||||||
|
|
||||||
</system.webServer>
|
</system.webServer>
|
||||||
|
</location>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -101,10 +101,11 @@ import Footer from '../components/Footer.astro';
|
|||||||
// ⚠️ Sostituire con l'URL reale dell'endpoint nella WebAPI
|
// ⚠️ Sostituire con l'URL reale dell'endpoint nella WebAPI
|
||||||
const API_ENDPOINT = 'https://api.smart-roots.net/api/contatti';
|
const API_ENDPOINT = 'https://api.smart-roots.net/api/contatti';
|
||||||
|
|
||||||
const form = document.getElementById('contact-form') as HTMLFormElement;
|
const form = document.getElementById('contact-form') as HTMLFormElement;
|
||||||
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement;
|
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement;
|
||||||
const okMsg = document.getElementById('form-ok') as HTMLElement;
|
const okMsg = document.getElementById('form-ok') as HTMLElement;
|
||||||
const errMsg = document.getElementById('form-err') as HTMLElement;
|
const errMsg = document.getElementById('form-err') as HTMLElement;
|
||||||
|
const errMsgDefault = errMsg.innerHTML;
|
||||||
|
|
||||||
form.addEventListener('submit', async function (e) {
|
form.addEventListener('submit', async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -144,11 +145,17 @@ import Footer from '../components/Footer.astro';
|
|||||||
form.reset();
|
form.reset();
|
||||||
okMsg.style.display = 'block';
|
okMsg.style.display = 'block';
|
||||||
okMsg.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
okMsg.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
} else if (response.status === 400) {
|
||||||
|
const data = await response.json().catch(() => null);
|
||||||
|
const msg = data?.Message || 'Dati non validi. Controlla i campi e riprova.';
|
||||||
|
errMsg.textContent = msg;
|
||||||
|
errMsg.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Errore invio form:', err);
|
console.error('Errore invio form:', err);
|
||||||
|
errMsg.innerHTML = errMsgDefault;
|
||||||
errMsg.style.display = 'block';
|
errMsg.style.display = 'block';
|
||||||
} finally {
|
} finally {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user