164 lines
20 KiB
JavaScript
164 lines
20 KiB
JavaScript
// Isolated, localhost-only UI fixture. It is never imported by production.
|
||
import {createServer} from 'vite';
|
||
import {spawn} from 'node:child_process';
|
||
import {mkdtempSync,rmSync} from 'node:fs';
|
||
import {tmpdir} from 'node:os';
|
||
import {join} from 'node:path';
|
||
import {randomBytes} from 'node:crypto';
|
||
import {createServer as netServer} from 'node:net';
|
||
import {readyBid} from './fixtures/ready-bid.mjs';
|
||
import {printBidFixture} from './fixtures/print-bid.mjs';
|
||
import {preparationSummary} from '../src/services/preparationSummary.js';
|
||
const dir=mkdtempSync(join(tmpdir(),'ihaleist-browser-'));
|
||
process.env.AUTH_SECRET=randomBytes(32).toString('hex');
|
||
const {signToken}=await import('../server/auth.mjs');
|
||
const user={email:'browser-qa@example.test',name:'Laboratuvar QA',isAdmin:false};
|
||
const token=signToken(user);
|
||
const keyUser={email:'keys-browser-qa@example.test',name:'API QA',isAdmin:true};
|
||
const keyToken=signToken(keyUser);
|
||
let apiKeyListFailure=false;
|
||
let briefFailure='none';
|
||
let holdDocuments=false;
|
||
let skipDocumentReads=0,qaFiles=null,outputDocumentReads=0;
|
||
const qaFile={id:'qa-owned-file',ad:'QA zorunlu firma belgesi.pdf',tur:'firma',boyut:100,olusturma:'2026-09-06T00:00:00Z',erisilebilir:true};
|
||
const pendingDocuments=new Set();
|
||
process.env.IHALEIST_USER_DATA_DIR=dir;
|
||
const {createBriefOutbox}=await import('../server/brief-outbox.mjs');
|
||
const qaBriefOutbox=createBriefOutbox(dir);
|
||
const qaBriefBatch=qaBriefOutbox.prepare('2026-09-07','weekly-browser@example.test',[{channel:'email',to:'weekly-browser@example.test'},{channel:'telegram',chatId:'synthetic-chat'}]);
|
||
for(const position of [0,1])qaBriefOutbox.hold(qaBriefOutbox.claim(qaBriefBatch.id,position),'QA: Sağlayıcı sonucu belirsiz.');
|
||
const qaJobs=await import('../server/ai-job-store.mjs');
|
||
const {qualifyOfferDraft}=await import('../server/offer-draft.mjs');
|
||
const {executeAnalysisJob}=await import('../server/analysis-job.mjs');
|
||
const pendingQaJobs=new Map();
|
||
const qaHeartbeat=setInterval(()=>{for(const job of pendingQaJobs.values())qaJobs.heartbeatAiJob(job.id,job.runId);},5000);qaHeartbeat.unref();
|
||
const probe=netServer();await new Promise(r=>probe.listen(0,'127.0.0.1',r));const port=probe.address().port;await new Promise(r=>probe.close(r));
|
||
const api=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{PATH:process.env.PATH,NODE_ENV:'test',AUTH_SECRET:process.env.AUTH_SECRET,IHALEIST_USER_DATA_DIR:dir,IHALEIST_SHARED_DATA_DIR:join(dir,'shared'),PORT:String(port),UYUM_GOMME:'0'},stdio:'ignore'});
|
||
const entries=[['1582991','Giresun','10.09.2026','10:00'],['1534665','Kayseri','22.09.2026','11:00'],['1499061','Akdeniz','22.09.2026','10:30'],['1573235','Aksaray','09.09.2026','10:00'],['1523851','Tepecik','11.09.2026','10:00']];
|
||
const tenders=entries.map(([id,city,date,time])=>({id:'qa-'+id,ikn:'2026/'+id,baslik:'QA — '+city+' laboratuvar',kurum:city+' TEST KURUMU',sehir:city,tarih:date,saat:time,alimTuru:'Mal Alımı',usul:id==='1534665'?'Açık İhale':'Pazarlık Usulü',kurumTipi:'Eğitim ve Araştırma Hastaneleri',sektor:'Laboratuvar & Ölçüm',sektorler:['Laboratuvar & Ölçüm'],durum:'Açık',takipte:false,kanbanStatus:'incelenecek',items:[{id:'1',ad:'Sentetik test kalemi',miktar:2,birim:'adet'}],aciklama:'Bu yalnızca yerel arayüz test verisidir. Teklif geçerlilik süresi 120 takvim günü. Birim fiyat teklif. '.repeat(3)}));
|
||
// Seed actual persistent history only in this disposable QA volume.
|
||
process.env.IHALEIST_SHARED_DATA_DIR=join(dir,'shared');
|
||
const {observeTenders}=await import('../server/tender-history.mjs');
|
||
observeTenders(tenders);
|
||
for(let revision=1;revision<=22;revision++) observeTenders([{...tenders[0],saat:revision%2?'11:00':'10:00'}]);
|
||
const qaNotifications=await import('../server/zeyilname.mjs');
|
||
qaNotifications.bildirimYaz({email:user.email,tur:'zeyilname',ikn:'2026/1582991',baslik:'QA tarih değişikliği',metin:'Sentetik bildirim: ihale tarihi güncellendi.'});
|
||
qaNotifications.bildirimYaz({email:user.email,tur:'sonuc',ikn:'2026/1534665',baslik:'QA sonuç bildirimi',metin:'Sentetik sonuç inceleme bildirimi.'});
|
||
let notificationFailure='read';
|
||
const qaSubscriptionStore=await import('../server/notify-store.mjs');
|
||
const qaReviewSub=qaSubscriptionStore.subscribe(user.email,{konular:['QA gönderim incelemesi']})[0];
|
||
const qaDelivery=qaSubscriptionStore.reserveDelivery(qaReviewSub.id,{to:user.email,subject:'QA belirsiz laboratuvar e-postası',html:'Sentetik içerik; gönderilmedi.'},['qa-review-tender'],{clock:Date.now()-24*3600000});
|
||
qaSubscriptionStore.failDelivery(qaDelivery,'Sentetik belirsiz sağlayıcı sonucu.');
|
||
qaSubscriptionStore.reserveDelivery(qaReviewSub.id,null,[]);
|
||
let subscriptionFailure='read';
|
||
let telegramFailure=true,telegramFixture=false,telegramInbox=null,telegramLatestCode=null,telegramClockOffset=0;
|
||
const qaTelegramStore=await import('../server/user-store.mjs');
|
||
const {createTelegramLinker}=await import('../server/telegram-link.mjs');
|
||
const qaTelegramLinker=createTelegramLinker(qaTelegramStore,()=>Date.now()+telegramClockOffset);
|
||
const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{target:'http://127.0.0.1:'+port}}},plugins:[{name:'qa-fixtures',configureServer(server){server.middlewares.use(async(req,res,next)=>{
|
||
if(req.url==='/qa-print-cetvel'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(printBidFixture().html);return;}
|
||
if(req.url?.startsWith('/api/urun-ara?')&&new URL(req.url,'http://localhost').searchParams.get('q')==='QA pencere testi'){
|
||
res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:true,totalCount:1,list:[{...tenders[0],ihaleTarihSaat:tenders[0].tarih+' '+tenders[0].saat,tur:'QA'}]}));return;
|
||
}
|
||
if(req.url==='/qa-responsive'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel ekran boyutu testi</h1><p>Gerçek uygulama ayrı bir görünüm alanında yüklenir.</p><button data-width="390">390 piksel</button><button data-width="768">768 piksel</button><button data-width="1440">1440 piksel</button><pre id="metrics" aria-label="Görünüm ölçümleri"></pre><iframe title="İhaleist test görünümü" src="/" style="display:block;width:390px;height:900px;border:0"></iframe><script>document.querySelectorAll('button').forEach(button=>button.onclick=()=>document.querySelector('iframe').style.width=button.dataset.width+'px');setInterval(()=>{const doc=document.querySelector('iframe').contentDocument;if(!doc?.body)return;const width=doc.documentElement.clientWidth;const groups=[...doc.querySelectorAll('nav[aria-label="Çalışma alanları"] button')].map(el=>({label:el.textContent.trim(),left:Math.round(el.getBoundingClientRect().left),right:Math.round(el.getBoundingClientRect().right)}));document.getElementById('metrics').textContent=JSON.stringify({width,scrollWidth:doc.documentElement.scrollWidth,groups},null,2);},400);</script></html>`);return;}
|
||
if(req.url==='/qa-telegram-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel Telegram testi</h1><p>Gerçek Telegram mesajı gönderilmez.</p><button id="recover">Telegram durum hatasını kapat</button><button id="enable">Sentetik bağlamayı aç</button><button id="receive">Son kodu sentetik sohbete ulaştır</button><button id="expire">Kod süresini bitir</button><p role="status"></p><script>for(const mode of ['recover','enable','receive','expire'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-telegram-fixture?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||
if(req.url?.startsWith('/qa-telegram-fixture?')&&req.method==='POST'){
|
||
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
|
||
if(mode==='recover')telegramFailure=false;
|
||
if(mode==='enable'){telegramFixture=true;telegramFailure=false;}
|
||
if(mode==='receive')telegramInbox=telegramLatestCode;
|
||
if(mode==='expire')telegramClockOffset+=11*60000;
|
||
res.end('Sentetik Telegram durumu: '+mode);return;
|
||
}
|
||
if(req.url==='/api/me/telegram'&&telegramFixture&&['GET','POST'].includes(req.method)){
|
||
const reply=(status,data)=>{res.writeHead(status,{'Content-Type':'application/json'});res.end(JSON.stringify(data));};
|
||
if(req.headers.authorization!=='Bearer '+token){reply(401,{success:false,error:'Test hesabı gerekli.'});return;}
|
||
try {
|
||
if(req.method==='GET'){const tg=qaTelegramStore.getData(user.email,'telegram');reply(200,{success:true,yapilandirilmis:true,bagli:!!tg?.chatId,bot:null,ad:tg?.ad||null});return;}
|
||
let raw='';for await(const chunk of req)raw+=chunk;const body=JSON.parse(raw);
|
||
if(body.islem==='kod'){const challenge=qaTelegramLinker.issue(user.email);telegramLatestCode=challenge.kod;reply(200,{success:true,...challenge});return;}
|
||
if(body.islem==='test'){reply(400,{success:false,error:'Bu test gerçek mesaj göndermez.'});return;}
|
||
const found=await qaTelegramLinker.link(user.email,body.kod,async code=>code===telegramInbox?{chatId:'123456789',ad:'QA Sentetik Sohbet'}:null);
|
||
reply(200,{success:true,ad:found.ad});
|
||
}catch(error){reply(error.status||400,{success:false,error:error.message,kod:error.kod});}return;
|
||
}
|
||
if(req.url==='/qa-telegram-recover'&&req.method==='POST'){telegramFailure=false;res.end('ok');return;}
|
||
if(req.url==='/api/me/telegram'&&req.method==='GET'&&telegramFailure){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Telegram durumu alınamadı.'}));return;}
|
||
if(req.url==='/qa-subscription-controls'){res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel abonelik testi</h1><button id="read">Abonelik liste hatası</button><button id="delete">Abonelik silme hatası</button><button id="none">Abonelik hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','delete','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-subscription-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||
if(req.url?.startsWith('/qa-subscription-failure?')&&req.method==='POST'){subscriptionFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Abonelik test durumu: '+subscriptionFailure);return;}
|
||
if(req.url?.startsWith('/api/notify/subscriptions')&&((subscriptionFailure==='read'&&req.method==='GET')||(subscriptionFailure==='delete'&&req.method==='DELETE'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Abonelik deposuna erişilemiyor.'}));return;}
|
||
if(req.url==='/qa-notification-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel bildirim testi</h1><button id="read">Liste hatası</button><button id="write">Okundu kayıt hatası</button><button id="none">Hataları kapat</button><p role="status"></p><script>for(const mode of ['read','write','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-notification-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||
if(req.url?.startsWith('/qa-notification-failure?')&&req.method==='POST'){notificationFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Bildirim test durumu: '+notificationFailure);return;}
|
||
if(req.url==='/api/me/bildirimler'&&((notificationFailure==='read'&&req.method==='GET')||(notificationFailure==='write'&&req.method==='POST'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Bildirim deposuna erişilemiyor.'}));return;}
|
||
if(req.url==='/qa-ai-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel AI işi testi</h1><p>Sağlayıcı taklidi kullanılır; ücretli AI çağrısı yapılmaz.</p><button id="finish">Bekleyen QA işini tamamla</button><p role="status"></p><script>document.querySelector('#finish').onclick=async()=>{const r=await fetch('/qa-ai-complete',{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||
if(req.url==='/qa-ai-complete' && req.method==='POST') {
|
||
for(const job of pendingQaJobs.values()) {
|
||
const result=job.operation==='analiz' ? await executeAnalysisJob(job,{pin:qaJobs.pinAiJobInput,analyze:async()=>({result:{summary:'QA kayıtlı genel analiz: Laboratuvar kaynak metni korundu.',items:[],requiredDocuments:[],roadmap:[]},mode:job.input.mode,model:{label:'QA',provider:'sentetik',model:'qa'},attempts:[]})})
|
||
: {success:true,...qualifyOfferDraft({bolumler:[{baslik:'Teklif Mektubu (Kapak)',icerik:'QA kayıtlı sonuç: Laboratuvar teklif taslağı. Bu sentetik metin resmi teklif değildir.'}]},['Teklif Mektubu (Kapak)'])};
|
||
qaJobs.finishAiJob(job.id,job.runId,result);
|
||
}
|
||
pendingQaJobs.clear();res.end('QA işleri tamamlandı');return;
|
||
}
|
||
if(req.url==='/api/me/ai-jobs' && req.method==='POST') {
|
||
if(req.headers.authorization!=='Bearer '+token){res.writeHead(401);res.end('{}');return;}
|
||
let body='';req.on('data',chunk=>body+=chunk);req.on('end',()=>{try{
|
||
const input=JSON.parse(body);const created=qaJobs.createAiJob(user.email,input.requestId,input.operation,input.input);
|
||
const claimed=qaJobs.claimAiJob();if(claimed)pendingQaJobs.set(claimed.id,claimed);
|
||
res.setHeader('Content-Type','application/json');res.end(JSON.stringify({success:true,job:qaJobs.getAiJob(user.email,created.id)}));
|
||
}catch(error){res.writeHead(400,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:error.message}));}});return;
|
||
}
|
||
if(req.url?.startsWith('/qa-output-documents?')&&req.method==='POST') {
|
||
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
|
||
if(mode==='hold'){holdDocuments=true;skipDocumentReads=0;outputDocumentReads=0;}
|
||
if(mode==='hold-final'){holdDocuments=true;skipDocumentReads=1;outputDocumentReads=0;}
|
||
if(mode==='revise')qaFiles=qaFiles?.map(file=>({...file,boyut:file.boyut+1}));
|
||
if(mode==='remove')qaFiles=[];
|
||
if(mode==='release'){holdDocuments=false;for(const pending of pendingDocuments){pending.writeHead(200,{'Content-Type':'application/json'});pending.end(JSON.stringify({success:true,belgeler:qaFiles || []}));}pendingDocuments.clear();}
|
||
res.end('Belge yanıtı: '+mode+'; okunan: '+outputDocumentReads+'; bekleyen: '+pendingDocuments.size);return;
|
||
}
|
||
if(req.url==='/api/me/documents'&&req.method==='GET'){
|
||
if(holdDocuments){outputDocumentReads++;if(skipDocumentReads>0)skipDocumentReads--;else{pendingDocuments.add(res);res.on('close',()=>pendingDocuments.delete(res));return;}}
|
||
if(qaFiles){res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:true,belgeler:qaFiles}));return;}
|
||
}
|
||
if(req.url==='/qa-source-update'||req.url==='/qa-source-update?ready=1'||req.url==='/qa-source-update?ready=1&file=1') {
|
||
const query=new URL(req.url,'http://localhost').searchParams;
|
||
qaFiles=query.has('file')?[{...qaFile}]:null;
|
||
const selected=query.has('ready')?readyBid(tenders[0],qaFiles?.[0]):tenders[0];
|
||
if(selected.bidDraft){const summary=preparationSummary(selected,{loading:false,error:null,documents:qaFiles || []});if(summary.issues.length)throw new Error('QA ready fixture: '+JSON.stringify(summary.issues));}
|
||
const html=`<html lang="tr"><head><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div id="root"></div><script type="module">
|
||
import React from '/node_modules/.vite/deps/react.js';
|
||
import ReactDOM from '/node_modules/.vite/deps/react-dom_client.js';
|
||
import {AppProvider,useApp} from '/src/context/AppContext.jsx';
|
||
import {BidPackageBuilder} from '/src/components/BidPackageBuilder.jsx';
|
||
import '/src/index.css';
|
||
const selected=${JSON.stringify(selected)};
|
||
function Probe(){const {setTenders}=useApp();const [status,setStatus]=React.useState('');const control=async mode=>setStatus(await (await fetch('/qa-output-documents?mode='+mode,{method:'POST'})).text());return React.createElement('main',{},
|
||
React.createElement('h1',{},'Yerel kaynak güncelleme testi'),
|
||
React.createElement('p',{},'Yalnız sentetik veri. Açık dosya korunurken güncel kaynak değişir.'),
|
||
React.createElement('button',{onClick:()=>control('hold')},'Belge yanıtını beklet'),
|
||
React.createElement('button',{onClick:()=>control('hold-final')},'Son belge kontrolünü beklet'),
|
||
React.createElement('button',{onClick:()=>control('revise')},'Bağlı belgenin sürümünü değiştir'),
|
||
React.createElement('button',{onClick:()=>control('remove')},'Bağlı belgeyi kaldır'),
|
||
React.createElement('button',{onClick:()=>control('status')},'Bekleyen belge isteklerini göster'),
|
||
React.createElement('button',{onClick:()=>control('release')},'Belge yanıtını serbest bırak'),
|
||
React.createElement('p',{role:'status'},status),
|
||
React.createElement('button',{onClick:()=>setTenders([{...selected,id:'qa-rotated-source',baslik:'QA Güncellenmiş alım',kazananFirma:'QA Kazanan'}])},'Aynı İKN için yeni kayıt ID ve kazanan yayımla'),
|
||
React.createElement(BidPackageBuilder,{tender:selected}));}
|
||
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(AppProvider,{},React.createElement(Probe)));
|
||
</script></body></html>`;
|
||
res.setHeader('Content-Type','text/html; charset=utf-8');res.end(await server.transformIndexHtml('/qa-source-update',html));return;
|
||
}
|
||
if(req.url==='/qa-brief-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel haftalık özet testi</h1><button id="read">Özet okuma hatası</button><button id="write">Özet kayıt hatası</button><button id="none">Özet hatalarını kapat</button><p role="status"></p><script>for(const mode of ['read','write','none'])document.getElementById(mode).onclick=async()=>{const r=await fetch('/qa-brief-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=await r.text();};</script></html>`);return;}
|
||
if(req.url?.startsWith('/qa-brief-failure?')&&req.method==='POST'){briefFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Özet test durumu: '+briefFailure);return;}
|
||
if(req.url?.startsWith('/api/me/brief')&&((briefFailure==='read'&&req.method==='GET')||(briefFailure==='write'&&req.method==='POST'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Haftalık özet deposuna erişilemiyor.'}));return;}
|
||
if(req.url==='/qa-api-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel API testi</h1><p>Yalnız geçici test deposunu etkiler.</p><button id="login">API test hesabıyla gir</button><button id="fail">Liste hatasını aç</button><button id="recover">Liste hatasını kapat</button><p role="status"></p><script type="module">import {setSession} from '/src/services/auth.js';document.querySelector('#login').onclick=()=>{setSession(${JSON.stringify(keyToken)},${JSON.stringify(keyUser)});location.href='/';};for(const [id,mode] of [['fail','on'],['recover','off']])document.getElementById(id).onclick=async()=>{await fetch('/qa-api-failure?mode='+mode,{method:'POST'});document.querySelector('[role=status]').textContent=mode==='on'?'Liste hatası açık':'Liste hatası kapalı';};</script></html>`);return;}
|
||
if(req.method==='POST' && req.url?.startsWith('/qa-api-failure?')) {apiKeyListFailure=new URL(req.url,'http://localhost').searchParams.get('mode')==='on';res.end('ok');return;}
|
||
if(apiKeyListFailure && req.method==='GET' && req.url==='/api/me/apikeys') {res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Anahtar deposuna erişilemiyor.'}));return;}
|
||
if(req.url==='/qa-signin') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`<html lang="tr"><h1>Yerel laboratuvar testi</h1><p>Sentetik ihale verileri ve geçici kullanıcı deposu kullanılır.</p><button id="login">Test hesabıyla gir</button><script type="module">import {setSession} from '/src/services/auth.js';document.querySelector('#login').onclick=()=>{setSession(${JSON.stringify(token)},${JSON.stringify(user)});location.href='/';};</script></html>`);return;}
|
||
if(req.url==='/api/tenders' || req.url?.startsWith('/api/tenders?')){res.setHeader('Content-Type','application/json');res.end(JSON.stringify({success:true,tenders,totalCount:5,todayCount:0,historyCount:0,source:'Yerel sentetik QA',fetchedAt:new Date().toISOString()}));return;}
|
||
next();
|
||
});}}]});
|
||
await ui.listen();console.log('QA_URL='+ui.resolvedUrls.local[0]+'qa-signin');
|
||
async function stop(){clearInterval(qaHeartbeat);await ui.close();api.kill();rmSync(dir,{recursive:true,force:true});process.exit();}
|
||
process.on('SIGINT',stop);process.on('SIGTERM',stop);
|