Compare commits
32 Commits
c24a3e5695
...
feature-da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d2217f8e1 | ||
|
|
704aab9265 | ||
|
|
046ef4cb9f | ||
|
|
d2f271e3cd | ||
|
|
5fdb23d6d5 | ||
|
|
734adf9a1e | ||
|
|
0e8d1957a3 | ||
|
|
e2a625ba12 | ||
|
|
eb89b3f8a6 | ||
|
|
343f97234a | ||
|
|
04f28a3be5 | ||
|
|
63099eda8b | ||
|
|
65d89b0462 | ||
|
|
c6ded099fa | ||
|
|
c072233593 | ||
|
|
4e296c1555 | ||
| 016fcaab29 | |||
|
|
bebc1ec9fa | ||
|
|
f71c3d2123 | ||
|
|
d79ce8d06b | ||
|
|
fc9c3a5e81 | ||
|
|
6df72a1ae3 | ||
|
|
372dbe50f7 | ||
|
|
68fde6d490 | ||
|
|
c398dee21e | ||
|
|
f2ab310b6d | ||
|
|
ca0f24465b | ||
|
|
3cc4b65460 | ||
|
|
a6cf95b76d | ||
|
|
484ab9fdae | ||
|
|
78bba2502f | ||
|
|
c78b3cb5c0 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,8 +1,3 @@
|
||||
.vscode
|
||||
.mypy_cache
|
||||
docker-stack.yml
|
||||
backend/pyvenv.cfg
|
||||
backend/Include/
|
||||
backend/Scripts/
|
||||
|
||||
log/api.log
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[config]
|
||||
SCM_DO_BUILD_DURING_DEPLOYMENT=true
|
||||
File diff suppressed because one or more lines are too long
@@ -1,58 +1,40 @@
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from datetime import timedelta
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.core import security
|
||||
from app.core.auth import authenticate_user, sign_up_new_user
|
||||
from app.core import security,tenantCacheService
|
||||
from app.core.dbmanager import get_db
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
auth_router = r = APIRouter()
|
||||
|
||||
|
||||
@r.post("/token")
|
||||
async def login(request: Request,db:Session= Depends(get_db) ,form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
if not db :
|
||||
async def login(
|
||||
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
||||
):
|
||||
user = authenticate_user(db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
user = authenticate_user(db, form_data.username, form_data.password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="abcIncorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(
|
||||
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
if user.is_superuser:
|
||||
roles = "super"
|
||||
permissions = "ALL"
|
||||
permissions = "admin"
|
||||
else:
|
||||
roles = ";".join(role.name for role in user.roles)
|
||||
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
|
||||
permissions =";".join(list(set(perlst)))
|
||||
|
||||
permissions = "user"
|
||||
access_token = security.create_access_token(
|
||||
data={"sub": user.id,"roles":roles,"permissions": permissions,"tenant":user.tenantid,},
|
||||
data={"sub": user.id, "permissions": permissions},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
request.state.user = user.id
|
||||
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
|
||||
|
||||
@r.post("/signup")
|
||||
async def signup(
|
||||
|
||||
@@ -3,32 +3,30 @@ from io import BytesIO
|
||||
import typing as t
|
||||
import pandas as pd
|
||||
import json
|
||||
import base64
|
||||
import httpx
|
||||
import deepdiff
|
||||
import app.core.config as config
|
||||
import os
|
||||
from pathlib import Path
|
||||
from app.core.dbmanager import get_db
|
||||
from app.db.crud import get_flows_by_app,get_kintoneformat
|
||||
from app.db.session import SessionLocal
|
||||
from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat
|
||||
from app.core.auth import get_current_active_user,get_current_user
|
||||
from app.core.apiexception import APIException
|
||||
from app.db.cruddb import domainService,appService
|
||||
|
||||
kinton_router = r = APIRouter()
|
||||
|
||||
def getkintoneenv(user = Depends(get_current_user),db = Depends(get_db)):
|
||||
#db = SessionLocal()
|
||||
domain = domainService.get_default_domain(db,user.id) #get_activedomain(db, user.id)
|
||||
#db.close()
|
||||
def getkintoneenv(user = Depends(get_current_user)):
|
||||
db = SessionLocal()
|
||||
domain = get_activedomain(db, user.id)
|
||||
db.close()
|
||||
kintoneevn = config.KINTONE_ENV(domain)
|
||||
return kintoneevn
|
||||
|
||||
|
||||
def getkintoneformat(db,user = Depends(get_current_user)):
|
||||
#db = SessionLocal()
|
||||
def getkintoneformat():
|
||||
db = SessionLocal()
|
||||
formats = get_kintoneformat(db)
|
||||
#db.close()
|
||||
db.close()
|
||||
return formats
|
||||
|
||||
|
||||
@@ -158,10 +156,10 @@ def getsettingfromexcel(df):
|
||||
des = df.iloc[2,2]
|
||||
return {"name":appname,"description":des}
|
||||
|
||||
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -173,69 +171,68 @@ def analysesettings(excel,kintone):
|
||||
updatesettings[key] = excel[key]
|
||||
return updatesettings
|
||||
|
||||
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||
data = {"app":app}
|
||||
data.update(updates)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
if revision != None:
|
||||
data = {"app":app,"revision":revision,"properties":fields}
|
||||
else:
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
params = {"app":app,"revision":revision,"fields":fields}
|
||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||
return r.json()
|
||||
|
||||
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
|
||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||
# スペース、枠線、ラベルを含まない
|
||||
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
# フォームに配置するフィールドのみ取得する
|
||||
# スペース、枠線、ラベルも含める
|
||||
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getformfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -288,10 +285,10 @@ def analysefields(excel,kintone):
|
||||
|
||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||
|
||||
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -376,95 +373,49 @@ def getkintoneorgs(c:config.KINTONE_ENV):
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
def uploadkintonefiles(file,env:config.KINTONE_ENV):
|
||||
def uploadkintonefiles(file,c:config.KINTONE_ENV):
|
||||
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
return {'fileKey':file}
|
||||
upload_files = {'file': open(file,'rb')}
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
data ={'name':'file','filename':os.path.basename(file)}
|
||||
url = f"{env.BASE_URL}/k/v1/file.json"
|
||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||
#{"name":data['filename'],'fileKey':r['fileKey']}
|
||||
url = f"{c.BASE_URL}/k/v1/file.json"
|
||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||
return r.json()
|
||||
|
||||
def updateappjscss(app,uploads,env:config.KINTONE_ENV):
|
||||
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||
dsjs = []
|
||||
dscss = []
|
||||
#mobile側
|
||||
mbjs = []
|
||||
mbcss = []
|
||||
customize = getappcustomize(app, env)
|
||||
current_js = customize['desktop'].get('js', [])
|
||||
current_css = customize['desktop'].get('css', [])
|
||||
current_mobile_js = customize['mobile'].get('js', [])
|
||||
current_mobile_css = customize['mobile'].get('css', [])
|
||||
current_js = [item for item in current_js if not (item.get('type') == 'URL' and item.get('url', '').endswith('alc_runtime.js'))]
|
||||
for upload in uploads:
|
||||
for key in upload:
|
||||
filename = os.path.basename(key)
|
||||
if key.endswith('.js'):
|
||||
existing_js = next((item for item in current_js
|
||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||
), None)
|
||||
if existing_js:
|
||||
current_js = [item for item in current_js if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||
else:
|
||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||
else:
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
elif key.endswith('.css'):
|
||||
existing_css = next((item for item in current_css
|
||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||
), None)
|
||||
if existing_css:
|
||||
current_css = [item for item in current_css if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||
else:
|
||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||
#現在のJSとCSSがdsjsに追加する
|
||||
dsjs.extend(current_js)
|
||||
dscss.extend(current_css)
|
||||
mbjs.extend(current_mobile_js)
|
||||
mbcss.extend(current_mobile_css)
|
||||
|
||||
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
ds ={'js':dsjs,'css':dscss}
|
||||
mb ={'js':mbjs,'css':mbcss}
|
||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
print(json.dumps(data))
|
||||
mb ={'js':[],'css':[]}
|
||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
print(data)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
#kintone カスタマイズ情報
|
||||
def getappcustomize(app,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
|
||||
def getTempPath(filename):
|
||||
scriptdir = Path(__file__).resolve().parent
|
||||
rootdir = scriptdir.parent.parent.parent.parent
|
||||
fpath = os.path.join(rootdir,"Temp",filename)
|
||||
return fpath
|
||||
|
||||
def createappjs(domain_url,app,db):
|
||||
#db = SessionLocal()
|
||||
flows = appService.get_flow(db,domain_url,app) #get_flows_by_app(db,domain_url,app)
|
||||
#db.close()
|
||||
def createappjs(domainid,app):
|
||||
db = SessionLocal()
|
||||
flows = get_flows_by_app(db,domainid,app)
|
||||
db.close()
|
||||
content={}
|
||||
for flow in flows:
|
||||
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
||||
js = 'const alcflow=' + json.dumps(content)
|
||||
# scriptdir = Path(__file__).resolve().parent
|
||||
# rootdir = scriptdir.parent.parent.parent.parent
|
||||
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
||||
fpath = getTempPath(f"alc_setting_{app}.js")
|
||||
scriptdir = Path(__file__).resolve().parent
|
||||
rootdir = scriptdir.parent.parent.parent.parent
|
||||
fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
||||
print(rootdir)
|
||||
print(fpath)
|
||||
with open(fpath,'w') as file:
|
||||
file.write(js)
|
||||
@@ -494,17 +445,6 @@ async def test(file:UploadFile= File(...),app:str=None):
|
||||
return test
|
||||
|
||||
|
||||
@r.get("/group")
|
||||
async def group(request:Request,kintoneurl:str,kintoneuser:str,kintonepwd:str):
|
||||
try:
|
||||
auth_value = base64.b64encode(bytes(f"{kintoneuser}:{kintonepwd}","utf-8"))
|
||||
headers={config.API_V1_AUTH_KEY:auth_value}
|
||||
url = f"{kintoneurl}/v1/user/groups.json?code={kintoneuser}"
|
||||
r = httpx.get(url,headers=headers)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:group',request.url._url, f"Error occurred while get group(url:{kintoneurl} user:{kintoneuser}):",e)
|
||||
|
||||
@r.post("/download",)
|
||||
async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
@@ -514,7 +454,7 @@ async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneen
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:download',request.url._url,f"Error occurred while download file.json:",e)
|
||||
raise APIException('kintone:upload',request.url._url,f"Error occurred while download file.json:",e)
|
||||
|
||||
@r.post("/upload")
|
||||
async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
||||
@@ -534,7 +474,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
||||
return {"files": [file.filename for file in files]}
|
||||
|
||||
@r.post("/updatejscss")
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
for file in files:
|
||||
@@ -555,103 +495,81 @@ async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env
|
||||
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
|
||||
|
||||
@r.get("/app")
|
||||
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
params ={"id":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/allapps")
|
||||
async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
offset = 0
|
||||
limit = 100
|
||||
all_apps = []
|
||||
|
||||
while True:
|
||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||
json_data = r.json()
|
||||
apps = json_data.get("apps",[])
|
||||
all_apps.extend(apps)
|
||||
if len(apps)<limit:
|
||||
break
|
||||
offset += limit
|
||||
return {"apps": all_apps}
|
||||
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
r = httpx.get(url,headers=headers)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({env.DOMAIN_NAME}):", e)
|
||||
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
|
||||
|
||||
@r.get("/appfields")
|
||||
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
return getfieldsfromkintone(app,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/allfields")
|
||||
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
field_resp = getfieldsfromkintone(app,env)
|
||||
form_resp = getformfromkintone(app,env)
|
||||
return merge_kintone_fields(field_resp,form_resp)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/appprocess")
|
||||
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
return getprocessfromkintone(app,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/alljscss")
|
||||
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.post("/createapp",)
|
||||
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
result = r.json()
|
||||
if result.get("app") != None:
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
data = {"apps":[result],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({env.DOMAIN_NAME}->{name}):",e)
|
||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
|
||||
|
||||
|
||||
@r.get("/defaultgroup")
|
||||
async def currentgroup(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
auth_value = env.API_V1_AUTH_VALUE
|
||||
headers={config.API_V1_AUTH_KEY:auth_value}
|
||||
url = f"{env.BASE_URL}/v1/user/groups.json?code={env.KINTONE_USER}"
|
||||
r = httpx.get(url,headers=headers)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:currentgroup',request.url._url, f"Error occurred while get default domain group(domain:{env.DOMAIN_NAME} url:{env.BASE_URL} user:{env.KINTONE_USER}):",e)
|
||||
|
||||
@r.post("/createappfromexcel",)
|
||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
mapping = getkintoneformat(db)[format]
|
||||
mapping = getkintoneformat()[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
@@ -688,9 +606,9 @@ async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...
|
||||
|
||||
|
||||
@r.post("/updateappfromexcel")
|
||||
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||
try:
|
||||
mapping = getkintoneformat(db)[format]
|
||||
mapping = getkintoneformat()[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
@@ -774,27 +692,25 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
|
||||
if deploy:
|
||||
result = deoployappfromkintone(app,revision,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@r.post("/createjstokintone",)
|
||||
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
files=[]
|
||||
files.append(createappjs(env.BASE_URL, app, db))
|
||||
files.append(getTempPath('alc_runtime.js'))
|
||||
files.append(getTempPath('alc_runtime.css'))
|
||||
files.append(createappjs(env.DOMAIN_ID, app))
|
||||
files.append('Temp\\alc_runtime.js')
|
||||
for file in files:
|
||||
upload = uploadkintonefiles(file,env)
|
||||
if upload.get('fileKey') != None:
|
||||
print(upload)
|
||||
jscs.append({ file :upload['fileKey']})
|
||||
appjscs = updateappjscss(app,jscs,env)
|
||||
if appjscs.get("revision") != None:
|
||||
deoployappfromkintone(app,appjscs["revision"],env)
|
||||
return appjscs
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@@ -1,152 +1,14 @@
|
||||
from http import HTTPStatus
|
||||
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from fastapi.responses import JSONResponse
|
||||
# from app.core.operation import log_operation
|
||||
# from app.db import Base,engine
|
||||
from app.core.dbmanager import get_db
|
||||
from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from app.db import Base,engine
|
||||
from app.db.session import get_db
|
||||
from app.db.crud import *
|
||||
from app.db.schemas import *
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from app.core.auth import get_current_active_user,get_current_user
|
||||
from app.core.apiexception import APIException
|
||||
from app.core.common import ApiReturnModel,ApiReturnPage
|
||||
#from fastapi_pagination import Page
|
||||
from app.db.cruddb import domainService,appService
|
||||
|
||||
import httpx
|
||||
import app.core.config as config
|
||||
from app.core import domainCacheService,tenantCacheService
|
||||
|
||||
platform_router = r = APIRouter()
|
||||
|
||||
@r.get(
|
||||
"/test",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def test(
|
||||
request: Request,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
domainService.select(db,{"tenantid":1,"name":["b","c"]})
|
||||
|
||||
|
||||
@r.get(
|
||||
"/apps",tags=["App"],
|
||||
response_model=ApiReturnModel[List[AppList]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def apps_list(
|
||||
request: Request,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
|
||||
domain = domainService.get_default_domain(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domain:
|
||||
return ApiReturnModel(data = None)
|
||||
filtered_apps = []
|
||||
platformapps = appService.get_apps(db,domain.url)
|
||||
kintoneevn = config.KINTONE_ENV(domain)
|
||||
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
||||
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
offset = 0
|
||||
limit = 100
|
||||
all_apps = []
|
||||
|
||||
while True:
|
||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||
json_data = r.json()
|
||||
apps = json_data.get("apps",[])
|
||||
all_apps.extend(apps)
|
||||
if len(apps)<limit:
|
||||
break
|
||||
offset += limit
|
||||
|
||||
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
||||
for papp in platformapps:
|
||||
if papp.appid in kintone_apps_dict:
|
||||
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
||||
filtered_apps.append(papp)
|
||||
return ApiReturnModel(data = filtered_apps)
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
|
||||
|
||||
@r.post("/apps", tags=["App"],
|
||||
response_model=ApiReturnModel[AppList|None],
|
||||
response_model_exclude_none=True)
|
||||
async def apps_update(
|
||||
request: Request,
|
||||
app: VersionUpdate,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data =appService.update_appversion(db, domainurl,app,user.id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
|
||||
|
||||
@r.delete("/apps/{appid}",tags=["App"],
|
||||
response_model=ApiReturnModel[AppList|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def apps_delete(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data =appService.delete_app(db, domainurl,appid))
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while delete app({appid}):",e)
|
||||
|
||||
@r.get(
|
||||
"/appversions/{appid}",tags=["App"],
|
||||
response_model=ApiReturnPage[AppVersion|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def appversions_list(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnPage(data = None)
|
||||
return appService.get_appversions(db,domainurl,appid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:appversions',request.url._url,f"Error occurred while get app({appid}) version :",e)
|
||||
|
||||
@r.put(
|
||||
"/appversions/{appid}/{version}",tags=["App"],
|
||||
response_model=ApiReturnModel[AppList|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def appversions_change(
|
||||
request: Request,
|
||||
appid: str,
|
||||
version: int,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.change_appversion(db, domainurl,appid,version,user.id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:appversions',request.url._url,f"Error occurred while change app version:",e)
|
||||
|
||||
@r.get(
|
||||
"/appsettings/{id}",
|
||||
response_model=App,
|
||||
@@ -237,177 +99,127 @@ async def action_data(
|
||||
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
|
||||
|
||||
@r.get(
|
||||
"/flow/{appid}",tags=["App"],
|
||||
response_model=ApiReturnModel[List[Flow]|None],
|
||||
"/flow/{flowid}",
|
||||
response_model=Flow,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_details(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
flowid: str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.get_flow(db, domainurl, appid))
|
||||
app = get_flow(db, flowid)
|
||||
return app
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/flows/{appid}", tags=["App"],
|
||||
response_model=List[Flow|None],
|
||||
"/flows/{appid}",
|
||||
response_model=List[Flow],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_list(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
return []
|
||||
#flows = get_flows_by_app(db, domainurl, appid)
|
||||
flows = appService.get_flow(db,domainurl,appid)
|
||||
domain = get_activedomain(db, user.id)
|
||||
print("domain=>",domain)
|
||||
flows = get_flows_by_app(db, domain.id, appid)
|
||||
return flows
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
||||
|
||||
|
||||
@r.post("/flow", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True)
|
||||
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
|
||||
async def flow_create(
|
||||
request: Request,
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
flow: FlowBase,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.create_flow(db, domainurl, flow,user.id))
|
||||
domain = get_activedomain(db, user.id)
|
||||
return create_flow(db, domain.id, flow)
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/flow", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_edit(
|
||||
request: Request,
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
flow: FlowBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.edit_flow(db,domainurl, flow,user.id))
|
||||
return edit_flow(db, flow)
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/flow/{flowid}", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
)
|
||||
async def flow_delete(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.delete_flow(db, flowid))
|
||||
return delete_flow(db, flowid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
||||
|
||||
@r.get(
|
||||
"/domains",tags=["Domain"],
|
||||
response_model=ApiReturnPage[Domain],
|
||||
"/domains/{tenantid}",
|
||||
response_model=List[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_list(
|
||||
async def domain_details(
|
||||
request: Request,
|
||||
user=Depends(get_current_active_user),
|
||||
tenantid:str,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
if user.is_superuser:
|
||||
domains = domainService.get_domains(db)
|
||||
else:
|
||||
domains = domainService.get_domains_by_manage(db,user.id)
|
||||
return domains
|
||||
domains = get_domains(db,tenantid)
|
||||
return domains
|
||||
except Exception as e:
|
||||
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
|
||||
|
||||
@r.get(
|
||||
"/domain/{domain_id}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[Domain|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_detail(
|
||||
request: Request,
|
||||
domain_id:int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.get(db,domain_id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while get domain detail:",e)
|
||||
|
||||
@r.post("/domain", tags=["Domain"],
|
||||
response_model=ApiReturnModel[Domain],
|
||||
response_model_exclude_none=True)
|
||||
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
|
||||
async def domain_create(
|
||||
request: Request,
|
||||
domain: DomainIn,
|
||||
user=Depends(get_current_active_user),
|
||||
domain: DomainBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.create_domain(db, domain,user.id))
|
||||
return create_domain(db, domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/domain", tags=["Domain"],
|
||||
response_model=ApiReturnModel[Domain|None],
|
||||
response_model_exclude_none=True
|
||||
"/domain", response_model=Domain, response_model_exclude_none=True
|
||||
)
|
||||
async def domain_edit(
|
||||
request: Request,
|
||||
domain: DomainIn,
|
||||
user=Depends(get_current_active_user),
|
||||
domain: DomainBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = domainService.edit_domain(db, domain,user.id)
|
||||
if domain :
|
||||
domainCacheService.clear_default_domainurl()
|
||||
return ApiReturnModel(data = domain)
|
||||
return edit_domain(db, domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/domain/{id}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
|
||||
)
|
||||
async def domain_delete(
|
||||
request: Request,
|
||||
@@ -415,9 +227,9 @@ async def domain_delete(
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.delete_domain(db,id))
|
||||
return delete_domain(db,id)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain({id}):",e)
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
|
||||
|
||||
@r.get(
|
||||
"/domain",
|
||||
@@ -426,164 +238,77 @@ async def domain_delete(
|
||||
)
|
||||
async def userdomain_details(
|
||||
request: Request,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domains = get_domain(db, userId if userId is not None else user.id)
|
||||
domains = get_domain(db, user.id)
|
||||
return domains
|
||||
except Exception as e:
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||
|
||||
@r.post(
|
||||
"/userdomain",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
"/domain/{userid}",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def create_userdomain(
|
||||
request: Request,
|
||||
userdomain:UserDomainParam,
|
||||
user=Depends(get_current_active_user),
|
||||
userid: int,
|
||||
domainids:list,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
userid = userdomain.userid
|
||||
domainid = userdomain.domainid
|
||||
if user.is_superuser:
|
||||
domain = domainService.add_userdomain(db,user.id,userid,domainid)
|
||||
else:
|
||||
domain = domainService.add_userdomain_by_owner(db,user.id,userid,domainid)
|
||||
return ApiReturnModel(data = domain)
|
||||
domain = add_userdomain(db, userid,domainids)
|
||||
return domain
|
||||
except Exception as e:
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while add user({userid}) domain({domainid}):",e)
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
||||
|
||||
@r.delete(
|
||||
"/domain/{domainid}/{userid}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
"/domain/{domainid}/{userid}", response_model_exclude_none=True
|
||||
)
|
||||
async def delete_userdomain(
|
||||
async def userdomain_delete(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userid: int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.delete_userdomain(db,userid,domainid))
|
||||
return delete_userdomain(db, userid,domainid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while delete user({userid}) domain({domainid}):",e)
|
||||
|
||||
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/managedomainuser/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnPage[UserOut|None],
|
||||
"/activedomain",
|
||||
response_model=Domain,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_managedomainuser(
|
||||
async def get_useractivedomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return domainService.get_managedomain_users(db,domainid)
|
||||
domain = get_activedomain(db, user.id)
|
||||
return domain
|
||||
except Exception as e:
|
||||
raise APIException('platform:managedomain',request.url._url,f"Error occurred while get managedomain({user.id}) user:",e)
|
||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
||||
|
||||
|
||||
|
||||
|
||||
@r.post(
|
||||
"/managedomain",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def create_managedomain(
|
||||
request: Request,
|
||||
userdomain:UserDomainParam,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
userid = userdomain.userid
|
||||
domainid = userdomain.domainid
|
||||
if user.is_superuser:
|
||||
domain = domainService.add_managedomain(db,user.id,userid,domainid)
|
||||
else:
|
||||
domain = domainService.add_managedomain_by_owner(db,user.id,userid,domainid)
|
||||
return ApiReturnModel(data = domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:managedomain',request.url._url,f"Error occurred while add manage({userid}) domain({domainid}):",e)
|
||||
|
||||
@r.delete(
|
||||
"/managedomain/{domainid}/{userid}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def delete_managedomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userid: int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.delete_managedomain(db,userid,domainid))
|
||||
except Exception as e:
|
||||
raise APIException('platform:managedomain',request.url._url,f"Error occurred while delete managedomain({userid}) domain({domainid}):",e)
|
||||
|
||||
@r.get(
|
||||
"/defaultdomain",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_defaultuserdomain(
|
||||
request: Request,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data =domainService.get_default_domain(db,user.id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while get user({user.id}) defaultdomain:",e)
|
||||
|
||||
@r.put(
|
||||
"/defaultdomain/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
"/activedomain/{domainid}",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def set_defualtuserdomain(
|
||||
async def update_activeuserdomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = domainCacheService.set_default_domain(db,user.id,domainid)
|
||||
return ApiReturnModel(data= domain)
|
||||
domain = active_userdomain(db, user.id,domainid)
|
||||
return domain
|
||||
except Exception as e:
|
||||
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while update user({user.id}) defaultdomain:",e)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/domainshareduser/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnPage[UserOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_domainshareduser(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return domainService.get_shareddomain_users(db,domainid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:sharedomain',request.url._url,f"Error occurred while get user({user.id}) sharedomain:",e)
|
||||
|
||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
|
||||
|
||||
@r.get(
|
||||
"/events",
|
||||
|
||||
@@ -1,187 +1,107 @@
|
||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
||||
from fastapi import APIRouter, Request, Depends, Response, encoders
|
||||
import typing as t
|
||||
from app.core.common import ApiReturnModel,ApiReturnPage
|
||||
from app.core.apiexception import APIException
|
||||
from app.core.dbmanager import get_db
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.db.crud import (
|
||||
get_allusers,
|
||||
get_users,
|
||||
get_user,
|
||||
create_user,
|
||||
delete_user,
|
||||
edit_user,
|
||||
assign_userrole,
|
||||
get_roles,
|
||||
)
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,AssignUserRoles,Permission
|
||||
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
|
||||
from app.db.cruddb import userService
|
||||
from app.core import tenantCacheService
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut
|
||||
from app.core.auth import get_current_active_user, get_current_active_superuser
|
||||
|
||||
users_router = r = APIRouter()
|
||||
|
||||
|
||||
@r.get(
|
||||
"/users",tags=["User"],
|
||||
response_model=ApiReturnPage[User],
|
||||
"/users",
|
||||
response_model=t.List[User],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def users_list(
|
||||
request: Request,
|
||||
response: Response,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
try:
|
||||
if current_user.is_superuser:
|
||||
users = userService.get_users(db)
|
||||
else:
|
||||
users = userService.get_users_not_admin(db)
|
||||
return users
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while get user list",e)
|
||||
"""
|
||||
Get all users
|
||||
"""
|
||||
users = get_users(db)
|
||||
# This is necessary for react-admin to work
|
||||
response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||
return users
|
||||
|
||||
@r.get("/users/me", tags=["User"],
|
||||
response_model=ApiReturnModel[User],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
|
||||
@r.get("/users/me", response_model=User, response_model_exclude_none=True)
|
||||
async def user_me(current_user=Depends(get_current_active_user)):
|
||||
return ApiReturnModel(data = current_user)
|
||||
"""
|
||||
Get own user
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@r.get(
|
||||
"/users/{user_id}",tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
"/users/{user_id}",
|
||||
response_model=User,
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def user_details(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
try:
|
||||
user = userService.get(db, user_id)
|
||||
if user:
|
||||
if user.is_superuser and not current_user.is_superuser:
|
||||
user = None
|
||||
return ApiReturnModel(data = user)
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while get user({user_id}) detail:",e)
|
||||
"""
|
||||
Get any user details
|
||||
"""
|
||||
user = get_user(db, user_id)
|
||||
return user
|
||||
# return encoders.jsonable_encoder(
|
||||
# user, skip_defaults=True, exclude_none=True,
|
||||
# )
|
||||
|
||||
|
||||
@r.post("/users", tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
@r.post("/users", response_model=User, response_model_exclude_none=True)
|
||||
async def user_create(
|
||||
request: Request,
|
||||
user: UserCreate,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
try:
|
||||
if user.is_superuser and not current_user.is_superuser:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data =userService.create_user(db, user,current_user.id))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while create user({user.email}):",e)
|
||||
"""
|
||||
Create a new user
|
||||
"""
|
||||
return create_user(db, user)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/users/{user_id}", tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
response_model_exclude_none=True,
|
||||
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||
)
|
||||
async def user_edit(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
user: UserEdit,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
try:
|
||||
if user.is_superuser and not current_user.is_superuser:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = userService.edit_user(db,user_id,user,current_user.id))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while edit user({user_id}):",e)
|
||||
"""
|
||||
Update existing user
|
||||
"""
|
||||
return edit_user(db, user_id, user)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/users/{user_id}", tags=["User"],
|
||||
response_model=ApiReturnModel[UserOut|None],
|
||||
response_model_exclude_none=True
|
||||
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||
)
|
||||
async def user_delete(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
try:
|
||||
user = userService.get(db,user_id)
|
||||
if user.is_superuser and not current_user.is_superuser:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = userService.delete_user(db, user_id))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while delete user({user_id}):",e)
|
||||
|
||||
|
||||
@r.post("/userrole",tags=["User"],
|
||||
response_model=ApiReturnModel[User],
|
||||
response_model_exclude_none=True,)
|
||||
async def assign_role(
|
||||
request: Request,
|
||||
userroles:AssignUserRoles,
|
||||
db=Depends(get_db)
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = userService.assign_userrole(db,userroles.userid,userroles.roleids))
|
||||
except Exception as e:
|
||||
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({userroles.userid}) roles({userroles.roleids}):",e)
|
||||
|
||||
@r.get(
|
||||
"/roles",tags=["User"],
|
||||
response_model=ApiReturnModel[t.List[RoleBase]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def roles_list(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
#current_user=Security(get_current_active_user, scopes=["role_list"]),
|
||||
):
|
||||
try:
|
||||
|
||||
if current_user.is_superuser:
|
||||
roles = userService.get_roles(db)
|
||||
else:
|
||||
if len(current_user.roles)>0:
|
||||
roles = userService.get_roles_by_level(db,current_user.roles)
|
||||
else:
|
||||
roles = []
|
||||
return ApiReturnModel(data = roles)
|
||||
except Exception as e:
|
||||
raise APIException('user:roles',request.url._url,f"Error occurred while get roles:",e)
|
||||
|
||||
@r.get(
|
||||
"/userpermssions",tags=["User"],
|
||||
response_model=ApiReturnModel[t.List[Permission]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def permssions_list(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
#current_user=Security(get_current_active_user, scopes=["role_list"]),
|
||||
):
|
||||
try:
|
||||
if current_user.is_superuser:
|
||||
permissions = userService.get_permissions(db)
|
||||
else:
|
||||
if len(current_user.roles)>0:
|
||||
permissions = userService.get_user_permissions(db,current_user.id)
|
||||
else:
|
||||
permissions = []
|
||||
return ApiReturnModel(data = permissions)
|
||||
except Exception as e:
|
||||
raise APIException('user:userpermssions',request.url._url,f"Error occurred while get user({current_user.id}) permissions:",e)
|
||||
"""
|
||||
Delete existing user
|
||||
"""
|
||||
return delete_user(db, user_id)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
from app.core.cache import domainCacheService
|
||||
from app.core.cache import tenantCacheService
|
||||
@@ -1,40 +1,26 @@
|
||||
from fastapi import HTTPException, status,Depends
|
||||
import httpx
|
||||
from fastapi import HTTPException, status
|
||||
from app.db.schemas import ErrorCreate
|
||||
from app.core.dbmanager import get_log_db
|
||||
from app.db.session import SessionLocal
|
||||
from app.db.crud import create_log
|
||||
|
||||
class APIException(Exception):
|
||||
def __init__(self, location: str, title: str, content: str, e: Exception):
|
||||
self.detail = str(e)
|
||||
self.status_code = 500
|
||||
if isinstance(e,httpx.HTTPStatusError):
|
||||
try:
|
||||
error_response = e.response.json()
|
||||
self.detail = error_response.get('message', self.detail)
|
||||
self.status_code = e.response.status_code
|
||||
content += self.detail
|
||||
except ValueError:
|
||||
pass
|
||||
elif hasattr(e, 'detail'):
|
||||
self.detail = e.detail
|
||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
||||
content += str(e.detail)
|
||||
else:
|
||||
self.detail = str(e)
|
||||
self.status_code = 500
|
||||
content += str(e)
|
||||
|
||||
if len(content) > 5000:
|
||||
content = content[:5000]
|
||||
|
||||
self.error = ErrorCreate(location=location, title=title, content=content)
|
||||
super().__init__(self.error)
|
||||
|
||||
def writedblog(exc: APIException,):
|
||||
#db = SessionLocal()
|
||||
db = get_log_db()
|
||||
def __init__(self,location:str,title:str,content:str,e:Exception):
|
||||
if(str(e) == ''):
|
||||
content += e.detail
|
||||
self.detail = e.detail
|
||||
self.status_code = e.status_code
|
||||
else:
|
||||
self.detail = str(e)
|
||||
content += str(e)
|
||||
self.status_code = 500
|
||||
if(len(content) > 5000):
|
||||
content =content[0:5000]
|
||||
self.error = ErrorCreate(location=location,title=title,content=content)
|
||||
|
||||
def writedblog(exc: APIException):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
create_log(db,exc.error)
|
||||
finally:
|
||||
db.close()
|
||||
db.close()
|
||||
@@ -1,16 +1,14 @@
|
||||
from fastapi.security import SecurityScopes
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, Request, Security, status
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from jwt import PyJWTError
|
||||
|
||||
from app.db import models, schemas
|
||||
from app.db import models, schemas, session
|
||||
from app.db.crud import get_user_by_email, create_user,get_user
|
||||
from app.core import security
|
||||
from app.db.cruddb import userService
|
||||
from app.core.dbmanager import get_db
|
||||
|
||||
async def get_current_user(request: Request,security_scopes: SecurityScopes,
|
||||
db=Depends(get_db), token: str = Depends(security.oauth2_scheme)
|
||||
|
||||
async def get_current_user(
|
||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -24,25 +22,13 @@ async def get_current_user(request: Request,security_scopes: SecurityScopes,
|
||||
id: int = payload.get("sub")
|
||||
if id is None:
|
||||
raise credentials_exception
|
||||
|
||||
tenant:str = payload.get("tenant")
|
||||
if tenant is None:
|
||||
raise credentials_exception
|
||||
|
||||
permissions: str = payload.get("permissions")
|
||||
if not permissions =="ALL":
|
||||
for scope in security_scopes.scopes:
|
||||
if scope not in permissions.split(";"):
|
||||
raise HTTPException(
|
||||
status_code=403, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||
except PyJWTError:
|
||||
raise credentials_exception
|
||||
user = userService.get_user(db, token_data.id)
|
||||
user = get_user(db, token_data.id)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
request.state.user = user.id
|
||||
return user
|
||||
|
||||
async def get_current_active_user(
|
||||
@@ -64,11 +50,11 @@ async def get_current_active_superuser(
|
||||
|
||||
|
||||
def authenticate_user(db, email: str, password: str):
|
||||
user = userService.get_user_by_email(db,email) #get_user_by_email(db, email)
|
||||
user = get_user_by_email(db, email)
|
||||
if not user:
|
||||
return None
|
||||
return False
|
||||
if not security.verify_password(password, user.hashed_password):
|
||||
return None
|
||||
return False
|
||||
return user
|
||||
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import time
|
||||
from typing import Any
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.cruddb import domainService,tenantService
|
||||
from app.db.session import Database
|
||||
|
||||
class MemoryCache:
|
||||
def __init__(self, max_cache_size: int = 100, ttl: int = 60):
|
||||
self.cache = {}
|
||||
self.max_cache_size = max_cache_size
|
||||
self.ttl = ttl
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
item = self.cache.get(key)
|
||||
if item:
|
||||
if time.time() - item['timestamp'] > self.ttl:
|
||||
self.cache.pop(key)
|
||||
return None
|
||||
return item['value']
|
||||
return None
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
if len(self.cache) >= self.max_cache_size:
|
||||
self.cache.pop(next(iter(self.cache)))
|
||||
self.cache[key] = {'value': value, 'timestamp': time.time()}
|
||||
|
||||
# def clear(self,key) -> None:
|
||||
# self.cache.pop(key,None)
|
||||
|
||||
def clear(self) -> None:
|
||||
self.cache.clear()
|
||||
|
||||
|
||||
|
||||
class domainCache:
|
||||
|
||||
def __init__(self):
|
||||
self.memoryCache = MemoryCache(max_cache_size=50, ttl=120)
|
||||
|
||||
def set_default_domain(self, db: Session,userid: int,domainid:str):
|
||||
domain = domainService.set_default_domain(db,userid,domainid)
|
||||
if domain:
|
||||
self.memoryCache.set(f"DOMAIN_{userid}",domain.url)
|
||||
return domain
|
||||
|
||||
def get_default_domainurl(self,db: Session, userid: int):
|
||||
if not self.memoryCache.get(f"DOMAIN_{userid}"):
|
||||
domain = domainService.get_default_domain(db,userid)
|
||||
if domain:
|
||||
self.memoryCache.set(f"DOMAIN_{userid}",domain.url)
|
||||
return self.memoryCache.get(f"DOMAIN_{userid}")
|
||||
|
||||
def clear_default_domainurl(self):
|
||||
self.memoryCache.clear()
|
||||
|
||||
domainCacheService =domainCache()
|
||||
|
||||
|
||||
class tenantCache:
|
||||
|
||||
def __init__(self):
|
||||
self.memoryCache = MemoryCache(max_cache_size=50, ttl=120)
|
||||
|
||||
def get_tenant_db(self,db: Session, tenantid: str):
|
||||
if not self.memoryCache.get(f"TENANT_{tenantid}"):
|
||||
tenant = tenantService.get_tenant(db,tenantid)
|
||||
if tenant:
|
||||
database = Database(tenant.db)
|
||||
self.memoryCache.set(f"TENANT_{tenantid}",database)
|
||||
return self.memoryCache.get(f"TENANT_{tenantid}")
|
||||
|
||||
tenantCacheService =tenantCache()
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
import math
|
||||
from fastapi import Query
|
||||
from fastapi_pagination.bases import AbstractPage,AbstractParams,RawParams
|
||||
from pydantic import BaseModel
|
||||
from typing import Any, Generic, List, Type,TypeVar,Generic,Sequence
|
||||
from fastapi_pagination import Page,utils
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class ApiReturnModel(BaseModel,Generic[T]):
|
||||
code:int = 0
|
||||
msg:str ="OK"
|
||||
data:T
|
||||
|
||||
class ApiReturnError(BaseModel):
|
||||
code:int = -1
|
||||
msg:str =""
|
||||
|
||||
|
||||
class Params(BaseModel, AbstractParams):
|
||||
page:int = Query(1,get=1, description="Page number")
|
||||
size:int = Query(20,get=0, le=100,description="Page size")
|
||||
|
||||
def to_raw_params(self) -> RawParams:
|
||||
return RawParams(
|
||||
limit=self.size,
|
||||
offset=self.size*(self.page-1)
|
||||
)
|
||||
|
||||
class ApiReturnPage(AbstractPage[T],Generic[T]):
|
||||
code:int =0
|
||||
msg:str ="OK"
|
||||
data:Sequence[T]
|
||||
total:int
|
||||
page:int
|
||||
size:int
|
||||
# next:str
|
||||
# previous:str
|
||||
total_pages:int
|
||||
|
||||
__params_type__ =Params
|
||||
|
||||
@classmethod
|
||||
def create(cls,items:Sequence[T],params:Params,**kwargs: Any) -> Type[Page[T]]:
|
||||
total = kwargs.get('total', 0)
|
||||
total_pages = math.ceil(total/params.size)
|
||||
|
||||
return utils.create_pydantic_model(cls,data=items,total=total,page=params.page,size=params.size,total_pages=total_pages)
|
||||
@@ -1,27 +1,24 @@
|
||||
import os
|
||||
import base64
|
||||
|
||||
|
||||
PROJECT_NAME = "KintoneAppBuilder"
|
||||
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/dev"
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/dev_v2"
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@ktune-prod-db.postgres.database.azure.com/postgres"
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
|
||||
SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
|
||||
|
||||
API_V1_STR = "/k/v1"
|
||||
|
||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||
|
||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||
DEPLOY_MODE = "DEV" #DEV,PROD
|
||||
|
||||
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
||||
#DEPLOY_JS_URL = "https://ce1c-133-139-70-194.ngrok-free.app/alc_runtime.js"
|
||||
|
||||
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
||||
|
||||
KINTONE_FIELD_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
||||
|
||||
KINTONE_PSW_CRYPTO_KEY=bytes.fromhex("53 6c 93 bd 48 ad b5 c0 93 df a1 27 25 a1 a3 32 a2 03 3b a0 27 1f 51 dc 20 0e 6c d7 be fc fb ea")
|
||||
|
||||
class KINTONE_ENV:
|
||||
|
||||
BASE_URL = ""
|
||||
@@ -39,4 +36,4 @@ class KINTONE_ENV:
|
||||
self.DOMAIN_ID=domain.id
|
||||
self.BASE_URL = domain.url
|
||||
self.KINTONE_USER = domain.kintoneuser
|
||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.decrypt_kintonepwd()}","utf-8"))
|
||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
from fastapi import Depends,Request
|
||||
from app.db.session import get_tenant_db
|
||||
from app.core import tenantCacheService
|
||||
from app.db.session import tenantdb
|
||||
|
||||
def get_db(request: Request,tenant:str = "1",tenantdb = Depends(get_tenant_db)):
|
||||
database = tenantCacheService.get_tenant_db(tenantdb,tenant)
|
||||
try:
|
||||
db = database.get_db()
|
||||
request.state.tenant = tenant
|
||||
request.state.db = db
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def get_log_db():
|
||||
db = tenantdb.get_db()
|
||||
return db
|
||||
@@ -1,131 +0,0 @@
|
||||
|
||||
from urllib.parse import parse_qs, urlencode
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.models import OperationLog,User
|
||||
from app.core.apiexception import APIException
|
||||
from app.core.dbmanager import get_log_db
|
||||
from app.db.crud import create_log
|
||||
import json
|
||||
|
||||
class LoggingMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
if request.method in ("POST", "PUT", "PATCH","DELETE"):
|
||||
content_type = request.headers.get('content-type', '')
|
||||
if content_type.startswith('multipart/form-data'):
|
||||
request.state.body = None
|
||||
else:
|
||||
try:
|
||||
request.state.body = await request.json()
|
||||
except json.JSONDecodeError:
|
||||
request.state.body = await request.body()
|
||||
else:
|
||||
request.state.body = None
|
||||
|
||||
try:
|
||||
response = await call_next(request)
|
||||
state = request.state
|
||||
except Exception as e:
|
||||
await self.log_error(request, e)
|
||||
response = JSONResponse(
|
||||
content={"detail": "Internal Server Error"},
|
||||
status_code=500
|
||||
)
|
||||
|
||||
if hasattr(request.state, "user") and hasattr(request.state, "tenant"):
|
||||
await self.log_request(request, response,state)
|
||||
|
||||
return response
|
||||
|
||||
def sanitize_password(self,data):
|
||||
"""
|
||||
データ内の password パラメータをフィルタリングする機能。
|
||||
dict、JSON 文字列、URL エンコード文字列、QueryDict をサポート。
|
||||
"""
|
||||
if data is None:
|
||||
return data
|
||||
elif isinstance(data, dict):
|
||||
data.pop('password', None)
|
||||
return data
|
||||
elif isinstance(data, list):
|
||||
return [self.sanitize_password(item) for item in data]
|
||||
elif isinstance(data, (str, bytes)):
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('utf-8') # bytes to str
|
||||
# JSON解析
|
||||
try:
|
||||
parsed_json = json.loads(data)
|
||||
sanitized_json = self.sanitize_password(parsed_json)
|
||||
return json.dumps(sanitized_json, separators=(',', ':'))
|
||||
except json.JSONDecodeError:
|
||||
# URL 解析
|
||||
try:
|
||||
parsed_dict = parse_qs(data)
|
||||
parsed_dict.pop('password', None)
|
||||
return urlencode(parsed_dict, doseq=True)
|
||||
except:
|
||||
parts = data.split('&')
|
||||
filtered_parts = []
|
||||
for part in parts:
|
||||
if '=' in part:
|
||||
key, _ = part.split('=', 1)
|
||||
if key == 'password':
|
||||
continue
|
||||
filtered_parts.append(part)
|
||||
return '&'.join(filtered_parts)
|
||||
# QueryDict 例えば FastAPI の request.query_params)
|
||||
elif hasattr(data, 'items'):
|
||||
return {k: v for k, v in data.items() if k != 'password'}
|
||||
return data
|
||||
|
||||
|
||||
async def log_request(self, request: Request, response,state):
|
||||
try:
|
||||
headers = dict(request.headers)
|
||||
route = request.scope.get("route")
|
||||
if route:
|
||||
path_template = route.path
|
||||
else:
|
||||
path_template = request.url.path
|
||||
|
||||
# passwordのパラメータを除外する
|
||||
safe_query = self.sanitize_password(request.query_params.items())
|
||||
|
||||
# passwordのパラメータを除外する
|
||||
safe_body = self.sanitize_password(request.state.body)
|
||||
|
||||
db_operation = OperationLog(
|
||||
tenantid =request.state.tenant,
|
||||
clientip = request.client.host if request.client else None,
|
||||
useragent =headers.get("user-agent", ""),
|
||||
userid = request.state.user,
|
||||
operation = request.method,
|
||||
function = path_template,
|
||||
parameters = str({
|
||||
"path": request.path_params,
|
||||
"query": safe_query,
|
||||
"body": safe_body
|
||||
}),
|
||||
response = f"status_code:{response.status_code }" )
|
||||
|
||||
db = request.state.db
|
||||
if db:
|
||||
await self.write_log_to_db(db_operation,db)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Logging failed: {str(e)}")
|
||||
|
||||
|
||||
async def log_error(self, request: Request, e: Exception):
|
||||
exc = APIException('operation:dispatch',request.url._url,f"Error occurred while writting operation log:",e)
|
||||
db = get_log_db()
|
||||
try:
|
||||
create_log(db,exc.error)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
async def write_log_to_db(self, db_operation,db):
|
||||
db.add(db_operation)
|
||||
db.commit()
|
||||
@@ -1,11 +1,7 @@
|
||||
import jwt
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from passlib.context import CryptContext
|
||||
from datetime import datetime, timedelta,timezone
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
||||
import os
|
||||
import base64
|
||||
from app.core import config
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
||||
|
||||
@@ -27,38 +23,9 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def chacha20Encrypt(plaintext:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||
if plaintext is None or plaintext == '':
|
||||
return None
|
||||
nonce = os.urandom(16)
|
||||
algorithm = algorithms.ChaCha20(key, nonce)
|
||||
cipher = Cipher(algorithm, mode=None)
|
||||
encryptor = cipher.encryptor()
|
||||
ciphertext = encryptor.update(plaintext.encode('utf-8')) + encryptor.finalize()
|
||||
return base64.b64encode(nonce +'𒀸'.encode('utf-8')+ ciphertext).decode('utf-8')
|
||||
|
||||
def chacha20Decrypt(encoded_str:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||
try:
|
||||
decoded_data = base64.b64decode(encoded_str)
|
||||
if len(decoded_data) < 18:
|
||||
return encoded_str
|
||||
special_char = decoded_data[16:20]
|
||||
if special_char != '𒀸'.encode('utf-8'):
|
||||
return encoded_str
|
||||
nonce = decoded_data[:16]
|
||||
ciphertext = decoded_data[20:]
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return encoded_str
|
||||
algorithm = algorithms.ChaCha20(key, nonce)
|
||||
cipher = Cipher(algorithm, mode=None)
|
||||
decryptor = cipher.decryptor()
|
||||
plaintext_bytes = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
@@ -1,11 +1,10 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
import typing as t
|
||||
|
||||
from . import models, schemas
|
||||
from app.core.security import chacha20Decrypt, get_password_hash
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
|
||||
def get_user(db: Session, user_id: int):
|
||||
@@ -19,15 +18,10 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase:
|
||||
return db.query(models.User).filter(models.User.email == email).first()
|
||||
|
||||
|
||||
def get_allusers(
|
||||
db: Session
|
||||
) -> t.List[schemas.UserOut]:
|
||||
return db.query(models.User).all()
|
||||
|
||||
def get_users(
|
||||
db: Session
|
||||
db: Session, skip: int = 0, limit: int = 100
|
||||
) -> t.List[schemas.UserOut]:
|
||||
return db.query(models.User).filter(models.User.is_superuser == False)
|
||||
return db.query(models.User).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_user(db: Session, user: schemas.UserCreate):
|
||||
@@ -76,80 +70,6 @@ def edit_user(
|
||||
return db_user
|
||||
|
||||
|
||||
def get_roles(
|
||||
db: Session
|
||||
) -> t.List[schemas.RoleBase]:
|
||||
return db.query(models.Role).all()
|
||||
|
||||
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
||||
db_user = db.query(models.User).get(user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
db_user.roles.remove(role)
|
||||
for roleid in roles:
|
||||
role = db.query(models.Role).get(roleid)
|
||||
if role:
|
||||
db_user.roles.append(role)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
def get_apps(
|
||||
db: Session,
|
||||
domainurl:str
|
||||
) -> t.List[schemas.AppList]:
|
||||
return db.query(models.App).filter(models.App.domainurl == domainurl).all()
|
||||
|
||||
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
db_app.version = db_app.version + 1
|
||||
appversion = models.AppVersion(
|
||||
domainurl = appedit.domainurl,
|
||||
appid=appedit.appid,
|
||||
appname=db_app.appname,
|
||||
version = db_app.version,
|
||||
versionname = appedit.versionname,
|
||||
comment = appedit.comment,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(appversion)
|
||||
db.add(db_app)
|
||||
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
|
||||
for flow in flows:
|
||||
db_flowhistory = models.FlowHistory(
|
||||
flowid = flow.flowid,
|
||||
appid = flow.appid,
|
||||
eventid = flow.eventid,
|
||||
domainurl = flow.domainurl,
|
||||
name = flow.name,
|
||||
content = flow.content,
|
||||
createuser = userid,
|
||||
version = db_app.version,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(db_flowhistory)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_app)
|
||||
return db_app
|
||||
|
||||
def delete_apps(db: Session, domainurl: str,appid: str ):
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found")
|
||||
db.delete(db_app)
|
||||
db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid))
|
||||
for flow in db_flows:
|
||||
db.delete(flow)
|
||||
db.commit()
|
||||
return db_app
|
||||
|
||||
def get_appsetting(db: Session, id: int):
|
||||
app = db.query(models.AppSetting).get(id)
|
||||
if not app:
|
||||
@@ -205,28 +125,16 @@ def get_actions(db: Session):
|
||||
return actions
|
||||
|
||||
|
||||
def create_flow(db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
||||
def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
|
||||
db_flow = models.Flow(
|
||||
flowid=flow.flowid,
|
||||
appid=flow.appid,
|
||||
eventid=flow.eventid,
|
||||
domainurl=domainurl,
|
||||
domainid=domainid,
|
||||
name=flow.name,
|
||||
content=flow.content,
|
||||
createuserid = userid,
|
||||
updateuserid = userid
|
||||
content=flow.content
|
||||
)
|
||||
db.add(db_flow)
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first()
|
||||
if not db_app:
|
||||
db_app = models.App(
|
||||
domainurl = domainurl,
|
||||
appid=flow.appid,
|
||||
appname=flow.appname,
|
||||
version = 0,
|
||||
createuserid= userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
@@ -241,20 +149,16 @@ def delete_flow(db: Session, flowid: str):
|
||||
|
||||
|
||||
def edit_flow(
|
||||
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int
|
||||
db: Session, flow: schemas.FlowBase
|
||||
) -> schemas.Flow:
|
||||
db_flow = get_flow(db, flow.flowid)
|
||||
if not db_flow:
|
||||
#見つからない時新規作成
|
||||
return create_flow(db,domainurl,flow,userid)
|
||||
|
||||
db_flow.appid =flow.appid
|
||||
db_flow.eventid=flow.eventid
|
||||
db_flow.domainurl=domainurl
|
||||
db_flow.name=flow.name
|
||||
db_flow.content=flow.content
|
||||
db_flow.updateuserid = userid
|
||||
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
update_data = flow.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(db_flow, key, value)
|
||||
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
@@ -269,149 +173,103 @@ def get_flows(db: Session, flowid: str):
|
||||
|
||||
def get_flow(db: Session, flowid: str):
|
||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||
# if not flow:
|
||||
# raise HTTPException(status_code=404, detail="Data not found")
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flow
|
||||
|
||||
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid)).all()
|
||||
def get_flows_by_app(db: Session, domainid: int, appid: str):
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
|
||||
if not flows:
|
||||
raise Exception("Data not found")
|
||||
return flows
|
||||
|
||||
def create_domain(db: Session, domain: schemas.DomainIn,userid:int):
|
||||
domain.encrypt_kintonepwd()
|
||||
def create_domain(db: Session, domain: schemas.DomainBase):
|
||||
db_domain = models.Domain(
|
||||
tenantid = domain.tenantid,
|
||||
name=domain.name,
|
||||
url=domain.url,
|
||||
is_active=domain.is_active,
|
||||
kintoneuser=domain.kintoneuser,
|
||||
kintonepwd=domain.kintonepwd,
|
||||
createuserid = userid,
|
||||
updateuserid = userid,
|
||||
ownerid = domain.ownerid
|
||||
kintonepwd=domain.kintonepwd
|
||||
)
|
||||
db.add(db_domain)
|
||||
#add_userdomain(db,userid,db_domain.id)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_domain(db: Session,id: int):
|
||||
db_domain = db.query(models.Domain).get(id)
|
||||
#if not db_domain:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
if db_domain:
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return True
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
|
||||
def edit_domain(
|
||||
db: Session, domain: schemas.DomainIn,userid:int
|
||||
db: Session, domain: schemas.DomainBase
|
||||
) -> schemas.Domain:
|
||||
if domain.kintonepwd != "":
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain = db.query(models.Domain).get(domain.id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db_domain.tenantid = domain.tenantid
|
||||
db_domain.name=domain.name
|
||||
db_domain.url=domain.url
|
||||
if db_domain.is_active == True and domain.is_active == False:
|
||||
db_userdomains = db.query(models.UserDomain).filter(and_(models.UserDomain.domainid == db_domain.id,models.UserDomain.active == True)).all()
|
||||
for userdomain in db_userdomains:
|
||||
userdomain.active = False
|
||||
db.add(userdomain)
|
||||
db_domain.is_active=domain.is_active
|
||||
db_domain.kintoneuser=domain.kintoneuser
|
||||
if domain.kintonepwd != "":
|
||||
db_domain.kintonepwd = domain.kintonepwd
|
||||
db_domain.updateuserid = userid
|
||||
db_domain.ownerid = domain.ownerid
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
update_data = domain.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
if(key != "id"):
|
||||
setattr(db_domain, key, value)
|
||||
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
|
||||
def add_admindomain(db: Session,userid:int,domainid:int):
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def add_userdomain(db: Session,ownerid:int, userid:int,domainid:int):
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.ownerid == ownerid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
||||
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
|
||||
db.bulk_save_objects(dbCommits)
|
||||
def add_userdomain(db: Session, userid:int,domainids:list):
|
||||
for domainid in domainids:
|
||||
db_domain = models.UserDomain(
|
||||
userid = userid,
|
||||
domainid = domainid
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
return dbCommits
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
||||
#if not db_domain:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
if db_domain:
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||
# if not db_userdomains:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
|
||||
for domain in db_userdomains:
|
||||
if domain.domainid == domainid:
|
||||
domain.active = True
|
||||
else:
|
||||
domain.active = False
|
||||
db.add(domain)
|
||||
db.commit()
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||
if not db_userdomains:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
for domain in db_userdomains:
|
||||
if domain.domainid == domainid:
|
||||
domain.active = True
|
||||
else:
|
||||
domain.active = False
|
||||
db.add(domain)
|
||||
db.commit()
|
||||
return db_userdomains
|
||||
|
||||
def get_activedomain(db: Session, userid: int):
|
||||
# user_domains = (db.query(models.Domain,models.UserDomain.active)
|
||||
# .join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
||||
# .filter(models.UserDomain.userid == userid)
|
||||
# .all())
|
||||
db_domain=(db.query(models.Domain).filter(models.Domain.is_active)
|
||||
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id).filter(and_(models.UserDomain.active,models.UserDomain.userid == userid)).first())
|
||||
# if len(user_domains)==1:
|
||||
# db_domain = user_domains[0][0];
|
||||
# else:
|
||||
# db_domain = next((domain for domain,active in user_domains if active),None)
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
return db_domain
|
||||
|
||||
def get_domain(db: Session, userid: str):
|
||||
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
||||
# if not domains:
|
||||
# raise HTTPException(status_code=404, detail="Data not found")
|
||||
# for domain in domains:
|
||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||
# domain.kintonepwd = decrypted_pwd
|
||||
if not domains:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return domains
|
||||
|
||||
def get_alldomains(db: Session):
|
||||
domains = db.query(models.Domain).all()
|
||||
return domains
|
||||
|
||||
def get_domains(db: Session,userid:int):
|
||||
domains = db.query(models.Domain).filter(models.Domain.ownerid == userid ).all()
|
||||
def get_domains(db: Session,tenantid:str):
|
||||
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
||||
if not domains:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return domains
|
||||
|
||||
def get_events(db: Session):
|
||||
@@ -420,35 +278,9 @@ def get_events(db: Session):
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return events
|
||||
|
||||
def get_category(db:Session):
|
||||
categorys=db.query(models.Category).all()
|
||||
return categorys
|
||||
|
||||
def get_eventactions(db: Session,eventid: str):
|
||||
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||
#category = get_category(db)
|
||||
blackactions = (
|
||||
db.query(models.EventAction.actionid)
|
||||
.filter(models.EventAction.eventid == eventid)
|
||||
.subquery()
|
||||
)
|
||||
eveactions = (
|
||||
db.query(
|
||||
models.Action.id,
|
||||
models.Action.name,
|
||||
models.Action.title,
|
||||
models.Action.subtitle,
|
||||
models.Action.outputpoints,
|
||||
models.Action.property,
|
||||
models.Action.categoryid,
|
||||
models.Action.nosort,
|
||||
models.Category.categoryname)
|
||||
.join(models.Category,models.Category.id == models.Action.categoryid)
|
||||
.filter(models.Action.id.notin_(blackactions))
|
||||
.order_by(models.Category.nosort,models.Action.nosort)
|
||||
.all()
|
||||
)
|
||||
|
||||
eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid != models.Action.id and models.EventAction.eventid == eventid ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||
if not eveactions:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return eveactions
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from app.db.cruddb.dbuser import userService
|
||||
from app.db.cruddb.dbdomain import domainService
|
||||
from app.db.cruddb.dbapp import appService
|
||||
from app.db.cruddb.dbtenant import tenantService
|
||||
@@ -1,104 +0,0 @@
|
||||
from sqlalchemy import asc, desc, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.query import Query
|
||||
from typing import Type, List, Optional
|
||||
from app.core.common import ApiReturnPage
|
||||
from sqlalchemy import and_ ,or_
|
||||
from pydantic import BaseModel
|
||||
from app.db import models
|
||||
|
||||
class crudbase:
|
||||
def __init__(self, model: Type[models.Base]):
|
||||
self.model = model
|
||||
|
||||
def _apply_filters(self, query: Query, filters: dict) -> Query:
|
||||
and_conditions = []
|
||||
or_conditions = []
|
||||
for column_name, value in filters.items():
|
||||
column = getattr(self.model, column_name, None)
|
||||
if column:
|
||||
if isinstance(value, dict):
|
||||
if 'operator' in value:
|
||||
operator = value['operator']
|
||||
filter_value = value['value']
|
||||
if operator == '!=':
|
||||
and_conditions.append(column != filter_value)
|
||||
elif operator == 'like':
|
||||
and_conditions.append(column.like(f"%{filter_value}%"))
|
||||
elif operator == '=':
|
||||
and_conditions.append(column == filter_value)
|
||||
elif operator == '>':
|
||||
and_conditions.append(column > filter_value)
|
||||
elif operator == '>=':
|
||||
and_conditions.append(column >= filter_value)
|
||||
elif operator == '<':
|
||||
and_conditions.append(column < filter_value)
|
||||
elif operator == '<=':
|
||||
and_conditions.append(column <= filter_value)
|
||||
elif operator == 'in':
|
||||
if isinstance(filter_value, list):
|
||||
or_conditions.append(column.in_(filter_value))
|
||||
else:
|
||||
and_conditions.append(column == filter_value)
|
||||
else:
|
||||
and_conditions.append(column == value)
|
||||
else:
|
||||
and_conditions.append(column == value)
|
||||
|
||||
if and_conditions:
|
||||
query = query.where(and_(*and_conditions))
|
||||
if or_conditions:
|
||||
query = query.where(or_(*or_conditions))
|
||||
|
||||
return query
|
||||
|
||||
def _apply_sorting(self, query: Query, sort_by: Optional[str], sort_order: Optional[str]) -> Query:
|
||||
if sort_by:
|
||||
column = getattr(self.model, sort_by, None)
|
||||
if column:
|
||||
if sort_order == "desc":
|
||||
query = query.order_by(desc(column))
|
||||
else:
|
||||
query = query.order_by(asc(column))
|
||||
return query
|
||||
|
||||
def get_all(self) -> Query:
|
||||
return select(self.model)
|
||||
|
||||
def get(self, db: Session, item_id: int) -> Optional[models.Base]:
|
||||
return db.execute(select(self.model).filter(self.model.id == item_id)).scalar_one_or_none()
|
||||
|
||||
def create(self, db: Session, obj_in: BaseModel) -> models.Base:
|
||||
db_obj = self.model(**obj_in.model_dump())
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(self, db: Session, item_id: int, obj_in: BaseModel) -> Optional[models.Base]:
|
||||
db_obj = self.get(db,item_id)
|
||||
if db_obj:
|
||||
for key, value in obj_in.model_dump(exclude_unset=True).items():
|
||||
setattr(db_obj, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
return None
|
||||
|
||||
def delete(self, db: Session, item_id: int) -> Optional[models.Base]:
|
||||
db_obj = self.get(db,item_id)
|
||||
if db_obj:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return db_obj
|
||||
return None
|
||||
|
||||
def get_by_conditions(self, filters: Optional[dict] = None, sort_by: Optional[str] = None,
|
||||
sort_order: Optional[str] = "asc") -> Query:
|
||||
query = select(self.model)
|
||||
if filters:
|
||||
query = self._apply_filters(query, filters)
|
||||
if sort_by:
|
||||
query = self._apply_sorting(query, sort_by, sort_order)
|
||||
print(str(query))
|
||||
return query
|
||||
@@ -1,219 +0,0 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy import select,and_
|
||||
import typing as t
|
||||
|
||||
from app.db.cruddb.crudbase import crudbase
|
||||
from fastapi_pagination.ext.sqlalchemy import paginate
|
||||
from app.core.common import ApiReturnPage
|
||||
|
||||
from app.db import models, schemas
|
||||
from app.core.security import chacha20Decrypt, get_password_hash
|
||||
|
||||
|
||||
class dbflowhistory(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.FlowHistory)
|
||||
|
||||
def get_flows_by_appid_version(self,db: Session,domainurl:str,appid:str,version:int):
|
||||
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid, "version":version})).scalars().all()
|
||||
|
||||
dbflowhistory = dbflowhistory()
|
||||
|
||||
class dbflow(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Flow)
|
||||
|
||||
def get_domain_apps(self):
|
||||
return None
|
||||
|
||||
def get_flow_by_flowid(self,db: Session,flowid:str):
|
||||
return db.execute(super().get_by_conditions({"flowid":flowid})).scalars().first()
|
||||
|
||||
def get_flows_by_appid(self,db: Session,domainurl:str,appid:str):
|
||||
return db.execute(select(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid))).scalars().all()
|
||||
|
||||
|
||||
def create_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
||||
db_flow = models.Flow(
|
||||
flowid=flow.flowid,
|
||||
appid=flow.appid,
|
||||
eventid=flow.eventid,
|
||||
domainurl=domainurl,
|
||||
name=flow.name,
|
||||
content=flow.content,
|
||||
createuserid = userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.add(db_flow)
|
||||
db_app = db.execute(select(models.App).where(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid))).scalars().first()
|
||||
if not db_app:
|
||||
db_app = models.App(
|
||||
domainurl = domainurl,
|
||||
appid=flow.appid,
|
||||
appname=flow.appname,
|
||||
version = 0,
|
||||
createuserid= userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.add(db_app)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
dbflow = dbflow()
|
||||
|
||||
|
||||
class dbappversion(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.AppVersion)
|
||||
|
||||
def get_appversions(self,domainurl:str,appid:str):
|
||||
return super().get_by_conditions({"domainurl":domainurl,"appid":appid})
|
||||
|
||||
def get_app_by_version(self,db: Session,domainurl:str,appid:str,version:int):
|
||||
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid,"version":version})).scalars().first()
|
||||
|
||||
def get_app_latestversion(self,db: Session,domainurl:str,appid:str):
|
||||
appversion = db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid},"version","desc")).scalars().first()
|
||||
if appversion:
|
||||
return appversion.version
|
||||
else:
|
||||
return 0
|
||||
|
||||
dbappversion = dbappversion()
|
||||
|
||||
class dbapp(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.App)
|
||||
|
||||
def get_app(self,db: Session,domainurl:str,appid:str):
|
||||
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid})).scalars().first()
|
||||
|
||||
def get_apps(self,db: Session,domainurl:str):
|
||||
return db.execute(super().get_by_conditions({"domainurl":domainurl})).scalars().all()
|
||||
|
||||
def update_appversion(self,db: Session,domainurl, newversion: schemas.VersionUpdate,userid:int):
|
||||
db_app = self.get_app(db,domainurl,newversion.appid)
|
||||
if db_app:
|
||||
db_app.version = dbappversion.get_app_latestversion(db,domainurl,newversion.appid)+1
|
||||
db_app.updateuserid = userid,
|
||||
db_app.versionname = newversion.versionname
|
||||
db_app.is_saved = False
|
||||
appversion = models.AppVersion(
|
||||
domainurl = db_app.domainurl,
|
||||
appid=db_app.appid,
|
||||
appname=db_app.appname,
|
||||
version = db_app.version,
|
||||
versionname = newversion.versionname,
|
||||
comment = newversion.comment,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(appversion)
|
||||
db.add(db_app)
|
||||
|
||||
flows = dbflow.get_flows_by_appid(db,domainurl,newversion.appid)
|
||||
for flow in flows:
|
||||
db_flowhistory = models.FlowHistory(
|
||||
flowid = flow.flowid,
|
||||
appid = flow.appid,
|
||||
eventid = flow.eventid,
|
||||
domainurl = flow.domainurl,
|
||||
name = flow.name,
|
||||
content = flow.content,
|
||||
version = db_app.version,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(db_flowhistory)
|
||||
db.add(db_flowhistory)
|
||||
db.commit()
|
||||
db.refresh(db_app)
|
||||
return db_app
|
||||
return None
|
||||
|
||||
def change_appversion(self,db: Session, domainurl:str,appid:str,version:int,userid:int):
|
||||
db_app = self.get_app(db, domainurl, appid)
|
||||
if not db_app:
|
||||
return None
|
||||
db_appversion = dbappversion.get_app_by_version(db, domainurl, appid, version)
|
||||
if not db_appversion:
|
||||
return None
|
||||
db_app.version = version
|
||||
db_app.versionname = db_appversion.versionname
|
||||
db_app.updateuserid = userid
|
||||
db_app.is_saved = False
|
||||
db.add(db_app)
|
||||
|
||||
flows = dbflow.get_flows_by_appid(db, domainurl, appid)
|
||||
for flow in flows:
|
||||
db.delete(flow)
|
||||
db.flush()
|
||||
flowhistorys = dbflowhistory.get_flows_by_appid_version(db, domainurl, appid, version)
|
||||
for flow in flowhistorys:
|
||||
db_flow = models.Flow(
|
||||
flowid = flow.flowid,
|
||||
appid = flow.appid,
|
||||
eventid = flow.eventid,
|
||||
domainurl = flow.domainurl,
|
||||
name = flow.name,
|
||||
content = flow.content,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(db_flow)
|
||||
db.commit()
|
||||
db.refresh(db_app)
|
||||
return db_app
|
||||
|
||||
def delete_app(self,db: Session, domainurl: str,appid: str ):
|
||||
db_app =self.get_app(db,domainurl,appid)
|
||||
if db_app:
|
||||
flows = dbflow.get_flows_by_appid(db,domainurl,appid)
|
||||
for flow in flows:
|
||||
db.delete(flow)
|
||||
db.delete(db_app)
|
||||
db.commit()
|
||||
return db_app
|
||||
return None
|
||||
|
||||
def get_appversions(self,db: Session, domainurl:str,appid:str):
|
||||
return paginate(db,dbappversion.get_appversions(domainurl,appid))
|
||||
|
||||
def get_flow(self,db: Session, domainurl: str, appid:str):
|
||||
return dbflow.get_flows_by_appid(db,domainurl,appid)
|
||||
|
||||
def create_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
||||
return dbflow.create_flow(db,domainurl,flow,userid)
|
||||
|
||||
def edit_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
||||
db_flow = dbflow.get_flow_by_flowid(db, flow.flowid)
|
||||
if not db_flow:
|
||||
return dbflow.create_flow(db,domainurl,flow,userid)
|
||||
db_flow.appid =flow.appid
|
||||
db_flow.eventid=flow.eventid
|
||||
db_flow.domainurl=domainurl
|
||||
db_flow.name=flow.name
|
||||
db_flow.content=flow.content
|
||||
db_flow.updateuserid = userid
|
||||
db.add(db_flow)
|
||||
db_app = self.get_app(db, domainurl, flow.appid)
|
||||
if db_app and db_app.version > 0:
|
||||
db_app.is_saved = True
|
||||
flag_modified(db_app, 'is_saved')
|
||||
db_app.updateuserid = userid
|
||||
db.add(db_app)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
|
||||
def delete_flow(self,db: Session, flowid: str):
|
||||
db_flow = dbflow.get_flow_by_flowid(db,flowid)
|
||||
if db_flow:
|
||||
return dbflow.delete(db,db_flow.id)
|
||||
return None
|
||||
|
||||
appService = dbapp()
|
||||
@@ -1,218 +0,0 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select,and_
|
||||
import typing as t
|
||||
|
||||
from app.db.cruddb.crudbase import crudbase
|
||||
from fastapi_pagination.ext.sqlalchemy import paginate
|
||||
from app.core.common import ApiReturnPage
|
||||
|
||||
from app.db import models, schemas
|
||||
from app.core.security import chacha20Decrypt, get_password_hash
|
||||
|
||||
class dbuserdomain(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.UserDomain)
|
||||
|
||||
def get_userdomain(self,db: Session,userid:int,domainid:int):
|
||||
return db.execute(super().get_by_conditions({"userid":userid,"domainid":domainid})).scalars().first()
|
||||
|
||||
def get_userdomain_by_domainid(self,db: Session,ownerid:int,domainid:int):
|
||||
return super().get_by_conditions({"domainid":domainid})
|
||||
|
||||
|
||||
def get_default_domains(self,db: Session,domainid:int):
|
||||
return db.execute(super().get_by_conditions({"domainid":domainid,"is_default":True})).scalars().all()
|
||||
|
||||
def get_user_default_domain(self,db: Session,userid:int):
|
||||
return db.execute(super().get_by_conditions({"userid":userid,"is_default":True})).scalars().first()
|
||||
|
||||
|
||||
dbuserdomain = dbuserdomain()
|
||||
|
||||
class dbmanagedomain(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.ManageDomain)
|
||||
|
||||
def get_managedomain(self,db: Session,userid:int,domainid:int):
|
||||
return db.execute(super().get_by_conditions({"userid":userid,"domainid":domainid})).scalars().first()
|
||||
|
||||
def get_managedomain_by_domain(self,db: Session,domainid:int):
|
||||
return db.execute(super().get_by_conditions({"domainid":domainid})).scalars().all()
|
||||
|
||||
dbmanagedomain = dbmanagedomain()
|
||||
|
||||
class dbdomain(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Domain)
|
||||
|
||||
def get_domains(self,db: Session)-> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_all())
|
||||
|
||||
def get_domains_by_manage(self,db: Session,userid:int)-> ApiReturnPage[models.Base]:
|
||||
query = select(models.Domain).join(models.ManageDomain,models.ManageDomain.domainid == models.Domain.id).where(models.ManageDomain.userid == userid)
|
||||
return paginate(db,query)
|
||||
|
||||
def get_domains_by_owner(self,db: Session,ownerid:int)-> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_by_conditions({"ownerid":ownerid}))
|
||||
|
||||
def create_domain(self,db: Session, domain: schemas.DomainIn,userid:int):
|
||||
#db_domain = super().get_by_conditions(db,{"url":domain.url,"kintoneuser":domain.kintoneuser,"onwerid":userid}).first()
|
||||
#if not db_domain:
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain = models.Domain(
|
||||
tenantid = domain.tenantid,
|
||||
name = domain.name,
|
||||
url = domain.url,
|
||||
kintoneuser = domain.kintoneuser,
|
||||
kintonepwd = domain.kintonepwd,
|
||||
is_active = domain.is_active,
|
||||
createuserid = userid,
|
||||
updateuserid = userid,
|
||||
ownerid = userid
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.flush()
|
||||
user_domain = models.UserDomain(userid = userid, domainid = db_domain.id ,createuserid = userid,updateuserid = userid)
|
||||
db.add(user_domain)
|
||||
manage_domain = models.ManageDomain(userid = userid, domainid = db_domain.id ,createuserid = userid,updateuserid = userid)
|
||||
db.add(manage_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_domain(self,db: Session,id: int):
|
||||
db_managedomains = dbmanagedomain.get_managedomain_by_domain(db,id)
|
||||
for manage in db_managedomains:
|
||||
db.delete(manage)
|
||||
return super().delete(db,id)
|
||||
|
||||
def edit_domain(self,db: Session, domain: schemas.DomainIn,userid:int) -> schemas.DomainOut:
|
||||
db_domain = super().get(db,domain.id)
|
||||
if db_domain:
|
||||
db_domain.tenantid = domain.tenantid
|
||||
db_domain.name=domain.name
|
||||
db_domain.url=domain.url
|
||||
if db_domain.is_active == True and domain.is_active == False:
|
||||
db_userdomains = dbuserdomain.get_default_domains(db,domain.id)
|
||||
for userdomain in db_userdomains:
|
||||
userdomain.is_default = False
|
||||
db.add(userdomain)
|
||||
db_domain.is_active=domain.is_active
|
||||
db_domain.kintoneuser=domain.kintoneuser
|
||||
if domain.kintonepwd != "" and domain.kintonepwd != None:
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain.kintonepwd = domain.kintonepwd
|
||||
db_domain.updateuserid = userid
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def add_userdomain(self,db: Session,ownerid:int,userid:int,domainid:int) -> schemas.DomainOut:
|
||||
db_domain = super().get(db,domainid)
|
||||
if db_domain and db_domain.is_active:
|
||||
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if not db_userdomain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid)
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def add_userdomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut:
|
||||
db_domain = db.execute(super().get_by_conditions({"id":domainid,"is_active":True})).scalars().first()
|
||||
if db_domain:
|
||||
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if not db_userdomain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid =ownerid,updateuserid = ownerid)
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def delete_userdomain(self,db: Session, userid: int,domainid: int) -> schemas.DomainOut:
|
||||
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if db_userdomain:
|
||||
domain = db_userdomain.domain
|
||||
db.delete(db_userdomain)
|
||||
db.commit()
|
||||
return domain
|
||||
return None
|
||||
|
||||
def get_default_domain(self,db: Session, userid: int) -> schemas.DomainOut:
|
||||
userdomain = dbuserdomain.get_user_default_domain(db,userid)
|
||||
if userdomain:
|
||||
return userdomain.domain
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_default_domain(self,db: Session, userid: int,domainid: int):
|
||||
db_domain =db.execute(super().get_by_conditions({"id":domainid,"is_active":True})).scalars().first()
|
||||
if db_domain:
|
||||
db_default_domain = dbuserdomain.get_user_default_domain(db,userid)
|
||||
db_userdomain =dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if db_default_domain:
|
||||
if db_default_domain.domainid != domainid:
|
||||
db_default_domain.is_default = False
|
||||
db_default_domain.updateuserid = userid
|
||||
db.add(db_default_domain)
|
||||
else:
|
||||
return db_domain
|
||||
if db_userdomain:
|
||||
db_userdomain.is_default = True
|
||||
db_userdomain.updateuserid = userid
|
||||
db.add(db_userdomain)
|
||||
else:
|
||||
db_userdomain = dbuserdomain.create(db,schemas.UserDomainIn(domainid=domainid,userid=userid,is_default = True))
|
||||
db.add(db_userdomain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_shareddomain_users(self,db: Session,domainid: int) -> ApiReturnPage[models.Base]:
|
||||
users = select(models.User).join(models.UserDomain,models.UserDomain.userid == models.User.id).filter(models.UserDomain.domainid ==domainid)
|
||||
return paginate(db,users)
|
||||
|
||||
|
||||
def add_managedomain(self,db: Session,ownerid:int,userid:int,domainid:int) -> schemas.DomainOut:
|
||||
db_domain = self.get(db,domainid)
|
||||
if db_domain:
|
||||
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
|
||||
if not db_managedomain:
|
||||
manage_domain = models.ManageDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid)
|
||||
db.add(manage_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def add_managedomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut:
|
||||
db_domain = db.execute(super().get_by_conditions({"id":domainid,"ownerid":ownerid,})).scalars().first()
|
||||
if db_domain:
|
||||
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
|
||||
if not db_managedomain:
|
||||
manage_domain = models.ManageDomain(userid = userid, domainid = domainid ,createuserid =ownerid,updateuserid = ownerid)
|
||||
db.add(manage_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def delete_managedomain(self,db: Session, userid: int,domainid: int) -> schemas.DomainOut:
|
||||
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
|
||||
if db_managedomain:
|
||||
domain = db_managedomain.domain
|
||||
if domain.ownerid != userid:
|
||||
db.delete(db_managedomain)
|
||||
db.commit()
|
||||
return domain
|
||||
return None
|
||||
|
||||
def get_managedomain_users(self,db: Session,domainid: int) -> ApiReturnPage[models.Base]:
|
||||
users = select(models.User).join(models.ManageDomain,models.ManageDomain.userid == models.User.id).where(models.ManageDomain.domainid ==domainid)
|
||||
return paginate(db,users)
|
||||
|
||||
domainService = dbdomain()
|
||||
@@ -1,13 +0,0 @@
|
||||
from app.db.cruddb.crudbase import crudbase
|
||||
from app.db import models, schemas
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
class dbtenant(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Tenant)
|
||||
|
||||
def get_tenant(sefl,db:Session,tenantid: str):
|
||||
tenant = db.execute(super().get_by_conditions({"tenantid":tenantid})).scalars().first()
|
||||
return tenant
|
||||
|
||||
tenantService = dbtenant()
|
||||
@@ -1,98 +0,0 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
import typing as t
|
||||
|
||||
from app.db.cruddb.crudbase import crudbase
|
||||
from fastapi_pagination.ext.sqlalchemy import paginate
|
||||
from app.core.common import ApiReturnPage
|
||||
|
||||
from app.db import models, schemas
|
||||
from app.core.security import chacha20Decrypt, get_password_hash
|
||||
|
||||
|
||||
class dbpermission(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Permission)
|
||||
|
||||
dbpermission = dbpermission()
|
||||
|
||||
class dbrole(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Role)
|
||||
|
||||
dbrole = dbrole()
|
||||
|
||||
class dbuser(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.User)
|
||||
|
||||
def get_user(self,db: Session, user_id: int) -> schemas.User:
|
||||
return super().get(db,user_id)
|
||||
|
||||
def get_user_by_email(self,db: Session, email: str) -> schemas.User:
|
||||
return db.execute(super().get_by_conditions({"email":email})).scalars().first()
|
||||
|
||||
def get_users(self,db: Session) -> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_all())
|
||||
|
||||
def get_users_not_admin(self,db: Session) -> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_by_conditions({"is_superuser":False}))
|
||||
|
||||
def create_user(self,db: Session, user: schemas.UserCreate,userid:int):
|
||||
hashed_password = get_password_hash(user.password)
|
||||
user.hashed_password = hashed_password
|
||||
user.createuserid = userid
|
||||
user.updateuserid = userid
|
||||
del user.password
|
||||
return super().create(db,user)
|
||||
|
||||
def delete_user(self,db: Session, user_id: int):
|
||||
return super().delete(db,user_id)
|
||||
|
||||
|
||||
def edit_user(self,db: Session, user_id:int,user: schemas.UserEdit,userid: int) -> schemas.User:
|
||||
if not user.password is None and user.password != "":
|
||||
user.hashed_password = get_password_hash(user.password)
|
||||
del user.password
|
||||
user.updateuserid = userid
|
||||
return super().update(db,user_id,user)
|
||||
|
||||
def get_roles(self,db: Session) -> t.List[schemas.RoleBase]:
|
||||
return db.execute(dbrole.get_all()).scalars().all()
|
||||
#return dbrole.get_all().all()
|
||||
|
||||
def get_roles_by_level(self,db: Session,roles:t.List[models.Role]) -> t.List[schemas.RoleBase]:
|
||||
level = 99999
|
||||
for role in roles:
|
||||
if role.level < level:
|
||||
level = role.level
|
||||
return db.execute(dbrole.get_by_conditions({"level":{"operator":">","value":level}})).scalars().all()
|
||||
|
||||
def assign_userrole(self,db: Session, user_id: int, roles: t.List[int]):
|
||||
db_user = super().get(db,user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
if role.id not in roles:
|
||||
db_user.roles.remove(role)
|
||||
for roleid in roles:
|
||||
role = dbrole.get(db,roleid)
|
||||
if role not in db_user.roles:
|
||||
db_user.roles.append(role)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
def get_permissions(self,db: Session) -> t.List[schemas.Permission]:
|
||||
return db.execute(dbpermission.get_all()).scalars().all()
|
||||
|
||||
def get_user_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]:
|
||||
permissions =[]
|
||||
db_user = super().get(db,user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
permissions += role.permissions
|
||||
return list(set(permissions))
|
||||
|
||||
userService = dbuser()
|
||||
@@ -1,256 +1,113 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
||||
from sqlalchemy.orm import Mapped,relationship,as_declarative,mapped_column
|
||||
from datetime import datetime,timezone
|
||||
from app.db import Base
|
||||
from app.core.security import chacha20Decrypt
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from datetime import datetime
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
create_time = Column(DateTime, default=datetime.now(timezone.utc))
|
||||
update_time = Column(DateTime, default=datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
|
||||
userrole = Table(
|
||||
"userrole",
|
||||
Base.metadata,
|
||||
Column("userid",Integer,ForeignKey("user.id")),
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
)
|
||||
|
||||
rolepermission = Table(
|
||||
"rolepermission",
|
||||
Base.metadata,
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
Column("permissionid",Integer,ForeignKey("permission.id")),
|
||||
)
|
||||
create_time = Column(DateTime, default=datetime.now)
|
||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
email = mapped_column(String(50), unique=True, index=True, nullable=False)
|
||||
first_name = mapped_column(String(100))
|
||||
last_name = mapped_column(String(100))
|
||||
hashed_password = mapped_column(String(200), nullable=False)
|
||||
is_active = mapped_column(Boolean, default=True)
|
||||
is_superuser = mapped_column(Boolean, default=False)
|
||||
tenantid = mapped_column(String(100))
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
roles = relationship("Role",secondary=userrole,back_populates="users")
|
||||
|
||||
|
||||
class Role(Base):
|
||||
__tablename__ = "role"
|
||||
|
||||
name = mapped_column(String(100))
|
||||
description = mapped_column(String(255))
|
||||
level = mapped_column(Integer)
|
||||
users = relationship("User",secondary=userrole,back_populates="roles")
|
||||
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
||||
|
||||
class Permission(Base):
|
||||
__tablename__ = "permission"
|
||||
|
||||
menu = mapped_column(String(100))
|
||||
function = mapped_column(String(255))
|
||||
link = mapped_column(String(100))
|
||||
privilege = mapped_column(String(100))
|
||||
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
|
||||
|
||||
|
||||
class App(Base):
|
||||
__tablename__ = "app"
|
||||
|
||||
domainurl = mapped_column(String(200), nullable=False)
|
||||
appname = mapped_column(String(200), nullable=False)
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
version = mapped_column(Integer)
|
||||
versionname = mapped_column(String(200), nullable=False)
|
||||
is_saved = mapped_column(Boolean, default=False)
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class AppVersion(Base):
|
||||
__tablename__ = "appversion"
|
||||
|
||||
domainurl = mapped_column(String(200), nullable=False)
|
||||
appname = mapped_column(String(200), nullable=False)
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
version = mapped_column(Integer)
|
||||
versionname = mapped_column(String(200), nullable=False)
|
||||
comment = mapped_column(String(200), nullable=False)
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
|
||||
email = Column(String(50), unique=True, index=True, nullable=False)
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
hashed_password = Column(String(200), nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "appsetting"
|
||||
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
setting = mapped_column(String(1000))
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
setting = Column(String(1000))
|
||||
|
||||
class Kintone(Base):
|
||||
__tablename__ = "kintone"
|
||||
|
||||
type = mapped_column(Integer, index=True, nullable=False)
|
||||
name = mapped_column(String(100), nullable=False)
|
||||
desc = mapped_column(String)
|
||||
content = mapped_column(String)
|
||||
type = Column(Integer, index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
desc = Column(String)
|
||||
content = Column(String)
|
||||
|
||||
class Action(Base):
|
||||
__tablename__ = "action"
|
||||
|
||||
name = mapped_column(String(100), index=True, nullable=False)
|
||||
title = mapped_column(String(200))
|
||||
subtitle = mapped_column(String(500))
|
||||
outputpoints = mapped_column(String)
|
||||
property = mapped_column(String)
|
||||
categoryid = mapped_column(Integer,ForeignKey("category.id"))
|
||||
nosort = mapped_column(Integer)
|
||||
name = Column(String(100), index=True, nullable=False)
|
||||
title = Column(String(200))
|
||||
subtitle = Column(String(500))
|
||||
outputpoints = Column(String)
|
||||
property = Column(String)
|
||||
|
||||
class Flow(Base):
|
||||
__tablename__ = "flow"
|
||||
|
||||
flowid = mapped_column(String(100), index=True, nullable=False)
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
eventid = mapped_column(String(100), index=True, nullable=False)
|
||||
domainurl = mapped_column(String(200))
|
||||
name = mapped_column(String(200))
|
||||
content = mapped_column(String)
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class FlowHistory(Base):
|
||||
__tablename__ = "flowhistory"
|
||||
|
||||
flowid = mapped_column(String(100), index=True, nullable=False)
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
eventid = mapped_column(String(100), index=True, nullable=False)
|
||||
domainurl = mapped_column(String(200))
|
||||
name = mapped_column(String(200))
|
||||
content = mapped_column(String)
|
||||
version = mapped_column(Integer)
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
flowid = Column(String(100), index=True, nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
eventid = Column(String(100), index=True, nullable=False)
|
||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||
name = Column(String(200))
|
||||
content = Column(String)
|
||||
|
||||
class Tenant(Base):
|
||||
__tablename__ = "tenant"
|
||||
|
||||
tenantid = mapped_column(String(100), index=True, nullable=False)
|
||||
name = mapped_column(String(200))
|
||||
licence = mapped_column(String(200))
|
||||
startdate = mapped_column(DateTime)
|
||||
enddate = mapped_column(DateTime)
|
||||
db = mapped_column(String(200))
|
||||
|
||||
tenantid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(200))
|
||||
licence = Column(String(200))
|
||||
startdate = Column(DateTime)
|
||||
enddate = Column(DateTime)
|
||||
|
||||
class Domain(Base):
|
||||
__tablename__ = "domain"
|
||||
|
||||
tenantid = mapped_column(String(100), index=True, nullable=False)
|
||||
name = mapped_column(String(100), nullable=False)
|
||||
url = mapped_column(String(200), nullable=False)
|
||||
kintoneuser = mapped_column(String(100), nullable=False)
|
||||
kintonepwd = mapped_column(String(100), nullable=False)
|
||||
is_active = mapped_column(Boolean, default=True)
|
||||
def decrypt_kintonepwd(self):
|
||||
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
||||
return decrypted_pwd
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
ownerid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
owner = relationship('User',foreign_keys=[ownerid])
|
||||
tenantid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
url = Column(String(200), nullable=False)
|
||||
kintoneuser = Column(String(100), nullable=False)
|
||||
kintonepwd = Column(String(100), nullable=False)
|
||||
|
||||
|
||||
class UserDomain(Base):
|
||||
__tablename__ = "userdomain"
|
||||
|
||||
userid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
domainid = mapped_column(Integer,ForeignKey("domain.id"))
|
||||
is_default = mapped_column(Boolean, default=False)
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
domain = relationship("Domain")
|
||||
user = relationship("User",foreign_keys=[userid])
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class ManageDomain(Base):
|
||||
__tablename__ = "managedomain"
|
||||
|
||||
userid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
domainid = mapped_column(Integer,ForeignKey("domain.id"))
|
||||
createuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
domain = relationship("Domain")
|
||||
user = relationship("User",foreign_keys=[userid])
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
userid = Column(Integer,ForeignKey("user.id"))
|
||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||
active = Column(Boolean, default=False)
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "event"
|
||||
|
||||
category = mapped_column(String(100), nullable=False)
|
||||
type = mapped_column(String(100), nullable=False)
|
||||
eventid= mapped_column(String(100), nullable=False)
|
||||
function = mapped_column(String(500), nullable=False)
|
||||
mobile = mapped_column(Boolean, default=False)
|
||||
eventgroup = mapped_column(Boolean, default=False)
|
||||
category = Column(String(100), nullable=False)
|
||||
type = Column(String(100), nullable=False)
|
||||
eventid= Column(String(100), nullable=False)
|
||||
function = Column(String(500), nullable=False)
|
||||
mobile = Column(Boolean, default=False)
|
||||
eventgroup = Column(Boolean, default=False)
|
||||
|
||||
class EventAction(Base):
|
||||
__tablename__ = "eventaction"
|
||||
|
||||
eventid = mapped_column(String(100),ForeignKey("event.eventid"))
|
||||
actionid = mapped_column(Integer,ForeignKey("action.id"))
|
||||
eventid = Column(Integer,ForeignKey("event.id"))
|
||||
actionid = Column(Integer,ForeignKey("action.id"))
|
||||
|
||||
|
||||
class ErrorLog(Base):
|
||||
__tablename__ = "errorlog"
|
||||
|
||||
title = mapped_column(String(50))
|
||||
location = mapped_column(String(500))
|
||||
content = mapped_column(String(5000))
|
||||
|
||||
class OperationLog(Base):
|
||||
__tablename__ = "operationlog"
|
||||
|
||||
tenantid = mapped_column(String(100))
|
||||
clientip = mapped_column(String(200))
|
||||
useragent = mapped_column(String(200))
|
||||
userid = mapped_column(Integer,ForeignKey("user.id"))
|
||||
operation = mapped_column(String(200))
|
||||
function = mapped_column(String(200))
|
||||
parameters = mapped_column(String)
|
||||
response = mapped_column(String(200))
|
||||
user = relationship('User')
|
||||
title = Column(String(50))
|
||||
location = Column(String(500))
|
||||
content = Column(String(5000))
|
||||
|
||||
class KintoneFormat(Base):
|
||||
__tablename__ = "kintoneformat"
|
||||
|
||||
name = mapped_column(String(50))
|
||||
startrow =mapped_column(Integer)
|
||||
startcolumn =mapped_column(Integer)
|
||||
typecolumn =mapped_column(Integer)
|
||||
codecolumn =mapped_column(Integer)
|
||||
field = mapped_column(String(5000))
|
||||
trueformat = mapped_column(String(10))
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = "category"
|
||||
|
||||
categoryname = mapped_column(String(20))
|
||||
nosort = mapped_column(Integer)
|
||||
name = Column(String(50))
|
||||
startrow =Column(Integer)
|
||||
startcolumn =Column(Integer)
|
||||
typecolumn =Column(Integer)
|
||||
codecolumn =Column(Integer)
|
||||
field = Column(String(5000))
|
||||
trueformat = Column(String(10))
|
||||
@@ -2,81 +2,46 @@ from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
import typing as t
|
||||
|
||||
from app.core.security import chacha20Decrypt, chacha20Encrypt
|
||||
|
||||
class Base(BaseModel):
|
||||
create_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
|
||||
class Permission(BaseModel):
|
||||
id: int
|
||||
menu:str
|
||||
function:str
|
||||
link:str
|
||||
privilege:str
|
||||
|
||||
class RoleBase(BaseModel):
|
||||
id: int
|
||||
name:str
|
||||
description:str
|
||||
level:int
|
||||
|
||||
|
||||
class RoleWithPermission(RoleBase):
|
||||
permissions:t.List[Permission] = []
|
||||
|
||||
class AssignUserRoles(BaseModel):
|
||||
userid:int
|
||||
roleids:t.List[int]
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
first_name: str = None
|
||||
last_name: str = None
|
||||
roles:t.List[RoleBase] = []
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
first_name: str = None
|
||||
last_name: str = None
|
||||
|
||||
class UserOut(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
email:str
|
||||
password: str
|
||||
hashed_password :str = None
|
||||
first_name: str
|
||||
last_name: str
|
||||
is_active:bool
|
||||
is_superuser:bool
|
||||
tenantid:t.Optional[str] = "1"
|
||||
createuserid:t.Optional[int] = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserEdit(UserBase):
|
||||
password: t.Optional[str] = None
|
||||
hashed_password :str = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class User(UserBase):
|
||||
id: int
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
@@ -84,32 +49,6 @@ class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
class AppList(Base):
|
||||
domainurl: str
|
||||
appname: str
|
||||
appid:str
|
||||
version:int
|
||||
is_saved:bool
|
||||
versionname: t.Optional[str] = None
|
||||
updateuser: UserOut
|
||||
createuser: UserOut
|
||||
|
||||
|
||||
class AppVersion(Base):
|
||||
domainurl: str
|
||||
appname: str
|
||||
versionname: str
|
||||
comment:str
|
||||
appid:str
|
||||
version:t.Optional[int] = None
|
||||
updateuser: UserOut
|
||||
createuser: UserOut
|
||||
|
||||
class VersionUpdate(BaseModel):
|
||||
appid:str
|
||||
versionname: str
|
||||
comment:str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
id:int = 0
|
||||
@@ -128,7 +67,7 @@ class AppBase(BaseModel):
|
||||
class App(AppBase):
|
||||
id: int
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
@@ -139,7 +78,7 @@ class Kintone(BaseModel):
|
||||
desc: str = None
|
||||
content: str = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Action(BaseModel):
|
||||
@@ -149,17 +88,13 @@ class Action(BaseModel):
|
||||
subtitle: str = None
|
||||
outputpoints: str = None
|
||||
property: str = None
|
||||
categoryid: int = None
|
||||
nosort: int
|
||||
categoryname : str =None
|
||||
class ConfigDict:
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class FlowIn(BaseModel):
|
||||
class FlowBase(BaseModel):
|
||||
flowid: str
|
||||
# domainurl:str
|
||||
appid: str
|
||||
appname:str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
@@ -169,55 +104,20 @@ class Flow(Base):
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
domainurl: str
|
||||
domainid: int
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class DomainIn(BaseModel):
|
||||
class DomainBase(BaseModel):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: t.Optional[str] = None
|
||||
is_active: bool
|
||||
createuserid:t.Optional[int] = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
ownerid:t.Optional[int] = None
|
||||
|
||||
def encrypt_kintonepwd(self):
|
||||
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
||||
self.kintonepwd = encrypted_pwd
|
||||
|
||||
class DomainOut(BaseModel):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
is_active: bool
|
||||
ownerid:int
|
||||
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
|
||||
class UserDomainParam(BaseModel):
|
||||
userid:int
|
||||
domainid:int
|
||||
|
||||
class UserDomain(BaseModel):
|
||||
id: int
|
||||
is_default: bool
|
||||
domain:DomainOut
|
||||
user:UserOut
|
||||
|
||||
class UserDomainIn(BaseModel):
|
||||
is_default: bool
|
||||
domainid:int
|
||||
userid:int
|
||||
kintonepwd: str
|
||||
|
||||
class Domain(Base):
|
||||
id: int
|
||||
@@ -225,13 +125,10 @@ class Domain(Base):
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
is_active: bool
|
||||
updateuser:UserOut
|
||||
owner:UserOut
|
||||
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
kintonepwd: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Event(Base):
|
||||
id: int
|
||||
@@ -242,7 +139,7 @@ class Event(Base):
|
||||
mobile: bool
|
||||
eventgroup: bool
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class ErrorCreate(BaseModel):
|
||||
|
||||
@@ -1,37 +1,21 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base, Session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core import config
|
||||
|
||||
engine = create_engine(
|
||||
config.SQLALCHEMY_DATABASE_URI,
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
# engine = create_engine(
|
||||
# config.SQLALCHEMY_DATABASE_URI,
|
||||
# )
|
||||
# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
class Database:
|
||||
def __init__(self, database_url: str):
|
||||
self.database_url = database_url
|
||||
self.engine = create_engine(self.database_url)
|
||||
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
|
||||
self.Base = declarative_base()
|
||||
|
||||
def get_db(self):
|
||||
db =self.SessionLocal()
|
||||
return db
|
||||
|
||||
tenantdb = Database(config.SQLALCHEMY_DATABASE_URI)
|
||||
|
||||
|
||||
def get_tenant_db():
|
||||
db = tenantdb.get_db()
|
||||
# Dependency
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def get_user_db(database_url: str):
|
||||
database = Database(database_url)
|
||||
db = database.get_db()
|
||||
return db
|
||||
db.close()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi_pagination import add_pagination
|
||||
from starlette.requests import Request
|
||||
import uvicorn
|
||||
from app.api.api_v1.routers.kintone import kinton_router
|
||||
@@ -8,29 +7,21 @@ from app.api.api_v1.routers.users import users_router
|
||||
from app.api.api_v1.routers.auth import auth_router
|
||||
from app.api.api_v1.routers.platform import platform_router
|
||||
from app.core import config
|
||||
#from app.db import Base,engine
|
||||
from app.db import Base,engine
|
||||
from app.core.auth import get_current_active_user
|
||||
from app.core.celery_app import celery_app
|
||||
from app import tasks
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import logging
|
||||
from app.core.apiexception import APIException, writedblog
|
||||
from app.core.common import ApiReturnError
|
||||
from app.db.crud import create_log
|
||||
from fastapi.responses import JSONResponse
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from app.core.operation import LoggingMiddleware
|
||||
|
||||
#Base.metadata.create_all(bind=engine)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
startup_event()
|
||||
yield
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(
|
||||
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api",lifespan=lifespan
|
||||
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
|
||||
)
|
||||
|
||||
origins = [
|
||||
@@ -45,10 +36,6 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.add_middleware(LoggingMiddleware)
|
||||
|
||||
add_pagination(app)
|
||||
|
||||
# @app.middleware("http")
|
||||
# async def db_session_middleware(request: Request, call_next):
|
||||
# request.state.db = SessionLocal()
|
||||
@@ -56,11 +43,12 @@ add_pagination(app)
|
||||
# request.state.db.close()
|
||||
# return response
|
||||
|
||||
def startup_event():
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
log_dir="log"
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
|
||||
logger = logging.getLogger("uvicorn.access")
|
||||
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
|
||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
||||
@@ -72,7 +60,7 @@ async def api_exception_handler(request: Request, exc: APIException):
|
||||
loop.run_in_executor(None,writedblog,exc)
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content= ApiReturnError(msg = f"{exc.detail}").model_dump(),
|
||||
content={"detail": f"{exc.detail}"},
|
||||
)
|
||||
|
||||
@app.get("/api/v1")
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from fastapi.testclient import TestClient
|
||||
import typing as t
|
||||
|
||||
from app.core import config, security
|
||||
from app.core.dbmanager import get_db
|
||||
from app.db import models,schemas
|
||||
from app.main import app
|
||||
|
||||
|
||||
from app.core import security
|
||||
import jwt
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
||||
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URI,echo=True)
|
||||
|
||||
test_session_maker = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_db():
|
||||
connection = engine.connect()
|
||||
transaction = connection.begin()
|
||||
test_session = test_session_maker(bind=connection)
|
||||
yield test_session
|
||||
test_session.close()
|
||||
transaction.rollback()
|
||||
#transaction.commit()
|
||||
connection.close()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_client(test_db):
|
||||
def get_test_db():
|
||||
try:
|
||||
yield test_db
|
||||
finally:
|
||||
test_db.close()
|
||||
|
||||
app.dependency_overrides[get_db] = get_test_db
|
||||
with TestClient(app) as test_client:
|
||||
yield test_client
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_tenant_id():
|
||||
return "1"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_user(test_db,test_tenant_id):
|
||||
password ="test"
|
||||
user = models.User(
|
||||
email = "test@test.com",
|
||||
first_name = "test",
|
||||
last_name = "abc",
|
||||
hashed_password = security.get_password_hash(password),
|
||||
is_active = True,
|
||||
is_superuser = False,
|
||||
tenantid = test_tenant_id
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
test_db.refresh(user)
|
||||
dicUser = user.__dict__
|
||||
dicUser["password"] = password
|
||||
return dicUser
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def password():
|
||||
return "password"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def user(test_db,password,test_tenant_id):
|
||||
user = models.User(
|
||||
email = "user@test.com",
|
||||
first_name = "user",
|
||||
last_name = "abc",
|
||||
hashed_password = security.get_password_hash(password),
|
||||
is_active = True,
|
||||
is_superuser = False,
|
||||
tenantid = test_tenant_id
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
test_db.refresh(user)
|
||||
return user.__dict__
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def admin(test_db,password,test_tenant_id):
|
||||
user = models.User(
|
||||
email = "admin@test.com",
|
||||
first_name = "admin",
|
||||
last_name = "abc",
|
||||
hashed_password = security.get_password_hash(password),
|
||||
is_active = True,
|
||||
is_superuser = True,
|
||||
tenantid =test_tenant_id
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
test_db.refresh(user)
|
||||
return user.__dict__
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def login_user(test_db,test_client,user,password):
|
||||
# test_db.add(user)
|
||||
# test_db.commit()
|
||||
#test_db.refresh(user)
|
||||
response = test_client.post("/api/token", data={"username": user["email"], "password":password })
|
||||
return response.json()["access_token"]
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def login_admin(test_db,test_client,admin,password):
|
||||
# test_db.add(admin)
|
||||
# test_db.commit()
|
||||
#test_db.refresh(admin)
|
||||
response = test_client.post("/api/token", data={"username": admin["email"], "password":password })
|
||||
return response.json()["access_token"]
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def login_user_id(login_user):
|
||||
payload = jwt.decode(login_user, security.SECRET_KEY, algorithms=[security.ALGORITHM])
|
||||
id = payload.get("sub")
|
||||
return id
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def login_admin_id(login_admin):
|
||||
payload = jwt.decode(login_admin, security.SECRET_KEY, algorithms=[security.ALGORITHM])
|
||||
id = payload.get("sub")
|
||||
return id
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_role(test_db):
|
||||
role = models.Role(
|
||||
name = "test",
|
||||
description = "test",
|
||||
level = 1
|
||||
)
|
||||
test_db.add(role)
|
||||
test_db.commit()
|
||||
test_db.refresh(role)
|
||||
return role.__dict__
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_domain(test_db,login_user_id):
|
||||
domain = models.Domain(
|
||||
tenantid = "1",
|
||||
name = "テスト環境",
|
||||
url = "https://mfu07rkgnb7c.cybozu.com",
|
||||
kintoneuser = "MXZ",
|
||||
kintonepwd = security.chacha20Encrypt("maxz1205"),
|
||||
is_active = True,
|
||||
createuserid =login_user_id,
|
||||
updateuserid =login_user_id,
|
||||
ownerid = login_user_id
|
||||
)
|
||||
test_db.add(domain)
|
||||
test_db.flush()
|
||||
user_domain = models.UserDomain(userid = login_user_id, domainid = domain.id ,createuserid = login_user_id,updateuserid = login_user_id)
|
||||
test_db.add(user_domain)
|
||||
manage_domain = models.ManageDomain(userid = login_user_id, domainid = domain.id ,createuserid = login_user_id,updateuserid = login_user_id)
|
||||
test_db.add(manage_domain)
|
||||
test_db.commit()
|
||||
test_db.refresh(domain)
|
||||
return domain
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_app_id():
|
||||
return "132"
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def test_password() -> str:
|
||||
# return "securepassword"
|
||||
|
||||
|
||||
# def get_password_hash() -> str:
|
||||
# """
|
||||
# Password hashing can be expensive so a mock will be much faster
|
||||
# """
|
||||
# return "supersecrethash"
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def test_user(test_db) -> models.User:
|
||||
# """
|
||||
# Make a test user in the database
|
||||
# """
|
||||
|
||||
# user = models.User(
|
||||
# email="fake@email.com",
|
||||
# hashed_password=get_password_hash(),
|
||||
# is_active=True,
|
||||
# )
|
||||
# test_db.add(user)
|
||||
# test_db.commit()
|
||||
# return user
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def test_superuser(test_db) -> models.User:
|
||||
# """
|
||||
# Superuser for testing
|
||||
# """
|
||||
|
||||
# user = models.User(
|
||||
# email="fakeadmin@email.com",
|
||||
# hashed_password=get_password_hash(),
|
||||
# is_superuser=True,
|
||||
# )
|
||||
# test_db.add(user)
|
||||
# test_db.commit()
|
||||
# return user
|
||||
|
||||
|
||||
# def verify_password_mock(first: str, second: str) -> bool:
|
||||
# return True
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def user_token_headers(
|
||||
# client: TestClient, test_user, test_password, monkeypatch
|
||||
# ) -> t.Dict[str, str]:
|
||||
# monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
# login_data = {
|
||||
# "username": test_user.email,
|
||||
# "password": test_password,
|
||||
# }
|
||||
# r = client.post("/api/token", data=login_data)
|
||||
# tokens = r.json()
|
||||
# a_token = tokens["access_token"]
|
||||
# headers = {"Authorization": f"Bearer {a_token}"}
|
||||
# return headers
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def superuser_token_headers(
|
||||
# client: TestClient, test_superuser, test_password, monkeypatch
|
||||
# ) -> t.Dict[str, str]:
|
||||
# monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
# login_data = {
|
||||
# "username": test_superuser.email,
|
||||
# "password": test_password,
|
||||
# }
|
||||
# r = client.post("/api/token", data=login_data)
|
||||
# tokens = r.json()
|
||||
# a_token = tokens["access_token"]
|
||||
# headers = {"Authorization": f"Bearer {a_token}"}
|
||||
# return headers
|
||||
@@ -1,5 +0,0 @@
|
||||
[pytest]
|
||||
log_cli = 1
|
||||
log_cli_level = CRITICAL
|
||||
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
|
||||
log_cli_date_format=%Y-%m-%d %H:%M:%S
|
||||
@@ -1,11 +0,0 @@
|
||||
import logging
|
||||
|
||||
def test_usr_login(test_client,test_user):
|
||||
response = test_client.post("/api/token", data={"username": test_user["email"], "password": test_user["password"]})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "access_token" in response.json()
|
||||
assert "token_type" in response.json()
|
||||
assert response.json()["user_name"] == test_user["first_name"]+ " " + test_user["last_name"]
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import logging
|
||||
def test_get_domains(test_client,test_domain,login_user):
|
||||
response = test_client.get("/api/domains",headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert len(data["data"]) == 1
|
||||
assert data["data"][0]["name"] == test_domain.name
|
||||
|
||||
def test_create_domain(test_client, login_user,login_user_id):
|
||||
create_domain ={
|
||||
"id": 0,
|
||||
"tenantid": "1",
|
||||
"name": "abc",
|
||||
"url": "efg",
|
||||
"kintoneuser": "eee",
|
||||
"kintonepwd": "fff",
|
||||
"is_active": True,
|
||||
}
|
||||
response = test_client.post("/api/domain", json=create_domain,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == create_domain["name"]
|
||||
assert data["data"]["url"] == create_domain["url"]
|
||||
assert data["data"]["kintoneuser"] == create_domain["kintoneuser"]
|
||||
assert data["data"]["is_active"] == create_domain["is_active"]
|
||||
assert data["data"]["owner"]["id"] == login_user_id
|
||||
|
||||
|
||||
def test_get_managedomainuser(test_client,test_domain,login_user,login_user_id):
|
||||
response = test_client.get("/api/managedomainuser/" + str(test_domain.id),headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert len(data["data"]) == 1
|
||||
assert data["data"][0]["id"] == login_user_id
|
||||
|
||||
|
||||
def test_add_delete_userdomain(test_client,test_domain,test_user,login_user,login_user_id):
|
||||
userdomain ={
|
||||
"userid":test_user["id"],
|
||||
"domainid":test_domain.id
|
||||
}
|
||||
response = test_client.post("/api/userdomain" , json=userdomain,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
response = test_client.delete(f"/api/domain/{test_domain.id}/{test_user["id"]}" , headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
|
||||
|
||||
def test_add_delete_managedomain(test_client,test_domain,test_user,login_user,login_user_id):
|
||||
userdomain ={
|
||||
"userid":test_user["id"],
|
||||
"domainid":test_domain.id
|
||||
}
|
||||
response = test_client.post("/api/managedomain" , json=userdomain,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
response = test_client.delete(f"/api/managedomain/{test_domain.id}/{test_user["id"]}" , headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
|
||||
|
||||
def test_delete_domain(test_client, login_user,login_user_id):
|
||||
delete_domain ={
|
||||
"id": 0,
|
||||
"tenantid": "1",
|
||||
"name": "delete",
|
||||
"url": "delete",
|
||||
"kintoneuser": "delete",
|
||||
"kintonepwd": "delete",
|
||||
"is_active": True,
|
||||
}
|
||||
response = test_client.post("/api/domain", json=delete_domain,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
id = data["data"]["id"]
|
||||
|
||||
response = test_client.delete(f"/api/domain/{id}/{login_user_id}",headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
|
||||
response = test_client.delete(f"/api/domain/{id}",headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["data"]["name"] == delete_domain["name"]
|
||||
response = test_client.get(f"/api/domain/{id}", headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
assert "data" not in response.json()
|
||||
|
||||
|
||||
def test_set_defaultuserdomain(test_client, test_domain,login_user):
|
||||
|
||||
response = test_client.put("/api/defaultdomain/"+str(test_domain.id), headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
assert data["data"]["kintoneuser"] == test_domain.kintoneuser
|
||||
assert data["data"]["is_active"] == test_domain.is_active
|
||||
|
||||
|
||||
def test_get_defaultuserdomain(test_client, test_domain,login_user):
|
||||
|
||||
response = test_client.get("/api/defaultdomain", headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == test_domain.name
|
||||
assert data["data"]["url"] == test_domain.url
|
||||
assert data["data"]["kintoneuser"] == test_domain.kintoneuser
|
||||
assert data["data"]["is_active"] == test_domain.is_active
|
||||
|
||||
|
||||
def test_get_domainshareduser(test_client, test_domain,login_user,login_user_id):
|
||||
response = test_client.get("/api/domainshareduser/"+str(test_domain.id), headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert len(data["data"]) == 1
|
||||
assert data["data"][0]["id"] == login_user_id
|
||||
|
||||
def test_edit_domain(test_client, test_domain, login_user):
|
||||
update_domain ={
|
||||
"id": test_domain.id,
|
||||
"tenantid": "1",
|
||||
"name": "テスト環境abc",
|
||||
"url": test_domain.url,
|
||||
"kintoneuser": test_domain.kintoneuser,
|
||||
"is_active": True
|
||||
}
|
||||
response = test_client.put("/api/domain", json=update_domain,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["name"] == update_domain["name"]
|
||||
assert data["data"]["url"] == update_domain["url"]
|
||||
assert data["data"]["kintoneuser"] == update_domain["kintoneuser"]
|
||||
assert data["data"]["is_active"] == update_domain["is_active"]
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
def test_read_main(test_client):
|
||||
response = test_client.get("/api/v1")
|
||||
def test_read_main(client):
|
||||
response = client.get("/api/v1")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "success"}
|
||||
assert response.json() == {"message": "Hello World"}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
|
||||
import logging
|
||||
def test_users_list(test_client,login_user):
|
||||
|
||||
response = test_client.get("/api/v1/users", headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["data"]) == 2
|
||||
|
||||
def test_users_list_for_admin(test_client,login_admin):
|
||||
|
||||
response = test_client.get("/api/v1/users", headers={"Authorization": "Bearer " + login_admin})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert len(data["data"]) == 3
|
||||
|
||||
def test_user_create(test_client,login_user):
|
||||
user_data = {
|
||||
"email": "newuser1@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "New",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": False
|
||||
}
|
||||
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["id"] > 0
|
||||
assert data["data"]["email"] == user_data["email"]
|
||||
assert data["data"]["first_name"] == user_data["first_name"]
|
||||
assert data["data"]["last_name"] == user_data["last_name"]
|
||||
assert data["data"]["is_active"] == user_data["is_active"]
|
||||
assert data["data"]["is_superuser"] == user_data["is_superuser"]
|
||||
|
||||
def test_admin_create(test_client,login_user):
|
||||
user_data = {
|
||||
"email": "newuser2@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "New",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": True
|
||||
}
|
||||
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" not in data
|
||||
|
||||
def test_admin_create_for_admin(test_client,login_admin):
|
||||
user_data = {
|
||||
"email": "admin@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "New",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": True
|
||||
}
|
||||
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_admin})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["id"] > 0
|
||||
assert data["data"]["email"] == user_data["email"]
|
||||
assert data["data"]["first_name"] == user_data["first_name"]
|
||||
assert data["data"]["last_name"] == user_data["last_name"]
|
||||
assert data["data"]["is_active"] == user_data["is_active"]
|
||||
assert data["data"]["is_superuser"] == user_data["is_superuser"]
|
||||
|
||||
def test_user_details(test_client,login_user_id, login_user,user):
|
||||
id = login_user_id
|
||||
response = test_client.get("/api/v1/users/"+ str(id), headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert data["data"]["email"] == user["email"]
|
||||
assert data["data"]["first_name"] == user["first_name"]
|
||||
assert data["data"]["last_name"] == user["last_name"]
|
||||
assert data["data"]["is_active"] == user["is_active"]
|
||||
assert data["data"]["is_superuser"] == user["is_superuser"]
|
||||
assert data["data"]["id"] == id
|
||||
|
||||
def test_user_edit(test_client, login_user_id,login_user,user):
|
||||
id = login_user_id
|
||||
user_data = {
|
||||
"email": user["email"],
|
||||
"first_name": "Updated",
|
||||
"last_name": "test",
|
||||
"is_active": True,
|
||||
"is_superuser": False
|
||||
}
|
||||
response = test_client.put("/api/v1/users/" + str(id), json=user_data, headers={"Authorization": "Bearer " + login_user})
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["data"]["email"] == user["email"]
|
||||
assert data["data"]["first_name"] == user_data["first_name"]
|
||||
assert data["data"]["last_name"] == user_data["last_name"]
|
||||
assert data["data"]["is_active"] == user["is_active"]
|
||||
assert data["data"]["id"] == id
|
||||
|
||||
def test_user_delete(test_client, login_user):
|
||||
user_data = {
|
||||
"email": "delete@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "delete",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": False
|
||||
}
|
||||
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
id = data["data"]["id"]
|
||||
response = test_client.delete("/api/v1/users/"+ str(id),headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["data"]["email"] == "delete@example.com"
|
||||
response = test_client.get("/api/v1/users/"+ str(id), headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
assert "data" not in response.json()
|
||||
|
||||
def test_role_assign(test_client, login_user_id,login_user,test_role):
|
||||
userroles ={
|
||||
"userid":login_user_id,
|
||||
"roleids":[test_role["id"]]
|
||||
}
|
||||
response = test_client.post("/api/v1/userrole", json=userroles, headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
response = test_client.get("/api/v1/users/"+ str(login_user_id), headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert len(data["data"]["roles"]) == 1
|
||||
|
||||
def test_roles_get(test_client,login_user):
|
||||
response = test_client.get("/api/v1/roles", headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert len(data["data"]) == 0
|
||||
|
||||
def test_roles_admin_get(test_client,login_admin):
|
||||
response = test_client.get("/api/v1/roles", headers={"Authorization": "Bearer " + login_admin})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert len(data["data"]) == 1
|
||||
@@ -1,121 +0,0 @@
|
||||
import logging
|
||||
def test_create_flow(test_client,test_domain,test_app_id,login_user):
|
||||
test_flow={
|
||||
"flowid": "73e82bee-76a2-4347-a069-e21bf5e21111",
|
||||
"appid": test_app_id,
|
||||
"appname": "test_app",
|
||||
"eventid": "a",
|
||||
"name": "保存をクリックしたとき",
|
||||
"content": ""
|
||||
}
|
||||
response = test_client.post("/api/flow", json=test_flow,headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["domainurl"] == test_domain.url
|
||||
assert data["data"]["flowid"] == test_flow["flowid"]
|
||||
assert data["data"]["appid"] == test_flow["appid"]
|
||||
assert data["data"]["eventid"] == test_flow["eventid"]
|
||||
assert data["data"]["content"] == test_flow["content"]
|
||||
|
||||
def test_delete_flow(test_client,test_domain,test_app_id,login_user):
|
||||
response = test_client.delete("/api/flow/73e82bee-76a2-4347-a069-e21bf5e21111",headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
|
||||
def test_edit_flow(test_client,test_domain,test_app_id,login_user):
|
||||
test_flow={
|
||||
"flowid": "73e82bee-76a2-4347-a069-e21bf5e21111",
|
||||
"appid": test_app_id,
|
||||
"appname": "test_app_new",
|
||||
"eventid": "abc",
|
||||
"name": "保存をクリックしたとき",
|
||||
"content": ""
|
||||
}
|
||||
response = test_client.put("/api/flow", json=test_flow,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["domainurl"] == test_domain.url
|
||||
assert data["data"]["flowid"] == test_flow["flowid"]
|
||||
assert data["data"]["appid"] == test_flow["appid"]
|
||||
assert data["data"]["eventid"] == test_flow["eventid"]
|
||||
assert data["data"]["content"] == test_flow["content"]
|
||||
|
||||
def test_appversions_update(test_client,test_domain,test_app_id,login_user):
|
||||
app_version ={
|
||||
"versionname": "version1",
|
||||
"comment": "save version1",
|
||||
"appid": test_app_id
|
||||
}
|
||||
response = test_client.post("/api/apps", json=app_version,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["domainurl"] == test_domain.url
|
||||
assert data["data"]["version"] == 1
|
||||
assert data["data"]["appid"] == app_version["appid"]
|
||||
assert data["data"]["versionname"] == app_version["versionname"]
|
||||
assert data["data"]["is_saved"] == False
|
||||
|
||||
def test_apps_list(test_client,login_user):
|
||||
response = test_client.get("/api/apps", headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert len(data["data"]) == 1
|
||||
|
||||
|
||||
def test_appversions_list(test_client,test_domain,test_app_id,login_user):
|
||||
response = test_client.get("/api/appversions/" + test_app_id , headers={"Authorization": "Bearer " + login_user})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert len(data["data"]) == 1
|
||||
assert "versionname" in data["data"][0]
|
||||
|
||||
def test_appversions_change(test_client,test_domain,test_app_id,login_user):
|
||||
app_version ={
|
||||
"versionname": "version2",
|
||||
"comment": "test",
|
||||
"appid": test_app_id
|
||||
}
|
||||
response = test_client.post("/api/apps", json=app_version,headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["version"] == 2
|
||||
assert data["data"]["versionname"] == app_version["versionname"]
|
||||
assert data["data"]["is_saved"] == False
|
||||
|
||||
|
||||
response = test_client.put("/api/appversions/" + test_app_id +"/1", headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
assert data["data"]["domainurl"] == test_domain.url
|
||||
assert data["data"]["version"] == 1
|
||||
assert data["data"]["appid"] == test_app_id
|
||||
|
||||
|
||||
def test_delete_app(test_client,test_app_id,login_user):
|
||||
response = test_client.delete("/api/apps/"+ test_app_id, headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "data" in data
|
||||
assert data["data"] is not None
|
||||
@@ -1,9 +0,0 @@
|
||||
import logging
|
||||
def test_get_allapps(test_client,test_domain,login_user):
|
||||
response = test_client.get("/api/v1/allapps", headers={"Authorization": "Bearer " + login_user})
|
||||
data = response.json()
|
||||
logging.error(data)
|
||||
assert response.status_code == 200
|
||||
assert "apps" in data
|
||||
assert data["apps"] is not None
|
||||
assert len(data["apps"]) > 0
|
||||
169
backend/conftest.py
Normal file
169
backend/conftest.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy_utils import database_exists, create_database, drop_database
|
||||
from fastapi.testclient import TestClient
|
||||
import typing as t
|
||||
|
||||
from app.core import config, security
|
||||
from app.db.session import Base, get_db
|
||||
from app.db import models
|
||||
from app.main import app
|
||||
|
||||
|
||||
def get_test_db_url() -> str:
|
||||
return f"{config.SQLALCHEMY_DATABASE_URI}_test"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_db():
|
||||
"""
|
||||
Modify the db session to automatically roll back after each test.
|
||||
This is to avoid tests affecting the database state of other tests.
|
||||
"""
|
||||
# Connect to the test database
|
||||
engine = create_engine(
|
||||
get_test_db_url(),
|
||||
)
|
||||
|
||||
connection = engine.connect()
|
||||
trans = connection.begin()
|
||||
|
||||
# Run a parent transaction that can roll back all changes
|
||||
test_session_maker = sessionmaker(
|
||||
autocommit=False, autoflush=False, bind=engine
|
||||
)
|
||||
test_session = test_session_maker()
|
||||
test_session.begin_nested()
|
||||
|
||||
@event.listens_for(test_session, "after_transaction_end")
|
||||
def restart_savepoint(s, transaction):
|
||||
if transaction.nested and not transaction._parent.nested:
|
||||
s.expire_all()
|
||||
s.begin_nested()
|
||||
|
||||
yield test_session
|
||||
|
||||
# Roll back the parent transaction after the test is complete
|
||||
test_session.close()
|
||||
trans.rollback()
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def create_test_db():
|
||||
"""
|
||||
Create a test database and use it for the whole test session.
|
||||
"""
|
||||
|
||||
test_db_url = get_test_db_url()
|
||||
|
||||
# Create the test database
|
||||
assert not database_exists(
|
||||
test_db_url
|
||||
), "Test database already exists. Aborting tests."
|
||||
create_database(test_db_url)
|
||||
test_engine = create_engine(test_db_url)
|
||||
Base.metadata.create_all(test_engine)
|
||||
|
||||
# Run the tests
|
||||
yield
|
||||
|
||||
# Drop the test database
|
||||
drop_database(test_db_url)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(test_db):
|
||||
"""
|
||||
Get a TestClient instance that reads/write to the test database.
|
||||
"""
|
||||
|
||||
def get_test_db():
|
||||
yield test_db
|
||||
|
||||
app.dependency_overrides[get_db] = get_test_db
|
||||
|
||||
yield TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_password() -> str:
|
||||
return "securepassword"
|
||||
|
||||
|
||||
def get_password_hash() -> str:
|
||||
"""
|
||||
Password hashing can be expensive so a mock will be much faster
|
||||
"""
|
||||
return "supersecrethash"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_user(test_db) -> models.User:
|
||||
"""
|
||||
Make a test user in the database
|
||||
"""
|
||||
|
||||
user = models.User(
|
||||
email="fake@email.com",
|
||||
hashed_password=get_password_hash(),
|
||||
is_active=True,
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_superuser(test_db) -> models.User:
|
||||
"""
|
||||
Superuser for testing
|
||||
"""
|
||||
|
||||
user = models.User(
|
||||
email="fakeadmin@email.com",
|
||||
hashed_password=get_password_hash(),
|
||||
is_superuser=True,
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
return user
|
||||
|
||||
|
||||
def verify_password_mock(first: str, second: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_token_headers(
|
||||
client: TestClient, test_user, test_password, monkeypatch
|
||||
) -> t.Dict[str, str]:
|
||||
monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
login_data = {
|
||||
"username": test_user.email,
|
||||
"password": test_password,
|
||||
}
|
||||
r = client.post("/api/token", data=login_data)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
headers = {"Authorization": f"Bearer {a_token}"}
|
||||
return headers
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def superuser_token_headers(
|
||||
client: TestClient, test_superuser, test_password, monkeypatch
|
||||
) -> t.Dict[str, str]:
|
||||
monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
login_data = {
|
||||
"username": test_superuser.email,
|
||||
"password": test_password,
|
||||
}
|
||||
r = client.post("/api/token", data=login_data)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
headers = {"Authorization": f"Bearer {a_token}"}
|
||||
return headers
|
||||
@@ -29,11 +29,3 @@ python -m venv env
|
||||
```bash
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
# ZCC対応
|
||||
1. ENV環境中Pythone現在使用している証明書(cacert.pem)のパス確認
|
||||
```
|
||||
python -m certifi
|
||||
# C:\Projects\AI-IOT\AppBuilderforkintone\backend\env\Scripts\python.exe: No module named certifi
|
||||
```
|
||||
2. 上記のコマンドを実行すると、証明書までのパスが出てくるので、どこかにメモしてください。次のコマンドで使います。
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,6 +1,2 @@
|
||||
#開発環境
|
||||
#KAB_BACKEND_URL="https://ktune-backend-dev-eba8fkeyffegc3cz.japanwest-01.azurewebsites.net/"
|
||||
#単体テスト環境
|
||||
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
|
||||
#ローカル開発環境
|
||||
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||
#KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "k-tune",
|
||||
"version": "2.0.0 Beta",
|
||||
"name": "kintone-automate",
|
||||
"version": "0.2.0",
|
||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||
"productName": "k-tune | kintoneジェネレーター",
|
||||
"productName": "kintone Automate",
|
||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,15 +12,14 @@
|
||||
"dev": "quasar dev",
|
||||
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
||||
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
||||
"build:dev": "set \"SOURCE_MAP=true\" && quasar build"
|
||||
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
|
||||
@@ -37,8 +37,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: [
|
||||
'axios',
|
||||
'error-handler',
|
||||
'permissions'
|
||||
'error-handler'
|
||||
],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
@@ -95,7 +94,6 @@ module.exports = configure(function (/* ctx */) {
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: {
|
||||
// https: true
|
||||
port:9001,
|
||||
open: true, // opens browser window automatically
|
||||
env: { ...dotenv },
|
||||
},
|
||||
@@ -105,7 +103,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
config: {},
|
||||
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
lang: 'ja', // Quasar language pack
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
|
||||
// For special cases outside of where the auto-import strategy can have an impact
|
||||
// (like functional components as one of the examples),
|
||||
@@ -116,8 +114,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
// Quasar plugins
|
||||
plugins: [
|
||||
'Notify',
|
||||
'Dialog'
|
||||
'Notify'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import {router} from 'src/router';
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
@@ -17,10 +15,30 @@ declare module '@vue/runtime-core' {
|
||||
// good idea to move this instance creation inside of the
|
||||
// "export default () => {}" function below (which runs individually
|
||||
// for each client)
|
||||
|
||||
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
||||
const token=localStorage.getItem('token')||'';
|
||||
if(token!==''){
|
||||
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||
}
|
||||
//axios例外キャプチャー
|
||||
api.interceptors.response.use(
|
||||
(response)=>response,
|
||||
(error)=>{
|
||||
if (error.response && error.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
||||
localStorage.removeItem('token');
|
||||
router.replace({
|
||||
path:"/login",
|
||||
query:{redirect:router.currentRoute.value.fullPath}
|
||||
});
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
export default boot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
app.config.globalProperties.$axios = axios;
|
||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||
// so you won't necessarily have to import axios in each vue file
|
||||
|
||||
@@ -4,14 +4,14 @@ import { Router } from 'vue-router';
|
||||
import { App } from 'vue';
|
||||
|
||||
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
||||
document.documentElement.lang='ja-JP';
|
||||
document.documentElement.lang="ja-JP";
|
||||
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
||||
if (err.response && err.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
console.error('(; ゚Д゚)/認証エラー(401):', err, info);
|
||||
localStorage.removeItem('token');
|
||||
router.replace({
|
||||
path:'/login',
|
||||
path:"/login",
|
||||
query:{redirect:router.currentRoute.value.fullPath}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// src/boot/permissions.ts
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { DirectiveBinding } from 'vue';
|
||||
|
||||
export const MenuMapping = {
|
||||
home: null,
|
||||
app: null,
|
||||
version: null,
|
||||
user: 'user',
|
||||
role: 'role',
|
||||
domain: null,
|
||||
userDomain: null,
|
||||
};
|
||||
|
||||
export const Actions = {
|
||||
user: {
|
||||
show: 'user_list',
|
||||
add: 'user_add',
|
||||
edit: 'user_edit',
|
||||
delete: 'user_delete',
|
||||
},
|
||||
role: {
|
||||
show: 'role_list',
|
||||
},
|
||||
domain: {
|
||||
show: 'domain_list',
|
||||
add: 'domain_add',
|
||||
edit: 'domain_edit',
|
||||
delete: 'domain_delete',
|
||||
grantUse: {
|
||||
list: 'domain_grant_use_list',
|
||||
edit: 'domain_grant_use_edit',
|
||||
},
|
||||
grantManage: {
|
||||
list: 'domain_grant_manage_list',
|
||||
edit: 'domain_grant_manage_edit',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = useAuthStore();
|
||||
|
||||
export default boot(({ app }) => {
|
||||
app.directive('permissions', {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
if (!hasPermission(binding.value)) {
|
||||
hideElement(el);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.config.globalProperties.$hasPermission = hasPermission;
|
||||
});
|
||||
|
||||
function hasPermission(value: any) {
|
||||
if (!value || store.isSuperAdmin) {
|
||||
return true;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return store.permissions[value];
|
||||
} else if (typeof value === 'object') {
|
||||
return Object.values(value).some((permission: any) => store.permissions[permission]);
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.some((permission) => store.permissions[permission]);
|
||||
}
|
||||
}
|
||||
|
||||
function hideElement(el: HTMLElement) {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
@@ -3,47 +3,20 @@
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-splitter
|
||||
v-model="splitterModel"
|
||||
style="height: 100%"
|
||||
before-class="tab"
|
||||
unit="px"
|
||||
v-else
|
||||
<q-table v-else row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
|
||||
class="action-table"
|
||||
flat bordered
|
||||
virtual-scroll
|
||||
:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
vertical
|
||||
active-color="white"
|
||||
indicator-color="primary"
|
||||
active-bg-color="primary"
|
||||
class="bg-grey-2 text-grey-8"
|
||||
@update:model-value="() => selected = []"
|
||||
dense
|
||||
>
|
||||
<q-tab :name="cate"
|
||||
:label="cate"
|
||||
v-for="(cate,) in categorys"
|
||||
:key="cate"
|
||||
></q-tab>
|
||||
</q-tabs>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
<q-table row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="actionForTab"
|
||||
class="action-table"
|
||||
flat bordered
|
||||
virtual-scroll
|
||||
:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"></q-table>
|
||||
</template>
|
||||
</q-splitter>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref,onMounted,reactive,watchEffect,computed,watch } from 'vue'
|
||||
import { ref,onMounted,reactive } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
export default {
|
||||
name: 'actionSelect',
|
||||
@@ -52,74 +25,30 @@ export default {
|
||||
type: String,
|
||||
filter:String
|
||||
},
|
||||
emits:[
|
||||
'clearFilter'
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
setup(props) {
|
||||
const isLoaded=ref(false);
|
||||
const columns = [
|
||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
||||
];
|
||||
const store = useFlowEditorStore();
|
||||
let actionData =reactive([]);
|
||||
const categorys = ref('');
|
||||
const tab=ref('');
|
||||
const actionForTab=computed(()=>{
|
||||
const rows=[];
|
||||
const actions= props.filter? actionData:actionData.filter(x=>x.categoryname===tab.value);
|
||||
actions.forEach((item,index) =>{
|
||||
rows.push({index,
|
||||
name:item.name,
|
||||
desc:item.title,
|
||||
outputPoints:item.outputpoints,
|
||||
property:item.property});
|
||||
});
|
||||
return rows;
|
||||
});
|
||||
const rows = reactive([])
|
||||
onMounted(async () => {
|
||||
let eventId='';
|
||||
if(store.selectedEvent ){
|
||||
eventId=store.selectedEvent.header!=='DELETABLE'? store.selectedEvent.eventId : store.selectedEvent.parentId;
|
||||
}
|
||||
const res =await api.get(`api/eventactions/${eventId}`);
|
||||
actionData= res.data;
|
||||
const categoryNames = Array.from(new Set(actionData.map(x=>x.categoryname)));
|
||||
categorys.value=categoryNames;
|
||||
tab.value = categoryNames.length>0? categoryNames[0]:'';
|
||||
const res =await api.get('api/actions');
|
||||
res.data.forEach((item,index) =>
|
||||
{
|
||||
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
|
||||
});
|
||||
isLoaded.value=true;
|
||||
});
|
||||
// watch(props.filter,()=>{
|
||||
// if(props.filter && props.filter!==''){
|
||||
// tab.value='';
|
||||
// }
|
||||
// });
|
||||
watch(tab,()=>{
|
||||
if(tab.value!==''){
|
||||
emit('clearFilter','');
|
||||
}
|
||||
});
|
||||
// watchEffect(()=>{
|
||||
// if(props.filter && props.filter!==''){
|
||||
// tab.value='';
|
||||
// }
|
||||
// if(tab.value!==''){
|
||||
// emit('update:filter','');
|
||||
// }
|
||||
// });
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
pagination:ref({
|
||||
rowsPerPage:0
|
||||
}),
|
||||
isLoaded,
|
||||
tab,
|
||||
actionData,
|
||||
categorys,
|
||||
splitterModel: ref(150),
|
||||
actionForTab
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,6 +58,5 @@ export default {
|
||||
.action-table{
|
||||
min-height: 10vh;
|
||||
max-height: 68vh;
|
||||
min-width: 550px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="row">
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
||||
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
||||
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
||||
:selectedFields="selField.fields"></field-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,10 +92,7 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const showSelectApp = ref(false);
|
||||
@@ -108,7 +105,7 @@ export default defineComponent({
|
||||
const updateSelectApp = (newAppinfo: IApp) => {
|
||||
selField.app = newAppinfo
|
||||
}
|
||||
|
||||
|
||||
const updateSelectFields = (newFields: IField[]) => {
|
||||
selField.fields = newFields
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
<q-field stack-label full-width label="アプリの説明">
|
||||
<q-field stack-label full-width label="アプリ説明">
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0">
|
||||
{{ appinfo?.description }}
|
||||
@@ -46,9 +46,9 @@ export default defineComponent({
|
||||
const { app } = toRefs(props);
|
||||
const authStore = useAuthStore();
|
||||
const appinfo = ref<AppInfo>({
|
||||
appId: '',
|
||||
name: '',
|
||||
description: ''
|
||||
appId: "",
|
||||
name: "",
|
||||
description: ""
|
||||
});
|
||||
const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
|
||||
const getAppInfo = async (appId:string|undefined) => {
|
||||
@@ -56,7 +56,7 @@ export default defineComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
let result : any ={appId:'',name:''};
|
||||
let result : any ={appId:"",name:""};
|
||||
let retry =0;
|
||||
while(retry<=3 && result && result.appId!==appId){
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
@@ -1,67 +1,64 @@
|
||||
<template>
|
||||
<detail-field-table
|
||||
detailField="description"
|
||||
:name="name"
|
||||
:type="type"
|
||||
:filter="filter"
|
||||
:columns="columns"
|
||||
:fetchData="fetchApps"
|
||||
@update:selected="(item) => { selected = item }"
|
||||
/>
|
||||
<div class="q-px-xs">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
|
||||
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
|
||||
:filter="filter" style="max-height: 65vh;">
|
||||
<template v-slot:body-cell-description="props">
|
||||
<q-td :props="props">
|
||||
<q-scroll-area class="description-cell">
|
||||
<div v-html="props.row.description"></div>
|
||||
</q-scroll-area>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, PropType, watchEffect } from 'vue';
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import DetailFieldTable from './dialog/DetailFieldTable.vue';
|
||||
|
||||
interface IAppDisplay {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AppSelectBox',
|
||||
components: {
|
||||
DetailFieldTable
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
filterInitRowsFunc: {
|
||||
type: Function as PropType<(app: IAppDisplay) => boolean>,
|
||||
},
|
||||
updateSelectApp: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const selected = ref<IAppDisplay[]>([]);
|
||||
const columns = [
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true, sort: (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10) },
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true },
|
||||
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
|
||||
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
||||
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
||||
];
|
||||
]
|
||||
const isLoaded = ref(false);
|
||||
const rows: any[] = reactive([]);
|
||||
const selected = ref([])
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||
props.updateSelectApp(selected.value[0])
|
||||
}
|
||||
});
|
||||
|
||||
const fetchApps = async () => {
|
||||
const res = await api.get('api/v1/allapps');
|
||||
return res.data.apps.map((item: any) => ({
|
||||
id: item.appId,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createdate: dateFormat(item.createdAt)
|
||||
})).filter(app => !props.filterInitRowsFunc || props.filterInitRowsFunc(app));
|
||||
};
|
||||
onMounted(() => {
|
||||
api.get('api/v1/allapps').then(res => {
|
||||
res.data.apps.forEach((item: any) => {
|
||||
rows.push({
|
||||
id: item.appId,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createdate: dateFormat(item.createdAt)
|
||||
});
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
const dateFormat = (dateStr: string) => {
|
||||
const date = new Date(dateStr);
|
||||
@@ -73,13 +70,31 @@ export default {
|
||||
const minutes = pad(date.getMinutes());
|
||||
const seconds = pad(date.getSeconds());
|
||||
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
fetchApps,
|
||||
selected
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
rows,
|
||||
selected,
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
rowsPerPage: 10
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.description-cell {
|
||||
height: 60px;
|
||||
width: 300px;
|
||||
max-height: 60px;
|
||||
max-width: 300px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
min-height: 300px;
|
||||
min-width: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-stepper v-model="step" ref="stepper" color="primary" animated flat>
|
||||
<q-step :name="1" title="データソースの設定" icon="app_registration" :done="step > 1">
|
||||
<div class="row justify-between items-center">
|
||||
<div>アプリの選択 :</div>
|
||||
<div>
|
||||
<a v-if="data.sourceApp?.name" class="q-mr-xs"
|
||||
:href="data.sourceApp ? `${authStore.currentDomain.kintoneUrl}/k/${data.sourceApp.id}` : ''"
|
||||
target="_blank" title="Kiontoneへ">
|
||||
{{ data.sourceApp?.name }}
|
||||
</a>
|
||||
<div v-else class="text-red">APPを選択してください</div>
|
||||
<q-btn v-if="data.sourceApp?.name" flat color="grey" icon="clear" size="sm" padding="none"
|
||||
@click="clearSelectedApp" />
|
||||
</div>
|
||||
<q-btn outline dense label="変更" padding="xs sm" color="primary" @click="showAppDialog" />
|
||||
</div>
|
||||
|
||||
<!-- フィールド設定部分 -->
|
||||
<template v-if="data.sourceApp?.name">
|
||||
<q-separator class="q-mt-md" />
|
||||
<div class="q-my-md row justify-between items-center">
|
||||
データ階層を設定する :
|
||||
<q-btn icon="add" size="sm" padding="xs" outline color="primary" @click="addRow" />
|
||||
</div>
|
||||
<q-virtual-scroll style="max-height: 13.5rem;" :items="data.fieldList" separator v-slot="{ item, index }">
|
||||
<div class="row justify-between items-center q-my-md">
|
||||
<div>{{ index + 1 }}階層 :</div>
|
||||
<div>{{ item.source?.name }}</div>
|
||||
<q-btn-group outline>
|
||||
<q-btn outline dense label="変更" padding="xs sm" color="primary"
|
||||
@click="() => showFieldDialog(item, 'source')" />
|
||||
<q-btn outline dense label="削除" padding="xs sm" color="primary" @click="() => delRow(index)" />
|
||||
</q-btn-group>
|
||||
</div>
|
||||
</q-virtual-scroll>
|
||||
</template>
|
||||
|
||||
<!-- アプリ選択ダイアログ -->
|
||||
<ShowDialog v-model:visible="data.sourceApp.showSelectApp" name="アプリ選択" @close="closeAppDialog" min-width="50vw"
|
||||
min-height="50vh">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="data.sourceApp.appFilter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="data.sourceApp.appFilter" />
|
||||
</ShowDialog>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="2" title="ドロップダウンフィールドの設定" icon="multiple_stop" :done="step > 2">
|
||||
<div class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||
<div class="col-grow row q-col-gutter-x-sm">
|
||||
<div class="col-6">データソース</div>
|
||||
<div class="col-6">ドロップダウンフィールド</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div style="width: 88px; height: 1px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item) in data.fieldList" :key="item.id" class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||
<div class="col-grow row q-col-gutter-x-sm">
|
||||
|
||||
<div class="col-6">{{ item.source.name }}</div>
|
||||
<div class="col-6">{{ item.dropDown?.name }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="row justify-end">
|
||||
<q-btn-group outline>
|
||||
<q-btn outline dense label="設定" padding="xs sm" color="primary"
|
||||
@click="() => showFieldDialog(item, 'dropDown')" />
|
||||
<q-btn outline dense label="クリア" padding="xs sm" color="primary"
|
||||
@click="() => item.dropDown = undefined" />
|
||||
</q-btn-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-step>
|
||||
|
||||
<!-- ステップナビゲーション -->
|
||||
<template v-slot:navigation>
|
||||
<q-stepper-navigation>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn v-if="step > 1" flat color="primary" @click="$refs.stepper.previous()" label="戻る" class="q-ml-sm" />
|
||||
<q-btn @click="stepperNext" color="primary" :label="step === 2 ? '確定' : '次へ'"
|
||||
:disable="nextBtnCheck()" />
|
||||
</div>
|
||||
</q-stepper-navigation>
|
||||
</template>
|
||||
</q-stepper>
|
||||
|
||||
<!-- フィールド選択ダイアログ -->
|
||||
<template v-for="(item, index) in data.fieldList" :key="`dg${item.id}`">
|
||||
<show-dialog v-model:visible="item.sourceDg.show" name="フィールド一覧" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="item.sourceDg.filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<FieldSelect name="フィールド" :appId="data.sourceApp.id" :selectedFields="item.source"
|
||||
:filter="item.sourceDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'source')"
|
||||
:blackListLabel="blackListLabel" />
|
||||
</show-dialog>
|
||||
|
||||
<show-dialog v-model:visible="item.dropDownDg.show" name="フィールド一覧" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="item.dropDownDg.filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<FieldSelect name="フィールド" :appId="data.dropDownApp.id" :selectedFields="item.source"
|
||||
:filter="item.dropDownDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'dropDown')"
|
||||
:blackListLabel="blackListLabel" />
|
||||
</show-dialog>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, watchEffect,watch } from 'vue';
|
||||
import ShowDialog from './ShowDialog.vue';
|
||||
import AppSelectBox from './AppSelectBox.vue';
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CascadingDropDownBox',
|
||||
inheritAttrs: false,
|
||||
components: { ShowDialog, AppSelectBox, FieldSelect },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
finishDialogHandler: Function,
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const authStore = useAuthStore();
|
||||
const flowStore = useFlowEditorStore();
|
||||
const $q = useQuasar();
|
||||
const appDg = ref();
|
||||
const stepper = ref();
|
||||
const step = ref(1);
|
||||
|
||||
const data =ref(props.modelValue);
|
||||
// const data = ref({
|
||||
// sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||
// dropDownApp: props.modelValue.dropDownApp,
|
||||
// fieldList: props.modelValue.fieldList ?? [],
|
||||
// });
|
||||
|
||||
// アプリ関連の関数
|
||||
const showAppDialog = () => data.value.sourceApp.showSelectApp = true;
|
||||
|
||||
const clearSelectedApp = () => {
|
||||
data.value.sourceApp = { appFilter: '', showSelectApp: false };
|
||||
data.value.fieldList = [];
|
||||
};
|
||||
|
||||
const closeAppDialog = (val: 'OK' | 'Cancel') => {
|
||||
data.value.sourceApp.showSelectApp = false;
|
||||
const selected = appDg.value?.selected[0];
|
||||
if (val === 'OK' && selected) {
|
||||
if (flowStore.appInfo?.appId === selected.id) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: 'データソースを現在のアプリにすることはできません。'
|
||||
});
|
||||
} else if (selected.id !== data.value.sourceApp.id) {
|
||||
clearSelectedApp();
|
||||
Object.assign(data.value.sourceApp, { id: selected.id, name: selected.name });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// フィールド関連の関数
|
||||
const defaultRow = () => ({
|
||||
id: uuidv4(),
|
||||
source: undefined,
|
||||
dropDown: undefined,
|
||||
sourceDg: { show: false, filter: '' },
|
||||
dropDownDg: { show: false, filter: '' },
|
||||
});
|
||||
|
||||
const addRow = () => data.value.fieldList.push(defaultRow());
|
||||
const delRow = (index: number) => data.value.fieldList.splice(index, 1);
|
||||
|
||||
const showFieldDialog = (item: any, keyName: string) => item[`${keyName}Dg`].show = true;
|
||||
|
||||
const updateSelectField = (f: any, item: any, index: number, keyName: 'source' | 'dropDown') => {
|
||||
const [selected] = f.value;
|
||||
const isDuplicate = data.value.fieldList.some((field, idx) =>
|
||||
idx !== index && (field[keyName]?.code === selected.code || field[keyName]?.label === selected.label)
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: '重複したフィールドは選択できません'
|
||||
});
|
||||
} else {
|
||||
item[keyName] = selected;
|
||||
}
|
||||
};
|
||||
|
||||
// ステッパー関連の関数
|
||||
const nextBtnCheck = () => {
|
||||
const stepNo = step.value
|
||||
if (stepNo === 1) {
|
||||
return !(data.value.sourceApp?.id && data.value.fieldList?.length > 0 && data.value.fieldList?.every(f => f.source?.name));
|
||||
} else if (stepNo === 2) {
|
||||
return !data.value.fieldList?.every(f => f.dropDown?.name);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const stepperNext = () => {
|
||||
if (step.value === 2) {
|
||||
props.finishDialogHandler?.(data.value);
|
||||
} else {
|
||||
data.value.dropDownApp = { name: flowStore.appInfo?.name, id: flowStore.appInfo?.appId };
|
||||
stepper.value?.next();
|
||||
}
|
||||
};
|
||||
|
||||
// // データ変更の監視
|
||||
// watchEffect(() =>{
|
||||
// emit('update:modelValue', data.value);
|
||||
// });
|
||||
|
||||
return {
|
||||
// 状態と参照
|
||||
authStore,
|
||||
step,
|
||||
stepper,
|
||||
appDg,
|
||||
data,
|
||||
// アプリ関連の関数
|
||||
showAppDialog,
|
||||
closeAppDialog,
|
||||
clearSelectedApp,
|
||||
|
||||
// フィールド関連の関数
|
||||
addRow,
|
||||
delRow,
|
||||
showFieldDialog,
|
||||
updateSelectField,
|
||||
|
||||
// ステッパー関連の関数
|
||||
nextBtnCheck,
|
||||
stepperNext,
|
||||
|
||||
// 定数
|
||||
blackListLabel: ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者', 'カテゴリー', 'ステータス'],
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -42,9 +42,9 @@ import { useQuasar } from 'quasar';
|
||||
}
|
||||
},
|
||||
emits:[
|
||||
'closed',
|
||||
'update:conditionTree',
|
||||
'update:show'
|
||||
"closed",
|
||||
"update:conditionTree",
|
||||
"update:show"
|
||||
],
|
||||
setup(props,context) {
|
||||
const appDg = ref();
|
||||
@@ -52,17 +52,17 @@ import { useQuasar } from 'quasar';
|
||||
const tree = ref(props.conditionTree);
|
||||
const closeDg = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
// if(tree.value.root.children.length===0){
|
||||
// $q.notify({
|
||||
// type: 'negative',
|
||||
// message: `条件式を設定してください。`
|
||||
// });
|
||||
// }
|
||||
context.emit('update:conditionTree',tree.value);
|
||||
if(tree.value.root.children.length===0){
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: `条件式を設定してください。`
|
||||
});
|
||||
}
|
||||
context.emit("update:conditionTree",tree.value);
|
||||
}
|
||||
showflg.value=false;
|
||||
context.emit('update:show',false);
|
||||
context.emit('closed',val);
|
||||
context.emit("update:show",false);
|
||||
context.emit("closed",val);
|
||||
};
|
||||
const showflg =ref(props.show);
|
||||
//条件式をコピーする
|
||||
|
||||
@@ -1,45 +1,41 @@
|
||||
<template>
|
||||
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
||||
:clearable="isSelected">
|
||||
<template v-slot:control>
|
||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name }}
|
||||
</q-chip>
|
||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name.name }}
|
||||
</q-chip>
|
||||
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
|
||||
<!-- <template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
-->
|
||||
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
|
||||
|
||||
<q-field labelColor="primary" class="condition-object" :clearable="isSelected" stack-label :dense="true"
|
||||
:outlined="true">
|
||||
<template v-slot:control>
|
||||
<!-- <q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name }}
|
||||
</q-chip>
|
||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name.name }}
|
||||
</q-chip> -->
|
||||
{{ selectedObject?.sharedText }}
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
|
||||
<!-- <template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
-->
|
||||
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" />
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, watchEffect, computed ,PropType} from 'vue';
|
||||
import { defineComponent, reactive, ref, watchEffect, computed } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
// import ConditionObjects from '../ConditionObjects.vue';
|
||||
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ConditionObject',
|
||||
components: {
|
||||
@@ -48,16 +44,8 @@ export default defineComponent({
|
||||
// ConditionObjects
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<IDynamicInputConfig>,
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
canInput: false,
|
||||
@@ -68,12 +56,6 @@ export default defineComponent({
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
options:
|
||||
{
|
||||
type:Array as PropType< string[]>,
|
||||
default:()=>[]
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
@@ -81,13 +63,12 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// const appDg = ref();
|
||||
const inputRef=ref();
|
||||
const show = ref(false);
|
||||
const selectedObject = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
// const sharedText = ref(''); // 共享的文本状态
|
||||
const isSelected = computed(() => {
|
||||
return selectedObject.value?.sharedText !== '';
|
||||
return selectedObject?.value?.sharedText !== '';
|
||||
});
|
||||
// const isSelected = computed(()=>{
|
||||
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
||||
@@ -104,7 +85,6 @@ export default defineComponent({
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
// selectedObject.value = appDg.value.selected[0];
|
||||
selectedObject.value = inputRef.value.selectedObjectRef
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,7 +93,6 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
return {
|
||||
inputRef,
|
||||
store,
|
||||
// appDg,
|
||||
show,
|
||||
|
||||
@@ -68,20 +68,18 @@
|
||||
<div @click.stop @keypress.stop v-else >
|
||||
<div class="row no-wrap items-center q-my-xs">
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
|
||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"
|
||||
:options="objectValueOptions(prop.node?.object?.options)"
|
||||
/>
|
||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"/>
|
||||
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
|
||||
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
||||
v-model="prop.node.value"
|
||||
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
|
||||
<!-- <q-select v-if="prop.node.object && ('options' in prop.node.object)"
|
||||
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
|
||||
v-model="prop.node.value"
|
||||
:options="objectValueOptions(prop.node.object.options)"
|
||||
clearable
|
||||
value-key="index"
|
||||
class="condition-value" :outlined="true" :dense="true" ></q-select> -->
|
||||
class="condition-value" :outlined="true" :dense="true" ></q-select>
|
||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
||||
<q-menu auto-close anchor="top right">
|
||||
<q-list>
|
||||
@@ -120,7 +118,6 @@ import { finished } from 'stream';
|
||||
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||
import ConditionObject from './ConditionObject.vue';
|
||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||
export default defineComponent( {
|
||||
name: 'NodeCondition',
|
||||
components: {
|
||||
@@ -148,18 +145,17 @@ export default defineComponent( {
|
||||
return opts;
|
||||
});
|
||||
|
||||
const operatorSet = inject<Array<any>>('Operator')
|
||||
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
|
||||
const operator = inject('Operator')
|
||||
const operators =computed(()=>{
|
||||
return operator ? operator : Object.values(Operator);
|
||||
});
|
||||
const tree = reactive(props.conditionTree);
|
||||
|
||||
const conditionString = computed(()=>{
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
|
||||
const objectValueOptions=(options:any):any[]|null=>{
|
||||
if(!options){
|
||||
return null;
|
||||
}
|
||||
const objectValueOptions=(options:any):any[]=>{
|
||||
const opts:any[] =[];
|
||||
Object.keys(options).forEach((key) =>
|
||||
{
|
||||
@@ -217,7 +213,7 @@ export default defineComponent( {
|
||||
const canMerge =(node:INode)=>{
|
||||
const checkedIndexs:number[] = ticked.value;
|
||||
const findNode = checkedIndexs.find(index=>node.index===index);
|
||||
console.log('findNode=>',findNode!==undefined,findNode);
|
||||
console.log("findNode=>",findNode!==undefined,findNode);
|
||||
return findNode!==undefined;
|
||||
}
|
||||
//グループ化解散
|
||||
@@ -226,14 +222,13 @@ export default defineComponent( {
|
||||
ticked.value=[];
|
||||
}
|
||||
|
||||
|
||||
const expanded=computed(()=>tree.getGroups(tree.root));
|
||||
// addCondition(tree.root);
|
||||
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
|
||||
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
|
||||
|
||||
return {
|
||||
leftDynamicItemConfig,
|
||||
rightDynamicItemConfig,
|
||||
leftDynamicItemConfig :inject('leftDynamicItemConfig'),
|
||||
rightDynamicItemConfig:inject('rightDynamicItemConfig'),
|
||||
showingCondition,
|
||||
conditionString,
|
||||
tree,
|
||||
@@ -265,12 +260,10 @@ export default defineComponent( {
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.operator{
|
||||
min-width: 150px;
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
@@ -1,105 +1,84 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-uploader
|
||||
style="max-width: 400px"
|
||||
:url="uploadUrl"
|
||||
:label="title"
|
||||
:headers="headers"
|
||||
accept=".xlsx"
|
||||
v-on:rejected="onRejected"
|
||||
v-on:uploaded="onUploadFinished"
|
||||
v-on:failed="onFailed"
|
||||
field-name="files"
|
||||
style="max-width: 400px"
|
||||
:url="uploadUrl"
|
||||
:label="title"
|
||||
:headers="headers"
|
||||
accept=".csv,.xlsx"
|
||||
v-on:rejected="onRejected"
|
||||
v-on:uploaded="onUploadFinished"
|
||||
v-on:failed="onFailed"
|
||||
field-name="files"
|
||||
></q-uploader>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar';
|
||||
import { createUploaderComponent, useQuasar } from 'quasar';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { ref } from 'vue';
|
||||
const $q=useQuasar();
|
||||
const authStore = useAuthStore();
|
||||
const emit =defineEmits(['uploaded']);
|
||||
/**
|
||||
* ファイルアップロードを拒否する時の処理
|
||||
* @param rejectedEntries
|
||||
*/
|
||||
function onRejected (rejectedEntries:any) {
|
||||
// Notify plugin needs to be installed
|
||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: `CSVおよびExcelファイルを選択してください。`
|
||||
})
|
||||
}
|
||||
|
||||
const $q = useQuasar();
|
||||
const authStore = useAuthStore();
|
||||
const emit = defineEmits(['uploaded']);
|
||||
/**
|
||||
* ファイルアップロード成功時の処理
|
||||
*/
|
||||
function onUploadFinished({xhr}:{xhr:XMLHttpRequest}){
|
||||
let msg="ファイルのアップロードが完了しました。";
|
||||
if(xhr && xhr.response){
|
||||
msg=`${msg} (${xhr.responseText})`;
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption:"通知",
|
||||
message: msg
|
||||
});
|
||||
setTimeout(() => {
|
||||
emit('uploaded',xhr.responseText);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
uploadUrl?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param info ファイルアップロード失敗時の処理
|
||||
*/
|
||||
function onFailed({files,xhr}:{files: readonly any[],xhr:any}){
|
||||
let msg ="ファイルアップロードが失敗しました。";
|
||||
if(xhr && xhr.status){
|
||||
msg=`${msg} (${xhr.status }:${xhr.statusText})`
|
||||
}
|
||||
$q.notify({
|
||||
type:"negative",
|
||||
message:msg
|
||||
});
|
||||
}
|
||||
|
||||
const headers = ref([
|
||||
{ name: 'Authorization', value: 'Bearer ' + authStore.token },
|
||||
]);
|
||||
interface Props {
|
||||
title: string;
|
||||
uploadUrl:string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '設計書から導入する(Excel)',
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`,
|
||||
});
|
||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title:"設計書から導入する(csv or excel)",
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||
|
||||
/**
|
||||
* ファイルアップロードを拒否する時の処理
|
||||
* @param rejectedEntries
|
||||
*/
|
||||
function onRejected(rejectedEntries: any) {
|
||||
// Notify plugin needs to be installed
|
||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Excelファイルを選択してください。',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ファイルアップロード成功時の処理
|
||||
*/
|
||||
function onUploadFinished({ xhr }: { xhr: XMLHttpRequest }) {
|
||||
let msg = 'ファイルのアップロードが完了しました。';
|
||||
if (xhr && xhr.response) {
|
||||
msg = `${msg} (${xhr.responseText})`;
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: '通知',
|
||||
message: msg,
|
||||
});
|
||||
setTimeout(() => {
|
||||
emit('uploaded', xhr.responseText);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 例外発生時、responseからエラー情報を取得する
|
||||
* @param xhr
|
||||
*/
|
||||
function getResponseError(xhr: XMLHttpRequest) {
|
||||
try {
|
||||
const resp = JSON.parse(xhr.responseText);
|
||||
return 'detail' in resp ? resp.detail : '';
|
||||
} catch (err) {
|
||||
return xhr.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param info ファイルアップロード失敗時の処理
|
||||
*/
|
||||
function onFailed({
|
||||
files,
|
||||
xhr,
|
||||
}: {
|
||||
files: readonly any[];
|
||||
xhr: XMLHttpRequest;
|
||||
}) {
|
||||
let msg = 'ファイルアップロードが失敗しました。';
|
||||
if (xhr && xhr.status) {
|
||||
const detail = getResponseError(xhr);
|
||||
msg = `${msg} (${xhr.status}:${detail})`;
|
||||
}
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: msg,
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,78 +1,37 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table :loading="loading" :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows">
|
||||
<template v-slot:body-cell-name="p">
|
||||
<q-td class="content-box flex justify-between items-center" :props="p">
|
||||
{{ p.row.name }}
|
||||
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
|
||||
<q-badge v-if="p.row.id == currentDomainId" color="primary">現在</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref,onMounted,reactive, computed } from 'vue'
|
||||
import { ref,onMounted,reactive } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
|
||||
export default {
|
||||
name: 'DomainSelect',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filterInitRowsFunc: {
|
||||
type: Function,
|
||||
},
|
||||
type: String
|
||||
},
|
||||
setup(props) {
|
||||
const authStore = useAuthStore();
|
||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
||||
const loading = ref(true);
|
||||
const inactiveRowClass = (row) => row.domainActive ? '' : 'inactive-row';
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||
{ name: 'name', align: 'left', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass },
|
||||
{ name: 'url', label: 'URL', field: 'url',align: 'left', sortable: true, classes: inactiveRowClass },
|
||||
{ name: 'user', label: 'アカウント', field: 'user',align: 'left', classes: inactiveRowClass },
|
||||
{ name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
||||
]
|
||||
const rows = reactive([]);
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
api.get('api/domains').then(res =>{
|
||||
res.data.data.forEach((data) => {
|
||||
const item = {
|
||||
id: data.id,
|
||||
tenantid: data.tenantid,
|
||||
domainActive: data.is_active,
|
||||
name: data.name,
|
||||
url: data.url,
|
||||
user: data.kintoneuser,
|
||||
owner: {
|
||||
id: data.owner.id,
|
||||
firstName: data.owner.first_name,
|
||||
lastName: data.owner.last_name,
|
||||
fullNameSearch: (data.owner.last_name + data.owner.first_name).toLowerCase(),
|
||||
fullName: data.owner.last_name + ' ' + data.owner.first_name,
|
||||
email: data.owner.email,
|
||||
isActive: data.owner.is_active,
|
||||
isSuperuser: data.owner.is_superuser,
|
||||
}
|
||||
}
|
||||
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) {
|
||||
return;
|
||||
}
|
||||
rows.push(item);
|
||||
})
|
||||
loading.value = false;
|
||||
setup() {
|
||||
const columns = [
|
||||
{ name: 'id'},
|
||||
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
||||
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
||||
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
||||
]
|
||||
const rows = reactive([])
|
||||
onMounted( () => {
|
||||
api.get(`api/domains/1`).then(res =>{
|
||||
res.data.forEach((item) =>
|
||||
{
|
||||
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
||||
}
|
||||
)
|
||||
});
|
||||
});
|
||||
});
|
||||
return {
|
||||
loading,
|
||||
currentDomainId,
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
@@ -81,11 +40,3 @@ export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.q-table td.inactive-row {
|
||||
color: #aaa;
|
||||
}
|
||||
.q-table .content-box {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,43 @@
|
||||
<template>
|
||||
<q-btn-dropdown
|
||||
class="customized-disabled-btn"
|
||||
color="primay"
|
||||
push
|
||||
flat
|
||||
no-caps
|
||||
icon="share"
|
||||
size="md"
|
||||
:label="userStore.currentDomain.domainName"
|
||||
:disable-dropdown="true"
|
||||
dropdown-icon="none"
|
||||
:disable="true"
|
||||
>
|
||||
<q-list>
|
||||
<q-item v-for="domain in domains" :key="domain.domainName"
|
||||
clickable v-close-popup @click="onItemClick(domain)">
|
||||
<q-item-section side>
|
||||
<q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{domain.domainName}}</q-item-label>
|
||||
<q-item-label caption>{{domain.kintoneUrl}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
</template>
|
||||
<script setup lang="ts" >
|
||||
import { IDomainInfo } from 'src/types/ActionTypes';
|
||||
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
||||
import { ref } from 'vue';
|
||||
const userStore = useAuthStore();
|
||||
const domains = ref<IDomainInfo[]>([]);
|
||||
(async ()=>{
|
||||
domains.value = await userStore.getUserDomains();
|
||||
})();
|
||||
|
||||
const onItemClick=(domain:IDomainInfo)=>{
|
||||
console.log(domain);
|
||||
userStore.setCurrentDomain(domain);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.q-btn.disabled.customized-disabled-btn {
|
||||
opacity: 1 !important;
|
||||
cursor: default !important;
|
||||
.q-icon.q-btn-dropdown__arrow {
|
||||
display: none;
|
||||
}
|
||||
* {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,30 +2,13 @@
|
||||
<div class="q-mx-md" style="max-width: 600px;">
|
||||
<!-- <q-card> -->
|
||||
<div class="q-mb-md">
|
||||
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
|
||||
outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||
v-model="sharedText" :readonly="!canInputFlag" autogrow>
|
||||
<q-input ref="inputRef" outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||
v-model="sharedText" :readonly="!canInput">
|
||||
<template v-slot:append>
|
||||
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-select v-if="optionsRef && optionsRef.length>0"
|
||||
:model-value="sharedText"
|
||||
:options="optionsRef"
|
||||
clearable
|
||||
value-key="index"
|
||||
outlined
|
||||
dense
|
||||
use-input
|
||||
hide-selected
|
||||
input-debounce="10"
|
||||
fill-input
|
||||
@input-value="setValue"
|
||||
@clear="sharedText=null"
|
||||
hide-dropdown-icon
|
||||
:readonly="!canInputFlag"
|
||||
>
|
||||
</q-select>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-sm">
|
||||
@@ -51,12 +34,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
|
||||
import { ref, inject, watchEffect, defineComponent } from 'vue';
|
||||
import FieldAdd from './FieldAdd.vue';
|
||||
import VariableAdd from './VariableAdd.vue';
|
||||
// import FunctionAdd from './FunctionAdd.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import { IButtonConfig } from 'src/types/ComponentTypes';
|
||||
|
||||
type ButtonConfig = {
|
||||
label: string;
|
||||
color: string;
|
||||
type: string;
|
||||
editable: boolean;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DynamicItemInput',
|
||||
@@ -67,21 +56,18 @@ export default defineComponent({
|
||||
ShowDialog
|
||||
},
|
||||
props: {
|
||||
canInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// canInput: {
|
||||
// type: Boolean,
|
||||
// default: false
|
||||
// },
|
||||
appId: {
|
||||
type: String,
|
||||
},
|
||||
selectedObject: {
|
||||
default: {}
|
||||
},
|
||||
options:{
|
||||
type:Array as PropType< string[]>
|
||||
},
|
||||
buttonsConfig: {
|
||||
type: Array as PropType<IButtonConfig[]>,
|
||||
type: Array as () => ButtonConfig[],
|
||||
default: () => [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
||||
]
|
||||
@@ -91,71 +77,65 @@ export default defineComponent({
|
||||
const filter = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
const currentDialogName = ref('');
|
||||
const selectedObjectRef = ref(props.selectedObject);
|
||||
const currentComponent = ref('FieldAdd');
|
||||
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
||||
const inputRef = ref();
|
||||
const canInputFlag = ref(props.canInput);
|
||||
const canInput = ref(true);
|
||||
const editable = ref(false);
|
||||
|
||||
const openDialog = (button: IButtonConfig) => {
|
||||
const openDialog = (button: ButtonConfig) => {
|
||||
currentDialogName.value = button.label;
|
||||
currentComponent.value = button.type;
|
||||
dialogVisible.value = true;
|
||||
editable.value = canInputFlag.value;
|
||||
editable.value = button.editable ?? true;
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const handleSelect = (value:any) => {
|
||||
|
||||
if (value && value._t && (value._t as string).length > 0) {
|
||||
canInputFlag.value = editable.value;
|
||||
}
|
||||
selectedObjectRef.value={ sharedText: value._t, ...value };
|
||||
const handleSelect = (value) => {
|
||||
// 获取当前光标位置
|
||||
// const cursorPosition = inputRef.value.getNativeElement().selectionStart;
|
||||
// if (cursorPosition === undefined || cursorPosition === 0) {
|
||||
sharedText.value = `${value._t}`;
|
||||
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||
// } else {
|
||||
// const textBefore = sharedText.value.substring(0, cursorPosition);
|
||||
// const textAfter = sharedText.value.substring(cursorPosition);
|
||||
// sharedText.value = `${textBefore}${value._t}${textAfter}`;
|
||||
// }
|
||||
|
||||
if (value && value._t && (value._t as string).length > 0) {
|
||||
canInput.value = editable.value;
|
||||
}
|
||||
emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const clearSharedText = () => {
|
||||
sharedText.value = '';
|
||||
selectedObjectRef.value={};
|
||||
canInputFlag.value = true;
|
||||
// emit('update:selectedObject', {});
|
||||
canInput.value = true;
|
||||
emit('update:selectedObject', {});
|
||||
}
|
||||
const updateSharedText = (value:string) => {
|
||||
const updateSharedText = (value) => {
|
||||
sharedText.value = value;
|
||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
||||
emit('update:selectedObject', { ...props.selectedObject, sharedText: value });
|
||||
}
|
||||
const setValue=(value:string)=>{
|
||||
sharedText.value = value;
|
||||
if(selectedObjectRef.value.sharedText!==value){
|
||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||
}
|
||||
}
|
||||
const optionsRef=ref(props.options);
|
||||
|
||||
return {
|
||||
filter,
|
||||
dialogVisible,
|
||||
currentDialogName,
|
||||
currentComponent,
|
||||
canInputFlag,
|
||||
canInput,
|
||||
openDialog,
|
||||
closeDialog,
|
||||
handleSelect,
|
||||
clearSharedText,
|
||||
updateSharedText,
|
||||
setValue,
|
||||
sharedText,
|
||||
inputRef,
|
||||
optionsRef,
|
||||
selectedObjectRef
|
||||
inputRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<q-item
|
||||
v-permissions="permission"
|
||||
clickable
|
||||
tag="a"
|
||||
:target="target?target:'_blank'"
|
||||
:href="link"
|
||||
:disable="disable"
|
||||
v-if="!isSeparator"
|
||||
>
|
||||
<q-item-section
|
||||
@@ -21,7 +19,6 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator
|
||||
class="q-my-sm"
|
||||
v-if="isSeparator"
|
||||
inset
|
||||
/>
|
||||
@@ -35,8 +32,6 @@ export interface EssentialLinkProps {
|
||||
icon?: string;
|
||||
isSeparator?: boolean;
|
||||
target?:string;
|
||||
disable?:boolean;
|
||||
permission?: string|null;
|
||||
}
|
||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
||||
caption: '',
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)"
|
||||
:filter="filter"
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:pagination="pagination"
|
||||
style="max-height: 55vh;"/>
|
||||
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { api } from 'boot/axios';
|
||||
import { computed ,Prop,PropType,ref} from 'vue';
|
||||
import {IField} from 'src/types/ComponentTypes';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FieldList',
|
||||
props: {
|
||||
fields: Array as PropType<IField[]>,
|
||||
fields: Array,
|
||||
name: String,
|
||||
type: String,
|
||||
appId: Number,
|
||||
@@ -36,32 +30,27 @@ export default {
|
||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||
]
|
||||
|
||||
|
||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||
if (props.fields && Object.keys(props.fields).length > 0) {
|
||||
return props.fields.map(f => ({ name: f.label, ...f ,objectType: 'field'}));
|
||||
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
} else {
|
||||
return api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
}).then(res => {
|
||||
const fields = res.data.properties;
|
||||
return Object.values(fields).map((f:any) => ({ name: f.label, objectType: 'field', ...f }));
|
||||
console.log(res);
|
||||
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
});
|
||||
}
|
||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
// selected: ref([]),
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
rowsPerPage: 25,
|
||||
sortBy: 'name',
|
||||
descending: false,
|
||||
page: 1,
|
||||
})
|
||||
isLoaded
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table flat bordered v-else row-key="id" :selection="type" v-model:selected="selected" :columns="columns"
|
||||
<q-table flat bordered v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns"
|
||||
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
selectedFields:{
|
||||
type:Array ,
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
fieldTypes:{
|
||||
@@ -37,10 +37,6 @@ export default {
|
||||
updateSelectFields: {
|
||||
type: Function
|
||||
},
|
||||
blackListLabel: {
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const isLoaded = ref(false);
|
||||
@@ -48,16 +44,16 @@ export default {
|
||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||
];
|
||||
]
|
||||
const pageSetting = ref({
|
||||
sortBy: 'name',
|
||||
sortBy: 'desc',
|
||||
descending: false,
|
||||
page: 1,
|
||||
rowsPerPage: props.not_page ? 0 : 25
|
||||
rowsPerPage: props.not_page ? 0 : 5
|
||||
// rowsNumber: xx if getting data from a server
|
||||
});
|
||||
const rows = reactive([]);
|
||||
const selected = ref((props.selectedFields && props.selectedFields.length>0)?props.selectedFields:[]);
|
||||
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
|
||||
|
||||
onMounted(async () => {
|
||||
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
||||
@@ -66,25 +62,13 @@ export default {
|
||||
app: props.appId
|
||||
}
|
||||
});
|
||||
let fields = Object.values(res.data.properties);
|
||||
for (const index in fields) {
|
||||
const fld = fields[index]
|
||||
if(props.blackListLabel.length > 0){
|
||||
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}else if(props.fieldTypes.includes('lookup') && ('lookup' in fld)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}else if(props.fieldTypes.includes('lookup') && ('lookup' in fld)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
let fields = res.data.properties;
|
||||
Object.keys(fields).forEach((key) => {
|
||||
const fld = fields[key];
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({ name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
}
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<q-badge class="q-mr-xs" v-if="isOwner" color="secondary">所有者</q-badge>
|
||||
<!-- <q-badge v-else-if="isManager" color="primary">管理者</q-badge> -->
|
||||
<q-badge v-if="isSelf" color="purple">自分</q-badge>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
interface Props {
|
||||
user: { id: number };
|
||||
domain: IDomainOwnerDisplay;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isSelf = computed(() => props.user.id === (Number)(authStore.userId));
|
||||
const isOwner = computed(() => props.user.id === props.domain.owner.id);
|
||||
const isManager = computed(() => props.user.id === props.domain.owner.id); // TODO
|
||||
</script>
|
||||
@@ -1,276 +0,0 @@
|
||||
<template>
|
||||
<q-dialog :auto-close="false" :model-value="visible" persistent bordered>
|
||||
<q-card class="dialog-content" >
|
||||
<q-toolbar class="bg-grey-4">
|
||||
<q-toolbar-title>{{ dialogTitle }}</q-toolbar-title>
|
||||
<q-btn flat round dense icon="close" @click="close" />
|
||||
</q-toolbar>
|
||||
|
||||
<q-card-section class="q-mx-md " >
|
||||
<q-select
|
||||
v-permissions="props.actionPermissions.edit"
|
||||
class="q-mt-md"
|
||||
:disable="loading||!domain.domainActive"
|
||||
filled
|
||||
dense
|
||||
v-model="canSharedUserFilter"
|
||||
use-input
|
||||
input-debounce="0"
|
||||
:options="canSharedUserFilteredOptions"
|
||||
clearable
|
||||
:placeholder="canSharedUserFilter ? '' : domain.domainActive ? '権限を付与するユーザーを選択' : '接続先が無効なため、権限を付与できません'"
|
||||
@filter="filterFn">
|
||||
|
||||
<template v-slot:selected-item="scope">
|
||||
<span v-if="canSharedUserFilter">
|
||||
{{ canSharedUserFilter.fullName }} ({{ canSharedUserFilter.email }})
|
||||
<role-label :domain="domain" :user="scope.opt"></role-label>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:after>
|
||||
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplayWithShareRole)" />
|
||||
</template>
|
||||
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section avatar>{{scope.opt.id}}</q-item-section>
|
||||
<q-item-section>{{scope.opt.fullName}}</q-item-section>
|
||||
<q-item-section>{{scope.opt.email}}</q-item-section>
|
||||
<q-item-section side>
|
||||
<div style="width: 6.5em;">
|
||||
<role-label :domain="domain" :user="scope.opt"></role-label>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" :title="userListTitle">
|
||||
<template v-slot:body-cell-role="{ row }">
|
||||
<q-td auto-width>
|
||||
<role-label :domain="domain" :user="row"></role-label>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:actions="{ row }">
|
||||
<q-btn v-permissions="props.actionPermissions.edit" round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
|
||||
</template>
|
||||
</sharing-user-list>
|
||||
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="確定" @click="checkClose" />
|
||||
<q-btn flat label="キャンセル" @click="close" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { IUser, IUserDisplay } from '../../types/UserTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
||||
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
|
||||
import { Dialog } from 'quasar'
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
domain: IDomainOwnerDisplay;
|
||||
dialogTitle: string;
|
||||
userListTitle: string;
|
||||
isActionDisable?: (user: IUserDisplay) => boolean;
|
||||
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
|
||||
actionPermissions: { 'list': string, 'edit': string };
|
||||
}
|
||||
|
||||
interface IUserDisplayWithShareRole extends IUserDisplay {
|
||||
isRemoving: boolean;
|
||||
role: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const addLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const visible = ref(props.modelValue);
|
||||
|
||||
const allUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const sharedUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const sharedUsersIdSet = new Set<number>();
|
||||
|
||||
const canSharedUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const canSharedUserFilter = ref<IUserDisplayWithShareRole>();
|
||||
const canSharedUserFilteredOptions = ref<IUserDisplayWithShareRole[]>([]);
|
||||
|
||||
const filterFn = (val:string, update: (cb: () => void) => void) => {
|
||||
update(() => {
|
||||
if (val === '') {
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||
return;
|
||||
}
|
||||
const needle = val.toLowerCase();
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value.filter(v =>
|
||||
v.email.toLowerCase().indexOf(needle) > -1 || v.fullNameSearch.toLowerCase().indexOf(needle) > -1);
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
visible.value = newValue;
|
||||
sharedUsers.value = [];
|
||||
canSharedUserFilter.value = undefined
|
||||
loading.value = false;
|
||||
addLoading.value = false;
|
||||
if (newValue) {
|
||||
if (Object.keys(allUsers.value).length == 0) {
|
||||
await getUsers();
|
||||
}
|
||||
await loadShared();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(newValue) => {
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
);
|
||||
|
||||
const checkClose = () => {
|
||||
if (!canSharedUserFilter.value) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
Dialog.create({
|
||||
title: '注意',
|
||||
message: '選択済だがまだ付与未完了のユーザーがあります。<br>必要な操作を選んでください。',
|
||||
html: true,
|
||||
persistent: true,
|
||||
ok: {
|
||||
color: 'primary',
|
||||
label: '付与'
|
||||
},
|
||||
cancel: '直接閉じる',
|
||||
}).onCancel(() => {
|
||||
close();
|
||||
}).onOk(() => {
|
||||
shareTo(canSharedUserFilter.value as IUserDisplayWithShareRole);
|
||||
});
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const shareTo = async (user: IUserDisplayWithShareRole) => {
|
||||
addLoading.value = true;
|
||||
loading.value = true;
|
||||
await props.shareApi(user, props.domain);
|
||||
await loadShared();
|
||||
canSharedUserFilter.value = undefined;
|
||||
loading.value = false;
|
||||
addLoading.value = false;
|
||||
}
|
||||
|
||||
const removeShareTo = async (user: IUserDisplayWithShareRole) => {
|
||||
loading.value = true;
|
||||
user.isRemoving = true;
|
||||
await props.removeSharedApi(user, props.domain);
|
||||
if (isCurrentDomain()) {
|
||||
await authStore.loadCurrentDomain();
|
||||
}
|
||||
await loadShared();
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const isCurrentDomain = () => {
|
||||
return props.domain.id === authStore.currentDomain.id;
|
||||
}
|
||||
|
||||
const loadShared = async () => {
|
||||
loading.value = true;
|
||||
sharedUsersIdSet.clear();
|
||||
|
||||
const { data } = await props.getSharedApi(props.domain);
|
||||
|
||||
sharedUsers.value = data.data.reduce((arr: IUserDisplayWithShareRole[], item: IUser) => {
|
||||
const val = itemToDisplay(item);
|
||||
if(!sharedUsersIdSet.has(val.id)) {
|
||||
sharedUsersIdSet.add(val.id);
|
||||
// for sort
|
||||
if (isOwner(val.id)) {
|
||||
val.role = 2;
|
||||
} else if (isManager(val.id)) {
|
||||
val.role = 1;
|
||||
} else {
|
||||
val.role = 0;
|
||||
}
|
||||
arr.push(val);
|
||||
}
|
||||
return arr;
|
||||
}, []).sort((a: IUserDisplayWithShareRole, b: IUserDisplayWithShareRole) => b.role - a.role);
|
||||
|
||||
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function isOwner(userId: number) {
|
||||
return userId === props.domain?.owner?.id
|
||||
}
|
||||
|
||||
function isManager(userId: number) {
|
||||
return false // TODO
|
||||
}
|
||||
|
||||
const getUsers = async () => {
|
||||
loading.value = true;
|
||||
const result = await api.get('api/v1/users');
|
||||
allUsers.value = result.data.data.map(itemToDisplay);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const itemToDisplay = (item: IUser) => {
|
||||
return {
|
||||
id: item.id,
|
||||
firstName: item.first_name,
|
||||
lastName: item.last_name,
|
||||
fullNameSearch: (item.last_name + item.first_name).toLowerCase(),
|
||||
fullName: item.last_name + ' ' + item.first_name,
|
||||
email: item.email,
|
||||
isSuperuser: item.is_superuser,
|
||||
isActive: item.is_active,
|
||||
role: 0,
|
||||
} as IUserDisplayWithShareRole
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dialog-content {
|
||||
width: 700px !important;
|
||||
max-width: 80vw !important;
|
||||
max-height: 80vh;
|
||||
.q-select {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<share-domain-dialog
|
||||
:dialogTitle="`「${domain.name}」の接続先管理権限設定`"
|
||||
userListTitle="接続先管理権限を持つユーザー"
|
||||
:domain="domain"
|
||||
:share-api="shareApi"
|
||||
:remove-shared-api="removeSharedApi"
|
||||
:get-shared-api="getSharedApi"
|
||||
:is-action-disable="(row) => row.id === authStore.userId"
|
||||
:model-value="modelValue"
|
||||
:action-permissions="Actions.domain.grantManage"
|
||||
@update:modelValue="updateModelValue"
|
||||
@close="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import { Actions } from 'boot/permissions';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
domain: IDomainOwnerDisplay;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
async function shareApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
|
||||
return api.post('api/managedomain', {
|
||||
userid: user.id,
|
||||
domainid: domain.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function removeSharedApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
|
||||
return api.delete<IResponse>(`api/managedomain/${domain.id}/${user.id}`);
|
||||
}
|
||||
|
||||
async function getSharedApi(domain: IDomainOwnerDisplay) {
|
||||
return api.get<IResponse>(`/api/managedomainuser/${domain.id}`);
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const updateModelValue = (value: boolean) => {
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<share-domain-dialog
|
||||
:dialogTitle="`「${domain.name}」の接続先利用権限設定`"
|
||||
userListTitle="接続先利用権限を持つユーザー"
|
||||
:domain="domain"
|
||||
:share-api="shareApi"
|
||||
:remove-shared-api="removeSharedApi"
|
||||
:get-shared-api="getSharedApi"
|
||||
:model-value="modelValue"
|
||||
:action-permissions="Actions.domain.grantUse"
|
||||
@update:modelValue="updateModelValue"
|
||||
@close="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import { Actions } from 'boot/permissions';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
domain: IDomainOwnerDisplay;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
async function shareApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
|
||||
return api.post('api/userdomain', {
|
||||
userid: user.id,
|
||||
domainid: domain.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function removeSharedApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
|
||||
return api.delete(`api/domain/${domain.id}/${user.id}`);
|
||||
}
|
||||
|
||||
async function getSharedApi(domain: IDomainOwnerDisplay) {
|
||||
return api.get(`/api/domainshareduser/${domain.id}`);
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const updateModelValue = (value: boolean) => {
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<q-table :rows="users" :filter="filter" dense :columns="columns" row-key="id" :loading="loading" :pagination="pagination">
|
||||
<template v-slot:top>
|
||||
<div class="h6 text-weight-bold">{{props.title}}</div>
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<template v-for="col in columns" :key="col.name" v-slot:[`body-cell-${col.name}`]="props">
|
||||
<slot :name="`body-cell-${col.name}`" :row="props.row" :column="props.col">
|
||||
<!-- 默认内容 -->
|
||||
<q-td v-if="col.name !== 'actions'" :props="props" >
|
||||
<span>{{ props.row[col.name] }}</span>
|
||||
</q-td>
|
||||
<!-- actions -->
|
||||
<q-td v-else auto-width :props="props">
|
||||
<slot name="actions" :row="props.row"></slot>
|
||||
</q-td>
|
||||
</slot>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, PropType } from 'vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
|
||||
const props = defineProps({
|
||||
users: {
|
||||
type: Array as PropType<IUserDisplay[]>,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: String
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'fullName', label: '名前', field: 'fullName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
{ name: 'role', label: '', field: 'role', align: 'left', sortable: false },
|
||||
{ name: 'actions', label: '', field: 'actions', sortable: false },
|
||||
];
|
||||
|
||||
const filter = ref('');
|
||||
const pagination = ref({ rowsPerPage: 10 });
|
||||
</script>
|
||||
@@ -4,16 +4,16 @@
|
||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||
<q-toolbar class="bg-grey-4">
|
||||
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
||||
<q-space v-if="$slots.toolbar"></q-space>
|
||||
<q-space></q-space>
|
||||
<slot name="toolbar"></slot>
|
||||
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
||||
</q-toolbar>
|
||||
<q-card-section class="q-mt-md" :style="sectionStyle">
|
||||
<slot></slot>
|
||||
</q-card-section>
|
||||
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
||||
<q-btn flat :label="okBtnLabel || '確定'" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
||||
<q-btn flat label="キャンセル" :disable="okBtnLoading" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@@ -29,21 +29,10 @@ export default {
|
||||
width:String,
|
||||
height:String,
|
||||
minWidth:String,
|
||||
minHeight:String,
|
||||
okBtnLabel:String,
|
||||
okBtnLoading:Boolean,
|
||||
okBtnAutoClose:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
minHeight:String
|
||||
},
|
||||
emits: [
|
||||
'close',
|
||||
'update:visible'
|
||||
'close'
|
||||
],
|
||||
setup(props, context) {
|
||||
const CloseDialogue = (val) => {
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<q-btn v-if="hasPermission()" flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
|
||||
<q-menu :max-width="maxWidth">
|
||||
<q-list dense :style="{ 'min-width': minWidth }">
|
||||
<template v-for="(item, index) in actions" :key="index" >
|
||||
<q-item v-if="isAction(item)" v-permissions="item.permission" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
|
||||
:class="item.class" clickable v-close-popup @click="item.action(row)">
|
||||
<q-item-section side style="color: inherit;">
|
||||
<q-icon size="1.2em" :name="item.icon" />
|
||||
</q-item-section>
|
||||
<q-item-section>{{ item.label }}</q-item-section>
|
||||
<q-tooltip v-if="item.tooltip && !isFunction(item.tooltip) || (isFunction(item.tooltip) && item.tooltip(row))" :delay="500" self="center middle">
|
||||
{{ isFunction(item.tooltip) ? item.tooltip(row) : item.tooltip }}
|
||||
</q-tooltip>
|
||||
</q-item>
|
||||
<q-separator v-else />
|
||||
</template>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, getCurrentInstance } from 'vue';
|
||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
||||
|
||||
interface Action {
|
||||
label: string;
|
||||
icon?: string;
|
||||
tooltip?: string|((row: IDomainOwnerDisplay) => string);
|
||||
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
|
||||
permission?: string|object;
|
||||
action: (row: any) => void|Promise<void>;
|
||||
class?: string;
|
||||
}
|
||||
interface Separator {
|
||||
separator: boolean;
|
||||
}
|
||||
type MenuItem = Action | Separator;
|
||||
|
||||
export default {
|
||||
name: 'TableActionMenu',
|
||||
props: {
|
||||
row: {
|
||||
type: Object as PropType<IDomainOwnerDisplay>,
|
||||
required: true
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
minWidth: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<MenuItem[]>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isAction(item: MenuItem): item is Action {
|
||||
return !('separator' in item);
|
||||
},
|
||||
|
||||
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
|
||||
return typeof item === 'function';
|
||||
},
|
||||
|
||||
hasPermission() {
|
||||
const proxy = getCurrentInstance()?.proxy;
|
||||
if (!proxy) {
|
||||
return false;
|
||||
}
|
||||
for (const item of this.actions) {
|
||||
if (this.isAction(item) && proxy.$hasPermission(item.permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.q-table tr > td:last-child .action-menu {
|
||||
opacity: 0.25 !important;
|
||||
}
|
||||
|
||||
.q-table tr:hover > td:last-child .action-menu:not([disabled]) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<q-card :class="['domain-card', item.id == activeId ? 'default': '']">
|
||||
<q-card-section>
|
||||
<div class="row no-wrap">
|
||||
<div class="col">
|
||||
<div class="text-h6 ellipsis">{{ item.name }}</div>
|
||||
<div class="text-subtitle2">{{ item.url }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<!-- <q-badge color="secondary" text-color="white" align="middle" class="q-mb-xs" label="他人の所有" /> -->
|
||||
<q-chip v-if="!isOwnerFunc(item.owner.id)" square color="secondary" text-color="white" icon="people" label="他人の所有" size="sm" />
|
||||
<q-chip v-else square color="purple" text-color="white" icon="people" label="自分" size="sm" />
|
||||
<div class="text-right">
|
||||
<!-- icon="add_moderator" -->
|
||||
<!-- <q-chip square color="primary" text-color="white" label="管理者" size="sm" /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap">
|
||||
<div class="col">
|
||||
<div class="text-grey-7 text-caption text-weight-medium">
|
||||
アカウント
|
||||
</div>
|
||||
<div class="smaller-font-size">{{ item.user }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="text-grey-7 text-caption text-weight-medium">
|
||||
所有者
|
||||
</div>
|
||||
<div class="smaller-font-size">{{ !isOwnerFunc(item.owner.id) ? item.owner.fullName : '自分' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator v-if="$slots.actions" />
|
||||
<slot name="actions" :item="item"></slot>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, computed } from 'vue';
|
||||
import { IDomainOwnerDisplay } from 'src/types/DomainTypes';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const props = defineProps<{
|
||||
item: IDomainOwnerDisplay;
|
||||
activeId: number;
|
||||
}>();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const isOwnerFunc = computed(() => (ownerId: string) => {
|
||||
return ownerId == authStore.userId;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.domain-card.default {
|
||||
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
|
||||
0 10px 14px 1px rgba(0, 0, 0, 0.14),
|
||||
0 4px 18px 3px rgba(0, 0, 0, 0.12),
|
||||
inset 0 0 0px 2px #1976D2;
|
||||
}
|
||||
.domain-card {
|
||||
width: 22rem;
|
||||
word-break: break-word;
|
||||
|
||||
.smaller-font-size {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<q-btn flat no-caps dense icon="account_circle" :label="userInfo.fullName">
|
||||
<q-menu class="bar-user-menu">
|
||||
<div class="row no-wrap q-px-md q-pt-sm ">
|
||||
<div class="column items-center justify-center">
|
||||
<q-icon name="account_circle" color="grey" size="3em" />
|
||||
</div>
|
||||
<div class="column q-ml-sm overflow-hidden">
|
||||
<div class="text-subtitle1 ellipsis full-width">{{ userInfo.fullName }}</div>
|
||||
<div class="text-grey-7 ellipsis text-caption q-mb-sm full-width">{{ userInfo.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-pb-sm q-px-md">
|
||||
<q-chip v-if="authStore.isSuperAdmin" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||
label="システム管理者" size="sm" />
|
||||
<q-chip v-else v-for="(item) in roles" square class="role-label" color="primary" text-color="white" :key="item.id" :label="item.name" size="sm" />
|
||||
</div>
|
||||
<div class="row q-pb-sm q-px-md">
|
||||
<q-btn outline color="negative" icon="logout" label="Logout" @click="authStore.logout()" class="full-width" size="sm" v-close-popup />
|
||||
</div>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const userInfo = computed(() => authStore.userInfo);
|
||||
const roles = computed(() => authStore.roles);
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.bar-user-menu {
|
||||
max-width: 230px !important;
|
||||
}
|
||||
.role-label {
|
||||
margin: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="props.filter" :loading="loading"
|
||||
:pagination="pagination" selection="single" v-model:selected="selected"></q-table>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
const props = defineProps<{filter:string}>()
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
];
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
||||
const rows = ref([]);
|
||||
const loading = ref(false);
|
||||
const selected = ref([]);
|
||||
defineExpose({
|
||||
selected
|
||||
})
|
||||
const getUsers = async (filter = () => true) => {
|
||||
loading.value = true;
|
||||
const result = await api.get('api/v1/users');
|
||||
rows.value = result.data.data.map((item) => {
|
||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
||||
}).filter(filter);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getUsers();
|
||||
})
|
||||
</script>
|
||||
@@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<div class="q-px-xs">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
|
||||
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
|
||||
:filter="filter" style="max-height: 65vh;" @update:selected="emitSelected">
|
||||
|
||||
<template v-for="col in columns" :key="col.name" v-slot:[`body-cell-${col.name}`]="props">
|
||||
<q-td :props="props">
|
||||
<!-- 使用动态插槽名称 -->
|
||||
<slot v-if="col.name !== detailField" :name="`body-cell-${col.name}`" :row="props.row" :column="props.col">
|
||||
<!-- 默认内容 -->
|
||||
<span>{{ props.row[col.name] }}</span>
|
||||
</slot>
|
||||
<q-scroll-area v-else class="description-cell">
|
||||
<div v-html="props.row[detailField]"></div>
|
||||
</q-scroll-area>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, PropType } from 'vue'
|
||||
|
||||
interface IRow {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DetailFieldTable',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
detailField: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array as PropType<any[]>,
|
||||
required: true
|
||||
},
|
||||
fetchData: {
|
||||
type: Function as PropType<() => Promise<IRow[]>>,
|
||||
required: true
|
||||
},
|
||||
sortBy: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
sortDesc: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
emits: ['update:selected'],
|
||||
setup(props, { emit }) {
|
||||
const isLoaded = ref(false);
|
||||
const rows = reactive<IRow[]>([]);
|
||||
const selected = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await props.fetchData();
|
||||
rows.push(...data);
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
const emitSelected = (selectedItems: any[]) => {
|
||||
emit('update:selected', selectedItems);
|
||||
};
|
||||
|
||||
return {
|
||||
rows,
|
||||
selected,
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
sortBy: props.sortBy || undefined,
|
||||
descending: props.sortDesc || undefined,
|
||||
rowsPerPage: 10
|
||||
}),
|
||||
emitSelected
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.description-cell {
|
||||
height: 60px;
|
||||
width: 300px;
|
||||
max-height: 60px;
|
||||
max-width: 300px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
min-height: 300px;
|
||||
min-width: 400px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<detail-field-table
|
||||
detailField="description"
|
||||
:name="name"
|
||||
:type="type"
|
||||
:filter="filter"
|
||||
:columns="columns"
|
||||
:fetchData="fetchUsers"
|
||||
@update:selected="(item) => { selected = item }">
|
||||
|
||||
<template v-slot:body-cell-status="props">
|
||||
<div class="row">
|
||||
<div v-if="props.row.isActive">
|
||||
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
|
||||
</div>
|
||||
|
||||
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||
label="システム管理者" size="sm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</detail-field-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, PropType } from 'vue';
|
||||
import { IUser } from 'src/types/UserTypes';
|
||||
import { api } from 'boot/axios';
|
||||
import DetailFieldTable from './DetailFieldTable.vue';
|
||||
|
||||
export default {
|
||||
name: 'UserSelectBox',
|
||||
components: {
|
||||
DetailFieldTable
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
filterInitRowsFunc: {
|
||||
type: Function as PropType<(user: IUser) => boolean>,
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const selected = ref<IUser[]>([]);
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
{ name: 'status', label: '状況', field: 'status', align: 'left' }
|
||||
];
|
||||
|
||||
const fetchUsers = async () => {
|
||||
const result = await api.get('api/v1/users');
|
||||
return result.data.data.map((item: any) => {
|
||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active, roles: item.roles.map(role => role.id) }
|
||||
}).filter(user => !props.filterInitRowsFunc || props.filterInitRowsFunc(user));
|
||||
};
|
||||
|
||||
return {
|
||||
columns,
|
||||
fetchUsers,
|
||||
selected
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<detail-field-table
|
||||
detailField="comment"
|
||||
type="single"
|
||||
:columns="columns"
|
||||
sortBy="id"
|
||||
:sortDesc="true"
|
||||
:fetchData="fetchVersionHistory"
|
||||
@update:selected="(item) => { selected = item }"
|
||||
>
|
||||
<template v-slot:body-cell-id="p">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ p.row.id }}</span>
|
||||
<q-badge v-if="p.row.isActive" color="primary">現在</q-badge>
|
||||
</div>
|
||||
</template>
|
||||
</detail-field-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref } from 'vue';
|
||||
import { IAppDisplay, IAppVersion, IAppVersionDisplay } from 'src/types/AppTypes';
|
||||
import { date } from 'quasar';
|
||||
import { api } from 'boot/axios';
|
||||
import DetailFieldTable from './DetailFieldTable.vue';
|
||||
import { IUser, IUserDisplay } from 'src/types/UserTypes';
|
||||
|
||||
interface IVersionDisplay extends IAppVersionDisplay {
|
||||
isActive : boolean
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'VersionHistory',
|
||||
components: {
|
||||
DetailFieldTable
|
||||
},
|
||||
props: {
|
||||
app: {
|
||||
type: Object as PropType<IAppDisplay>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selected = ref<IVersionDisplay[]>();
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'バージョン', field: 'version', align: 'left', sortable: true },
|
||||
{ name: 'name', label: 'バージョン名', field: 'name', align: 'left', sortable: true },
|
||||
{ name: 'comment', label: 'コメント', field: 'comment', align: 'left', sortable: true },
|
||||
// { name: 'creator', label: '作成者', field: (row: IVersionDisplay) => row.creator.fullName, align: 'left', sortable: true },
|
||||
// { name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
|
||||
// { name: 'updater', label: '更新者', field: (row: IVersionDisplay) => row.updater.fullName, align: 'left', sortable: true },
|
||||
// { name: 'updateTime', label: '更新日時', field: 'updateTime', align: 'left', sortable: true },
|
||||
];
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return date.formatDate(dateStr, 'YYYY/MM/DD HH:mm:ss');
|
||||
};
|
||||
|
||||
const toUserDisplay = (user: IUser) => {
|
||||
return {
|
||||
id: user.id,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
|
||||
fullName: user.last_name + ' ' + user.first_name,
|
||||
email: user.email,
|
||||
isActive: user.is_active,
|
||||
isSuperuser: user.is_superuser,
|
||||
}
|
||||
}
|
||||
|
||||
const fetchVersionHistory = async () => {
|
||||
const { data } = await api.get(`api/appversions/${props.app.id}`);
|
||||
return data.data.reduce((arr: IVersionDisplay[], item: any) => {
|
||||
const val = {
|
||||
id: item.version,
|
||||
isActive: item.version === props.app.version,
|
||||
version: item.version,
|
||||
appid: item.appid,
|
||||
name: item.versionname,
|
||||
comment: item.comment,
|
||||
// updater: toUserDisplay(item.updateuser),
|
||||
// updateTime: formatDate(item.updatetime),
|
||||
// creator: toUserDisplay(item.createuser),
|
||||
// createTime: formatDate(item.createtime),
|
||||
} as IVersionDisplay;
|
||||
arr.push(val);
|
||||
return arr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return {
|
||||
fetchVersionHistory,
|
||||
columns,
|
||||
selected,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<q-input
|
||||
ref="nameRef"
|
||||
v-model="versionInfo.name"
|
||||
filled
|
||||
autofocus
|
||||
label="バージョン名"
|
||||
:rules="[
|
||||
val => !!val || 'バージョン名を入力してください。',
|
||||
(val) => !val || val.length <= 80 || '80字以内で入力ください'
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
ref="commentRef"
|
||||
v-model="versionInfo.comment"
|
||||
filled
|
||||
type="textarea"
|
||||
:rules="[(val) => !val || val.length <= 300 || '300字以内で入力ください']"
|
||||
label="説明"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||
import { QInput } from 'quasar';
|
||||
import { IVersionSubmit } from 'src/types/AppTypes';
|
||||
|
||||
const nameRef = ref();
|
||||
const commentRef = ref();
|
||||
|
||||
const isValid = () => {
|
||||
const nameHasError = nameRef.value?.hasError ?? false;
|
||||
const commentHasError = commentRef.value?.hasError ?? false;
|
||||
return !nameHasError && !commentHasError;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: IVersionSubmit;
|
||||
}>();
|
||||
|
||||
const defaultTitle = `${new Date().toLocaleString()}`;
|
||||
|
||||
const versionInfo = ref({
|
||||
...props.modelValue,
|
||||
name: props.modelValue.name || defaultTitle,
|
||||
comment: props.modelValue.comment || '',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
defineExpose({
|
||||
isValid
|
||||
})
|
||||
|
||||
watch(
|
||||
versionInfo,
|
||||
() => {
|
||||
emit('update:modelValue', { ...versionInfo.value });
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
@@ -44,7 +44,7 @@ import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
export default defineComponent({
|
||||
name: 'AppSelector',
|
||||
emits:[
|
||||
'appSelected'
|
||||
"appSelected"
|
||||
],
|
||||
components:{
|
||||
AppSelectBox,
|
||||
@@ -59,7 +59,7 @@ export default defineComponent({
|
||||
|
||||
const closeDg=(val :any)=>{
|
||||
showSelectApp.value=false;
|
||||
console.log('Dialog closed->',val);
|
||||
console.log("Dialog closed->",val);
|
||||
if (val == 'OK') {
|
||||
const data = appDg.value.selected[0];
|
||||
console.log(data);
|
||||
|
||||
@@ -7,14 +7,18 @@
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
||||
class="q-mr-sm">
|
||||
</q-icon>
|
||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<div class="no-wrap"
|
||||
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{
|
||||
prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:header-CHANGE="prop">
|
||||
<div class="row col items-start no-wrap event-node">
|
||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<div class="no-wrap"
|
||||
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''"
|
||||
>{{ prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
||||
@click="addChangeEvent(prop.node)"></q-icon>
|
||||
@@ -23,14 +27,14 @@
|
||||
<template v-slot:header-DELETABLE="prop">
|
||||
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
|
||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<div class="no-wrap" :class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''" >{{ prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
||||
</div>
|
||||
</template>
|
||||
</q-tree>
|
||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
||||
<field-select ref="appDg" name="フィールド" type="single" :fieldTypes="fieldTypes" :appId="store.appInfo?.appId"></field-select>
|
||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
@@ -38,8 +42,8 @@
|
||||
import { QTree, useQuasar } from 'quasar';
|
||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
export default defineComponent({
|
||||
@@ -54,33 +58,15 @@ export default defineComponent({
|
||||
const store = useFlowEditorStore();
|
||||
const showDialog = ref(false);
|
||||
const tree = ref<QTree>();
|
||||
const fieldTypes=[
|
||||
'RADIO_BUTTON',
|
||||
'DROP_DOWN',
|
||||
'CHECK_BOX',
|
||||
'MULTI_SELECT',
|
||||
'USER_SELECT',
|
||||
'GROUP_SELECT',
|
||||
'ORGANIZATION_SELECT',
|
||||
'DATE',
|
||||
'DATETIME',
|
||||
'TIME',
|
||||
'SINGLE_LINE_TEXT',
|
||||
'NUMBER'];
|
||||
// const eventTree=ref(kintoneEvents);
|
||||
// const selectedFlow = store.currentFlow;
|
||||
|
||||
// const expanded=ref();
|
||||
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
|
||||
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
|
||||
const selectedEvent = ref<IKintoneEvent | null>(null);
|
||||
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null);
|
||||
const isFieldChange = (node: IKintoneEventNode) => {
|
||||
return node.header == 'EVENT' && node.eventId.indexOf('.change.') > -1;
|
||||
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
|
||||
}
|
||||
|
||||
const getSelectedClass = (node: IKintoneEventNode) => {
|
||||
return store.selectedEvent && node.eventId === store.selectedEvent.eventId ? 'selected-node' : '';
|
||||
};
|
||||
|
||||
//フィールド値変更イベント追加
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
@@ -90,12 +76,12 @@ export default defineComponent({
|
||||
if (store.eventTree.findEventById(eventid)) {
|
||||
return;
|
||||
}
|
||||
selectedChangeEvent.value?.events.push(new kintoneEvent(
|
||||
field.name,
|
||||
eventid,
|
||||
selectedChangeEvent.value.eventId,
|
||||
'DELETABLE'
|
||||
));
|
||||
selectedChangeEvent.value?.events.push({
|
||||
eventId: eventid,
|
||||
label: field.name,
|
||||
parentId: selectedChangeEvent.value.eventId,
|
||||
header: 'DELETABLE'
|
||||
});
|
||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
||||
tree.value?.expandAll();
|
||||
}
|
||||
@@ -117,7 +103,7 @@ export default defineComponent({
|
||||
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: '通知',
|
||||
caption: "通知",
|
||||
message: `イベント ${node.label} 削除`
|
||||
})
|
||||
}
|
||||
@@ -133,7 +119,7 @@ export default defineComponent({
|
||||
const screen = store.eventTree.findEventById(node.parentId);
|
||||
|
||||
let flow = store.findFlowByEventId(node.eventId);
|
||||
let screenName = screen !== null ? screen.label : '';
|
||||
let screenName = screen !== null ? screen.label : "";
|
||||
let nodeLabel = node.label;
|
||||
// if(isFieldChange(node)){
|
||||
// screenName=nodeLabel;
|
||||
@@ -150,9 +136,6 @@ export default defineComponent({
|
||||
selectedEvent.value.flowData = flow;
|
||||
}
|
||||
};
|
||||
watchEffect(()=>{
|
||||
store.setCurrentEvent(selectedEvent.value);
|
||||
});
|
||||
return {
|
||||
// eventTree,
|
||||
// expanded,
|
||||
@@ -160,14 +143,12 @@ export default defineComponent({
|
||||
tree,
|
||||
showDialog,
|
||||
isFieldChange,
|
||||
getSelectedClass,
|
||||
onSelected,
|
||||
selectedEvent,
|
||||
addChangeEvent,
|
||||
deleteEvent,
|
||||
closeDg,
|
||||
store,
|
||||
fieldTypes
|
||||
store
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,11 +92,11 @@ export default defineComponent({
|
||||
},
|
||||
emits: [
|
||||
'addNode',
|
||||
'nodeSelected',
|
||||
'nodeEdit',
|
||||
'deleteNode',
|
||||
'deleteAllNextNodes',
|
||||
'copyFlow'
|
||||
"nodeSelected",
|
||||
"nodeEdit",
|
||||
"deleteNode",
|
||||
"deleteAllNextNodes",
|
||||
"copyFlow"
|
||||
],
|
||||
setup(props, context) {
|
||||
const store = useFlowEditorStore();
|
||||
@@ -204,7 +204,7 @@ export default defineComponent({
|
||||
* 変数名取得
|
||||
*/
|
||||
const varName =(node:IActionNode)=>{
|
||||
const prop = node.actionProps.find((prop) => prop.props.name === 'verName');
|
||||
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
||||
return prop?.props.modelValue.name;
|
||||
};
|
||||
const copyFlow=()=>{
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
import { ref, defineComponent, computed, PropType } from 'vue';
|
||||
import { IActionNode, ActionNode, ActionFlow, RootAction } from '../../types/ActionTypes';
|
||||
export enum Direction {
|
||||
Default = 'None',
|
||||
Left = 'LEFT',
|
||||
Right = 'RIGHT',
|
||||
LeftNotNext = 'LEFTNOTNEXT',
|
||||
RightNotNext = 'RIGHTNOTNEXT',
|
||||
Default = "None",
|
||||
Left = "LEFT",
|
||||
Right = "RIGHT",
|
||||
LeftNotNext = "LEFTNOTNEXT",
|
||||
RightNotNext = "RIGHTNOTNEXT",
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NodeLine',
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-name="p">
|
||||
<q-td class="flex justify-between items-center" :props="p">
|
||||
{{ p.row.name }}
|
||||
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
|
||||
<q-badge v-if="p.row.id == currendDomainId" color="primary">現在</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<q-td :props="p">
|
||||
<q-btn-group flat>
|
||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||
</q-btn-group>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
</template>
|
||||
|
||||
@@ -55,7 +55,7 @@ export default defineComponent({
|
||||
});
|
||||
const connectProps=(props:IProp)=>{
|
||||
const connProps:any={};
|
||||
if(props && 'connectProps' in props && props.connectProps!=undefined){
|
||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
||||
for(let connProp of props.connectProps){
|
||||
let targetProp = componentData.value.find((prop)=>prop.props.name===connProp.propName);
|
||||
if(targetProp){
|
||||
|
||||
@@ -1,165 +1,152 @@
|
||||
<template>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||
:rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
|
||||
<template v-slot:control>
|
||||
{{ isSelected ? selectedField.app?.name : "(未選択)" }}
|
||||
</template>
|
||||
<template v-slot:hint v-if="!isSelected">
|
||||
{{ placeholder }}
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-card flat>
|
||||
<q-card-section class="q-pa-none q-my-sm q-mr-md">
|
||||
<!-- <div class=" q-my-none ">App Field Select</div> -->
|
||||
<div class="row q-mb-xs">
|
||||
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section class="q-pa-none q-ma-none">
|
||||
<div style="">
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
|
||||
v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
</div>
|
||||
<!-- <div v-else class="row q-mt-lg">
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <q-separator /> -->
|
||||
</q-card-section>
|
||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length === 0">
|
||||
<div class="row">
|
||||
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
|
||||
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
|
||||
:fieldTypes="fieldTypes" />
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"/>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
export interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
export interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string,
|
||||
label?: string
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect2',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
setup(props, { emit }) {
|
||||
const show = ref(false);
|
||||
const afBox = ref();
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
|
||||
const closeAFBox = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
console.log(afBox.value);
|
||||
|
||||
selectedField.value = afBox.value.selField;
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
afBox,
|
||||
show,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
clear,
|
||||
removeField,
|
||||
closeAFBox,
|
||||
};
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const show = ref(false);
|
||||
const afBox = ref();
|
||||
const fieldRef = ref();
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
|
||||
const closeAFBox = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
console.log(afBox.value);
|
||||
selectedField.value = afBox.value.selField;
|
||||
fieldRef.value.validate();
|
||||
}
|
||||
};
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return !!selectedField.value.app
|
||||
});
|
||||
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required ? [((val: any) => (val && val.app && val.fields && val.fields.length > 0) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
afBox,
|
||||
show,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
clear,
|
||||
removeField,
|
||||
closeAFBox,
|
||||
isSelected,
|
||||
rulesExp,
|
||||
fieldRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
:rules="rulesExp"
|
||||
lazy-rules="ondemand"
|
||||
v-model="selectedApp"
|
||||
ref="fieldRef">
|
||||
<div>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="selectedApp.app.name">
|
||||
{{ selectedApp.app.name }}
|
||||
<div v-if="selectedField.app.name">
|
||||
{{ selectedField.app.name }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
@@ -47,6 +43,10 @@ export default defineComponent({
|
||||
AppSelectBox
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -62,50 +62,31 @@ export default defineComponent({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref();
|
||||
const fieldRef=ref();
|
||||
const appDg = ref()
|
||||
const dgIsShow = ref(false)
|
||||
const selectedApp = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
if (state == 'OK') {
|
||||
selectedApp.app = appDg.value.selected[0];
|
||||
fieldRef.value.validate();
|
||||
selectedField.app = appDg.value.selected[0];
|
||||
}
|
||||
};
|
||||
//ルール設定
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => (!!val && !!val.app && !!val.app.name) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
console.log(selectedField);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedApp);
|
||||
emit('update:modelValue', selectedField);
|
||||
});
|
||||
|
||||
return {
|
||||
filter: ref(''),
|
||||
dgIsShow,
|
||||
appDg,
|
||||
fieldRef,
|
||||
closeDg,
|
||||
selectedApp,
|
||||
rulesExp
|
||||
selectedField
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label lazy-rules="ondemand" ref="fieldRef">
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="data.dropDownApp?.name">
|
||||
{{ `${data.sourceApp?.name} -> ${data.dropDownApp?.name}` }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<ShowDialog v-model:visible="dgIsShow" name="ドロップダウン階層化設定" @close="closeDg" min-width="50vw" min-height="20vh" disableBtn>
|
||||
<template v-slot:toolbar>
|
||||
<q-btn flat round dense icon="more_vert" >
|
||||
<q-menu auto-close anchor="bottom start">
|
||||
<q-list>
|
||||
<q-item clickable @click="copySetting()">
|
||||
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
||||
<q-item-section >コピー</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="pasteSetting()">
|
||||
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
|
||||
<q-item-section >貼り付け</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</template>
|
||||
<div class="q-mb-md q-ml-md q-mr-md">
|
||||
<CascadingDropDownBox v-model:model-value="data" :finishDialogHandler="finishDialogHandler" />
|
||||
</div>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import CascadingDropDownBox from '../CascadingDropDownBox.vue';
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'CascadingDropDown',
|
||||
components: {
|
||||
ShowDialog,
|
||||
CascadingDropDownBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => { ({}) }
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const dgIsShow = ref(false);
|
||||
// const data = ref(props.modelValue);
|
||||
const data = ref({
|
||||
sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||
dropDownApp: props.modelValue.dropDownApp,
|
||||
fieldList: props.modelValue.fieldList ?? [],
|
||||
});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
};
|
||||
|
||||
const finishDialogHandler = (boxData) => {
|
||||
data.value = boxData
|
||||
dgIsShow.value = false
|
||||
emit('update:modelValue', data.value);
|
||||
}
|
||||
|
||||
//設定をコピーする
|
||||
const copySetting=()=>{
|
||||
if (navigator.clipboard) {
|
||||
const jsonData= JSON.stringify(data.value);
|
||||
navigator.clipboard.writeText(jsonData).then(() => {
|
||||
console.log('Text successfully copied to clipboard');
|
||||
},
|
||||
(err) => {
|
||||
console.error('Error in copying text: ', err);
|
||||
});
|
||||
} else {
|
||||
console.log('Clipboard API not available');
|
||||
}
|
||||
};
|
||||
//設定を貼り付ける
|
||||
const pasteSetting=async ()=>{
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
console.log('Text from clipboard:', text);
|
||||
const jsonData=JSON.parse(text);
|
||||
if('sourceApp' in jsonData && 'dropDownApp' in jsonData && 'fieldList' in jsonData){
|
||||
const {sourceApp,dropDownApp, fieldList}=jsonData;
|
||||
data.value.sourceApp=sourceApp;
|
||||
data.value.dropDownApp=dropDownApp;
|
||||
data.value.fieldList=fieldList;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to read text from clipboard: ', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// watchEffect(() => {
|
||||
// emit('update:modelValue', data.value);
|
||||
// });
|
||||
|
||||
return {
|
||||
dgIsShow,
|
||||
closeDg,
|
||||
data,
|
||||
finishDialogHandler,
|
||||
copySetting,
|
||||
pasteSetting
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="" v-bind="$attrs">
|
||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" :rules="rulesExp">
|
||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
|
||||
<template v-slot:control>
|
||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
||||
<div class="row">
|
||||
@@ -57,34 +57,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const color = ref(props.modelValue??'');
|
||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=='');
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),'anyColor']:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
const color = ref(props.modelValue??"");
|
||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
||||
watchEffect(()=>{
|
||||
emit('update:modelValue', color.value);
|
||||
});
|
||||
return {
|
||||
color,
|
||||
isSelected,
|
||||
rulesExp
|
||||
isSelected
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,11 +18,39 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
||||
import { IActionProperty } from 'src/types/ActionTypes';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
},
|
||||
fields: {
|
||||
type: string;
|
||||
label: string;
|
||||
code: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type InputConfg = {
|
||||
canInput: boolean;
|
||||
buttonsConfig: {
|
||||
label: string;
|
||||
color: string;
|
||||
type: string;
|
||||
}[]
|
||||
};
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldInput',
|
||||
@@ -32,7 +60,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<IActionProperty>,
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
@@ -59,10 +87,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'field'
|
||||
},
|
||||
connectProps:{
|
||||
type:Object,
|
||||
default:()=>({})
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -91,10 +115,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
let source = reactive(props.connectProps['source']);
|
||||
if(!source){
|
||||
source = props.context.find(element => element.props.name === 'sources');
|
||||
}
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
if (props.sourceType === 'field') {
|
||||
@@ -136,8 +157,7 @@ export default defineComponent({
|
||||
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
||||
|
||||
const conditionString = computed(() => {
|
||||
const condiStr= tree.buildConditionString(tree.root);
|
||||
return condiStr==='()'?'(条件なし)':condiStr;
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
|
||||
const showDg = () => {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
v-model="mappingProps"
|
||||
:rules="rulesExp"
|
||||
ref="fieldRef"
|
||||
>
|
||||
<div>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
@@ -20,77 +16,64 @@
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
||||
<div class="">
|
||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||
|
||||
<div class="q-mx-md">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<div class="col-6">
|
||||
<div class="q-mx-xs">ソース</div>
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<div class="row justify-between q-mr-md">
|
||||
<div class="">{{ sourceApp?.name }}</div>
|
||||
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
||||
@click="() => updateFields(sourceAppId!)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1 q-pl-sm">
|
||||
キー
|
||||
<div class="col-6">
|
||||
<div class="q-mx-xs">目標</div>
|
||||
</div>
|
||||
<!-- <div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" /> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
|
||||
<q-virtual-scroll style="max-height: 75vh;" :items="mappingProps" separator v-slot="{ item, index }">
|
||||
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
||||
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
|
||||
<div class="col-5">
|
||||
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
||||
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
||||
<div class="row q-my-md q-col-gutter-x-md flex-center">
|
||||
<div class="col-6">
|
||||
<ConditionObject :config="config" v-model="item.from" />
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
||||
<div class="col-6">
|
||||
<q-field v-model="item.vName" type="text" outlined dense>
|
||||
<!-- <template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer"
|
||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||
</template> -->
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||
{{ `${item.to.fields[0].label}` }}
|
||||
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
||||
<q-tooltip>
|
||||
<div>アプリ : {{ item.to.app.name }}</div>
|
||||
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
||||
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
|
||||
<div v-if="item.to.fields[0].required">必須項目</div>
|
||||
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
|
||||
<div>フィールド : {{ item.isKey }}</div> -->
|
||||
<div>フィールド : {{ item.to.fields[0] }}</div>
|
||||
</q-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
|
||||
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" />
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="mappingProps.data[index].to.isDialogVisible" name="フィールド一覧"
|
||||
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧"
|
||||
@close="closeToDg" ref="fieldDlg">
|
||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||
:selectedFields="mappingProps.data[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
|
||||
:selectedFields="mappingProps[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
|
||||
</FieldSelect>
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
|
||||
</show-dialog>
|
||||
<!-- </div> -->
|
||||
</q-virtual-scroll>
|
||||
|
||||
<div class="q-mt-lg q-ml-md row ">
|
||||
<q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
||||
</div>
|
||||
</div>
|
||||
</show-dialog>
|
||||
</div>
|
||||
@@ -103,10 +86,10 @@ import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import { IApp, IField } from './AppFieldSelect.vue';
|
||||
import IAppFields from './AppFieldSelect.vue';
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
type ContextProps = {
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
@@ -117,25 +100,14 @@ type ContextProps = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface IMappingSetting {
|
||||
data: IMappingValueType[];
|
||||
createWithNull: boolean;
|
||||
}
|
||||
|
||||
interface IMappingValueType {
|
||||
type ValueType = {
|
||||
id: string;
|
||||
from: { sharedText?: string };
|
||||
to: {
|
||||
app?: IApp,
|
||||
fields: IField[],
|
||||
from: object;
|
||||
to: typeof IAppFields & {
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
isKey: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataMapping',
|
||||
@@ -148,7 +120,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<ContextProps>,
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
@@ -160,7 +132,7 @@ export default defineComponent({
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as () => IMappingSetting,
|
||||
type: Object as () => ValueType[],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -169,114 +141,73 @@ export default defineComponent({
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const fieldRef=ref();
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||
|
||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
||||
|
||||
//ルール設定
|
||||
const checkMapping = (val:IMappingSetting)=>{
|
||||
if(!val || !val.data){
|
||||
return false;
|
||||
}
|
||||
console.log(val);
|
||||
const mappingDatas = val.data.filter(item=>item.from?.sharedText && item.to.fields?.length > 0);
|
||||
return mappingDatas.length>0;
|
||||
}
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => checkMapping(val) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
// const mappingProps = ref(props.modelValue?.data ?? []);
|
||||
|
||||
// const createWithNull = ref(props.modelValue?.createWithNull ?? false);
|
||||
|
||||
const mappingProps = reactive<IMappingSetting>({
|
||||
data:props.modelValue?.data ?? [],
|
||||
createWithNull:props.modelValue?.createWithNull ?? false
|
||||
});
|
||||
|
||||
const closeDg = () => {
|
||||
fieldRef.value.validate();
|
||||
emit('update:modelValue',mappingProps);
|
||||
emit('update:modelValue', mappingProps.value
|
||||
);
|
||||
}
|
||||
|
||||
const closeToDg = () => {
|
||||
emit('update:modelValue',mappingProps);
|
||||
emit('update:modelValue', mappingProps.value
|
||||
);
|
||||
}
|
||||
|
||||
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
||||
watch(() => sourceAppId.value, async (newId,) => {
|
||||
if (!newId) return;
|
||||
updateFields(newId)
|
||||
})
|
||||
const mappingProps = computed(() => props.modelValue ?? []);
|
||||
|
||||
const updateFields = async (sourceAppId: string) => {
|
||||
const ktAppFields = await api.get('api/v1/appfields', {
|
||||
watch(() => sourceAppId.value, async (newId, oldId) => {
|
||||
if (!newId) return;
|
||||
const a = await api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: sourceAppId
|
||||
app: newId
|
||||
}
|
||||
}).then(res => {
|
||||
return Object.values(res.data.properties)
|
||||
// kintoneのデフォルトの非表示フィールドフィルタリング
|
||||
.filter(f => !blackListLabelName.find(label => f.label === label))
|
||||
.map(f => ({ name: f.label, objectType: 'field', ...f }))
|
||||
.map(f => {
|
||||
// 更新前の値を求める
|
||||
const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
|
||||
return {
|
||||
id: uuidv4(),
|
||||
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
||||
from: {},
|
||||
to: {
|
||||
app: sourceApp.value,
|
||||
fields: [f],
|
||||
isDialogVisible: false
|
||||
},
|
||||
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
||||
disabled: false
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
const modelValue = props.modelValue ?? [];
|
||||
|
||||
// 「ルックアップ」によってロックされているフィールドを検索する
|
||||
const lookupFixedField = ktAppFields
|
||||
.filter(field => field.to.fields[0].lookup !== undefined)
|
||||
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
|
||||
|
||||
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
|
||||
if (lookupFixedField.length > 0) {
|
||||
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
|
||||
if (modelValue.length === 0 || newId !== oldId) {
|
||||
emit('update:modelValue', a);
|
||||
return;
|
||||
}
|
||||
const modelValueFieldNames = modelValue.map(item => item.to.fields[0].name);
|
||||
|
||||
mappingProps.data = ktAppFields
|
||||
}
|
||||
const newFields = a.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name));
|
||||
|
||||
const updatedModelValue = [...modelValue, ...newFields];
|
||||
|
||||
emit('update:modelValue', updatedModelValue);
|
||||
})
|
||||
|
||||
console.log(mappingProps.value);
|
||||
|
||||
// const deleteMappingObject = (index: number) => mappingProps.length === 1
|
||||
// ? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
|
||||
// : mappingProps.splice(index, 1);
|
||||
|
||||
const mappingObjectsInputDisplay = computed(() =>
|
||||
(mappingProps.data && Array.isArray(mappingProps.data)) ?
|
||||
mappingProps.data
|
||||
(mappingProps.value && Array.isArray(mappingProps.value)) ?
|
||||
mappingProps.value
|
||||
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
||||
.map(item => {
|
||||
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
||||
@@ -284,39 +215,36 @@ export default defineComponent({
|
||||
: []
|
||||
);
|
||||
|
||||
|
||||
|
||||
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', mappingProps);
|
||||
});
|
||||
//集計処理方法
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', mappingProps.value);
|
||||
});
|
||||
return {
|
||||
uuidv4,
|
||||
dgIsShow: ref(false),
|
||||
fieldRef,
|
||||
closeDg,
|
||||
toDgIsShow: ref(false),
|
||||
closeToDg,
|
||||
mappingProps,
|
||||
updateFields,
|
||||
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||
// deleteMappingObject,
|
||||
mappingObjectsInputDisplay,
|
||||
sourceApp,
|
||||
sourceAppId,
|
||||
btnDisable,
|
||||
rulesExp,
|
||||
checkMapping,
|
||||
config: {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd',editable:false },
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
v-model="processingProps"
|
||||
:rules="rulesExp"
|
||||
lazy-rules="ondemand"
|
||||
ref="fieldRef"
|
||||
>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
@@ -45,7 +40,7 @@
|
||||
<div class="col-5">
|
||||
<ConditionObject v-model="item.field" />
|
||||
</div>
|
||||
<div class="col-2 q-pa-sm">
|
||||
<div class="col-2">
|
||||
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@@ -83,8 +78,14 @@ type Props = {
|
||||
|
||||
type ProcessingObjectType = {
|
||||
field?: {
|
||||
sharedText: string;
|
||||
objectType: 'field';
|
||||
name: string | {
|
||||
name: string;
|
||||
};
|
||||
objectType: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
};
|
||||
logicalOperator?: string;
|
||||
vName?: string;
|
||||
@@ -125,19 +126,9 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const fieldRef=ref();
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
@@ -154,104 +145,65 @@ export default defineComponent({
|
||||
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
||||
|
||||
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
||||
? reactive(props.modelValue)
|
||||
? props.modelValue
|
||||
: reactive({
|
||||
name: '',
|
||||
actionName: actionName?.props?.modelValue as string,
|
||||
displayName: '結果(戻り値)',
|
||||
vars: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
}]
|
||||
vars: [{ id: uuidv4() }]
|
||||
});
|
||||
|
||||
const closeDg = () => {
|
||||
fieldRef.value.validate();
|
||||
emit('update:modelValue', processingProps);
|
||||
emit('update:modelValue', processingProps
|
||||
);
|
||||
}
|
||||
|
||||
const processingObjects = processingProps.vars;
|
||||
|
||||
const deleteProcessingObject = (index: number) => {
|
||||
if(processingObjects.length >0){
|
||||
processingObjects.splice(index, 1);
|
||||
}
|
||||
if(processingObjects.length===0){
|
||||
addProcessingObject();
|
||||
}
|
||||
}
|
||||
const deleteProcessingObject = (index: number) => processingObjects.length === 1
|
||||
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
|
||||
: processingObjects.splice(index, 1);
|
||||
|
||||
const processingObjectsInputDisplay = computed(() =>
|
||||
processingObjects ?
|
||||
processingObjects
|
||||
.filter(item => item.field && item.logicalOperator && item.vName)
|
||||
.map(item => {
|
||||
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
|
||||
return`var(${processingProps.name}.${item.vName}) = ${item.field.sharedText}`
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const addProcessingObject=()=>{
|
||||
processingObjects.push({
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
});
|
||||
}
|
||||
//集計処理方法
|
||||
const logicalOperators = ref([
|
||||
{
|
||||
'operator': '',
|
||||
'label': 'なし'
|
||||
"operator": "",
|
||||
"label": "なし"
|
||||
},
|
||||
{
|
||||
'operator': 'SUM',
|
||||
'label': '合計'
|
||||
"operator": "SUM",
|
||||
"label": "合計"
|
||||
},
|
||||
{
|
||||
'operator': 'AVG',
|
||||
'label': '平均'
|
||||
"operator": "AVG",
|
||||
"label": "平均"
|
||||
},
|
||||
{
|
||||
'operator': 'MAX',
|
||||
'label': '最大値'
|
||||
"operator": "MAX",
|
||||
"label": "最大値"
|
||||
},
|
||||
{
|
||||
'operator': 'MIN',
|
||||
'label': '最小値'
|
||||
"operator": "MIN",
|
||||
"label": "最小値"
|
||||
},
|
||||
{
|
||||
'operator': 'COUNT',
|
||||
'label': 'カウント'
|
||||
"operator": "COUNT",
|
||||
"label": "カウント"
|
||||
},
|
||||
{
|
||||
'operator': 'FIRST',
|
||||
'label': '最初の値'
|
||||
"operator": "FIRST",
|
||||
"label": "最初の値"
|
||||
}
|
||||
]);
|
||||
const checkInput=(val:ValueType)=>{
|
||||
if(!val){
|
||||
return false;
|
||||
}
|
||||
if(!val.name){
|
||||
return '集計結果の変数名を入力してください';
|
||||
}
|
||||
if(!val.vars || val.vars.length==0){
|
||||
return '集計処理を設定してください';
|
||||
}
|
||||
if(val.vars.some((x)=>!x.vName)){
|
||||
return '集計結果変数名を入力してください';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const requiredExp = props.required ? [(val: any) => checkInput(val)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', processingProps);
|
||||
@@ -262,12 +214,10 @@ export default defineComponent({
|
||||
closeDg,
|
||||
processingObjects,
|
||||
processingProps,
|
||||
addProcessingObject,
|
||||
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
|
||||
deleteProcessingObject,
|
||||
logicalOperators,
|
||||
processingObjectsInputDisplay,
|
||||
rulesExp,
|
||||
fieldRef
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="rulesExp" stack-label>
|
||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label>
|
||||
<template v-slot:append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
@@ -43,32 +43,16 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedDate = ref(props.modelValue);
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const requiredExp = props.required?[((val:any)=>!!val||`${props.displayName}が必須です。`),'date']:['date'];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedDate.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedDate,
|
||||
rulesExp
|
||||
selectedDate
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
||||
:placeholder="placeholder"
|
||||
:rules="rulesExp"
|
||||
stack-label>
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
|
||||
<template v-slot:append>
|
||||
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
||||
</template>
|
||||
@@ -43,60 +40,43 @@ export default defineComponent({
|
||||
connectProps:{
|
||||
type:Object,
|
||||
default:undefined
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props , { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を入力してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => !!val || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
const addButtonEvent=()=>{
|
||||
const eventId =store.currentFlow?.getRoot()?.name;
|
||||
if(eventId===undefined){return;}
|
||||
let displayName = inputValue.value;
|
||||
if(props.connectProps!==undefined && 'displayName' in props.connectProps){
|
||||
displayName =props.connectProps['displayName'].props.modelValue;
|
||||
if(props.connectProps!==undefined && "displayName" in props.connectProps){
|
||||
displayName =props.connectProps["displayName"].props.modelValue;
|
||||
}
|
||||
const customButtonId=`${eventId}.customButtonClick`;
|
||||
const findedEvent = store.eventTree.findEventById(customButtonId);
|
||||
if(findedEvent && 'events' in findedEvent){
|
||||
if(findedEvent && "events" in findedEvent){
|
||||
const customEvents = findedEvent as IKintoneEventGroup;
|
||||
const addEventId = customButtonId+'.' + inputValue.value;
|
||||
const addEventId = customButtonId+"." + inputValue.value;
|
||||
if(store.eventTree.findEventById(addEventId)){
|
||||
return;
|
||||
}
|
||||
customEvents.events.push(new kintoneEvent(
|
||||
displayName,
|
||||
addEventId,
|
||||
customButtonId,
|
||||
'DELETABLE'
|
||||
));
|
||||
customEvents.events.push({
|
||||
eventId: addEventId,
|
||||
label: displayName,
|
||||
parentId: customButtonId,
|
||||
header: 'DELETABLE'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
addButtonEvent,
|
||||
rulesExp
|
||||
addButtonEvent
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||
:bottom-slots="!isSelected"
|
||||
:rules="rulesExp"
|
||||
>
|
||||
:bottom-slots="!isSelected">
|
||||
<template v-slot:control>
|
||||
<q-chip color="primary" text-color="white" v-if="isSelected">
|
||||
{{ selectedField.name }}
|
||||
@@ -17,15 +15,8 @@
|
||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :selectedFields="selectedFields" :fieldTypes="fieldTypes" :filter="filter"></field-select>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
|
||||
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :fieldTypes="fieldTypes"></field-select>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -76,35 +67,16 @@ export default defineComponent({
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref();
|
||||
const show = ref(false);
|
||||
const selectedField = ref(props.modelValue);
|
||||
const selectedFields =computed(()=>!selectedField.value?[]: [selectedField.value]);
|
||||
const store = useFlowEditorStore();
|
||||
const isSelected = computed(() => {
|
||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
||||
});
|
||||
//ルール設定
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required ? [((val: any) => (!!val && typeof val==='object' && !!val.name) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
@@ -127,10 +99,7 @@ export default defineComponent({
|
||||
showDg,
|
||||
closeDg,
|
||||
selectedField,
|
||||
isSelected,
|
||||
filter:ref(''),
|
||||
selectedFields,
|
||||
rulesExp
|
||||
isSelected
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { kMaxLength } from 'buffer';
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -49,16 +50,8 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: null as any,
|
||||
// type: Any,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
@@ -82,11 +75,8 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
// const inputValue = ref(props.modelValue);
|
||||
const rulesExp = props.rules === undefined ? null : eval(props.rules);
|
||||
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
// const finalValue = computed(() => {
|
||||
// return props.name !== 'verName' ? inputValue.value : {
|
||||
// name: inputValue.value,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" label-color="primary" v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
:rules="rulesExp"
|
||||
autogrow
|
||||
stack-label />
|
||||
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
|
||||
stack-label />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,34 +32,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
rulesExp
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user