23 changed files with 764 additions and 33 deletions
+6 -1
View File
@@ -18,6 +18,11 @@ export default defineConfig({
theme: 'github-light',
},
},
integrations: [preact(), mdx()],
integrations: [
preact({
compat: true,
}),
mdx(),
],
output: 'static',
})
BIN
View File
Binary file not shown.
+2
View File
@@ -21,12 +21,14 @@
"@fontsource/source-sans-pro": "^5.0.8",
"@fontsource/space-mono": "^5.0.20",
"@phosphor-icons/core": "^2.1.1",
"@phosphor-icons/react": "^2.1.7",
"@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7",
"astro": "5.1.0",
"fuse.js": "^7.0.0",
"katex": "^0.16.9",
"lucide-static": "^0.468.0",
"marked": "^15.0.6",
"preact": "^10.19.6",
"typescript": "^5.3.3"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

+116
View File
@@ -0,0 +1,116 @@
import { useEffect, useState } from 'preact/hooks'
import { Funnel } from '@phosphor-icons/react'
import { marked } from 'marked'
import type { Database } from '@/data/domande-esami.yaml'
const useRemoteValue = <T,>(url: string): T | null => {
const [value, setValue] = useState<T | null>(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(value => setValue(value))
}, [url])
return value
}
type Props = {
course: string
}
export const DomandeEsamiCourse = ({ course }: Props) => {
const database = useRemoteValue<Database>(`/domande-esami/api/${course}.json`)
if (!database) {
return <>Loading...</>
}
// @ts-ignore
requestIdleCallback(() => window.renderMath())
console.log(database)
const courseTags = [
...new Set(
database.questions
.filter(question => question.course === course)
.flatMap(question => question.tags)
),
]
const [selectedTags, setSelectedTags] = useState<string[]>([])
const filteredQuestions = database.questions
.filter(question => question.course === course)
.filter(
question => selectedTags.length === 0 || selectedTags.every(tag => question.tags.includes(tag))
)
return (
<>
<div class="grid-center">
<h3>
<a href="/domande-esami">Domande Esami</a>
</h3>
<h1>{database.names[course]}</h1>
</div>
<div class="card filter">
<div class="grid-h">
<Funnel />
<strong>Filtra Tag</strong>
</div>
<div class="flex-row-wrap">
{selectedTags.length === 0
? courseTags.map(tag => (
<div class="chip clickable" onClick={() => setSelectedTags([tag])}>
{tag}
</div>
))
: courseTags.map(tag => (
<div
class={
selectedTags.includes(tag)
? 'chip clickable'
: 'chip clickable disabled'
}
onClick={() =>
setSelectedTags(
selectedTags.includes(tag)
? selectedTags.filter(t => t !== tag)
: [...selectedTags, tag]
)
}
>
{tag}
</div>
))}
</div>
</div>
<div class="wide-card-list" id="questions">
{filteredQuestions.length === 0 ? (
<div class="grid-center">
<em>No questions found</em>
</div>
) : (
filteredQuestions.map(question => (
<div class="card">
<div
class="text"
dangerouslySetInnerHTML={{
__html: marked(question.content, { async: false }),
}}
/>
<div class="metadata">
{question.tags.map(tag => (
<div class="chip small">{tag}</div>
))}
</div>
</div>
))
)}
</div>
</>
)
}
@@ -0,0 +1,31 @@
---
title: Incidente del 2-7 Gennaio 2025
description: Riassunto dell'incidente che ha portato al guasto del server Poisson e delle azioni intraprese per ripristinare i servizi.
publishDate: 2025-01-07
---
# Incidente 2-7 Gennaio 2025
In questi giorni il server fisico principale del PHC, **Poisson**, non è stato operativo. Di conseguenza, i servizi ospitati direttamente su Poisson, come le pagine web degli utenti, l'accesso SSH e Gitea, sono rimasti offline. Al contrario, altri servizi web del PHC che non sono hostati su Poisson non hanno subito interruzioni. La buona notizia è che tutti i dati sono stati recuperati con successo e che l'infrastruttura è tornata pienamente operativa senza ulteriori complicazioni.
## Cosa è successo?
Sospettiamo che la causa principale dell'incidente sia stata una serie di brusche accensioni e spegnimenti, che hanno portato al guasto della scheda madre di Poisson. Il server non riusciva più ad avviarsi, neanche accedendo al BIOS, rendendo evidente che il problema era di natura hardware.
### Diagnosi e sostituzione
La mattina del 7 Gennaio, grazie alla disponibilità del dipartimento, abbiamo trovato una scheda madre di recupero, simile a quella originale ma meno potente. Questa è stata installata immediatamente, permettendo di riportare Poisson in funzione. Dal punto di vista software non è cambiato nulla: l'architettura della CPU è rimasta invariata, sebbene con meno core e la metà della RAM. Gli Hard Disks e una delle schede di rete sono stati riutilizzati senza problemi.
In un futuro prossimo si prevede di aggiornare la scheda madre con una più potente, per garantire le prestazioni di Poisson. Preghiamo per ora quindi di evitare di eseguire operazioni troppo pesanti sul server.
Tutto è stato rimontato nel consueto case bianco che ospita Poisson. Anche se ora è operativo, il server potrebbe essere considerato una nuova incarnazione, sollevando una riflessione interessante: **Poisson è ancora lo stesso server?** Proprio come nel paradosso della nave di Teseo, molti componenti di Poisson sono stati sostituiti nel corso degli anni. Dal 1992, pezzi come HDD, CPU, RAM e schede madri sono stati aggiornati per mantenere il server al passo con i tempi, mentre i dati e le funzionalità sono rimasti intatti.
![Visione vs Visione](/images/misc/meme-vision-vs-vision.webp)
## Ritorno alla normalità
Grazie all’intervento, i servizi principali sono tornati online. Gli utenti possono nuovamente accedere alle proprie pagine web, utilizzare SSH e lavorare su Gitea senza alcuna modifica ai dati o alle configurazioni precedenti.
Per chi avesse riscontrato difficoltà nell'accesso, invitiamo a contattare i macchinisti per ricevere supporto.
> Be a System Administrator, they said. It will be fun, they said 🤦‍♂️
+201
View File
@@ -0,0 +1,201 @@
names:
aritmetica: Aritmetica
geometria-1: Geometria 1
analisi-1: Analisi 1
algebra-1: Algebra 1
geometria-2: Geometria 2
analisi-2: Analisi 2
algebra-2: Algebra 2
analisi-3: Analisi 3
ricerca-operativa: Ricerca Operativa
fisica-3: Fisica 3
meccanica-razionale: Meccanica Razionale
istituzioni-di-geometria: Istituzioni di Geometria
istituzioni-di-analisi: Istituzioni di Analisi
istituzioni-di-algebra: Istituzioni di Algebra
istituzioni-di-fisica: Istituzioni di Fisica
istituzioni-di-probabilità: Istituzioni di Probabilità
geometria-e-topologia-differenziale: Geometria e Topologia Differenziale
analisi-armonica: Analisi Armonica
elementi-di-analisi-complessa: Elementi di Analisi Complessa
groups:
- id: triennale-anno-1
name: I Anno Triennale
items:
- aritmetica
- geometria-1
- analisi-1
- algebra-1
- id: triennale-anno-2
name: II Anno Triennale
items:
- geometria-2
- analisi-2
- algebra-1
- id: triennale-anno-3
name: III Anno Triennale
items:
- ricerca-operativa
- fisica-3
- meccanica-razionale
- id: istituzioni
name: Istituzioni
items:
- istituzioni-di-geometria
- istituzioni-di-analisi
- istituzioni-di-algebra
- istituzioni-di-fisica
- istituzioni-di-probabilità
- id: other
name: Esami a scelta
items:
- geometria-e-topologia-differenziale
- analisi-armonica
- elementi-di-analisi-complessa
questions:
- course: geometria-1
content: |
Dimostrare il teorema spettrale reale
tags:
- 2024
- course: gtd
content: |
Calcolare la curvatura gaussiana di una sfera di raggio $r$
tags:
- 2024
- course: geometria-1
content: |
Dimostrare che _l'area_ di un cerchio è $A = \pi r^2$
tags:
- 2023
- course: algebra-1
content: |
Dimostrare il teorema di Sylow
tags:
- 2024
- course: algebra-1
content: |
Dimostrare che ogni sottogruppo di un gruppo ciclico è ciclico
tags:
- 2023
- course: analisi-1
content: |
Dimostrare il teorema di Weierstrass
tags:
- 2024
- course: analisi-1
content: |
Dimostrare che se $f$ è continua su $[a,b]$, allora $f$ è limitata
tags:
- 2023
- course: analisi-2
content: |
Dimostrare il teorema di Stokes
tags:
- 2024
- course: analisi-2
content: |
Dimostrare il teorema di Green
tags:
- 2023
- course: analisi-3
content: |
Dimostrare il teorema di Cauchy
tags:
- 2024
- course: analisi-3
content: |
Dimostrare il teorema di Liouville
tags:
- 2023
- course: algebra-2
content: |
Dimostrare il teorema di struttura per i gruppi abeliani finiti
tags:
- 2024
- course: algebra-2
content: |
Dimostrare il teorema di struttura per i gruppi finiti
tags:
- 2023
- course: ricerca-operativa
content: |
Dimostrare il teorema di dualità per i problemi di programmazione lineare
tags:
- 2024
- course: geometria-topologia-differenziale
content: |
Dimostrare il teorema di Poincaré-Hopf
tags:
- 2024
- course: analisi-armonica
content: |
Dimostrare il teorema di Hahn-Banach
tags:
- 2024
- course: analisi-complessa
content: |
Dimostrare il teorema di Morera
tags:
- 2024
- course: analisi-complessa
content: |
Dimostrare il teorema degli zeri di Weierstrass
tags:
- 2023
- course: analisi-complessa
content: |
Dimostrare il teorema della mappa aperta
tags:
- 2022
- course: analisi-complessa
content: |
Dimostrare il teorema della mappa aperta
tags:
- 2021
- course: geometria-2
content: |
Dimostrare il teorema di Van Kampen
tags:
- 2024
- Frigerio
- course: geometria-2
content: |
Definizione di gruppo fondamentale
tags:
- 2023
- Frigerio
- course: geometria-2
content: |
Dimostrare il teorema Cauchy
tags:
- 2022
- Abate
+35 -5
View File
@@ -32,11 +32,41 @@
- fullName: Illya Serdyuk
entranceDate: 2020
description: Bla bla Void Linux
social:
github: https://github.com/Kratacoa
- fullName: Francesco Manicastri
entranceDate: 2022
exitDate: 2024
description: Bla bla Keenan Crane
# Vecchi Macchinisti
# - fullName: Francesco Manicastri
# entranceDate: 2022
# exitDate: 2024
# description: Bla bla Keenan Crane
- fullName: Cristiano Cricci
entranceDate: 2010
exitDate: 2019
social:
website: https://poisson.phc.dm.unipi.it/~cricci/
- fullName: Tommaso Biannucci
entranceDate: 2019
exitDate: 2022
social:
github: https://gitlab.com/churli
website: https://churli.gitlab.io/
- fullName: Letizia D'Achille
entranceDate: 2018
exitDate: 2022
description: Appassionata di crittografia, teoria dei codici e matematica computazionale.
social:
github: https://github.com/letizia-dachille
website: https://letizia-dachille.github.io/
linkedin: https://www.linkedin.com/in/letizia-dachille/
- fullName: Emiliano Rago
entranceDate: 2000
exitDate: 2006
description: Traviato da Linux in età troppo giovane ha difficoltà ad usare il mouse ma ama gli shortcuts con una decina di tasti.
social:
linkedin: https://www.linkedin.com/in/emiliano-rago-1a8018109/
+23
View File
@@ -15,3 +15,26 @@ declare module '@/data/macchinisti.yaml' {
const value: Macchinista[]
export default value
}
declare module '@/data/domande-esami.yaml' {
export type Question = {
course: string
content: string
tags: string[]
}
export type Group = {
id: string
name: string
items: Array<string>
}
export type Database = {
names: Record<string, string>
groups: Group[]
questions: Question[]
}
const value: Database
export default value
}
+7 -2
View File
@@ -44,7 +44,7 @@ const { title, description, thumbnail, pageTags } = Astro.props
<script>
import renderMathInElement from 'katex/contrib/auto-render'
document.addEventListener('DOMContentLoaded', function () {
const renderMath = () => {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
@@ -54,7 +54,12 @@ const { title, description, thumbnail, pageTags } = Astro.props
],
throwOnError: false,
})
})
}
document.addEventListener('DOMContentLoaded', () => renderMath())
// @ts-ignore
window.renderMath = renderMath
</script>
<script is:inline>
+28
View File
@@ -0,0 +1,28 @@
---
import type { GetStaticPaths } from 'astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
import { marked } from 'marked'
import database from '@/data/domande-esami.yaml'
import PhosphorIcon from '@/components/PhosphorIcon.astro'
import { DomandeEsamiCourse } from '@/client/DomandeEsamiCourse'
export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({
params: { course },
}))
}) satisfies GetStaticPaths
const { course } = Astro.params
---
<BaseLayout title="Domande Esami | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<DomandeEsamiCourse client:load course={course} />
</main>
<Footer />
</BaseLayout>
+11
View File
@@ -0,0 +1,11 @@
import type { APIRoute } from 'astro'
import database from '@/data/domande-esami.yaml'
export const GET: APIRoute = ({}) => {
return new Response(JSON.stringify(database), {
headers: {
'content-type': 'application/json',
},
})
}
@@ -0,0 +1,24 @@
import type { APIRoute, GetStaticPaths } from 'astro'
import database from '@/data/domande-esami.yaml'
export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({
params: { course },
}))
}) satisfies GetStaticPaths
export const GET: APIRoute = ({ params: { course } }) => {
return new Response(
JSON.stringify({
groups: [],
names: Object.fromEntries(Object.entries(database.names).filter(([key]) => key === course)),
questions: database.questions.filter(question => question.course === course),
}),
{
headers: {
'content-type': 'application/json',
},
}
)
}
+59
View File
@@ -0,0 +1,59 @@
---
import { PhosphorIcon } from '@/client/Icon'
import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
import database from '@/data/domande-esami.yaml'
const courseQuestionCounts = Object.fromEntries(
database.questions.reduce((acc, question) => {
acc.set(question.course, (acc.get(question.course) || 0) + 1)
return acc
}, new Map())
)
---
<BaseLayout title="Domande Esami | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<h1>Domande Esami</h1>
{
database.groups.map(group => (
<details open>
<summary>
<h2 id={group.id}>
<div class="details-closed">
<PhosphorIcon name="caret-down" />
</div>
<div class="details-openned">
<PhosphorIcon name="caret-up" />
</div>
{group.name}
</h2>
</summary>
<div class="wide-card-list">
{group.items.map(course => (
<a href={`/domande-esami/${course}`}>
<div class="card">
<h2>{database.names[course]}</h2>
<div class="text">
<p>{courseQuestionCounts[course] || 0} domande</p>
</div>
</div>
</a>
))}
</div>
</details>
))
}
<h3>Come Contribuire</h3>
<div class="card large">
<div class="text">
<p>Se hai raccolte di domande da condividere, puoi...</p>
</div>
</div>
</main>
<Footer />
</BaseLayout>
+1 -1
View File
@@ -10,7 +10,7 @@ const news = await getCollection('news')
<h1><a href="/notizie">Notizie</a></h1>
<div class="card-list">
{
news.map(newsItem => (
news.toReversed().map(newsItem => (
<div class="card">
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
+125 -24
View File
@@ -14,7 +14,7 @@
width: 22px;
height: 22px;
display: grid;
display: grid inline;
place-content: center;
}
@@ -79,29 +79,6 @@
}
}
.flex-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
.search-results {
width: 100%;
@@ -592,6 +569,13 @@
}
}
.metadata {
display: grid;
grid-auto-flow: column;
justify-content: start;
gap: 0.5rem;
}
@media screen and (max-width: $screen-desktop-min) {
padding: 0.9rem;
@@ -609,6 +593,37 @@
}
}
.chip {
user-select: none;
display: grid;
place-content: center;
place-items: center;
background: #0003;
padding: 0 0.25rem;
border-radius: 0.25rem;
line-height: 1.5;
font-size: 16px;
font-weight: 500;
&.small {
font-size: 13px;
font-weight: 600;
}
&.disabled {
color: #0005;
background: #0002;
}
}
a:has(> .card) {
display: contents;
}
//
// Card List
//
@@ -875,4 +890,90 @@
}
}
}
.wide-card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(auto, 30rem));
gap: 2rem;
padding: 2rem;
width: 100%;
justify-content: center;
.text > * {
max-width: none;
}
@media screen and (max-width: $screen-desktop-min) {
grid-template-columns: 1fr;
padding: 0;
gap: 1rem;
}
}
.filter {
min-width: 15rem;
}
.flex-column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.flex-row-wrap {
display: flex;
flex-direction: row;
gap: 0.5rem;
flex-wrap: wrap;
}
.grid-h {
display: grid;
grid-auto-flow: column;
justify-content: start;
align-items: center;
gap: 0.5rem;
}
.grid-v {
display: grid;
justify-items: start;
align-content: start;
grid-auto-flow: row;
gap: 0.5rem;
}
.grid-center {
display: grid;
place-content: center;
place-items: center;
gap: 0.5rem;
grid-auto-flow: row;
}
.clickable {
cursor: pointer;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
}
+43
View File
@@ -129,4 +129,47 @@
justify-self: center;
}
}
details {
width: 100%;
summary {
display: grid;
place-content: center;
place-items: center;
list-style: none;
&::-webkit-details-marker {
display: none;
}
}
.details-openned,
.details-closed {
display: contents;
}
&:not([open]) {
.details-openned {
display: none;
}
}
&[open] {
summary {
padding-bottom: 1rem;
}
.details-closed {
display: none;
}
}
@media screen and (max-width: $screen-desktop-min) {
summary {
place-content: stretch;
place-items: stretch;
}
}
}
}
+4
View File
@@ -208,6 +208,10 @@ body {
@media screen and (max-width: $screen-desktop-min) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
+29
View File
@@ -437,6 +437,35 @@
}
}
.domande-esami {
background: hsl(45, 100%, 95%);
--card-base: hsl(45, 100%, 80%);
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 4rem 0;
gap: 2rem;
.search {
max-width: 80ch;
}
button {
background: hsl(45, 100%, 72%);
}
.card a {
color: color-mix(in srgb, var(--card-base-internal), #000 80%);
}
}
}
// .login {
// background: #ddfaff;
+19
View File
@@ -33,6 +33,18 @@
@layer typography {
@include geometric-headings;
strong {
font-weight: 600;
}
em {
font-style: italic;
}
.text-center {
text-align: center;
}
.text {
// text-align: justify;
// hyphens: auto;
@@ -129,6 +141,9 @@
display: block;
margin: 0 auto;
width: 50ch;
max-width: 100%;
@include neo-brutalist-card(2px);
&.fill {
@@ -201,6 +216,10 @@
}
}
li + li {
margin-top: 0.5rem;
}
a,
a:visited {
color: var(--zone-color, #1e6733);