Friday, March 20, 2026

tailwind grid grid-flow-col grid-rows-5, grid grid-flow-row grid-cols-5, h-screen overflow-auto strategy

return <div className="relative h-screen">

            {/* <AnalyticsWidget /> */}

            <div className="grid grid-flow-col grid-rows-5 h-screen display-none">

                <div className="row-span-2 border-b border-b-gray-300">

                    aaaaaaa

                </div>

                <div className="row-span-3 border-b border-b-gray-300">

                    bbbbbbb

                </div>

                <div className="row-span-5 border-l border-r border-l-gray-300 border-r-gray-500">

                    ccccccc

                </div>

            </div>

            <div className="grid grid-flow-row grid-cols-5 h-screen overflow-auto">

                <div className="col-span-2 border-b border-b-gray-300">

                    aaaaaaa

                </div>

                <div className="col-span-3 border-b border-b-gray-300">

                    bbbbbbb

                </div>

                <div className="col-span-5 border-l border-r border-l-gray-300 border-r-gray-500">

                    ccccccc

                    <div className='h-296'>dddddddd</div>

                </div>

            </div>

        </div> 

Wednesday, February 11, 2026

next.js дээр markdown файлыг уншаад хэрхэн html хуудас болгож харуулах вэ?

 'use client';

import { ChevronDown, FileText } from 'lucide-react';
import React, { useEffect, useState } from 'react';

import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';

type TreeNode = {
    title: string;
    docPath: string;
    children?: TreeNode[];
};

const helpNodes: TreeNode[] = [
    {
        title: 'Үндсэн ойлголт',
        docPath: 'index.md',
    },
    {
        title: 'Хэрэглэгчид',
        docPath: 'users.md',
    },
];

function TreeItem({
    par,
    node,
    onLoadDoc,
}: {
    par: string;
    node: TreeNode;
    onLoadDoc: (docPath: string) => void;
}) {
    const hasChildren = node.children && node.children.length > 0;

    const handleItemClick = (e: React.MouseEvent) => {
        e.preventDefault();
        onLoadDoc(node.docPath);
    };

    return (
        <div className="ml-2">
            <div className="flex items-center gap-1">
                {hasChildren ? (
                    <ChevronDown size={16} />
                ) : (
                    <FileText size={16} />
                )}
                <a href="#" onClick={handleItemClick}>
                    <span>{node.title}</span>
                </a>
            </div>
            {hasChildren && (
                <div className="ml-4 mt-1 space-y-1">
                    {node.children!.map((child, index) => (
                        <TreeItem
                            key={par + '-' + index}
                            par={par + '-' + index}
                            node={child}
                            onLoadDoc={onLoadDoc}
                        />
                    ))}
                </div>
            )}
        </div>
    );
}

export default function HelpPage() {
    const [doc, setDoc] = useState('');

    const handleLoadDoc = async (docPath: string) => {
        try {
            const queryParams = new URLSearchParams();
            queryParams.append('md', `${docPath}`);
            fetch(`/api/md-docs/help?${queryParams.toString()}`)
                .then((res) => res.text())
                .then(async (data) => {
                    setDoc(data);
                });
        } catch (error) {
            console.error('Error loading document:', error);
        }
    };

    useEffect(() => {
        handleLoadDoc(helpNodes[0].docPath);
    }, []);

    return (
        <div className="flex-row-container gap-4">
            <div className="y-scroll-transparent w-64 bg-gray-100 border-r border-gray-200">
                <h1 className="text-xl font-semibold my-4 mx-2">Тусламж</h1>
                {helpNodes.map((node, index) => {
                    return (
                        <TreeItem
                            key={'TreeItem-' + index}
                            par={'TreeItem-' + index}
                            node={node}
                            onLoadDoc={handleLoadDoc}
                        />
                    );
                })}
                <div className="h-screen display-none">hello</div>
            </div>
            <div className="flex-scrollable-panel">
                <article
                    id="md-docs"
                    className="prose prose-slate max-w-none
                        prose-headings:font-bold
                        prose-a:text-blue-600
                        prose-pre:bg-gray-900
                        prose-pre:text-gray-100"
                >
                    <ReactMarkdown
                        rehypePlugins={[
                            rehypeRaw,
                            [rehypeHighlight, { ignoreMissing: true }],
                        ]}
                    >
                        {doc}
                    </ReactMarkdown>
                </article>
            </div>
        </div>
    );
}

Tuesday, February 10, 2026

how to response markdown file using next.js , nest.js

 on nest.js

import {
    Body,
    Controller,
    Get,
    HttpStatus,
    Post,
    Render,
    Req,
    Res,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Throttle } from '@nestjs/throttler';
import * as fs from 'fs';
import * as path from 'path';

import { AppSessionService } from '../core/app.session.service';
import { AppConstants } from '../core/app.constants';
import { DataMemoryCacher } from '../libs/memory-cacher/data.memory-cacher';

@Controller()
export class AppController {
    constructor(
        private readonly dataCacher: DataMemoryCacher,
        private readonly appSessionService: AppSessionService,
    ) {}

    @Get('/')
    @Render('home')
    app_home(@Req() req: Request, @Res() res: Response): any {
        var hosts = [req.headers.host];
        var username = this.appSessionService.getUserName();
        let last_one = this.dataCacher.get('puu_last_one_from');

        return {
            title: 'PUU - PAAS',
            hosts: hosts,
            username: username,
            last_one: last_one ? last_one.doc_num : '-0-',
        };
    }

    @Get('/about')
    async app_about(@Req() req: Request, @Res() res: Response): Promise<any> {
        let apiDoc = '';

        // // load from cache
        // const keyCache = 'site-home-md';
        // // const dataCache = this.dataCacher.get(keyCache);
        // const dataCache = null;

        // if (dataCache) {
        //     apiDoc = dataCache;
        // } else {
        //     const mdPath = path.join(process.cwd(), 'views', 'about-api.md');
        //     apiDoc = fs.readFileSync(mdPath, 'utf8');
        //     this.dataCacher.set(keyCache, apiDoc);
        // }

        res.render('about', {
            title: 'PUU - About API',
            apiDoc,
        });
    }

    @Get('apiDocs.md')
    getApiDoc(@Res() res: Response) {
        const mdPath = path.join(process.cwd(), 'views', 'about-api.md');
        const md = fs.readFileSync(mdPath, 'utf8');
        res.type('text/markdown').send(md);
    }

    // for API

    @Throttle(AppConstants.throttler_limits.website_send)
    @Post('/language')
    app_language_sel(@Body() body: any, @Res() res: Response) {
        if (body.code && AppConstants.supported_languages.includes(body.code)) {
            this.appSessionService.setLanguage(body.code);

            return res
                .status(HttpStatus.OK)
                .json({ success: true, result: body.code });
        }
        return res.status(HttpStatus.BAD_REQUEST).json({ success: false });
    }

    @Throttle(AppConstants.throttler_limits.brute_force)
    @Get('/ip')
    ip(@Req() req: Request, @Res() res: Response) {
        return res.status(HttpStatus.OK).json({
            ip: req.ip,
            forwarded: req.headers['x-forwarded-for'],
        });
    }
}


on next.js


import fs from 'fs';
import path from 'path';
import { NextResponse } from 'next/server';

export async function GET() {
    const mdPath = path.join(process.cwd(), 'docs', 'about-api.md');
    const md = fs.readFileSync(mdPath, 'utf8');

    return new NextResponse(md, {
        headers: {
            'Content-Type': 'text/markdown; charset=utf-8',
        },
    });
}




Tuesday, February 3, 2026

PM2-г Ubuntu дээр бүрэн устгах хэдэн алхам байна

 PM2-г Ubuntu дээр бүрэн устгах хэдэн алхам байна. Энэ нь зөвхөн програмыг устгахгүй, мөн лог файлууд болон тохиргоог устгана, иймээс диск чөлөөлөхөд тохиромжтой.


1️⃣ PM2 процессыг зогсоох

pm2 kill
  • Бүх PM2 процессуудыг зогсооно.


2️⃣ PM2-г uninstall хийх

sudo npm uninstall -g pm2
  • PM2-г глобал түвшинд uninstall хийж байна.


3️⃣ PM2 тохиргоо, лог файлуудыг устгах

rm -rf ~/.pm2
  • ~/.pm2 хавтас дотор:

    • logs/ → бүх stdout/stderr log

    • dump.pm2 → процессын snapshot

    • бусад config файлууд бүгд устна


4️⃣ (Optional) PM2 logrotate module-г устгах

Хэрвээ суулгасан бол:

pm2 uninstall pm2-logrotate

5️⃣ Диск зайг шалгах

df -h
  • /home хавтасны зай чөлөөлөгдсөн эсэхийг харах.


⚠️ Санамж:

  • Энэ аргаар PM2 бүх тохиргоо, лог, процессыг устгана.

  • Хэрвээ ирээдүйд дахин ашиглах гэж байгаа бол зөвхөн лог файлуудыг truncate хийх нь илүү аюулгүй арга.

PM2 лог файлуудыг ирээдүйд автомат ротаци хийж, зай дүүрэхээс сэргийлэх тохиргоог ингэж хийх боломжтой

 PM2 лог файлуудыг ирээдүйд автомат ротаци хийж, зай дүүрэхээс сэргийлэх тохиргоог ингэж хийх боломжтой:


1️⃣ PM2 лог ротаци тохируулах

PM2 нь log rotate module-тай. Суулгаж тохируулах:

pm2 install pm2-logrotate

2️⃣ Log ротацигийн үндсэн тохиргоо

# Лог файлын хэмжээг 100MB-с хэтэрвэл шинэчлэх pm2 set pm2-logrotate:max_size 100M # Хуучин log файлуудыг 7 хоног хадгалах pm2 set pm2-logrotate:retain 7 # Ротаци хийх давтамж (минут) pm2 set pm2-logrotate:rotateInterval '0 0 * * *' # Old logs-г автоматаар цэвэрлэх pm2 set pm2-logrotate:compress true

💡 Тайлбар:

  • max_size → нэг log файлын хамгийн их хэмжээ

  • retain → хэд хоногийн log хадгалах

  • rotateInterval → cron хэлбэрээр давтамж (энд өдөрт нэг удаа)

  • compress → хуучин log-ыг zip хийж хадгалах


3️⃣ PM2-г дахин эхлүүлэх (лог ротаци ажиллуулахын тулд)

pm2 reload all

ubuntu host хэр хэмжээний hdd sdd зарцуулсан байгааг яаж харах вэ?

 Ubuntu дээр таны HDD/SSD-д хэдэн хэмжээний зай ашиглагдаж, хэдэн нь чөлөөтэй байгааг харах хэд хэдэн арга бий. Би хамгийн энгийн, үр дүнтэй аргуудыг зааж өгье:


1️⃣ df командыг ашиглах (Disk Free)

Таны файлын системийн зай хэр ашиглагдаж байгааг харуулна.

df -h
  • -h → human-readable, хүн ойлгох хэмжээ (GB, MB) дээр харуулна.

  • Үр дүнгийн жишээ:

Filesystem Size Used Avail Use% Mounted on /dev/sda1 50G 20G 28G 42% / tmpfs 3.9G 0 3.9G 0% /dev/shm /dev/sdb1 100G 60G 35G 65% /data

Тайлбар:

  • Size – нийт хэмжээ

  • Used – хэрэглэсэн хэмжээ

  • Avail – чөлөөтэй зай

  • Use% – ашиглалтын хувь


2️⃣ lsblk командыг ашиглах (Disk Devices)

Хард болон SSD-д хамаарах блокуудын мэдээллийг харуулна:

lsblk -f
  • NAME → диск эсвэл партишн нэр

  • SIZE → нийт хэмжээ

  • MOUNTPOINT → хаана холбогдсон (mount) байгааг харуулна

  • FSTYPE → файлын системийн төрөл (ext4, ntfs гэх мэт)

Жишээ:

NAME FSTYPE LABEL MOUNTPOINT SIZE sda ├─sda1 ext4 / 50G sdb └─sdb1 ext4 /data 100G

3️⃣ du командыг ашиглах (Folder-wise usage)

Нэг хавтас эсвэл бүх систем дээр хэдэн хэмжээний зай ашиглагдаж байгааг харах:

du -sh /path/to/folder

Жишээ: root хавтасны хэмжээг харах:

sudo du -sh /*
  • -s → summary (нэгтгэсэн дүн)

  • -h → human-readable


4️⃣ График хэрэгсэл ашиглах (опцион)

Ubuntu Desktop бол Disk Usage Analyzer (baobab) ашиглаж болно:

sudo apt install baobab baobab
  • Энэ нь графикаар диск ашиглалт харуулна, том файлуудыг хурдан тодорхойлно.


💡 Тэмдэглэл:

  • df нь файлын системийн хэмжээг харуулдаг.

  • lsblk нь диск/SSD-н физик хэмжээ, партишн-г харуулдаг.

  • du нь тухайн хавтасны зай хэр ашиглагдаж байгааг харуулна.

Monday, February 2, 2026

өөрийн веб хуудасыг facebook share яаж хийх вэ? next.js ашиглан


* /api/og-images/route.tsx

import { ImageResponse } from 'next/og';

export async function GET(request: Request) {
    // URL parse
    const { searchParams } = new URL(request.url);
    // ?title=Hello
    const title = searchParams.get('title') ?? 'Default гарчиг';

    return new ImageResponse(
        <div style={{ fontSize: 60, background: '#000', color: '#fff' }}>
            {title || `Миний гарчиг`}
        </div>,
        {
            width: 1200,
            height: 600,
        },
    );
}

* page.tsx

 import { Metadata } from 'next';

import HomeSections from './sections';

export async function generateMetadata({
    params,
}: {
    params: { slug: string };
}): Promise<Metadata> {
    // const article = await getArticle(params.slug); // DB / API

    return {
        title: `article.title`,
        description: `article.summary`,
        openGraph: {
            title: `article.title`,
            description: `article.summary`,
            url: `https://example.com/article/${params.slug}`,
            siteName: 'My Website',
            images: [
                {
                    url: `/api/og-images?title=Hello`,
                    width: 1200,
                    height: 600,
                },
            ],
            locale: 'mn_MN',
            type: 'article',
        },
    };
}

export default function HomePage() {
    return <HomeSections />;
}


* sections.tsx

'use client';

import { postRequestOptions } from '@/libs/helpers/networker';
import { useEffect, useState } from 'react';

export default function HomeSections() {
    const [accessToken, setAccessToken] = useState<string | null>(null);

    useEffect(() => {
        // Facebook SDK script нэмэх
        const loadFbSdk = () =>
            new Promise<void>((resolve) => {
                ((d, s, id) => {
                    const fjs = d.getElementsByTagName(s)[0];
                    if (d.getElementById(id)) return resolve();
                    const js = d.createElement(s) as HTMLScriptElement;
                    js.id = id;
                    js.src = 'https://connect.facebook.net/en_US/sdk.js';
                    js.onload = () => resolve();
                    d.body.appendChild(js);
                })(document, 'script', 'facebook-jssdk');
            });

        // SDK init
        loadFbSdk().then(() => {
            (window as any).FB.init({
                appId: process.env.NEXT_PUBLIC_FB_APP_ID,
                cookie: true,
                xfbml: false,
                version: 'v19.0',
            });
        });
    }, []);

    // Login function
    const fbLogin = () => {
        (window as any).FB.login(
            (response: any) => {
                if (response.authResponse) {
                    const accessToken = response.authResponse.accessToken;
                    console.log('User Access Token:', accessToken);
                    // Backend руу дамжуулахад ашиглана
                    setAccessToken(accessToken);
                } else {
                    console.log(
                        'User cancelled login or did not fully authorize.',
                    );
                    setAccessToken(null);
                }
            },
            { scope: 'public_profile,email,user_posts' },
        ); // user_posts scope зөвхөн тест токен дээр ажиллана
    };

    // Share function
    const shareArticle = (articleUrl: string) => {
        // const linkedMessage = `${articleUrl}`;
        // const shareUrl =
        //     'https://www.facebook.com/sharer/sharer.php?quote="hello"&u=' +
        //     encodeURIComponent(linkedMessage);
        // window.open(shareUrl, '_blank', 'width=600,height=400');
        const url = encodeURIComponent(`https://news.mn/r/2847199/`);
        window.open(
            `https://www.facebook.com/sharer/sharer.php?u=${url}`,
            '_blank',
            'width=600,height=500',
        );
    };

    const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

    return (
        <div className="flex flex-col justify-center">
            <div>
                {accessToken ? (
                    <div>Нэвтэрсэн :)</div>
                ) : (
                    <div>Нэвтрээгүй (:</div>
                )}
            </div>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={fbLogin}
            >
                Login with Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    shareArticle('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Share content on Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    checkSharedPosts('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Check out the posts shared on Facebook
            </button>
        </div>
    );
}



facebook дээр post share хийсэн сүүлийн 10 дотроос миний холбоос message дотор байна уу? шалгах код жишээ

 FRONT-END next.js

const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

BACK-END nest.js

@Post('/check-fb-posts')

    async checkFbPosts(@Body() body: { accessToken: string; siteUrl: string }) {
        const { accessToken, siteUrl } = body;

        // Graph API руу сүүлийн 10 пост авах
        const url =
            `https://graph.facebook.com/me/posts` +
            `?fields=id,message,permalink_url,created_time&limit=10&access_token=${accessToken}`;
        const res = await fetch(url);
        const data = await res.json();

        // Сүүлд 10 постоос таны сайтын URL байгаа эсэхийг шалгах
        const found = data.data.find(
            (post: any) =>
                (post.message && post.message.includes(siteUrl)) ||
                (post.permalink_url && post.permalink_url.includes(siteUrl)),
        );

        return { founded: !!found, posts: data.data };
    }