astro-admin dist (patched 2026-10-08: mobile lists fix) + Dockerfile + nginx.conf + docs
This commit is contained in:
commit
9435fce119
|
|
@ -0,0 +1,6 @@
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# astro-admin — админка Астромаяка (admin.astromayak.ru)
|
||||||
|
|
||||||
|
Vue 3 + Vite + Tailwind. Бэкенд: `admin_api.py` в astro-bot (`/admin/api/*`).
|
||||||
|
|
||||||
|
## Важно: исходников .vue на сервере нет
|
||||||
|
|
||||||
|
Они жили на старом DEV (194.113.209.6, `/opt/astro-deploy/astro-admin/frontend/`), который выведен из эксплуатации.
|
||||||
|
В этом репозитории каноникой является **`dist/`** — собранные файлы, которые nginx раздаёт как есть
|
||||||
|
(Dockerfile просто `COPY dist /usr/share/nginx/html`).
|
||||||
|
|
||||||
|
## Деплой
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# на .175 (193.187.92.66):
|
||||||
|
cd /tmp && rm -rf astro-admin-deploy && git clone https://git.mkn8n.ru/Mk/astro-admin.git astro-admin-deploy
|
||||||
|
cp -r astro-admin-deploy/dist/* /opt/astro-deploy/astro-admin/dist/
|
||||||
|
docker cp /opt/astro-deploy/astro-admin/dist astro-admin:/usr/share/nginx/html/dist_tmp # или напрямую в контейнер:
|
||||||
|
for f in $(ls astro-admin-deploy/dist/assets); do docker cp astro-admin-deploy/dist/assets/$f astro-admin:/usr/share/nginx/html/assets/$f; done
|
||||||
|
docker cp astro-admin-deploy/dist/index.html astro-admin:/usr/share/nginx/html/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед правкой живого контейнера — бэкап файла рядом (`cp X.js X.js.bak-<дата>`).
|
||||||
|
|
||||||
|
## Известный фикс (2026-10-08): пустые списки на мобильных
|
||||||
|
|
||||||
|
В `Subscriptions-*.js`, `Users-*.js`, `Payments-*.js` был шаблонный баг: `данные?таблица:данные?карточки:«пусто»`
|
||||||
|
— вторая ветка недостижима, а таблица скрыта на телефоне (`hidden md:block`). На Android при живых данных
|
||||||
|
показывало «Подписок нет» / «Нет клиентов» / пустые платежи.
|
||||||
|
|
||||||
|
Фикс (скрипт `scripts/mobilefix.py`): `length?(desktop):length?(mobile):(empty)` →
|
||||||
|
`length?[(desktop),(mobile)]:(empty)`. Проверка синтаксиса: `node --check`.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{D as e,E as t,_ as n,d as r,g as i,m as a,n as o,u as s,v as c,w as l}from"./index-DzvFzRSs.js";var u={class:`bg-white rounded-xl shadow-sm border border-slate-200 p-3 md:p-5`},d={class:`flex items-center justify-between`},f={class:`min-w-0`},p={class:`text-xs md:text-sm text-slate-500 truncate`},m={class:`text-xl md:text-2xl flex-shrink-0 ml-2`},h={__name:`StatCard`,props:{label:String,value:[String,Number],icon:String,color:String},setup(n){let i=n,o=s(()=>({blue:`text-blue-600`,green:`text-green-600`,amber:`text-amber-600`,red:`text-red-600`,indigo:`text-indigo-600`,purple:`text-purple-600`,yellow:`text-yellow-600`})[i.color]||`text-slate-900`);return(i,s)=>(c(),a(`div`,u,[r(`div`,d,[r(`div`,f,[r(`p`,p,e(n.label),1),r(`p`,{class:t([`text-lg md:text-2xl font-bold mt-1 truncate`,o.value])},e(n.value),3)]),r(`div`,m,e(n.icon),1)])]))}},g={key:0,class:`text-slate-500`},_={key:1,class:`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4`},v={__name:`Dashboard`,setup(e){let t=l({}),s=l(!0);n(async()=>{try{let{data:e}=await o.get(`/admin/api/dashboard`);t.value=e}finally{s.value=!1}});function u(e){return new Intl.NumberFormat(`ru-RU`).format(e||0)}function d(e){return e>1e6?(e/1e6).toFixed(1)+`M`:e>1e3?(e/1e3).toFixed(0)+`K`:e}return(e,n)=>(c(),a(`div`,null,[n[0]||=r(`h1`,{class:`text-2xl font-bold mb-6`},`📊 Дашборд`,-1),s.value?(c(),a(`div`,g,`Загрузка...`)):(c(),a(`div`,_,[i(h,{label:`Всего клиентов`,value:t.value.total_users,icon:`👥`,color:`blue`},null,8,[`value`]),i(h,{label:`Активных подписок`,value:t.value.active_subscriptions,icon:`📱`,color:`green`},null,8,[`value`]),i(h,{label:`Доход (₽)`,value:u(t.value.total_revenue_rub),icon:`💰`,color:`amber`},null,8,[`value`]),i(h,{label:`Платежей успешно`,value:t.value.successful_payments,icon:`✅`,color:`green`},null,8,[`value`]),i(h,{label:`Запросов за 30д`,value:t.value.requests_30d,icon:`📨`,color:`indigo`},null,8,[`value`]),i(h,{label:`Токенов за 30д`,value:d(t.value.tokens_30d),icon:`🤖`,color:`purple`},null,8,[`value`]),i(h,{label:`Стоимость $ за 30д`,value:`$`+t.value.cost_usd_30d,icon:`💵`,color:`red`},null,8,[`value`]),i(h,{label:`Отзывы`,value:(t.value.positive_count+t.value.negative_count)>0?Math.round(t.value.positive_share*100)+`% 👍 (`+t.value.positive_count+`/`+(t.value.positive_count+t.value.negative_count)+`)`:`—`,icon:`⭐`,color:`yellow`},null,8,[`value`])]))]))}};export{v as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{D as e,E as t,S as n,T as r,d as i,g as a,i as o,l as s,m as c,p as l,t as u,v as d,w as f,x as p,y as m}from"./index-DzvFzRSs.js";var h={class:`min-h-screen bg-slate-50 md:h-screen md:flex md:overflow-hidden`},g={class:`md:hidden sticky top-0 z-30 bg-slate-900 text-white px-4 py-3 flex items-center justify-between`},_={class:`px-5 py-4 border-b border-slate-700 flex items-center justify-between`},v={class:`flex-1 px-3 py-4 space-y-1 overflow-y-auto`},y={class:`px-5 py-3 border-t border-slate-700 text-xs`},b={class:`text-slate-400`},x={class:`text-slate-600`},S={class:`flex-1 overflow-auto`},C={class:`max-w-7xl mx-auto px-4 md:px-6 py-4 md:py-6`},w={__name:`Layout`,setup(w){let T=u(),E=o(),D=f(!1),O=[{path:`/`,icon:`📊`,label:`Дашборд`},{path:`/users`,icon:`👥`,label:`Клиенты`},{path:`/payments`,icon:`💳`,label:`Платежи`},{path:`/subscriptions`,icon:`📱`,label:`Подписки`},{path:`/plans`,icon:`💰`,label:`Тарифы`},{path:`/prompts`,icon:`📝`,label:`Промпты`},{path:`/llm`,icon:`🤖`,label:`LLM`}];function k(){T.logout(),E.push(`/login`)}return(o,u)=>{let f=p(`router-link`),w=p(`router-view`);return d(),c(`div`,h,[i(`div`,g,[u[5]||=i(`div`,{class:`flex items-center gap-2`},[i(`span`,{class:`text-lg`},`🔮`),i(`span`,{class:`font-semibold`},`AstroMayak`)],-1),i(`button`,{onClick:u[0]||=e=>D.value=!0,class:`p-2 -mr-2`,"aria-label":`Меню`},[...u[4]||=[i(`svg`,{width:`24`,height:`24`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},[i(`line`,{x1:`3`,y1:`6`,x2:`21`,y2:`6`}),i(`line`,{x1:`3`,y1:`12`,x2:`21`,y2:`12`}),i(`line`,{x1:`3`,y1:`18`,x2:`21`,y2:`18`})],-1)]])]),D.value?(d(),c(`div`,{key:0,class:`md:hidden fixed inset-0 bg-black/50 z-30`,onClick:u[1]||=e=>D.value=!1})):l(``,!0),i(`aside`,{class:t([`fixed md:static inset-y-0 left-0 z-40 w-64 bg-slate-900 text-slate-300 flex flex-col transform transition-transform duration-200 md:transform-none`,D.value?`translate-x-0`:`-translate-x-full md:translate-x-0`])},[i(`div`,_,[u[6]||=i(`div`,null,[i(`h1`,{class:`text-lg font-semibold text-white`},`🔮 AstroMayak`),i(`p`,{class:`text-xs text-slate-500 mt-0.5`},`Admin Panel`)],-1),i(`button`,{onClick:u[2]||=e=>D.value=!1,class:`md:hidden text-slate-400 hover:text-white`},`✕`)]),i(`nav`,v,[(d(),c(s,null,m(O,r=>a(f,{key:r.path,to:r.path,onClick:u[3]||=e=>D.value=!1,class:t([`flex items-center gap-3 px-3 py-2 rounded-lg text-sm hover:bg-slate-800 transition`,o.$route.path===r.path?`bg-slate-800 text-white`:``])},{default:n(()=>[i(`span`,null,e(r.icon),1),i(`span`,null,e(r.label),1)]),_:2},1032,[`to`,`class`])),64))]),i(`div`,y,[i(`p`,b,e(r(T).admin?.username||`admin`),1),i(`p`,x,e(r(T).admin?.role),1),i(`button`,{onClick:k,class:`mt-2 text-slate-500 hover:text-slate-300`},`Выйти`)])],2),i(`main`,S,[i(`div`,C,[a(w)])])])}}};export{w as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{v as p,l as l}from"./index-DzvFzRSs.js";var s={src:"/llm.html",title:"LLM",style:"width:100%;height:calc(100vh - 140px);border:0;background:#fff;border-radius:12px"};var c={__name:"Llm",setup(){return(_ctx,_cache)=>{return p(),l("iframe",s)}}};export{c as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{C as e,D as t,c as n,d as r,i,m as a,p as o,s,t as c,v as l,w as u}from"./index-DzvFzRSs.js";var d={class:`min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 to-indigo-900 p-4`},f={class:`bg-white rounded-2xl shadow-xl p-6 md:p-8 w-full max-w-md`},p=[`disabled`],m={key:0,class:`text-sm text-red-600 bg-red-50 p-3 rounded-lg`},h={__name:`Login`,setup(h){let g=c(),_=i(),v=u(``),y=u(!1),b=u(``);async function x(){y.value=!0,b.value=``;try{await g.login(v.value),_.push(`/`)}catch(e){b.value=e.response?.data?.detail||`Ошибка входа`}finally{y.value=!1}}return(i,c)=>(l(),a(`div`,d,[r(`div`,f,[c[2]||=r(`div`,{class:`text-center mb-6`},[r(`div`,{class:`text-4xl mb-2`},`🔮`),r(`h1`,{class:`text-xl font-bold text-slate-900`},`AstroMayak Admin`),r(`p`,{class:`text-sm text-slate-500 mt-1`},`Вход в панель управления`)],-1),r(`form`,{onSubmit:n(x,[`prevent`]),class:`space-y-4`},[r(`div`,null,[c[1]||=r(`label`,{class:`block text-sm font-medium text-slate-700 mb-1`},`Пароль`,-1),e(r(`input`,{"onUpdate:modelValue":c[0]||=e=>v.value=e,type:`password`,required:``,autofocus:``,placeholder:`••••••••••••`,class:`w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent`},null,512),[[s,v.value]])]),r(`button`,{type:`submit`,disabled:y.value,class:`w-full bg-indigo-600 text-white py-2.5 rounded-lg font-medium hover:bg-indigo-700 disabled:opacity-50`},t(y.value?`Вход...`:`Войти`),9,p),b.value?(l(),a(`div`,m,t(b.value),1)):o(``,!0)],32)])]))}};export{h as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{D as e,E as t,S as n,_ as r,d as i,g as a,h as o,l as s,m as c,n as l,p as u,v as d,w as f,x as p,y as m}from"./index-DzvFzRSs.js";var h={class:`flex gap-2 mb-4 overflow-x-auto pb-1 -mx-1 px-1`},g=[`onClick`],_={key:0,class:`text-slate-500 py-12 text-center`},v={key:1,class:`hidden md:block bg-white rounded-xl shadow-sm border overflow-hidden`},y={class:`w-full text-sm`},b={class:`divide-y`},x={class:`px-4 py-3 text-slate-500`},S={class:`px-4 py-3`},C={class:`px-4 py-3 text-right font-medium`},w={class:`px-4 py-3 text-center`},T={class:`px-4 py-3 font-mono text-xs text-slate-400`},E={class:`px-4 py-3`},D=[`onClick`],O={key:2,class:`md:hidden space-y-3`},k={class:`flex items-start justify-between`},A={class:`font-semibold`},j={class:`text-xs text-slate-500 mt-2`},M={class:`mt-2 flex items-center justify-between`},N={class:`font-mono text-xs text-slate-400 truncate`},P=[`onClick`],F={key:3,class:`text-slate-400 text-center py-12`},I={__name:`Payments`,setup(I){let L=f([]),R=f(!0),z=f(``),B=[{value:``,label:`Все`},{value:`succeeded`,label:`Успешные`},{value:`pending`,label:`Ожидают`},{value:`failed`,label:`Неудачные`},{value:`refunded`,label:`Возвраты`}];r(()=>V());async function V(){R.value=!0;try{let e=z.value?{status:z.value}:{},{data:t}=await l.get(`/admin/api/payments`,{params:e});L.value=t.payments}finally{R.value=!1}}async function H(e){if(confirm(`Вернуть средства?`))try{await l.post(`/admin/api/payments/${e}/refund`),V()}catch(e){alert(e.response?.data?.detail||`Ошибка`)}}function U(e){return{succeeded:`px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-xs`,pending:`px-2 py-0.5 bg-amber-100 text-amber-700 rounded-full text-xs`,failed:`px-2 py-0.5 bg-red-100 text-red-700 rounded-full text-xs`,refunded:`px-2 py-0.5 bg-purple-100 text-purple-700 rounded-full text-xs`}[e]||`text-slate-600`}function W(e){return e?new Date(e).toLocaleString(`ru-RU`,{day:`2-digit`,month:`2-digit`,year:`2-digit`,hour:`2-digit`,minute:`2-digit`}):`—`}return(r,l)=>{let f=p(`router-link`);return d(),c(`div`,null,[l[1]||=i(`h1`,{class:`text-xl md:text-2xl font-bold mb-4 md:mb-6`},`💳 Платежи`,-1),i(`div`,h,[(d(),c(s,null,m(B,n=>i(`button`,{key:n.value,onClick:e=>{z.value=n.value,V()},class:t([`px-3 py-1.5 rounded-lg text-sm whitespace-nowrap`,z.value===n.value?`bg-indigo-600 text-white`:`bg-white border`])},e(n.label),11,g)),64))]),R.value?(d(),c(`div`,_,`Загрузка...`)):L.value.length?[(d(),c(`div`,v,[i(`table`,y,[l[0]||=i(`thead`,{class:`bg-slate-50`},[i(`tr`,null,[i(`th`,{class:`px-4 py-3 text-left`},`Дата`),i(`th`,{class:`px-4 py-3 text-left`},`Клиент`),i(`th`,{class:`px-4 py-3 text-right`},`Сумма`),i(`th`,{class:`px-4 py-3 text-center`},`Статус`),i(`th`,{class:`px-4 py-3 text-left`},`YooKassa ID`),i(`th`,{class:`px-4 py-3`})])],-1),i(`tbody`,b,[(d(!0),c(s,null,m(L.value,r=>(d(),c(`tr`,{key:r.id,class:`hover:bg-slate-50`},[i(`td`,x,e(W(r.paid_at||r.created_at)),1),i(`td`,S,[a(f,{to:`/users/${r.user_id}`,class:`text-indigo-600 hover:underline`},{default:n(()=>[o(e(r.user_id),1)]),_:2},1032,[`to`])]),i(`td`,C,e(r.amount_rub)+`₽`,1),i(`td`,w,[i(`span`,{class:t(U(r.status))},e(r.status),3)]),i(`td`,T,e(r.yookassa_payment_id?.slice(0,16)),1),i(`td`,E,[r.status===`succeeded`?(d(),c(`button`,{key:0,onClick:e=>H(r.yookassa_payment_id),class:`text-red-600 hover:underline text-xs`},`возврат`,8,D)):u(``,!0)])]))),128))])])])),(d(),c(`div`,O,[(d(!0),c(s,null,m(L.value,r=>(d(),c(`div`,{key:r.id,class:`bg-white rounded-xl border border-slate-200 shadow-sm p-4`},[i(`div`,k,[i(`div`,null,[i(`p`,A,e(r.amount_rub)+`₽`,1),a(f,{to:`/users/${r.user_id}`,class:`text-xs text-indigo-600`},{default:n(()=>[o(`клиент `+e(r.user_id),1)]),_:2},1032,[`to`])]),i(`span`,{class:t(U(r.status))},e(r.status),3)]),i(`p`,j,e(W(r.paid_at||r.created_at)),1),i(`div`,M,[i(`span`,N,e(r.yookassa_payment_id?.slice(0,16)),1),r.status===`succeeded`?(d(),c(`button`,{key:0,onClick:e=>H(r.yookassa_payment_id),class:`text-red-600 text-xs ml-2`},`возврат`,8,P)):u(``,!0)])]))),128))]))]:(d(),c(`div`,F,`Платежей нет`))])}}};export{I as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{C as e,D as t,E as n,S as r,_ as i,d as a,g as o,h as s,l as c,m as l,n as u,p as d,s as f,v as p,w as m,x as h,y as g}from"./index-DzvFzRSs.js";import{t as _}from"./Section-CyoxMKA8.js";var v={key:0,class:`text-slate-500`},y={key:1,class:`space-y-3`},b={class:`flex items-start justify-between`},x={class:`font-semibold text-lg`},S={class:`text-xs text-slate-400`},C={class:`text-sm text-slate-500`},w={class:`grid grid-cols-3 gap-3 mt-4 text-sm`},T=[`onUpdate:modelValue`],E=[`onUpdate:modelValue`],D=[`onUpdate:modelValue`],O=[`onClick`],k={key:0,class:`ml-2 text-sm text-green-600`},A={key:0,class:`space-y-2 text-sm`},j={class:`font-medium`},M={class:`text-slate-400 text-xs`},N=[`onClick`],P={key:1,class:`text-slate-400 text-sm`},F={__name:`Plans`,setup(F){let I=m([]),L=m([]),R=m(!0);i(async()=>{try{let[e,t]=await Promise.all([u.get(`/admin/api/plans`),u.get(`/admin/api/price-overrides`)]);I.value=e.data.plans.map(e=>({...e,_price:e.price_rub,_days:e.duration_days,_yk_desc:e.yookassa_description,_saved:!1})),L.value=t.data.overrides}finally{R.value=!1}});async function z(e){try{await u.patch(`/admin/api/plans/${e.id}`,{price_rub:e._price,duration_days:e._days,yookassa_description:e._yk_desc}),e._saved=!0,setTimeout(()=>e._saved=!1,2e3)}catch(e){alert(e.response?.data?.detail||`Ошибка`)}}async function B(e){confirm(`Удалить override?`)&&(await u.delete(`/admin/api/price-overrides/${e}`),L.value=L.value.filter(t=>t.id!==e))}return(i,u)=>{let m=h(`router-link`);return p(),l(`div`,null,[u[3]||=a(`h1`,{class:`text-2xl font-bold mb-6`},`💰 Тарифы`,-1),R.value?(p(),l(`div`,v,`Загрузка...`)):(p(),l(`div`,y,[(p(!0),l(c,null,g(I.value,r=>(p(),l(`div`,{key:r.id,class:`bg-white rounded-xl shadow-sm border p-5`},[a(`div`,b,[a(`div`,null,[a(`h3`,x,[s(t(r.name)+` `,1),a(`span`,S,`(`+t(r.slug)+`)`,1)]),a(`p`,C,t(r.description),1)]),a(`span`,{class:n([r.is_active?`bg-green-100 text-green-700`:`bg-slate-100 text-slate-500`,`px-2 py-0.5 rounded-full text-xs`])},t(r.is_active?`активен`:`выключен`),3)]),a(`div`,w,[a(`div`,null,[u[0]||=a(`label`,{class:`text-xs text-slate-400`},`Цена (₽)`,-1),e(a(`input`,{"onUpdate:modelValue":e=>r._price=e,type:`number`,step:`0.01`,class:`w-full px-2 py-1 border rounded text-sm`},null,8,T),[[f,r._price,void 0,{number:!0}]])]),a(`div`,null,[u[1]||=a(`label`,{class:`text-xs text-slate-400`},`Длительность (дни)`,-1),e(a(`input`,{"onUpdate:modelValue":e=>r._days=e,type:`number`,class:`w-full px-2 py-1 border rounded text-sm`},null,8,E),[[f,r._days,void 0,{number:!0}]])]),a(`div`,null,[u[2]||=a(`label`,{class:`text-xs text-slate-400`},`Описание YooKassa`,-1),e(a(`input`,{"onUpdate:modelValue":e=>r._yk_desc=e,class:`w-full px-2 py-1 border rounded text-sm`},null,8,D),[[f,r._yk_desc]])])]),a(`button`,{onClick:e=>z(r),class:`mt-3 px-3 py-1.5 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700`},` Сохранить `,8,O),r._saved?(p(),l(`span`,k,`✅ Сохранено`)):d(``,!0)]))),128))])),o(_,{title:`💲 Индивидуальные цены (overrides)`,class:`mt-8`},{default:r(()=>[L.value.length?(p(),l(`div`,A,[(p(!0),l(c,null,g(L.value,e=>(p(),l(`div`,{key:e.id,class:`flex items-center justify-between bg-slate-50 rounded-lg px-3 py-2`},[o(m,{to:`/users/${e.user_id}`,class:`text-indigo-600`},{default:r(()=>[s(`user `+t(e.user_id),1)]),_:2},1032,[`to`]),a(`span`,null,t(e.plan_slug||`все`),1),a(`span`,j,t(e.price_rub)+`₽`,1),a(`span`,M,t(e.reason),1),a(`button`,{onClick:t=>B(e.id),class:`text-red-600 text-xs`},`удалить`,8,N)]))),128))])):(p(),l(`p`,P,`Нет overrides`))]),_:1})])}}};export{F as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{C as e,D as t,E as n,_ as r,a as i,d as a,h as o,l as s,m as c,n as l,p as u,s as d,u as f,v as p,w as m,y as h}from"./index-DzvFzRSs.js";var g={key:0,class:`text-slate-500`},_={key:1},v={class:`flex gap-2 mb-4 flex-wrap`},y=[`onClick`],b={class:`grid grid-cols-1 lg:grid-cols-3 gap-4`},x={class:`space-y-1 lg:col-span-1`},S=[`onClick`],C={class:`flex items-center justify-between`},w={class:`font-medium`},T={key:0,class:`text-xs text-red-500`},E={class:`text-xs text-slate-400 mt-0.5`},D={key:0,class:`lg:col-span-2`},O={class:`bg-white rounded-xl border shadow-sm p-5`},k={class:`flex items-center justify-between mb-3`},A={class:`font-semibold`},j={class:`flex items-center gap-1 text-sm`},M={class:`text-xs text-slate-400 mb-2`},N={class:`mt-3 flex items-center gap-3`},P={key:0,class:`text-sm text-green-600`},F={key:1,class:`lg:col-span-2 text-slate-400 text-center py-12`},I={__name:`Prompts`,setup(I){let L=m([]),R=m(!0),z=m(null),B=m(!1),V=m(``),H=f(()=>[...new Set(L.value.map(e=>e.category))].sort()),U=f(()=>V.value?L.value.filter(e=>e.category===V.value):L.value);r(async()=>{try{let{data:e}=await l.get(`/admin/api/prompts`);L.value=e.prompts,H.value[0]&&(V.value=H.value[0])}finally{R.value=!1}});function W(e){z.value={...e},B.value=!1}async function G(){if(z.value){B.value=!1;try{await l.patch(`/admin/api/prompts/${z.value.key}`,{system_prompt:z.value.system_prompt,model:z.value.model||void 0,is_active:z.value.is_active}),B.value=!0;let e=L.value.findIndex(e=>e.key===z.value.key);e>=0&&Object.assign(L.value[e],z.value),setTimeout(()=>B.value=!1,2e3)}catch(e){alert(e.response?.data?.detail||`Ошибка`)}}}return(r,l)=>(p(),c(`div`,null,[l[4]||=a(`h1`,{class:`text-2xl font-bold mb-6`},`📝 Промпты`,-1),R.value?(p(),c(`div`,g,`Загрузка...`)):(p(),c(`div`,_,[a(`div`,v,[(p(!0),c(s,null,h(H.value,e=>(p(),c(`button`,{key:e,onClick:t=>V.value=e,class:n([`px-3 py-1.5 rounded-lg text-sm`,V.value===e?`bg-indigo-600 text-white`:`bg-white border`])},t(e),11,y))),128))]),a(`div`,b,[a(`div`,x,[(p(!0),c(s,null,h(U.value,e=>(p(),c(`div`,{key:e.key,onClick:t=>W(e),class:n([`p-3 rounded-lg cursor-pointer text-sm`,z.value?.key===e.key?`bg-indigo-50 border border-indigo-200`:`hover:bg-slate-50`])},[a(`div`,C,[a(`span`,w,t(e.icon)+` `+t(e.name),1),e.is_active?u(``,!0):(p(),c(`span`,T,`off`))]),a(`p`,E,t(e.key),1)],10,S))),128))]),z.value?(p(),c(`div`,D,[a(`div`,O,[a(`div`,k,[a(`h3`,A,t(z.value.icon)+` `+t(z.value.name),1),a(`label`,j,[e(a(`input`,{type:`checkbox`,"onUpdate:modelValue":l[0]||=e=>z.value.is_active=e},null,512),[[i,z.value.is_active]]),l[3]||=o(` активен `,-1)])]),a(`p`,M,`key: `+t(z.value.key)+` · model: `+t(z.value.model||`—`),1),e(a(`textarea`,{"onUpdate:modelValue":l[1]||=e=>z.value.system_prompt=e,class:`w-full h-64 md:h-96 px-3 py-2 border rounded-lg font-mono text-xs`},null,512),[[d,z.value.system_prompt]]),a(`div`,N,[e(a(`input`,{"onUpdate:modelValue":l[2]||=e=>z.value.model=e,placeholder:`model (опционально)`,class:`px-2 py-1 border rounded text-sm`},null,512),[[d,z.value.model]]),a(`button`,{onClick:G,class:`px-4 py-1.5 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700`},` Сохранить `),B.value?(p(),c(`span`,P,`✅ Сохранено`)):u(``,!0)])])])):(p(),c(`div`,F,` Выберите промпт слева `))])]))]))}};export{I as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{D as e,b as t,d as n,h as r,m as i,v as a}from"./index-DzvFzRSs.js";var o={class:`bg-white rounded-xl shadow-sm border border-slate-200 p-5 mb-4`},s={class:`text-lg font-semibold mb-3 flex items-center gap-2`},c={__name:`Section`,props:{title:String},setup(c){return(l,u)=>(a(),i(`div`,o,[n(`h2`,s,[t(l.$slots,`icon`),r(` `+e(c.title),1)]),t(l.$slots,`default`)]))}};export{c as t};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{D as e,E as t,S as n,_ as r,d as i,g as a,h as o,l as s,m as c,n as l,v as u,w as d,x as f,y as p}from"./index-DzvFzRSs.js";var m={class:`flex gap-2 mb-4 overflow-x-auto pb-1`},h=[`onClick`],g={key:0,class:`text-slate-500 py-12 text-center`},_={key:1,class:`hidden md:block bg-white rounded-xl shadow-sm border overflow-hidden`},v={class:`w-full text-sm`},y={class:`divide-y`},b={class:`px-4 py-3 text-slate-500`},x={class:`px-4 py-3`},S={class:`px-4 py-3 text-center`},C={class:`px-4 py-3 text-slate-500`},w={class:`px-4 py-3 text-slate-500`},T={class:`px-4 py-3 text-center`},E={key:2,class:`md:hidden space-y-3`},D={class:`flex items-start justify-between`},O={class:`grid grid-cols-2 gap-2 mt-3 text-xs`},k={class:`text-slate-700`},A={class:`text-slate-700`},j={key:3,class:`text-slate-400 text-center py-12`},M={__name:`Subscriptions`,setup(M){let N=d([]),P=d(!0),F=d(``),I=[``,`active`,`expired`,`pending`,`cancelled`];r(()=>L());async function L(){P.value=!0;try{let e=F.value?{status:F.value}:{},{data:t}=await l.get(`/admin/api/subscriptions`,{params:e});N.value=t.subscriptions}finally{P.value=!1}}function R(e){return{active:`px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-xs`,pending:`px-2 py-0.5 bg-amber-100 text-amber-700 rounded-full text-xs`,expired:`px-2 py-0.5 bg-slate-100 text-slate-600 rounded-full text-xs`,cancelled:`px-2 py-0.5 bg-red-100 text-red-700 rounded-full text-xs`}[e]||`text-slate-600`}function z(e){return e?new Date(e).toLocaleDateString(`ru-RU`,{day:`2-digit`,month:`2-digit`,year:`2-digit`}):`—`}return(r,l)=>{let d=f(`router-link`);return u(),c(`div`,null,[l[3]||=i(`h1`,{class:`text-xl md:text-2xl font-bold mb-4 md:mb-6`},`📱 Подписки`,-1),i(`div`,m,[(u(),c(s,null,p(I,n=>i(`button`,{key:n,onClick:e=>{F.value=n,L()},class:t([`px-3 py-1.5 rounded-lg text-sm whitespace-nowrap capitalize`,F.value===n?`bg-indigo-600 text-white`:`bg-white border`])},e(n||`Все`),11,h)),64))]),P.value?(u(),c(`div`,g,`Загрузка...`)):N.value.length?[(u(),c(`div`,_,[i(`table`,v,[l[0]||=i(`thead`,{class:`bg-slate-50`},[i(`tr`,null,[i(`th`,{class:`px-4 py-3 text-left`},`Создана`),i(`th`,{class:`px-4 py-3 text-left`},`Клиент`),i(`th`,{class:`px-4 py-3 text-center`},`Статус`),i(`th`,{class:`px-4 py-3 text-left`},`Начало`),i(`th`,{class:`px-4 py-3 text-left`},`Конец`),i(`th`,{class:`px-4 py-3 text-center`},`Auto-renew`)])],-1),i(`tbody`,y,[(u(!0),c(s,null,p(N.value,r=>(u(),c(`tr`,{key:r.id,class:`hover:bg-slate-50`},[i(`td`,b,e(z(r.created_at)),1),i(`td`,x,[a(d,{to:`/users/${r.user_id}`,class:`text-indigo-600 hover:underline`},{default:n(()=>[o(e(r.user_id),1)]),_:2},1032,[`to`])]),i(`td`,S,[i(`span`,{class:t(R(r.status))},e(r.status),3)]),i(`td`,C,e(z(r.started_at)),1),i(`td`,w,e(z(r.ends_at)),1),i(`td`,T,e(r.auto_renew?`✅`:`—`),1)]))),128))])])])),(u(),c(`div`,E,[(u(!0),c(s,null,p(N.value,r=>(u(),c(`div`,{key:r.id,class:`bg-white rounded-xl border border-slate-200 shadow-sm p-4`},[i(`div`,D,[a(d,{to:`/users/${r.user_id}`,class:`text-indigo-600 text-sm`},{default:n(()=>[o(`клиент `+e(r.user_id),1)]),_:2},1032,[`to`]),i(`span`,{class:t(R(r.status))},e(r.status),3)]),i(`div`,O,[i(`div`,null,[l[1]||=i(`p`,{class:`text-slate-400`},`Начало`,-1),i(`p`,k,e(z(r.started_at)),1)]),i(`div`,null,[l[2]||=i(`p`,{class:`text-slate-400`},`Конец`,-1),i(`p`,A,e(z(r.ends_at)),1)])])]))),128))]))]:(u(),c(`div`,j,`Подписок нет`))])}}};export{M as default};
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
import{C as e,D as t,S as n,_ as r,d as i,g as a,h as o,l as s,m as c,n as l,s as u,v as d,w as f,x as p,y as m}from"./index-DzvFzRSs.js";var h={class:`flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-4 md:mb-6`},g={key:0,class:`text-slate-500 py-12 text-center`},_={key:1,class:`hidden md:block bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden`},v={class:`w-full text-sm`},y={class:`divide-y divide-slate-100`},b={class:`px-4 py-3 font-mono text-xs text-slate-500`},x={class:`px-4 py-3`},S={class:`px-4 py-3`},C={class:`px-4 py-3 text-slate-500`},w={class:`px-4 py-3 text-slate-500`},T={class:`px-4 py-3 text-center`},E={class:`px-4 py-3 text-center`},D={class:`px-4 py-3 text-center`},O={key:0,class:`px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-xs`},k={key:1,class:`text-slate-400 text-xs`},A={class:`px-4 py-3`},j={key:2,class:`md:hidden space-y-3`},M={class:`flex items-start justify-between gap-2`},N={class:`min-w-0 flex-1`},P={class:`font-semibold truncate`},F={class:`text-xs text-slate-500 truncate`},I={class:`grid grid-cols-3 gap-2 mt-3 text-xs`},L={class:`text-slate-700`},R={class:`text-slate-700`},z={class:`mt-2`},B={key:0,class:`inline-block px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-xs`},V={key:1,class:`text-slate-400 text-xs`},H={key:3,class:`text-slate-400 text-center py-12`},U={__name:`Users`,setup(U){let W=f([]),G=f(!0),K=f(``),q=null;r(()=>J());async function J(){G.value=!0;try{let e=K.value?{search:K.value}:{},{data:t}=await l.get(`/admin/api/users`,{params:e});W.value=t.users}finally{G.value=!1}}function Y(){clearTimeout(q),q=setTimeout(J,400)}function X(e){return e?new Date(e).toLocaleDateString(`ru-RU`,{day:`2-digit`,month:`2-digit`,year:`2-digit`}):`—`}return(r,l)=>{let f=p(`router-link`);return d(),c(`div`,null,[i(`div`,h,[l[1]||=i(`h1`,{class:`text-xl md:text-2xl font-bold`},`👥 Клиенты`,-1),e(i(`input`,{"onUpdate:modelValue":l[0]||=e=>K.value=e,onInput:Y,placeholder:`Поиск по имени / ID...`,class:`px-3 py-2 border border-slate-300 rounded-lg text-sm w-full md:w-64 focus:ring-2 focus:ring-indigo-500`},null,544),[[u,K.value]])]),G.value?(d(),c(`div`,g,`Загрузка...`)):W.value.length?[(d(),c(`div`,_,[i(`table`,v,[l[3]||=i(`thead`,{class:`bg-slate-50 text-slate-600`},[i(`tr`,null,[i(`th`,{class:`px-4 py-3 text-left`},`ID`),i(`th`,{class:`px-4 py-3 text-left`},`Имя`),i(`th`,{class:`px-4 py-3 text-left`},`Username`),i(`th`,{class:`px-4 py-3 text-left`},`Регистрация`),i(`th`,{class:`px-4 py-3 text-left`},`Last seen`),i(`th`,{class:`px-4 py-3 text-center`},`Birth`),i(`th`,{class:`px-4 py-3 text-center`},`Запросов`),i(`th`,{class:`px-4 py-3 text-center`},`Подписка`),i(`th`,{class:`px-4 py-3`})])],-1),i(`tbody`,y,[(d(!0),c(s,null,m(W.value,e=>(d(),c(`tr`,{key:e.user_id,class:`hover:bg-slate-50`},[i(`td`,b,t(e.user_id),1),i(`td`,x,t(e.name||`—`),1),i(`td`,S,`@`+t(e.username||`—`),1),i(`td`,C,t(X(e.registered_at)),1),i(`td`,w,t(X(e.last_seen)),1),i(`td`,T,t(e.has_birth_data?`✅`:`❌`),1),i(`td`,E,t(e.request_count),1),i(`td`,D,[e.subscription_active?(d(),c(`span`,O,` до `+t(X(e.subscription_ends_at)),1)):(d(),c(`span`,k,`нет`))]),i(`td`,A,[a(f,{to:`/users/${e.user_id}`,class:`text-indigo-600 hover:underline text-xs`},{default:n(()=>[...l[2]||=[o(`открыть →`,-1)]]),_:1},8,[`to`])])]))),128))])])])),(d(),c(`div`,j,[(d(!0),c(s,null,m(W.value,e=>(d(),c(`div`,{key:e.user_id,class:`bg-white rounded-xl border border-slate-200 shadow-sm p-4`},[i(`div`,M,[i(`div`,N,[i(`p`,P,t(e.name||e.username||`Без имени`),1),i(`p`,F,`@`+t(e.username||`—`)+` · ID `+t(e.user_id),1)]),a(f,{to:`/users/${e.user_id}`,class:`text-indigo-600 text-xs whitespace-nowrap`},{default:n(()=>[...l[4]||=[o(`открыть →`,-1)]]),_:1},8,[`to`])]),i(`div`,I,[i(`div`,null,[l[5]||=i(`p`,{class:`text-slate-400`},`Рег.`,-1),i(`p`,L,t(X(e.registered_at)),1)]),i(`div`,null,[l[6]||=i(`p`,{class:`text-slate-400`},`Запросов`,-1),i(`p`,R,t(e.request_count),1)]),i(`div`,null,[l[7]||=i(`p`,{class:`text-slate-400`},`Birth`,-1),i(`p`,null,t(e.has_birth_data?`✅`:`❌`),1)])]),i(`div`,z,[e.subscription_active?(d(),c(`span`,B,` Подписка до `+t(X(e.subscription_ends_at)),1)):(d(),c(`span`,V,`нет подписки`))])]))),128))]))]:(d(),c(`div`,H,`Нет клиентов`))])}}};export{U as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,65 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AstroMayak Admin</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-DzvFzRSs.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-CMrKVZZb.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* Навигация с дашборда: карточки метрик ведут в соответствующий раздел */
|
||||||
|
(function () {
|
||||||
|
var MAP = [
|
||||||
|
['Всего клиентов', '/users'],
|
||||||
|
['Активных подписок', '/subscriptions'],
|
||||||
|
['Доход', '/payments'],
|
||||||
|
['Платежей успешно', '/payments'],
|
||||||
|
['Запросов за 30д', '/users'],
|
||||||
|
['Токенов за 30д', '/users'],
|
||||||
|
['Стоимость $ за 30д', '/users']
|
||||||
|
];
|
||||||
|
function decorate() {
|
||||||
|
var cards = document.querySelectorAll('div[class*="bg-white"]');
|
||||||
|
for (var i = 0; i < cards.length; i++) {
|
||||||
|
(function (card) {
|
||||||
|
if (card.getAttribute('data-nav')) return;
|
||||||
|
var ps = card.querySelectorAll('p');
|
||||||
|
if (ps.length < 2) return;
|
||||||
|
var label = (ps[0].textContent || '').trim();
|
||||||
|
for (var j = 0; j < MAP.length; j++) {
|
||||||
|
if (label.indexOf(MAP[j][0]) === 0) {
|
||||||
|
card.setAttribute('data-nav', MAP[j][1]);
|
||||||
|
card.style.cursor = 'pointer';
|
||||||
|
card.style.transition = 'box-shadow .15s, transform .15s';
|
||||||
|
card.title = 'Открыть раздел';
|
||||||
|
card.addEventListener('mouseenter', function () {
|
||||||
|
card.style.boxShadow = '0 6px 18px rgba(0,0,0,.12)';
|
||||||
|
card.style.transform = 'translateY(-1px)';
|
||||||
|
});
|
||||||
|
card.addEventListener('mouseleave', function () {
|
||||||
|
card.style.boxShadow = '';
|
||||||
|
card.style.transform = '';
|
||||||
|
});
|
||||||
|
card.addEventListener('click', function () {
|
||||||
|
location.href = card.getAttribute('data-nav');
|
||||||
|
});
|
||||||
|
var hint = document.createElement('div');
|
||||||
|
hint.textContent = 'перейти →';
|
||||||
|
hint.style.cssText = 'font-size:11px;color:#6366f1;margin-top:2px';
|
||||||
|
card.appendChild(hint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(cards[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new MutationObserver(decorate).observe(document.body, {childList: true, subtree: true});
|
||||||
|
setInterval(decorate, 1000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script>(function(){function add(){var nav=document.querySelector('nav')||document.querySelector('aside');if(!nav||document.getElementById('nav-llm'))return;var a=document.createElement('a');a.id='nav-llm';a.href='/llm.html';a.innerHTML='\u{1F916} LLM';a.style.cssText='display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:8px;color:inherit;text-decoration:none';nav.appendChild(a);}new MutationObserver(add).observe(document.body,{childList:true,subtree:true});setInterval(add,1000);})();</script></body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name admin.astromayak.ru;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
# ACME challenge — serve files directly, no SPA fallback
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin API proxy
|
||||||
|
location /admin/api/ {
|
||||||
|
resolver 127.0.0.11 valid=30s;
|
||||||
|
set $backend "astro-bot:18793";
|
||||||
|
proxy_pass http://$backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
client_max_body_size 10m;
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA fallback
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# No cache for index.html
|
||||||
|
location = /index.html {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name pay.astromayak.ru;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
|
||||||
|
# ACME challenge
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Payment API + YooKassa webhook
|
||||||
|
location /admin/api/ {
|
||||||
|
resolver 127.0.0.11 valid=30s;
|
||||||
|
set $backend "astro-bot:18793";
|
||||||
|
proxy_pass http://$backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public thank-you page after payment (see dist/status.html)
|
||||||
|
location /status {
|
||||||
|
try_files /status.html =404;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import subprocess, sys
|
||||||
|
|
||||||
|
jobs = {
|
||||||
|
"Subscriptions-CGNWQzow.js": ("N", "j", "u"),
|
||||||
|
"Users-CxW1h-sB.js": ("W", "H", "d"),
|
||||||
|
"Payments-BjNHIYXT.js": ("L", "F", "d"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for fname, (arr, empty, fn) in jobs.items():
|
||||||
|
p = f"/tmp/admin-assets/{fname}"
|
||||||
|
src = open(p, encoding="utf-8").read()
|
||||||
|
cond = f"{arr}.value.length?"
|
||||||
|
assert src.count(cond) == 2, f"{fname}: cond count={src.count(cond)}"
|
||||||
|
# 1. open array before desktop branch (first occurrence; include paren
|
||||||
|
# so step 2 still uniquely matches the second condition)
|
||||||
|
s1 = src.replace(":" + cond + "(", ":" + cond + "[(", 1)
|
||||||
|
# 2. second condition -> comma (mobile branch becomes 2nd array element)
|
||||||
|
s2 = s1.replace(":" + cond + "(", ",(", 1)
|
||||||
|
# 3. close array before empty branch: `:(FN(),c(`div`,EMPTY,`
|
||||||
|
marker = f":({fn}(),c(`div`,{empty},"
|
||||||
|
assert s2.count(marker) == 1, f"{fname}: marker count={s2.count(marker)}"
|
||||||
|
s3 = s2.replace(marker, "]:" + "(" + fn + "(),c(`div`," + empty + ",", 1)
|
||||||
|
open(p, "w", encoding="utf-8").write(s3)
|
||||||
|
print(f"{fname}: patched ok, len {len(src)}->{len(s3)}")
|
||||||
|
|
||||||
|
print("ALL PATCHED")
|
||||||
Loading…
Reference in New Issue