kirin-ri / memo

0 stars 0 forks source link

azure #27

Open kirin-ri opened 4 months ago

kirin-ri commented 4 months ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 4 months ago

Error

The name 'cpu_cost' does not refer to any known table, tabular variable or function.

kirin-ri commented 4 months ago
let cpu_rate_per_core_hour = 0.048;  // 1 vCPUあたりの料金(USD/時間)
let mem_rate_per_gb_hour = 0.012;    // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算
let cpu_cost = KubePodInventory
| summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace
| extend CPUHours = AvgCPU * (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9
| extend CPUCost = CPUHours * cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算
let mem_cost = KubePodInventory
| summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace
| extend MemoryHours = AvgMemory * (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 * 1024 * 1024)
| extend MemoryCost = MemoryHours * mem_rate_per_gb_hour;

// CPUとメモリの料金を統合
cpu_cost
| join kind=inner (mem_cost) on Namespace
| project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost
kirin-ri commented 4 months ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | summarize AvgCPU=avg(TotalCpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | summarize AvgMemory=avg(TotalMemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 4 months ago

let cpu_rate_per_core_hour = 0.048; // 1 vCPUあたりの料金(USD/時間) let mem_rate_per_gb_hour = 0.012; // 1 GiB RAMあたりの料金(USD/時間)

// 各名前空間のCPU使用量の料金計算 let cpu_cost = KubePodInventory | where TimeGenerated >= ago(30d) | summarize AvgCPU=avg(CpuUsageNanoCores) by Namespace | extend CPUHours = AvgCPU (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / 1e9 | extend CPUCost = CPUHours cpu_rate_per_core_hour;

// 各名前空間のメモリ使用量の料金計算 let mem_cost = KubePodInventory | where TimeGenerated >= ago(30d) | summarize AvgMemory=avg(MemoryUsageBytes) by Namespace | extend MemoryHours = AvgMemory (datetime_diff('hour', max(TimeGenerated), min(TimeGenerated))) / (1024 1024 1024) | extend MemoryCost = MemoryHours mem_rate_per_gb_hour;

// CPUとメモリの料金を統合 cpu_cost | join kind=inner (mem_cost) on Namespace | project Namespace, CPUCost, MemoryCost, TotalCost = CPUCost + MemoryCost

kirin-ri commented 4 months ago

TenantId c88bf5c1-8f71-4cea-ac74-f7c80746b79e SourceSystem Containers TimeGenerated [UTC] 2024-07-24T01:19:58Z Computer aks-nodepool1-20355066-vmss000003 ClusterId /subscriptions/234a74a7-bd43-4159-906b-bee68f8574be/resourceGroups/rg-i-dna-aks/providers/Microsoft.ContainerService/managedClusters/I-DNA-Cluster ContainerCreationTimeStamp [UTC] 2024-03-22T07:37:02Z PodUid f5da154d-a68a-4d2d-a9c9-03a18afa0cc6 PodCreationTimeStamp [UTC] 2024-03-22T07:36:54Z ContainerRestartCount 0 PodRestartCount 0 PodStartTime [UTC] 2024-03-22T07:36:54Z ControllerKind DaemonSet ControllerName csi-blob-node ContainerStatus running ContainerID cce984b8b3981ebc47dd752747a2458870c3654ef088a7323ce6751e4b930a3e ContainerName f5da154d-a68a-4d2d-a9c9-03a18afa0cc6/blob Name csi-blob-node-9lfwz PodLabel [{"app":"csi-blob-node","controller-revision-hash":"6fb8776f9d","kubernetes.azure.com\/managedby":"aks","pod-template-generation":"1"}] Namespace kube-system PodStatus Running ClusterName I-DNA-Cluster PodIp 10.224.0.4 ContainerLastStatus {} Type KubePodInventory _ResourceId /subscriptions/234a74a7-bd43-4159-90

kirin-ri commented 4 months ago

KubePodInventory | distinct ContainerID, Namespace | join kind=innerunique ( ContainerLog | where _IsBillable == true | summarize BillableDataBytes = sum(_BilledSize) by ContainerID ) on ContainerID | union ( KubePodInventory | distinct ContainerID, Namespace | join kind=innerunique ( ContainerLogV2 | project-rename ContainerID = ContainerId | where _IsBillable == true | summarize BillableDataBytes = sum(_BilledSize) by ContainerID ) on ContainerID ) | summarize Total=sum(BillableDataBytes) by Namespace | render piechart

kirin-ri commented 4 months ago
import subprocess
import pandas as pd

# 名前空間を指定
namespace = "<your-namespace>"

# kubectlコマンドを実行してリソース使用量を取得
kubectl_command = f"kubectl top pod --namespace {namespace}"
result = subprocess.run(kubectl_command.split(), capture_output=True, text=True)

# コマンド出力を処理
data = []
lines = result.stdout.split("\n")
for line in lines[1:]:
    if line:
        parts = line.split()
        pod_name = parts[0]
        cpu_usage = parts[1]
        memory_usage = parts[2]
        data.append({"pod_name": pod_name, "cpu_usage": cpu_usage, "memory_usage": memory_usage})

# データフレームに変換
df = pd.DataFrame(data)

# 単位変換 (mCPU to CPU, Mi to GiB)
df['cpu_usage'] = df['cpu_usage'].apply(lambda x: float(x.replace('m', '')) / 1000)
df['memory_usage'] = df['memory_usage'].apply(lambda x: float(x.replace('Mi', '')) / 1024)

# 料金計算用のレート (仮定の値を使用)
cpu_rate_per_core_hour = 0.048  # 1 vCPUあたりの料金(USD/時間)
mem_rate_per_gb_hour = 0.012    # 1 GiB RAMあたりの料金(USD/時間)

# 使用時間を仮定 (1時間の使用量)
usage_hours = 1

# CPUとメモリのコストを計算
df['cpu_cost'] = df['cpu_usage'] * cpu_rate_per_core_hour * usage_hours
df['memory_cost'] = df['memory_usage'] * mem_rate_per_gb_hour * usage_hours

# 合計コストを計算
df['total_cost'] = df['cpu_cost'] + df['memory_cost']

# 名前空間ごとのコストを集計
namespace_costs = df.sum()

print(namespace_costs)
kirin-ri commented 3 months ago
(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
unrecognized arguments: --enable-cost-analysis

Examples from AI knowledge base:
az aks update --resource-group MyResourceGroup --name MyManagedCluster --load-balancer-managed-outbound-ip-count 2
Update a kubernetes cluster with standard SKU load balancer to use two AKS created IPs for the load balancer outbound connection usage.

az aks update --resource-group MyResourceGroup --name MyManagedCluster --api-server-authorized-ip-ranges 0.0.0.0/32
Restrict apiserver traffic in a kubernetes cluster to agentpool nodes.

az version
Show the versions of Azure CLI modules and extensions in JSON format by default or format configured by --output (autogenerated)

https://docs.microsoft.com/en-US/cli/azure/aks#az_aks_update
Read more about the command in reference docs
kirin-ri commented 3 months ago

表示するコスト データがありません クラスターでコスト分析アドオンが有効になっていることを確認してください。アドオンが有効になっている場合は、正しいフィルターが選択されていることを確認してください。

kirin-ri commented 3 months ago

az aks enable-addons --resource-group --name --addons cost-management

kirin-ri commented 3 months ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks enable-addons --resource-group rg-i-dna-aks --name I-DNA-Cluster --addons cost-management Invalid addon name: cost-management. (base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis unrecognized arguments: --enable-cost-analysis

kirin-ri commented 3 months ago

az monitor log-analytics workspace list --resource-group --query "[].{Name:name, ID:id}" -o table

kirin-ri commented 3 months ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az monitor log-analytics workspace list --resource-group --query "[].{Name:name, ID:id}" -o table argument --resource-group/-g: expected one argument

kirin-ri commented 3 months ago

az monitor log-analytics workspace list --resource-group rg-i-dna-aks --query "[].{Name:name, ID:id}" -o table

kirin-ri commented 3 months ago

az monitor log-analytics workspace create --resource-group rg-i-dna-aks --workspace-name myLogAnalyticsWorkspace

kirin-ri commented 3 months ago

(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis unrecognized arguments: --enable-cost-analysis

Examples from AI knowledge base: az aks update --resource-group MyResourceGroup --name MyManagedCluster --load-balancer-managed-outbound-ip-count 2 Update a kubernetes cluster with standard SKU load balancer to use two AKS created IPs for the load balancer outbound connection usage.

az aks update --resource-group MyResourceGroup --name MyManagedCluster --api-server-authorized-ip-ranges 0.0.0.0/32 Restrict apiserver traffic in a kubernetes cluster to agentpool nodes.

az version Show the versions of Azure CLI modules and extensions in JSON format by default or format configured by --output (autogenerated)

https://docs.microsoft.com/en-US/cli/azure/aks#az_aks_update Read more about the command in reference docs

kirin-ri commented 3 months ago
(base) q_li@vm-I-DNA-daas-2:~/Desktop/catalog-web-app$ az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
The command failed with an unexpected error. Here is the traceback:
module 'azure.core.pipeline.policies' has no attribute 'SensitiveHeaderCleanupPolicy'
Traceback (most recent call last):
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/knack/cli.py", line 233, in invoke
    cmd_result = self.invocation.execute(args)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 664, in execute
    raise ex
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 731, in _run_jobs_serially
    results.append(self._run_job(expanded_arg, cmd_copy))
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 701, in _run_job
    result = cmd_copy(params)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/__init__.py", line 334, in __call__
    return self.handler(*args, **kwargs)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/command_operation.py", line 112, in handler
    client = self.client_factory(self.cli_ctx, command_args) if self.client_factory else None
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/command_modules/acs/_client_factory.py", line 27, in cf_managed_clusters
    return get_container_service_client(cli_ctx).managed_clusters
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/command_modules/acs/_client_factory.py", line 19, in get_container_service_client
    return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_CONTAINERSERVICE, subscription_id=subscription_id)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/client_factory.py", line 83, in get_mgmt_service_client
    client, _ = _get_mgmt_service_client(cli_ctx, client_type, subscription_id=subscription_id,
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/cli/core/commands/client_factory.py", line 254, in _get_mgmt_service_client
    client = client_type(credential, subscription_id, **client_kwargs)
  File "/home/uenv/q_li/.local/lib/python3.10/site-packages/azure/mgmt/containerservice/_container_service_client.py", line 101, in __init__
    policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
AttributeError: module 'azure.core.pipeline.policies' has no attribute 'SensitiveHeaderCleanupPolicy'
To check existing issues, please visit: https://github.com/Azure/azure-cli/issues
kirin-ri commented 3 months ago
az aks update --resource-group rg-i-dna-aks --name I-DNA-Cluster --enable-cost-analysis
(OperationNotAllowed) The 'costAnalysis' feature cannot be used unless the cluster SKU.Tier is at least 'Standard'
Code: OperationNotAllowed
Message: The 'costAnalysis' feature cannot be used unless the cluster SKU.Tier is at least 'Standard'``
kirin-ri commented 3 months ago

az aks show --resource-group rg-i-dna-aks --name I-DNA-Cluster --query "sku.tier"

kirin-ri commented 3 months ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  metricsList = []
kirin-ri commented 3 months ago
const metricsList: MetricsList[] = [
  {
    subCategory: 'Performance',
    metrics: [
      { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
      { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
      { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
    ]
  },
  {
    subCategory: 'Availability',
    metrics: [
      { name: 'Uptime', provided: true, subCategory: 'Availability' },
      { name: 'Downtime', provided: false, subCategory: 'Availability' }
    ]
  },
  {
    subCategory: 'Security',
    metrics: [
      { name: 'Vulnerability Scans', provided: true, subCategory: 'Security' },
      { name: 'Intrusion Detection', provided: false, subCategory: 'Security' }
    ]
  }
];
kirin-ri commented 3 months ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    },
    {
      subCategory: 'Security',
      metrics: [
        { name: 'Vulnerability Scans', provided: true, subCategory: 'Security' },
        { name: 'Intrusion Detection', provided: false, subCategory: 'Security' }
      ]
    }
  ];

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 3 months ago
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    }
  ];

  const handleSubmenu = (e: MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 3 months ago
import React from 'react';
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

const defaultCategories = [
  { name: 'Performance', icon: 'fa-tachometer-alt' },
  { name: 'Availability', icon: 'fa-clock' }
];

const poppers: { [key: string]: any } = {};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' }
      ]
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' }
      ]
    }
  ];

  const handleSubmenu = (e: React.MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/new-metrics-list" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
    </>
  );
}
export default AppMenu;
kirin-ri commented 3 months ago
function TopPage() {

  const props = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <div className="content-wrapper top-page">
      <section className="page-cover page-cover-big">
        <div className="page-cover-title-frame">
          <h1>指標一覧</h1>
        </div>
      </section>
      <section className="content-header">
        <div className="content-header-left">
          <h1>指標一覧</h1>
          <div className='sub'>見たい指標を選択してください</div>
        </div>
      </section>
    </div>
        <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{title}</h1>
        </section>
        {/* Content Header (Page header) */}
        <section className="content-header">
          <div className="content-header-left">

            <h1>{title}</h1>
          </div>
        </section>
      {/* Metrics List (Grid) */}
      {Spacer({ size: 50 })}
      {view(props as any)}
    </div>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <>
        <section className="content">
          <div className="list-wrapper">
            {GridMetricsList(props)}
          </div>
        </section>
      </>
    );
  }
};

// API List
function GridMetricsList(props: MetricsListProps) {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
}

// Metrics Card
function MetricsCard(props: MetricsListProps, metrics: MetricsList) {
  return (
    <>
      <div>
        <div className="card card-tertiary card-collapse-metrics">
          <div className="card-header">
            <div
              className="name-font"
              style={{
                fontSize: `clamp(0.65rem, ${
                  30 / metrics.subCategory.length
                }vw, 1rem)`,
              }}
            >
              {metrics.subCategory}
            </div>
          </div>
          <div className="card-body" id={metrics.subCategory}>
            {
              <>
                {metrics.metrics.map((val) => {
                  return (
                    <a
                      href={`#/metrics-details/${val.name}`}
                      className="link"
                    >
                      <p>
                        <i
                          className={`fa fa-circle ${
                            val.provided
                              ? 'metrics-provide-icon'
                              : 'metrics-no-provide-icon'
                          }`}
                          aria-hidden="true"
                        />
                        &nbsp;
                        {val.name}
                      </p>
                    </a>
                  );
                })}
              </>
            }
          </div>
        </div>
      </div>
    </>
  );
}
export default TopPage;
kirin-ri commented 3 months ago

ERROR in src/app2/components/pages/topPage.tsx:98:39 TS7006: Parameter 'val' implicitly has an 'any' type.

kirin-ri commented 3 months ago
import React from 'react';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const metricsList: MetricsList[] = []; // Initialize with actual data
  const setMetricsList = () => {}; // Replace with actual setter function
  const category = ''; // Replace with actual state
  const setCategory = () => {}; // Replace with actual setter function
  const isCategoryTxtDisplayed = false; // Replace with actual state
  const setCategoryTxtDisplayed = () => {}; // Replace with actual setter function
  const text = ''; // Replace with actual state
  const setText = () => {}; // Replace with actual setter function
  const isAnalyzeBtnDisabled = false; // Replace with actual state
  const setAnalyzeBtnDisabled = () => {}; // Replace with actual setter function
  const enableCreatingMetrics = false; // Replace with actual state

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className="sub">見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{props.category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{props.category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 3 months ago
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const [metricsList, setMetricsList] = useState<MetricsList[]>([
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' },
      ],
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' },
      ],
    },
  ]);

  const [category, setCategory] = useState('Performance');
  const [isCategoryTxtDisplayed, setCategoryTxtDisplayed] = useState(true);
  const [text, setText] = useState('Sample Text');
  const [isAnalyzeBtnDisabled, setAnalyzeBtnDisabled] = useState(false);
  const [enableCreatingMetrics, setEnableCreatingMetrics] = useState(true);

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className="sub">見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 3 months ago
import React, { useState } from 'react';
import { Link } from 'react-router-dom';

type MetricsListProps = {
  metricsList: MetricsList[];
  setMetricsList: React.Dispatch<React.SetStateAction<MetricsList[]>>;
  category: string;
  setCategory: React.Dispatch<React.SetStateAction<string>>;
  isCategoryTxtDisplayed: boolean;
  setCategoryTxtDisplayed: React.Dispatch<React.SetStateAction<boolean>>;
  // graph: any; // Uncomment if needed
  // setGraph: any; // Uncomment if needed
  text: string;
  setText: React.Dispatch<React.SetStateAction<string>>;
  isAnalyzeBtnDisabled: boolean;
  setAnalyzeBtnDisabled: React.Dispatch<React.SetStateAction<boolean>>;
  enableCreatingMetrics: boolean;
};

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};

type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

function TopPage() {
  const [metricsList, setMetricsList] = useState<MetricsList[]>([
    {
      subCategory: 'Performance',
      metrics: [
        { name: 'CPU Usage', provided: true, subCategory: 'Performance' },
        { name: 'Memory Usage', provided: false, subCategory: 'Performance' },
        { name: 'Disk I/O', provided: true, subCategory: 'Performance' },
      ],
    },
    {
      subCategory: 'Availability',
      metrics: [
        { name: 'Uptime', provided: true, subCategory: 'Availability' },
        { name: 'Downtime', provided: false, subCategory: 'Availability' },
      ],
    },
  ]);

  const [category, setCategory] = useState('Performance');
  const [isCategoryTxtDisplayed, setCategoryTxtDisplayed] = useState(true);
  const [text, setText] = useState('Sample Text');
  const [isAnalyzeBtnDisabled, setAnalyzeBtnDisabled] = useState(false);
  const [enableCreatingMetrics, setEnableCreatingMetrics] = useState(true);

  const props: MetricsListProps = {
    metricsList,
    setMetricsList,
    category,
    setCategory,
    isCategoryTxtDisplayed,
    setCategoryTxtDisplayed,
    // graph,
    // setGraph,
    text,
    setText,
    isAnalyzeBtnDisabled,
    setAnalyzeBtnDisabled,
    enableCreatingMetrics,
  };

  return (
    <>
      <div className="content-wrapper top-page">
        <section className="page-cover page-cover-big">
          <div className="page-cover-title-frame">
            <h1>指標一覧</h1>
          </div>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>指標一覧</h1>
            <div className='sub'>見たい指標を選択してください</div>
          </div>
        </section>
      </div>
      <div className="content-wrapper metrics-list">
        <section className="page-cover">
          <h1>{category}</h1>
        </section>
        <section className="content-header">
          <div className="content-header-left">
            <h1>{category}</h1>
          </div>
        </section>
        {Spacer({ size: 50 })}
        {view(props)}
        <div className="columns">
          <div className="column">
            <div className="column-title">Column 1</div>
            <div className="column-item">Item 1</div>
            <div className="column-item">Item 2</div>
            <div className="column-item">Item 3</div>
          </div>
          <div className="column">
            <div className="column-title">Column 2</div>
            <div className="column-item">Item 1</div>
            <div className="column-item">Item 2</div>
            <div className="column-item">Item 3</div>
          </div>
        </div>
      </div>
    </>
  );
}

const view = (props: MetricsListProps) => {
  if (props.metricsList.length) {
    return (
      <section className="content">
        <div className="list-wrapper">
          {GridMetricsList(props)}
        </div>
      </section>
    );
  }
  return null;
};

const GridMetricsList = (props: MetricsListProps) => {
  return (
    <>
      {props.metricsList.map((metrics: MetricsList) =>
        MetricsCard(props, metrics)
      )}
    </>
  );
};

const MetricsCard = (props: MetricsListProps, metrics: MetricsList) => {
  return (
    <div>
      <div className="card card-tertiary card-collapse-metrics">
        <div className="card-header">
          <div
            className="name-font"
            style={{
              fontSize: `clamp(0.65rem, ${
                30 / metrics.subCategory.length
              }vw, 1rem)`,
            }}
          >
            {metrics.subCategory}
          </div>
        </div>
        <div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    <i
                      className={`fa fa-circle ${
                        val.provided
                          ? 'metrics-provide-icon'
                          : 'metrics-no-provide-icon'
                      }`}
                      aria-hidden="true"
                    />
                    &nbsp;
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
        </div>
      </div>
    </div>
  );
};

const Spacer = ({ size }: { size: number }) => (
  <div style={{ height: size }} />
);

export default TopPage;
kirin-ri commented 3 months ago
.content-wrapper {
  padding: 20px;
}

.page-cover-title-frame h1 {
  font-size: 2rem;
  margin: 0;
  padding: 20px;
  background-color: black;
  color: white;
  text-align: center;
}

.content-header-left h1 {
  font-size: 1.5rem;
  margin-bottom: 10px;
}

.content-header-left .sub {
  font-size: 1rem;
  color: gray;
}

.columns {
  display: flex;
  justify-content: space-around;
  margin-top: 30px;
}

.column {
  width: 45%;
}

.column-title {
  background-color: black;
  color: white;
  padding: 10px;
  font-size: 1.2rem;
  text-align: center;
}

.column-item {
  background-color: white;
  color: black;
  padding: 10px;
  margin: 5px 0;
  border: 1px solid black;
  text-align: center;
}
kirin-ri commented 3 months ago
.metrics-list .list-wrapper {
  /* display: flex; */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: start;
  gap: 15vw;
  padding-left: 5vw;
  padding-right: 5vw;
}
kirin-ri commented 3 months ago
/* COLLAPSE */
.metrics-list .card-collapse-metrics .card-header {
  display: grid;
  /* grid-template-columns: 1fr auto; */
  cursor: pointer;
  position: relative;
  /* white-space: nowrap; */
}

.metrics-list .card-collapse-metrics .card-header:after {
  content: ' ';
  display: block;
  transition: transform linear 0.3s;
  text-align: center;
  font-size: 1rem;
}

.metrics-list .card-collapse-metrics .card-header.collapsed:after {
  content: '+';
  display: block;
  transition: transform linear 0.3s;
  font-size: 1rem;
}

.metrics-list .card-header .name-font {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  padding-left: 5%;
  white-space: nowrap;
  text-align: center;
}

.metrics-list .centering-btn {
  text-align: center;
}

.metrics-list .btn-datainsert {
  background-color: rgb(235, 235, 235);
}

/* BASE */
.metrics-list .list-wrapper {
  /* display: flex; */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: start;
  gap: 5vw;
  padding-left: 5vw;
  padding-right: 5vw;
}

/* COLLAPSE */
.metrics-list .list {
  width: 100%;
}

.metrics-list .list .card {
  transition: top, left linear 0.3s;
  /* border-radius: 0px 0px 10px 10px; */
}

.metrics-list .list .update ul {
  list-style: none;
  padding-inline-start: 0;
}

.metrics-list .list .metrics img {
  width: 100%;
}

.metrics-list .list .favorities .fav-list {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.metrics-list .list .favorities .card-collapse .card-header {
  border-bottom: none;
}

.metrics-list .list .favorities .card-collapse {
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 0;
}

.metrics-list .list .favorities .fav-list .btn {
  padding-left: 10px;
  padding-right: 10px;
  font-weight: normal;
}

/* List Contents */
/* .metrics-list .list-title {
  padding-left: 1vw;
  font-weight: bold;
}

.metrics-list .list-contents-grid {
  display: grid;
  grid-template-columns: 3fr 0.5fr;
  align-items: start;
  gap: 3vw;
  padding-left: 1.5vw;
  padding-right: 1.5vw;
}

.metrics-list .col-delete-btn {
  text-align: right;
}

.metrics-list .col-delete-btn .fa-times-circle {
  color: red;
} */

/* Metrics Detail Modal */
.metrics-list .metrics-dtl-modal-disp-area {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

.metrics-list .metrics-dtl-modal-disp-area .metrics-name {
  display: flex;
  padding-top: 0.5vw;
}

.metrics-list .metrics-dtl-modal-disp-area .sample-data-btn {
  text-align: right;
}

.metrics-list .dtl-modal-font-color {
  color: #ce638e;
}

.metrics-list .metrics-text {
  display: inline-block;
  _display: inline;
}

.metrics-list .modal-body .dividing-line {
  border: 1px solid #ced4da;
}

.metrics-list .metrics-provide-icon {
  color: rgb(0, 255, 135);
}

.metrics-list .metrics-no-provide-icon {
  color: red;
}

.metrics-list .link {
  color: var(--font-color-main);
  text-decoration: underline;
}
kirin-ri commented 3 months ago
.metrics-list .metrics-provide-icon {
  color: rgb(0, 255, 135);
}
kirin-ri commented 3 months ago
                {val.name}
                {!val.provided && (
                  <i className="fa fa-exclamation metrics-no-provide-icon" aria-hidden="true" />
                )}``
kirin-ri commented 3 months ago
.card-tertiary .card-body{
  background-color: var(--content-bg-color);
}
kirin-ri commented 3 months ago
<div className="card-body" id={metrics.subCategory}>
          <>
            {metrics.metrics.map((val: Metrics) => {
              return (
                <a href={`#/metrics-details/${val.name}`} className="link" key={val.name}>
                  <p>
                    {!val.alert && (
                      <i
                        className="fa fa-exclamation metrics-no-provide-icon"
                        aria-hidden="true"
                        style={{marginLeft:'10px'}}/>)}
                    {val.name}
                  </p>
                </a>
              );
            })}
          </>
kirin-ri commented 3 months ago
.card-tertiary .card-body {
  background-color: white; /* 设置背景颜色为白色 */
  color: black; /* 设置文字颜色为黑色 */
  border-radius: 10px; /* 设置圆角 */
  padding: 20px; /* 设置内边距 */
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 设置阴影效果 */
}
kirin-ri commented 3 months ago
.metrics-item {
  background-color: white;
  color: black;
  padding: 10px;
  margin: 5px 0;
  border: 1px solid black;
  border-radius: 10px; /* 设置圆角 */
  text-align: center;
  display: flex;
  justify-content: space-between; /* 在两端之间保持间距 */
  align-items: center; /* 垂直方向居中对齐 */
}
kirin-ri commented 3 months ago
.metrics-item-container {
  display: flex;
  align-items: center; /* 垂直方向居中对齐 */
  margin: 5px 0; /* 调整项目之间的间距 */
}
kirin-ri commented 3 months ago
.metrics-item::after {
  content: ''; /* 添加一个空白占位符 */
  display: inline-block;
  width: 2rem; /* 设置与感叹号相同的宽度 */
  height: 2rem; /* 设置与感叹号相同的高度 */
  visibility: hidden; /* 确保占位符不可见 */
}
kirin-ri commented 3 months ago
import * as Popper from '@popperjs/core';
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';

type MetricsList = {
  subCategory: string;
  metrics: Metrics[];
};
type Metrics = {
  name: string;
  provided: boolean;
  subCategory: string;
};

const defaultCategories = [
  { name: 'Performance', icon: 'fa-tachometer-alt' },
  { name: 'Availability', icon: 'fa-clock' }
];

const poppers: { [key: string]: Popper.Instance } = {};

function AppMenu() {
  const metricsList: MetricsList[] = [
    {
      subCategory: '経営指標',
      metrics: [
        { name: '売上', provided: true, subCategory: '経営指標' },
        { name: '売上高総利益', provided: true, subCategory: '経営指標' },
        { name: '営業利益', provided: true, subCategory: '経営指標' },
        { name: '売上高総利益率', provided: true, subCategory: '経営指標' },
        { name: '売上高営業利益率', provided: true, subCategory: '経営指標' },
        { name: 'EBITDA', provided: true, subCategory: '経営指標' },
        { name: '当座比率', provided: true, subCategory: '経営指標' },
        { name: '運転資本', provided: true, subCategory: '経営指標' },
        { name: '損益分岐点比率', provided: true, subCategory: '経営指標' },
        { name: '在庫回転率', provided: true, subCategory: '経営指標' },
        { name: '労働生産性①従業員あたり', provided: true, subCategory: '経営指標' },
        { name: '労働生産性②労働時間あたり', provided: true, subCategory: '経営指標' },
        { name: '資金繰り表', provided: true, subCategory: '経営指標' }
      ]
    },
    {
      subCategory: '管理指標',
      metrics: [
        { name: '総合設備効率(OEE)', provided: true, subCategory: '管理指標' },
        { name: '設備稼働率', provided: false, subCategory: '管理指標' },
        { name: '工程別生産性', provided: false, subCategory: '管理指標' },
        { name: '歩留まり率', provided: false, subCategory: '管理指標' },
        { name: '良品率', provided: false, subCategory: '管理指標' },
        { name: '直行率生', provided: false, subCategory: '管理指標' },
        { name: '生産実績', provided: false, subCategory: '管理指標' },
        { name: '在庫数', provided: false, subCategory: '管理指標' },
        { name: '工程管理', provided: false, subCategory: '管理指標' },
        { name: 'マーケット情報', provided: false, subCategory: '管理指標' }
      ]
    }
  ];
  useEffect(() => {
    $('.dropdown').each((idx, menuItem) => {
      const $menuItem = $(menuItem);
      const id = $menuItem.data('target');
      const $submenu = $(`#${id}`);
      const popper = Popper.createPopper(menuItem, $submenu[0], {
        placement: 'right-start',
        strategy: 'fixed',
        modifiers: [
          {
            name: 'offset',
            options: {
              offset: [-1, 0],
            },
          },
        ],
      });
      poppers[id] = popper;
    });

    $('.dropdown-menu .dropdown-item').on('click', () => {
      $('.dropdown-menu').removeClass('show');
      $(document).off('click.openSubmenu');
    });
  }, [metricsList]);

  const handleSubmenu = (e: React.MouseEvent<HTMLAnchorElement>) => {
    const $menuItem = $(e.currentTarget as HTMLElement);
    const id = $menuItem.data('target');
    const $submenu = $(`#${id}`);

    $('.dropdown-menu').removeClass('show');
    $(document).off('click.openSubmenu');
    $submenu.addClass('show');
    setTimeout(() => {
      $(document).on('click.openSubmenu', (e) => {
        if ($(e.target).closest($('.dropdown-menu')).length === 0) {
          $('.dropdown-menu').removeClass('show');
          $(document).off('click.openSubmenu');
        }
      });
    });
    poppers[id].update();
  };

  function CategoryItem({
    category: group,
    idx,
  }: {
    category: MetricsList;
    idx: number;
  }) {
    const defaultCat = defaultCategories.find(
      (e) => e.name === group.subCategory,
    );
    return (
      <li className="nav-item">
        <a
          className="nav-link dropdown"
          data-target={`sidebar-cat-${idx + 1}`}
          onClick={handleSubmenu}
        >
          <div className="item-icon">
            <i className={`fa ${defaultCat?.icon ?? 'fa-calculator'}`} />
          </div>
          <div className="item-label">{group.subCategory}</div>
        </a>
      </li>
    );
  }

  return (
    <>
      <aside className="main-sidebar sidebar-light-primary elevation-2">
        {/* Brand Logo */}
        <Link to="/" className="brand-link">
          <img
            src="/logo_fix.02.png"
            alt="iQuattro Logo"
            className="brand-image"
          />

          <img
            src="/logo_fix.02_logo.png"
            alt="iQuattro Logo"
            className="brand-text"
          />
        </Link>

        {/* Sidebar */}
        <div className="sidebar">
          {/* Sidebar Menu */}
          <nav>
            <ul className="nav nav-pills nav-sidebar flex-column">
              <li className="nav-header">
                <Link to="/" className="nav-link">
                  指標一覧
                </Link>
              </li>
              {metricsList?.map((c, idx) => (
                <CategoryItem category={c} idx={idx} key={idx} />
              ))}
            </ul>
          </nav>
          {/* /.sidebar-menu */}
          <div className="copyright">Copyright ©NTT DATA Corporation</div>
        </div>
        {/* /.sidebar */}
      </aside>
      <div className="sidebar-dropdowns">
        {metricsList?.map((g, idx) => (
          <ul className="dropdown-menu" id={`sidebar-cat-${idx + 1}`} key={idx}>
            {g.metrics.map((m) => (
              <li className="dropdown-item" key={m.name}>
                <Link to={`/metrics-details/${m.name}`} className="nav-link">
                  {m.name}
                </Link>
              </li>
            ))}
          </ul>
        ))}
      </div>
    </>
  );
}
export default AppMenu;
kirin-ri commented 3 months ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 3 months ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 3 months ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 3 months ago

<i className={fa ${val.icon} metrics-icon} aria-hidden="true" />

kirin-ri commented 3 months ago
.alert-box {
  background-color: white;
  color: black;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 10px;
  margin: 10px 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.alert-box span {
  flex-grow: 1;
}

.close-btn {
  background-color: transparent;
  border: none;
  color: red;
  cursor: pointer;
  font-size: 1rem;
  padding: 5px;
  margin-left: 10px;
}
kirin-ri commented 3 months ago
import React, { useState } from 'react';
import AlertBox from './AlertBox'; // 引入警告弹窗组件

function AppMenu() {
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示

  return (
    <>
      {/* 其他代码 */}
      {showAlert && (
        <AlertBox
          message="これは警告メッセージです"
          onClose={() => setShowAlert(false)}
        />
      )}
    </>
  );
}

export default AppMenu;
kirin-ri commented 3 months ago
import React from 'react';

const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
  return (
    <div className="alert-box">
      <span>{message}</span>
      <button className="close-btn" onClick={onClose}>非表示</button>
    </div>
  );
};

export default AlertBox;
kirin-ri commented 3 months ago
import { useState } from "react";

function EmptyPage() {
  // function get() {
  const test = '資金繰り表';
  //   return arr.map((str) => {
  //     SampleDataModal(str);
  //   });
  // }
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示
  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <span>{message}</span>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };
  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {/* Content Header (Page header) */}
      <section className="content-header">
        <div className="content-header-left">
          <div>期中に資金がマイナスとなる期間があります</div>
        </div>
      </section>
    </div>
  );
}

export default EmptyPage;
kirin-ri commented 3 months ago
import { useState } from "react";

function EmptyPage() {
  const test = '資金繰り表';
  const [showAlert, setShowAlert] = useState(true); // 控制警告弹窗的显示

  const AlertBox = ({ message, onClose }: { message: string; onClose: () => void }) => {
    return (
      <div className="alert-box">
        <span>{message}</span>
        <button className="close-btn" onClick={onClose}>非表示</button>
      </div>
    );
  };

  return (
    <div className="content-wrapper metrics-details">
      <section className="page-cover">
        <div className="page-cover-title-frame">
          <h1>{test}</h1>
        </div>
      </section>
      {/* Content Header (Page header) */}
      <section className="content-header">
        <div className="content-header-left">
          <div>期中に資金がマイナスとなる期間があります</div>
        </div>
      </section>
      {/* 警告弹窗 */}
      {showAlert && (
        <AlertBox
          message="これは警告メッセージです"
          onClose={() => setShowAlert(false)}
        />
      )}
    </div>
  );
}

export default EmptyPage;