something's working...
This commit is contained in:
parent
5eba13e51e
commit
7d298eb48b
12 changed files with 2486 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.env
|
||||
1969
Cargo.lock
generated
Normal file
1969
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "solarmon"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8.6"
|
||||
chrono = "0.4.42"
|
||||
dotenv = "0.15.0"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
tokio = { version = "1.47", features = ["full"] }
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
tower = "0.5.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
anyhow = "1.0"
|
||||
tower-livereload = "0.9.6"
|
||||
39
src/details.json
Normal file
39
src/details.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"details": {
|
||||
"id": 2193535,
|
||||
"name": "S. Hautvast",
|
||||
"accountId": 30166,
|
||||
"status": "Active",
|
||||
"peakPower": 3.51,
|
||||
"lastUpdateTime": "2025-10-07",
|
||||
"installationDate": "2021-04-13",
|
||||
"ptoDate": null,
|
||||
"notes": "",
|
||||
"type": "Optimizers & Inverters",
|
||||
"location": {
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"address": "A. Moenstraat, 22",
|
||||
"address2": "",
|
||||
"zip": "1022 KJ",
|
||||
"timeZone": "Europe/Amsterdam",
|
||||
"countryCode": "NL",
|
||||
"latitude": "52.4069905",
|
||||
"longitude": "4.932790799999999"
|
||||
},
|
||||
"primaryModule": {
|
||||
"manufacturerName": "Hyundai Heavy Industries",
|
||||
"modelName": "HiE-S390VG",
|
||||
"maximumPower": 0.0,
|
||||
"temperatureCoef": 0.0
|
||||
},
|
||||
"alertQuantity": 0,
|
||||
"highestImpact": "0",
|
||||
"uris": {
|
||||
"DETAILS": "/site/2193535/details",
|
||||
"DATA_PERIOD": "/site/2193535/dataPeriod",
|
||||
"OVERVIEW": "/site/2193535/overview"
|
||||
},
|
||||
"publicSettings": { "isPublic": null }
|
||||
}
|
||||
}
|
||||
104
src/energy.json
Normal file
104
src/energy.json
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"energy": {
|
||||
"timeUnit": "QUARTER_OF_AN_HOUR",
|
||||
"unit": "Wh",
|
||||
"values": [
|
||||
{ "date": "2025-10-06 00:00:00", "value": null },
|
||||
{ "date": "2025-10-06 00:15:00", "value": null },
|
||||
{ "date": "2025-10-06 00:30:00", "value": null },
|
||||
{ "date": "2025-10-06 00:45:00", "value": null },
|
||||
{ "date": "2025-10-06 01:00:00", "value": null },
|
||||
{ "date": "2025-10-06 01:15:00", "value": null },
|
||||
{ "date": "2025-10-06 01:30:00", "value": null },
|
||||
{ "date": "2025-10-06 01:45:00", "value": null },
|
||||
{ "date": "2025-10-06 02:00:00", "value": null },
|
||||
{ "date": "2025-10-06 02:15:00", "value": null },
|
||||
{ "date": "2025-10-06 02:30:00", "value": null },
|
||||
{ "date": "2025-10-06 02:45:00", "value": null },
|
||||
{ "date": "2025-10-06 03:00:00", "value": null },
|
||||
{ "date": "2025-10-06 03:15:00", "value": null },
|
||||
{ "date": "2025-10-06 03:30:00", "value": null },
|
||||
{ "date": "2025-10-06 03:45:00", "value": null },
|
||||
{ "date": "2025-10-06 04:00:00", "value": null },
|
||||
{ "date": "2025-10-06 04:15:00", "value": null },
|
||||
{ "date": "2025-10-06 04:30:00", "value": null },
|
||||
{ "date": "2025-10-06 04:45:00", "value": null },
|
||||
{ "date": "2025-10-06 05:00:00", "value": null },
|
||||
{ "date": "2025-10-06 05:15:00", "value": null },
|
||||
{ "date": "2025-10-06 05:30:00", "value": null },
|
||||
{ "date": "2025-10-06 05:45:00", "value": null },
|
||||
{ "date": "2025-10-06 06:00:00", "value": null },
|
||||
{ "date": "2025-10-06 06:15:00", "value": null },
|
||||
{ "date": "2025-10-06 06:30:00", "value": null },
|
||||
{ "date": "2025-10-06 06:45:00", "value": null },
|
||||
{ "date": "2025-10-06 07:00:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 07:15:00", "value": null },
|
||||
{ "date": "2025-10-06 07:30:00", "value": null },
|
||||
{ "date": "2025-10-06 07:45:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 08:00:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 08:15:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 08:30:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 08:45:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 09:00:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 09:15:00", "value": 1.0 },
|
||||
{ "date": "2025-10-06 09:30:00", "value": 21.0 },
|
||||
{ "date": "2025-10-06 09:45:00", "value": 14.0 },
|
||||
{ "date": "2025-10-06 10:00:00", "value": 29.0 },
|
||||
{ "date": "2025-10-06 10:15:00", "value": 38.0 },
|
||||
{ "date": "2025-10-06 10:30:00", "value": 48.0 },
|
||||
{ "date": "2025-10-06 10:45:00", "value": 44.0 },
|
||||
{ "date": "2025-10-06 11:00:00", "value": 54.0 },
|
||||
{ "date": "2025-10-06 11:15:00", "value": 53.0 },
|
||||
{ "date": "2025-10-06 11:30:00", "value": 58.0 },
|
||||
{ "date": "2025-10-06 11:45:00", "value": 35.0 },
|
||||
{ "date": "2025-10-06 12:00:00", "value": 25.0 },
|
||||
{ "date": "2025-10-06 12:15:00", "value": 28.0 },
|
||||
{ "date": "2025-10-06 12:30:00", "value": 56.0 },
|
||||
{ "date": "2025-10-06 12:45:00", "value": 56.0 },
|
||||
{ "date": "2025-10-06 13:00:00", "value": 63.0 },
|
||||
{ "date": "2025-10-06 13:15:00", "value": 183.0 },
|
||||
{ "date": "2025-10-06 13:30:00", "value": 180.0 },
|
||||
{ "date": "2025-10-06 13:45:00", "value": 104.0 },
|
||||
{ "date": "2025-10-06 14:00:00", "value": 68.0 },
|
||||
{ "date": "2025-10-06 14:15:00", "value": 67.0 },
|
||||
{ "date": "2025-10-06 14:30:00", "value": 60.0 },
|
||||
{ "date": "2025-10-06 14:45:00", "value": 57.0 },
|
||||
{ "date": "2025-10-06 15:00:00", "value": 25.0 },
|
||||
{ "date": "2025-10-06 15:15:00", "value": 42.0 },
|
||||
{ "date": "2025-10-06 15:30:00", "value": 24.0 },
|
||||
{ "date": "2025-10-06 15:45:00", "value": 11.0 },
|
||||
{ "date": "2025-10-06 16:00:00", "value": 11.0 },
|
||||
{ "date": "2025-10-06 16:15:00", "value": 9.0 },
|
||||
{ "date": "2025-10-06 16:30:00", "value": 9.0 },
|
||||
{ "date": "2025-10-06 16:45:00", "value": 3.0 },
|
||||
{ "date": "2025-10-06 17:00:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 17:15:00", "value": 2.0 },
|
||||
{ "date": "2025-10-06 17:30:00", "value": 1.0 },
|
||||
{ "date": "2025-10-06 17:45:00", "value": 1.0 },
|
||||
{ "date": "2025-10-06 18:00:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 18:15:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 18:30:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 18:45:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 19:00:00", "value": null },
|
||||
{ "date": "2025-10-06 19:15:00", "value": null },
|
||||
{ "date": "2025-10-06 19:30:00", "value": null },
|
||||
{ "date": "2025-10-06 19:45:00", "value": null },
|
||||
{ "date": "2025-10-06 20:00:00", "value": null },
|
||||
{ "date": "2025-10-06 20:15:00", "value": null },
|
||||
{ "date": "2025-10-06 20:30:00", "value": 0.0 },
|
||||
{ "date": "2025-10-06 20:45:00", "value": null },
|
||||
{ "date": "2025-10-06 21:00:00", "value": null },
|
||||
{ "date": "2025-10-06 21:15:00", "value": null },
|
||||
{ "date": "2025-10-06 21:30:00", "value": null },
|
||||
{ "date": "2025-10-06 21:45:00", "value": null },
|
||||
{ "date": "2025-10-06 22:00:00", "value": null },
|
||||
{ "date": "2025-10-06 22:15:00", "value": null },
|
||||
{ "date": "2025-10-06 22:30:00", "value": null },
|
||||
{ "date": "2025-10-06 22:45:00", "value": null },
|
||||
{ "date": "2025-10-06 23:00:00", "value": null },
|
||||
{ "date": "2025-10-06 23:15:00", "value": null },
|
||||
{ "date": "2025-10-06 23:30:00", "value": null },
|
||||
{ "date": "2025-10-06 23:45:00", "value": null }
|
||||
]
|
||||
}
|
||||
}
|
||||
93
src/main.rs
Normal file
93
src/main.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use axum::{
|
||||
Extension, Json, Router,
|
||||
response::{ErrorResponse, Html},
|
||||
routing::get,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use dotenv::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv().ok();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/energy", get(energy))
|
||||
.route("/", get(index))
|
||||
.nest_service(
|
||||
"/static",
|
||||
ServiceBuilder::new().service(ServeDir::new("static")),
|
||||
)
|
||||
.layer(LiveReloadLayer::new());
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
println!("server on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn index() -> Html<String> {
|
||||
let html = format!(
|
||||
r#"html<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="0; url=/static/index.html">
|
||||
</head>"#
|
||||
);
|
||||
|
||||
Html(html)
|
||||
}
|
||||
|
||||
async fn energy() -> axum::response::Result<Json<EnergyResponse>, ErrorResponse> {
|
||||
let site_id = std::env::var("SITE_ID").unwrap();
|
||||
let api_key = std::env::var("API_KEY").unwrap();
|
||||
let utc_now = Utc::now().date_naive();
|
||||
|
||||
let url = format!(
|
||||
"https://monitoringapi.solaredge.com/site/{}/energy?timeUnit=QUARTER_OF_AN_HOUR&endDate={}&startDate={}&api_key={}",
|
||||
site_id, utc_now, utc_now, api_key,
|
||||
);
|
||||
|
||||
let mut energy_response = reqwest::get(url)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?
|
||||
.json::<EnergyResponse>()
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::from(e.to_string()))?;
|
||||
|
||||
let values: Vec<EnergyValue> = energy_response
|
||||
.energy
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| EnergyValue {
|
||||
date: format!("{}+02:00", v.date.replace(' ', "T")).to_string(),
|
||||
value: v.value,
|
||||
})
|
||||
.collect();
|
||||
|
||||
energy_response.energy.values = values;
|
||||
Ok(Json(energy_response))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct EnergyResponse {
|
||||
energy: Energy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Energy {
|
||||
timeUnit: String,
|
||||
unit: String,
|
||||
values: Vec<EnergyValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct EnergyValue {
|
||||
date: String,
|
||||
value: Option<f32>,
|
||||
}
|
||||
11
src/overview.json
Normal file
11
src/overview.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"overview": {
|
||||
"lastUpdateTime": "2025-10-07 11:00:14",
|
||||
"lifeTimeData": { "energy": 1.511646e7, "revenue": 0.0 },
|
||||
"lastYearData": { "energy": 876057.0 },
|
||||
"lastMonthData": { "energy": 38313.0 },
|
||||
"lastDayData": { "energy": 544.0 },
|
||||
"currentPower": { "power": 431.809 },
|
||||
"measuredBy": ""
|
||||
}
|
||||
}
|
||||
40
src/sites.json
Normal file
40
src/sites.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"sites": {
|
||||
"count": 1,
|
||||
"site": [
|
||||
{
|
||||
"id": 2193535,
|
||||
"name": "S. Hautvast",
|
||||
"accountId": 30166,
|
||||
"status": "Active",
|
||||
"peakPower": 3.51,
|
||||
"lastUpdateTime": "2025-10-07",
|
||||
"installationDate": "2021-04-13",
|
||||
"ptoDate": null,
|
||||
"notes": "",
|
||||
"type": "Optimizers & Inverters",
|
||||
"location": {
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"address": "A. Moenstraat, 22",
|
||||
"address2": "",
|
||||
"zip": "1022 KJ",
|
||||
"timeZone": "Europe/Amsterdam",
|
||||
"countryCode": "NL"
|
||||
},
|
||||
"primaryModule": {
|
||||
"manufacturerName": "Hyundai Heavy Industries",
|
||||
"modelName": "HiE-S390VG",
|
||||
"maximumPower": 390.0,
|
||||
"temperatureCoef": -0.34
|
||||
},
|
||||
"uris": {
|
||||
"DETAILS": "/site/2193535/details",
|
||||
"DATA_PERIOD": "/site/2193535/dataPeriod",
|
||||
"OVERVIEW": "/site/2193535/overview"
|
||||
},
|
||||
"publicSettings": { "isPublic": false }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
184
static/chart.js
Normal file
184
static/chart.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Declare the chart dimensions and margins.
|
||||
const width = 928;
|
||||
const height = 500;
|
||||
const marginTop = 20;
|
||||
const marginRight = 30;
|
||||
const marginBottom = 30;
|
||||
const marginLeft = 40;
|
||||
|
||||
async function chart() {
|
||||
const response = await fetch("/api/energy");
|
||||
if (!response.ok) {
|
||||
throw new Error(`Response status: ${response.status}`);
|
||||
}
|
||||
const energyResponse = await response.json();
|
||||
const data = energyResponse.energy.values;
|
||||
|
||||
// Declare the x (horizontal position) scale.
|
||||
const x = d3.scaleTime(
|
||||
d3.extent(data, (d) => new Date(d.date)),
|
||||
[marginLeft, width - marginRight],
|
||||
);
|
||||
|
||||
// Declare the y (vertical position) scale.
|
||||
const y = d3.scaleLinear(
|
||||
[0, d3.max(data, (d) => d.value)],
|
||||
[height - marginBottom, marginTop],
|
||||
);
|
||||
|
||||
// Declare the line generator.
|
||||
const line = d3
|
||||
.line()
|
||||
.x((d) => x(new Date(d.date)))
|
||||
.y((d) => {
|
||||
let v = d.value;
|
||||
if (v == null) {
|
||||
// no measurement from provider
|
||||
v = 0;
|
||||
}
|
||||
return y(v);
|
||||
});
|
||||
|
||||
// Create the SVG container.
|
||||
const svg = d3
|
||||
.select("body")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("viewBox", [0, 0, width, height])
|
||||
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
|
||||
|
||||
// Add the x-axis.
|
||||
svg
|
||||
.append("g")
|
||||
.attr("transform", `translate(0,${height - marginBottom})`)
|
||||
.call(
|
||||
d3
|
||||
.axisBottom(x)
|
||||
.ticks(width / 80)
|
||||
.tickSizeOuter(0),
|
||||
);
|
||||
|
||||
// Add the y-axis, remove the domain line, add grid lines and a label.
|
||||
svg
|
||||
.append("g")
|
||||
.attr("transform", `translate(${marginLeft},0)`)
|
||||
.call(d3.axisLeft(y).ticks(height / 48))
|
||||
.call((g) => g.select(".domain").remove())
|
||||
.call((g) =>
|
||||
g
|
||||
.selectAll(".tick line")
|
||||
.clone()
|
||||
.attr("x2", width - marginLeft - marginRight)
|
||||
.attr("stroke-opacity", 0.1),
|
||||
)
|
||||
.call((g) =>
|
||||
g
|
||||
.append("text")
|
||||
.attr("x", -marginLeft)
|
||||
.attr("y", 10)
|
||||
.attr("fill", "currentColor")
|
||||
.attr("text-anchor", "start")
|
||||
.text(`energy (${energyResponse.energy.unit})`),
|
||||
);
|
||||
|
||||
// Append a path for the line.
|
||||
svg
|
||||
.append("path")
|
||||
.attr("fill", "lightgreen")
|
||||
.attr("stroke", "steelblue")
|
||||
.attr("stroke-width", 2.0)
|
||||
.attr("d", line(energyResponse.energy.values));
|
||||
|
||||
// ============ HOVER FUNCTIONALITY ============
|
||||
|
||||
// Create a group for hover elements
|
||||
const hoverGroup = svg
|
||||
.append("g")
|
||||
.attr("class", "hover-group")
|
||||
.style("display", "none");
|
||||
|
||||
// Add vertical line (crosshair)
|
||||
const verticalLine = hoverGroup
|
||||
.append("line")
|
||||
.attr("stroke", "#999")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("stroke-dasharray", "4,4")
|
||||
.attr("y1", marginTop)
|
||||
.attr("y2", height - marginBottom);
|
||||
|
||||
// Add circle at intersection point
|
||||
const hoverCircle = hoverGroup
|
||||
.append("circle")
|
||||
.attr("r", 5)
|
||||
.attr("fill", "steelblue")
|
||||
.attr("stroke", "white")
|
||||
.attr("stroke-width", 2);
|
||||
|
||||
// Create tooltip
|
||||
const tooltip = d3.select("body").append("div").attr("class", "tooltip");
|
||||
|
||||
// Bisector for finding closest data point
|
||||
const bisect = d3.bisector((d) => new Date(d.date)).left;
|
||||
|
||||
// Create invisible overlay for capturing mouse events
|
||||
svg
|
||||
.append("rect")
|
||||
.attr("width", width - marginLeft - marginRight)
|
||||
.attr("height", height - marginTop - marginBottom)
|
||||
.attr("x", marginLeft)
|
||||
.attr("y", marginTop)
|
||||
.attr("fill", "none")
|
||||
.attr("pointer-events", "all")
|
||||
.on("mousemove", function (event) {
|
||||
const [mouseX] = d3.pointer(event);
|
||||
|
||||
// Convert mouse x position to date
|
||||
const xDate = x.invert(mouseX);
|
||||
|
||||
// Find closest data point
|
||||
const index = bisect(data, xDate, 1);
|
||||
const d0 = data[index - 1];
|
||||
const d1 = data[index];
|
||||
|
||||
// Choose closer point
|
||||
const d =
|
||||
d1 && xDate - new Date(d0.date) > new Date(d1.date) - xDate ? d1 : d0;
|
||||
|
||||
if (d) {
|
||||
const xPos = x(new Date(d.date));
|
||||
const yPos = y(d.value || 0);
|
||||
|
||||
// Show hover elements
|
||||
hoverGroup.style("display", null);
|
||||
|
||||
// Update vertical line position
|
||||
verticalLine.attr("x1", xPos).attr("x2", xPos);
|
||||
|
||||
// Update circle position
|
||||
hoverCircle.attr("cx", xPos).attr("cy", yPos);
|
||||
|
||||
// Format date
|
||||
const formatDate = d3.timeFormat("%H:%M");
|
||||
const formatValue = d3.format(",.2f");
|
||||
|
||||
// Update tooltip
|
||||
tooltip
|
||||
.style("display", "block")
|
||||
.html(
|
||||
`
|
||||
<strong>${formatDate(new Date(d.date))}</strong><br/>
|
||||
${formatValue(d.value || 0)} ${energyResponse.energy.unit}
|
||||
`,
|
||||
)
|
||||
.style("left", event.pageX + 15 + "px")
|
||||
.style("top", event.pageY - 28 + "px");
|
||||
}
|
||||
})
|
||||
.on("mouseout", function () {
|
||||
hoverGroup.style("display", "none");
|
||||
tooltip.style("display", "none");
|
||||
});
|
||||
}
|
||||
|
||||
chart();
|
||||
2
static/d3.v7.min.js
vendored
Normal file
2
static/d3.v7.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
static/index.html
Normal file
10
static/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="/static/d3.v7.min.js"></script>
|
||||
<script src="/static/chart.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
14
static/style.css
Normal file
14
static/style.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.tooltip {
|
||||
position: absolute;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, sans-serif;
|
||||
background: rgba(100, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue