Compare commits
36 Commits
dev3-versi
...
feature-au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50a4349a84 | ||
|
|
eb50f72df9 | ||
|
|
3540becf6f | ||
|
|
5ca16d7b45 | ||
|
|
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 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
docker-stack.yml
|
docker-stack.yml
|
||||||
backend/pyvenv.cfg
|
|
||||||
backend/Include/
|
|
||||||
backend/Scripts/
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -25,15 +25,11 @@ async def login(
|
|||||||
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
)
|
)
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
roles = "super"
|
permissions = "admin"
|
||||||
permissions = "ALL"
|
|
||||||
else:
|
else:
|
||||||
roles = ";".join(role.name for role in user.roles)
|
permissions = "user"
|
||||||
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
|
|
||||||
permissions =";".join(list(set(perlst)))
|
|
||||||
|
|
||||||
access_token = security.create_access_token(
|
access_token = security.create_access_token(
|
||||||
data={"sub": user.id, "roles":roles,"permissions": permissions ,},
|
data={"sub": user.id, "permissions": permissions},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,15 @@ import app.core.config as config
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from app.db.session import SessionLocal
|
from app.db.session import SessionLocal
|
||||||
from app.db.crud import get_flows_by_app,get_kintoneformat
|
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.auth import get_current_active_user,get_current_user
|
||||||
from app.core.apiexception import APIException
|
from app.core.apiexception import APIException
|
||||||
from app.db.cruddb.dbdomain import dbdomain
|
|
||||||
|
|
||||||
kinton_router = r = APIRouter()
|
kinton_router = r = APIRouter()
|
||||||
|
|
||||||
def getkintoneenv(user = Depends(get_current_user)):
|
def getkintoneenv(user = Depends(get_current_user)):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
|
domain = get_activedomain(db, user.id)
|
||||||
db.close()
|
db.close()
|
||||||
kintoneevn = config.KINTONE_ENV(domain)
|
kintoneevn = config.KINTONE_ENV(domain)
|
||||||
return kintoneevn
|
return kintoneevn
|
||||||
@@ -157,10 +156,10 @@ def getsettingfromexcel(df):
|
|||||||
des = df.iloc[2,2]
|
des = df.iloc[2,2]
|
||||||
return {"name":appname,"description":des}
|
return {"name":appname,"description":des}
|
||||||
|
|
||||||
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
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)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -172,69 +171,68 @@ def analysesettings(excel,kintone):
|
|||||||
updatesettings[key] = excel[key]
|
updatesettings[key] = excel[key]
|
||||||
return updatesettings
|
return updatesettings
|
||||||
|
|
||||||
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||||
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}
|
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))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||||
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"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||||
data = {"app":app}
|
data = {"app":app}
|
||||||
data.update(updates)
|
data.update(updates)
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||||
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"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
if revision != None:
|
if revision != None:
|
||||||
data = {"app":app,"revision":revision,"properties":fields}
|
data = {"app":app,"revision":revision,"properties":fields}
|
||||||
else:
|
else:
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
r.raise_for_status()
|
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||||
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"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||||
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"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
params = {"app":app,"revision":revision,"fields":fields}
|
params = {"app":app,"revision":revision,"fields":fields}
|
||||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||||
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"}
|
||||||
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":[{"app":app,"revision":revision}],"revert": False}
|
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
|
|
||||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||||
# スペース、枠線、ラベルを含まない
|
# スペース、枠線、ラベルを含まない
|
||||||
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
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)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
# フォームに配置するフィールドのみ取得する
|
# フォームに配置するフィールドのみ取得する
|
||||||
# スペース、枠線、ラベルも含める
|
# スペース、枠線、ラベルも含める
|
||||||
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
def getformfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
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)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -287,10 +285,10 @@ def analysefields(excel,kintone):
|
|||||||
|
|
||||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||||
|
|
||||||
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
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)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -375,86 +373,46 @@ def getkintoneorgs(c:config.KINTONE_ENV):
|
|||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
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"):
|
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||||
return {'fileKey':file}
|
return {'fileKey':file}
|
||||||
upload_files = {'file': open(file,'rb')}
|
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)}
|
data ={'name':'file','filename':os.path.basename(file)}
|
||||||
url = f"{env.BASE_URL}/k/v1/file.json"
|
url = f"{c.BASE_URL}/k/v1/file.json"
|
||||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||||
#{"name":data['filename'],'fileKey':r['fileKey']}
|
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updateappjscss(app,uploads,env:config.KINTONE_ENV):
|
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||||
dsjs = []
|
dsjs = []
|
||||||
dscss = []
|
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 upload in uploads:
|
||||||
for key in upload:
|
for key in upload:
|
||||||
filename = os.path.basename(key)
|
|
||||||
if key.endswith('.js'):
|
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]}})
|
|
||||||
else:
|
|
||||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||||
else:
|
else:
|
||||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||||
elif key.endswith('.css'):
|
elif key.endswith('.css'):
|
||||||
existing_css = next((item for item in current_css
|
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||||
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)
|
|
||||||
|
|
||||||
ds ={'js':dsjs,'css':dscss}
|
ds ={'js':dsjs,'css':dscss}
|
||||||
mb ={'js':mbjs,'css':mbcss}
|
mb ={'js':[],'css':[]}
|
||||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
|
||||||
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"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||||
print(json.dumps(data))
|
print(data)
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
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):
|
def getTempPath(filename):
|
||||||
scriptdir = Path(__file__).resolve().parent
|
scriptdir = Path(__file__).resolve().parent
|
||||||
rootdir = scriptdir.parent.parent.parent.parent
|
rootdir = scriptdir.parent.parent.parent.parent
|
||||||
fpath = os.path.join(rootdir,"Temp",filename)
|
fpath = os.path.join(rootdir,"Temp",filename)
|
||||||
return fpath
|
return fpath
|
||||||
|
|
||||||
def createappjs(domain_url,app):
|
def createappjs(domainid,app):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
flows = get_flows_by_app(db,domain_url,app)
|
flows = get_flows_by_app(db,domainid,app)
|
||||||
db.close()
|
db.close()
|
||||||
content={}
|
content={}
|
||||||
for flow in flows:
|
for flow in flows:
|
||||||
@@ -522,7 +480,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
|||||||
return {"files": [file.filename for file in files]}
|
return {"files": [file.filename for file in files]}
|
||||||
|
|
||||||
@r.post("/updatejscss")
|
@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:
|
try:
|
||||||
jscs=[]
|
jscs=[]
|
||||||
for file in files:
|
for file in files:
|
||||||
@@ -543,47 +501,35 @@ 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)
|
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")
|
@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:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
||||||
params ={"id":app}
|
params ={"id":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
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_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/allapps")
|
@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:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||||
offset = 0
|
r = httpx.get(url,headers=headers)
|
||||||
limit = 100
|
return r.json()
|
||||||
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}
|
|
||||||
|
|
||||||
except Exception as e:
|
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_NAME}):",e)
|
||||||
|
|
||||||
@r.get("/appfields")
|
@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:
|
try:
|
||||||
return getfieldsfromkintone(app,env)
|
return getfieldsfromkintone(app,env)
|
||||||
except Exception as e:
|
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_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/allfields")
|
@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:
|
try:
|
||||||
field_resp = getfieldsfromkintone(app,env)
|
field_resp = getfieldsfromkintone(app,env)
|
||||||
form_resp = getformfromkintone(app,env)
|
form_resp = getformfromkintone(app,env)
|
||||||
@@ -592,38 +538,38 @@ async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(get
|
|||||||
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_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/appprocess")
|
@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:
|
try:
|
||||||
return getprocessfromkintone(app,env)
|
return getprocessfromkintone(app,env)
|
||||||
except Exception as e:
|
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_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/alljscss")
|
@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:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
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_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.post("/createapp",)
|
@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:
|
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}
|
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))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
result = r.json()
|
result = r.json()
|
||||||
if result.get("app") != None:
|
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}
|
data = {"apps":[result],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
except Exception as e:
|
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_NAME}->{name}):",e)
|
||||||
|
|
||||||
|
|
||||||
@r.post("/createappfromexcel",)
|
@r.post("/createappfromexcel",)
|
||||||
@@ -762,13 +708,12 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
|
|||||||
try:
|
try:
|
||||||
jscs=[]
|
jscs=[]
|
||||||
files=[]
|
files=[]
|
||||||
files.append(createappjs(env.BASE_URL, app))
|
files.append(createappjs(env.DOMAIN_ID, app))
|
||||||
files.append(getTempPath('alc_runtime.js'))
|
files.append(getTempPath('alc_runtime.js'))
|
||||||
files.append(getTempPath('alc_runtime.css'))
|
files.append(getTempPath('alc_runtime.css'))
|
||||||
for file in files:
|
for file in files:
|
||||||
upload = uploadkintonefiles(file,env)
|
upload = uploadkintonefiles(file,env)
|
||||||
if upload.get('fileKey') != None:
|
if upload.get('fileKey') != None:
|
||||||
print(upload)
|
|
||||||
jscs.append({ file :upload['fileKey']})
|
jscs.append({ file :upload['fileKey']})
|
||||||
appjscs = updateappjscss(app,jscs,env)
|
appjscs = updateappjscss(app,jscs,env)
|
||||||
if appjscs.get("revision") != None:
|
if appjscs.get("revision") != None:
|
||||||
|
|||||||
@@ -1,105 +1,14 @@
|
|||||||
from http import HTTPStatus
|
from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||||
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.db import Base,engine
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.db.crud import *
|
from app.db.crud import *
|
||||||
from app.db.schemas 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.auth import get_current_active_user,get_current_user
|
||||||
from app.core.apiexception import APIException
|
from app.core.apiexception import APIException
|
||||||
from app.core.common import ApiReturnModel,ApiReturnPage
|
|
||||||
#from fastapi_pagination import Page
|
|
||||||
from app.db.cruddb import dbdomain
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import app.core.config as config
|
|
||||||
|
|
||||||
platform_router = r = APIRouter()
|
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),
|
|
||||||
):
|
|
||||||
dbdomain.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 = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
|
|
||||||
if not domain:
|
|
||||||
return ApiReturnModel(data = None)
|
|
||||||
filtered_apps = []
|
|
||||||
platformapps = 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", response_model=AppList, response_model_exclude_none=True)
|
|
||||||
async def apps_update(
|
|
||||||
request: Request,
|
|
||||||
app: AppVersion,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return update_appversion(db, 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/{domainurl}/{appid}", response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def apps_delete(
|
|
||||||
request: Request,
|
|
||||||
domainurl:str,
|
|
||||||
appid: str,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return delete_apps(db, domainurl,appid)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e)
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/appsettings/{id}",
|
"/appsettings/{id}",
|
||||||
response_model=App,
|
response_model=App,
|
||||||
@@ -208,57 +117,48 @@ async def flow_details(
|
|||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/flows/{appid}",
|
"/flows/{appid}",
|
||||||
response_model=List[Flow|None],
|
response_model=List[Flow],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def flow_list(
|
async def flow_list(
|
||||||
request: Request,
|
request: Request,
|
||||||
appid: str,
|
appid: str,
|
||||||
user=Depends(get_current_active_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
|
domain = get_activedomain(db, user.id)
|
||||||
if not domain:
|
|
||||||
return []
|
|
||||||
print("domain=>",domain)
|
print("domain=>",domain)
|
||||||
flows = get_flows_by_app(db, domain.url, appid)
|
flows = get_flows_by_app(db, domain.id, appid)
|
||||||
return flows
|
return flows
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.post("/flow", response_model=Flow|None, response_model_exclude_none=True)
|
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
|
||||||
async def flow_create(
|
async def flow_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
flow: FlowIn,
|
flow: FlowBase,
|
||||||
user=Depends(get_current_active_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
|
domain = get_activedomain(db, user.id)
|
||||||
if not domain:
|
return create_flow(db, domain.id, flow)
|
||||||
return None
|
|
||||||
return create_flow(db, domain.url, flow)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@r.put(
|
||||||
"/flow/{flowid}", response_model=Flow|None, response_model_exclude_none=True
|
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||||
)
|
)
|
||||||
async def flow_edit(
|
async def flow_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
flowid: str,
|
flow: FlowBase,
|
||||||
flow: FlowIn,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
|
return edit_flow(db, flow)
|
||||||
if not domain:
|
|
||||||
return None
|
|
||||||
return edit_flow(db,domain.url, flow,user.id)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
||||||
|
|
||||||
@@ -277,60 +177,49 @@ async def flow_delete(
|
|||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/domains",tags=["Domain"],
|
"/domains/{tenantid}",
|
||||||
response_model=ApiReturnPage[Domain],
|
response_model=List[Domain],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def domain_details(
|
async def domain_details(
|
||||||
request: Request,
|
request: Request,
|
||||||
user=Depends(get_current_active_user),
|
tenantid:str,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if user.is_superuser:
|
domains = get_domains(db,tenantid)
|
||||||
domains = dbdomain.get_domains(db)
|
|
||||||
else:
|
|
||||||
domains = dbdomain.get_domains_by_owner(db,user.id)
|
|
||||||
return domains
|
return domains
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
|
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
|
||||||
|
|
||||||
@r.post("/domain", tags=["Domain"],
|
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
|
||||||
response_model=ApiReturnModel[Domain],
|
|
||||||
response_model_exclude_none=True)
|
|
||||||
async def domain_create(
|
async def domain_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
domain: DomainIn,
|
domain: DomainBase,
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = dbdomain.create_domain(db, domain,user.id))
|
return create_domain(db, domain)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@r.put(
|
||||||
"/domain", tags=["Domain"],
|
"/domain", response_model=Domain, response_model_exclude_none=True
|
||||||
response_model=ApiReturnModel[Domain|None],
|
|
||||||
response_model_exclude_none=True
|
|
||||||
)
|
)
|
||||||
async def domain_edit(
|
async def domain_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
domain: DomainIn,
|
domain: DomainBase,
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = dbdomain.edit_domain(db, domain,user.id))
|
return edit_domain(db, domain)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
"/domain/{id}",tags=["Domain"],
|
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
)
|
||||||
async def domain_delete(
|
async def domain_delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -338,7 +227,7 @@ async def domain_delete(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = dbdomain.delete_domain(db,id))
|
return delete_domain(db,id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
|
||||||
|
|
||||||
@@ -349,104 +238,77 @@ async def domain_delete(
|
|||||||
)
|
)
|
||||||
async def userdomain_details(
|
async def userdomain_details(
|
||||||
request: Request,
|
request: Request,
|
||||||
userId: Optional[int] = Query(None, alias="userId"),
|
user=Depends(get_current_user),
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domains = get_domain(db, userId if userId is not None else user.id)
|
domains = get_domain(db, user.id)
|
||||||
return domains
|
return domains
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',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(
|
@r.post(
|
||||||
"/domain/{userid}",tags=["Domain"],
|
"/domain/{userid}",
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def create_userdomain(
|
async def create_userdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
userid: int,
|
userid: int,
|
||||||
domainid:int ,
|
domainids:list,
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if user.is_superuser:
|
domain = add_userdomain(db, userid,domainids)
|
||||||
domain = dbdomain.add_userdomain(db,user.id,userid,domainid)
|
return domain
|
||||||
else:
|
|
||||||
domain = dbdomain.add_userdomain_by_owner(db,user.id,userid,domainid)
|
|
||||||
return ApiReturnModel(data = domain)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
"/domain/{domainid}/{userid}",tags=["Domain"],
|
"/domain/{domainid}/{userid}", response_model_exclude_none=True
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
)
|
||||||
async def delete_userdomain(
|
async def userdomain_delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
domainid:int,
|
domainid:int,
|
||||||
userid: int,
|
userid: int,
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = dbdomain.delete_userdomain(db,userid,domainid))
|
return delete_userdomain(db, userid,domainid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/defaultdomain",tags=["Domain"],
|
"/activedomain",
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
response_model=Domain,
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def get_defaultuserdomain(
|
async def get_useractivedomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
user=Depends(get_current_active_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data =dbdomain.get_default_domain(db, user.id))
|
domain = get_activedomain(db, user.id)
|
||||||
|
return domain
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while get user({user.id}) defaultdomain:",e)
|
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
||||||
|
|
||||||
@r.put(
|
@r.put(
|
||||||
"/defaultdomain/{domainid}",tags=["Domain"],
|
"/activedomain/{domainid}",
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def update_activeuserdomain(
|
async def update_activeuserdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
domainid:int,
|
domainid:int,
|
||||||
user=Depends(get_current_active_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = dbdomain.set_default_domain(db,user.id,domainid)
|
domain = active_userdomain(db, user.id,domainid)
|
||||||
return ApiReturnModel(data= domain)
|
return domain
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while update user({user.id}) defaultdomain:",e)
|
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",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 dbdomain.get_shareddomain_users(db,user.id,domainid)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:sharedomain',request.url._url,f"Error occurred while get user({user.id}) sharedomain:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/events",
|
"/events",
|
||||||
|
|||||||
@@ -1,186 +1,107 @@
|
|||||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
from fastapi import APIRouter, Request, Depends, Response, encoders
|
||||||
import typing as t
|
import typing as t
|
||||||
from app.core.common import ApiReturnModel,ApiReturnPage
|
|
||||||
from app.core.apiexception import APIException
|
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.db.crud import (
|
from app.db.crud import (
|
||||||
get_allusers,
|
|
||||||
get_users,
|
get_users,
|
||||||
get_user,
|
get_user,
|
||||||
create_user,
|
create_user,
|
||||||
delete_user,
|
delete_user,
|
||||||
edit_user,
|
edit_user,
|
||||||
assign_userrole,
|
|
||||||
get_roles,
|
|
||||||
)
|
)
|
||||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,Permission
|
from app.db.schemas import UserCreate, UserEdit, User, UserOut
|
||||||
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
|
from app.core.auth import get_current_active_user, get_current_active_superuser
|
||||||
from app.db.cruddb import dbuser
|
|
||||||
|
|
||||||
users_router = r = APIRouter()
|
users_router = r = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/users",tags=["User"],
|
"/users",
|
||||||
response_model=ApiReturnPage[User],
|
response_model=t.List[User],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def users_list(
|
async def users_list(
|
||||||
request: Request,
|
response: Response,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_superuser),
|
||||||
):
|
):
|
||||||
try:
|
"""
|
||||||
if current_user.is_superuser:
|
Get all users
|
||||||
users = dbuser.get_users(db)
|
"""
|
||||||
else:
|
users = get_users(db)
|
||||||
users = dbuser.get_users_not_admin(db)
|
# This is necessary for react-admin to work
|
||||||
|
response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||||
return users
|
return users
|
||||||
except Exception as e:
|
|
||||||
raise APIException('user:users',request.url._url,f"Error occurred while get user list",e)
|
|
||||||
|
|
||||||
@r.get("/users/me", tags=["User"],
|
|
||||||
response_model=ApiReturnModel[User],
|
@r.get("/users/me", response_model=User, response_model_exclude_none=True)
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def user_me(current_user=Depends(get_current_active_user)):
|
async def user_me(current_user=Depends(get_current_active_user)):
|
||||||
return ApiReturnModel(data = current_user)
|
"""
|
||||||
|
Get own user
|
||||||
|
"""
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/users/{user_id}",tags=["User"],
|
"/users/{user_id}",
|
||||||
response_model=ApiReturnModel[User|None],
|
response_model=User,
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def user_details(
|
async def user_details(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_superuser),
|
||||||
):
|
):
|
||||||
try:
|
"""
|
||||||
user = dbuser.get(db, user_id)
|
Get any user details
|
||||||
if user:
|
"""
|
||||||
if user.is_superuser and not current_user.is_superuser:
|
user = get_user(db, user_id)
|
||||||
user = None
|
return user
|
||||||
return ApiReturnModel(data = user)
|
# return encoders.jsonable_encoder(
|
||||||
except Exception as e:
|
# user, skip_defaults=True, exclude_none=True,
|
||||||
raise APIException('user:users',request.url._url,f"Error occurred while get user({user_id}) detail:",e)
|
# )
|
||||||
|
|
||||||
|
|
||||||
@r.post("/users", tags=["User"],
|
@r.post("/users", response_model=User, response_model_exclude_none=True)
|
||||||
response_model=ApiReturnModel[User|None],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def user_create(
|
async def user_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
user: UserCreate,
|
user: UserCreate,
|
||||||
db=Depends(get_db),
|
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:
|
Create a new user
|
||||||
return ApiReturnModel(data = None)
|
"""
|
||||||
return ApiReturnModel(data =dbuser.create_user(db, user,current_user.id))
|
return create_user(db, user)
|
||||||
except Exception as e:
|
|
||||||
raise APIException('user:users',request.url._url,f"Error occurred while create user({user.email}):",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@r.put(
|
||||||
"/users/{user_id}", tags=["User"],
|
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||||
response_model=ApiReturnModel[User|None],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
)
|
||||||
async def user_edit(
|
async def user_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user: UserEdit,
|
user: UserEdit,
|
||||||
db=Depends(get_db),
|
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:
|
Update existing user
|
||||||
return ApiReturnModel(data = None)
|
"""
|
||||||
return ApiReturnModel(data = dbuser.edit_user(db,user_id,user,current_user.id))
|
return edit_user(db, user_id, user)
|
||||||
except Exception as e:
|
|
||||||
raise APIException('user:users',request.url._url,f"Error occurred while edit user({user_id}):",e)
|
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
"/users/{user_id}", tags=["User"],
|
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||||
response_model=ApiReturnModel[UserOut|None],
|
|
||||||
response_model_exclude_none=True
|
|
||||||
)
|
)
|
||||||
async def user_delete(
|
async def user_delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_superuser),
|
||||||
):
|
):
|
||||||
try:
|
"""
|
||||||
user = dbuser.get(db,user_id)
|
Delete existing user
|
||||||
if user.is_superuser and not current_user.is_superuser:
|
"""
|
||||||
return ApiReturnModel(data = None)
|
return delete_user(db, user_id)
|
||||||
return ApiReturnModel(data = dbuser.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,
|
|
||||||
user_id:int,
|
|
||||||
roles:t.List[int],
|
|
||||||
db=Depends(get_db)
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return ApiReturnModel(data = dbuser.assign_userrole(db,user_id,roles))
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({user_id}) roles({roles}):",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 = dbuser.get_roles(db)
|
|
||||||
else:
|
|
||||||
if len(current_user.roles)>0:
|
|
||||||
roles = dbuser.get_roles_by_level(db,current_user.roles[0].level)
|
|
||||||
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 = dbuser.get_permissions(db)
|
|
||||||
else:
|
|
||||||
if len(current_user.roles)>0:
|
|
||||||
permissions = dbuser.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)
|
|
||||||
|
|||||||
@@ -1,35 +1,22 @@
|
|||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
import httpx
|
|
||||||
from app.db.schemas import ErrorCreate
|
from app.db.schemas import ErrorCreate
|
||||||
from app.db.session import SessionLocal
|
from app.db.session import SessionLocal
|
||||||
from app.db.crud import create_log
|
from app.db.crud import create_log
|
||||||
|
|
||||||
class APIException(Exception):
|
class APIException(Exception):
|
||||||
def __init__(self, location: str, title: str, content: str, e: Exception):
|
|
||||||
self.detail = str(e)
|
def __init__(self,location:str,title:str,content:str,e:Exception):
|
||||||
self.status_code = 500
|
if(str(e) == ''):
|
||||||
if isinstance(e,httpx.HTTPStatusError):
|
content += e.detail
|
||||||
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.detail = e.detail
|
||||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
self.status_code = e.status_code
|
||||||
content += str(e.detail)
|
|
||||||
else:
|
else:
|
||||||
self.detail = str(e)
|
self.detail = str(e)
|
||||||
self.status_code = 500
|
|
||||||
content += str(e)
|
content += str(e)
|
||||||
|
self.status_code = 500
|
||||||
if len(content) > 5000:
|
if(len(content) > 5000):
|
||||||
content = content[:5000]
|
content =content[0:5000]
|
||||||
|
self.error = ErrorCreate(location=location,title=title,content=content)
|
||||||
self.error = ErrorCreate(location=location, title=title, content=content)
|
|
||||||
super().__init__(self.error)
|
|
||||||
|
|
||||||
def writedblog(exc: APIException):
|
def writedblog(exc: APIException):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
from fastapi.security import SecurityScopes
|
|
||||||
import jwt
|
import jwt
|
||||||
from fastapi import Depends, HTTPException, Request, Security, status
|
from fastapi import Depends, HTTPException, status
|
||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
|
|
||||||
from app.db import models, schemas, session
|
from app.db import models, schemas, session
|
||||||
from app.db.crud import get_user_by_email, create_user,get_user
|
from app.db.crud import get_user_by_email, create_user,get_user
|
||||||
from app.core import security
|
from app.core import security
|
||||||
from app.db.cruddb import dbuser
|
|
||||||
|
|
||||||
async def get_current_user(security_scopes: SecurityScopes,
|
|
||||||
|
async def get_current_user(
|
||||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
||||||
):
|
):
|
||||||
credentials_exception = HTTPException(
|
credentials_exception = HTTPException(
|
||||||
@@ -17,25 +16,17 @@ async def get_current_user(security_scopes: SecurityScopes,
|
|||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||||
)
|
)
|
||||||
id: int = payload.get("sub")
|
id: int = payload.get("sub")
|
||||||
if id is None:
|
if id is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
|
||||||
permissions: str = payload.get("permissions")
|
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)
|
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||||
except PyJWTError:
|
except PyJWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
user = dbuser.get_user(db, token_data.id)
|
user = get_user(db, token_data.id)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -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,13 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
PROJECT_NAME = "KintoneAppBuilder"
|
PROJECT_NAME = "KintoneAppBuilder"
|
||||||
|
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/dev"
|
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
|
||||||
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/postgres"
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/unittest"
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@ktune-prod-db.postgres.database.azure.com/postgres"
|
|
||||||
API_V1_STR = "/k/v1"
|
API_V1_STR = "/k/v1"
|
||||||
|
|
||||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||||
@@ -15,13 +13,12 @@ API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
|||||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||||
|
|
||||||
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
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_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_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:
|
class KINTONE_ENV:
|
||||||
|
|
||||||
BASE_URL = ""
|
BASE_URL = ""
|
||||||
@@ -39,4 +36,4 @@ class KINTONE_ENV:
|
|||||||
self.DOMAIN_ID=domain.id
|
self.DOMAIN_ID=domain.id
|
||||||
self.BASE_URL = domain.url
|
self.BASE_URL = domain.url
|
||||||
self.KINTONE_USER = domain.kintoneuser
|
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"))
|
||||||
@@ -2,10 +2,6 @@ import jwt
|
|||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
|
||||||
import os
|
|
||||||
import base64
|
|
||||||
from app.core import config
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
||||||
|
|
||||||
@@ -33,32 +29,3 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
|||||||
to_encode.update({"exp": expire})
|
to_encode.update({"exp": expire})
|
||||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
return encoded_jwt
|
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 fastapi import HTTPException, status
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from . import models, schemas
|
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):
|
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()
|
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(
|
def get_users(
|
||||||
db: Session
|
db: Session, skip: int = 0, limit: int = 100
|
||||||
) -> t.List[schemas.UserOut]:
|
) -> 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):
|
def create_user(db: Session, user: schemas.UserCreate):
|
||||||
@@ -76,80 +70,6 @@ def edit_user(
|
|||||||
return db_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):
|
def get_appsetting(db: Session, id: int):
|
||||||
app = db.query(models.AppSetting).get(id)
|
app = db.query(models.AppSetting).get(id)
|
||||||
if not app:
|
if not app:
|
||||||
@@ -205,28 +125,16 @@ def get_actions(db: Session):
|
|||||||
return actions
|
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(
|
db_flow = models.Flow(
|
||||||
flowid=flow.flowid,
|
flowid=flow.flowid,
|
||||||
appid=flow.appid,
|
appid=flow.appid,
|
||||||
eventid=flow.eventid,
|
eventid=flow.eventid,
|
||||||
domainurl=domainurl,
|
domainid=domainid,
|
||||||
name=flow.name,
|
name=flow.name,
|
||||||
content=flow.content,
|
content=flow.content
|
||||||
createuserid = userid,
|
|
||||||
updateuserid = userid
|
|
||||||
)
|
)
|
||||||
db.add(db_flow)
|
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.commit()
|
||||||
db.refresh(db_flow)
|
db.refresh(db_flow)
|
||||||
return db_flow
|
return db_flow
|
||||||
@@ -241,19 +149,15 @@ def delete_flow(db: Session, flowid: str):
|
|||||||
|
|
||||||
|
|
||||||
def edit_flow(
|
def edit_flow(
|
||||||
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int
|
db: Session, flow: schemas.FlowBase
|
||||||
) -> schemas.Flow:
|
) -> schemas.Flow:
|
||||||
db_flow = get_flow(db, flow.flowid)
|
db_flow = get_flow(db, flow.flowid)
|
||||||
if not db_flow:
|
if not db_flow:
|
||||||
#見つからない時新規作成
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||||
return create_flow(db,domainurl,flow,userid)
|
update_data = flow.dict(exclude_unset=True)
|
||||||
|
|
||||||
db_flow.appid =flow.appid
|
for key, value in update_data.items():
|
||||||
db_flow.eventid=flow.eventid
|
setattr(db_flow, key, value)
|
||||||
db_flow.domainurl=domainurl
|
|
||||||
db_flow.name=flow.name
|
|
||||||
db_flow.content=flow.content
|
|
||||||
db_flow.updateuserid = userid
|
|
||||||
|
|
||||||
db.add(db_flow)
|
db.add(db_flow)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -269,111 +173,78 @@ def get_flows(db: Session, flowid: str):
|
|||||||
|
|
||||||
def get_flow(db: Session, flowid: str):
|
def get_flow(db: Session, flowid: str):
|
||||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||||
# if not flow:
|
if not flow:
|
||||||
# raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return flow
|
return flow
|
||||||
|
|
||||||
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
def get_flows_by_app(db: Session, domainid: int, appid: str):
|
||||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid)).all()
|
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
|
||||||
if not flows:
|
if not flows:
|
||||||
raise Exception("Data not found")
|
raise Exception("Data not found")
|
||||||
return flows
|
return flows
|
||||||
|
|
||||||
def create_domain(db: Session, domain: schemas.DomainIn,userid:int):
|
def create_domain(db: Session, domain: schemas.DomainBase):
|
||||||
domain.encrypt_kintonepwd()
|
|
||||||
db_domain = models.Domain(
|
db_domain = models.Domain(
|
||||||
tenantid = domain.tenantid,
|
tenantid = domain.tenantid,
|
||||||
name=domain.name,
|
name=domain.name,
|
||||||
url=domain.url,
|
url=domain.url,
|
||||||
is_active=domain.is_active,
|
|
||||||
kintoneuser=domain.kintoneuser,
|
kintoneuser=domain.kintoneuser,
|
||||||
kintonepwd=domain.kintonepwd,
|
kintonepwd=domain.kintonepwd
|
||||||
createuserid = userid,
|
|
||||||
updateuserid = userid,
|
|
||||||
ownerid = domain.ownerid
|
|
||||||
)
|
)
|
||||||
db.add(db_domain)
|
db.add(db_domain)
|
||||||
#add_userdomain(db,userid,db_domain.id)
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_domain)
|
db.refresh(db_domain)
|
||||||
return db_domain
|
return db_domain
|
||||||
|
|
||||||
def delete_domain(db: Session,id: int):
|
def delete_domain(db: Session,id: int):
|
||||||
db_domain = db.query(models.Domain).get(id)
|
db_domain = db.query(models.Domain).get(id)
|
||||||
#if not db_domain:
|
if not db_domain:
|
||||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
if db_domain:
|
|
||||||
db.delete(db_domain)
|
db.delete(db_domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return db_domain
|
||||||
|
|
||||||
|
|
||||||
def edit_domain(
|
def edit_domain(
|
||||||
db: Session, domain: schemas.DomainIn,userid:int
|
db: Session, domain: schemas.DomainBase
|
||||||
) -> schemas.Domain:
|
) -> schemas.Domain:
|
||||||
if domain.kintonepwd != "":
|
|
||||||
domain.encrypt_kintonepwd()
|
|
||||||
db_domain = db.query(models.Domain).get(domain.id)
|
db_domain = db.query(models.Domain).get(domain.id)
|
||||||
if not db_domain:
|
if not db_domain:
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
db_domain.tenantid = domain.tenantid
|
update_data = domain.dict(exclude_unset=True)
|
||||||
db_domain.name=domain.name
|
|
||||||
db_domain.url=domain.url
|
for key, value in update_data.items():
|
||||||
if db_domain.is_active == True and domain.is_active == False:
|
if(key != "id"):
|
||||||
db_userdomains = db.query(models.UserDomain).filter(and_(models.UserDomain.domainid == db_domain.id,models.UserDomain.active == True)).all()
|
setattr(db_domain, key, value)
|
||||||
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
|
|
||||||
db.add(db_domain)
|
db.add(db_domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_domain)
|
db.refresh(db_domain)
|
||||||
return db_domain
|
return db_domain
|
||||||
|
|
||||||
|
def add_userdomain(db: Session, userid:int,domainids:list):
|
||||||
def add_admindomain(db: Session,userid:int,domainid:int):
|
for domainid in domainids:
|
||||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
|
db_domain = models.UserDomain(
|
||||||
if db_domain:
|
userid = userid,
|
||||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
domainid = domainid
|
||||||
db.add(user_domain)
|
)
|
||||||
|
db.add(db_domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
db.refresh(db_domain)
|
||||||
return db_domain
|
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)
|
|
||||||
db.commit()
|
|
||||||
return dbCommits
|
|
||||||
|
|
||||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
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()
|
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
||||||
#if not db_domain:
|
if not db_domain:
|
||||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
if db_domain:
|
|
||||||
db.delete(db_domain)
|
db.delete(db_domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
return True
|
return db_domain
|
||||||
|
|
||||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
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()
|
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||||
# if not db_userdomains:
|
if not db_userdomains:
|
||||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
|
|
||||||
for domain in db_userdomains:
|
for domain in db_userdomains:
|
||||||
if domain.domainid == domainid:
|
if domain.domainid == domainid:
|
||||||
domain.active = True
|
domain.active = True
|
||||||
@@ -381,37 +252,24 @@ def active_userdomain(db: Session, userid: int,domainid: int):
|
|||||||
domain.active = False
|
domain.active = False
|
||||||
db.add(domain)
|
db.add(domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
return db_domain
|
return db_userdomains
|
||||||
|
|
||||||
def get_activedomain(db: Session, userid: int):
|
def get_activedomain(db: Session, userid: int):
|
||||||
# user_domains = (db.query(models.Domain,models.UserDomain.active)
|
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()
|
||||||
# .join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
if not db_domain:
|
||||||
# .filter(models.UserDomain.userid == userid)
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
# .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")
|
|
||||||
return db_domain
|
return db_domain
|
||||||
|
|
||||||
def get_domain(db: Session, userid: str):
|
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()
|
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
||||||
# if not domains:
|
if not domains:
|
||||||
# raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
# for domain in domains:
|
|
||||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
|
||||||
# domain.kintonepwd = decrypted_pwd
|
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
def get_alldomains(db: Session):
|
def get_domains(db: Session,tenantid:str):
|
||||||
domains = db.query(models.Domain).all()
|
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
||||||
return domains
|
if not domains:
|
||||||
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
def get_domains(db: Session,userid:int):
|
|
||||||
domains = db.query(models.Domain).filter(models.Domain.ownerid == userid ).all()
|
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
def get_events(db: Session):
|
def get_events(db: Session):
|
||||||
@@ -420,35 +278,9 @@ def get_events(db: Session):
|
|||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return events
|
return events
|
||||||
|
|
||||||
def get_category(db:Session):
|
|
||||||
categorys=db.query(models.Category).all()
|
|
||||||
return categorys
|
|
||||||
|
|
||||||
def get_eventactions(db: Session,eventid: str):
|
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()
|
#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)
|
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()
|
||||||
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()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not eveactions:
|
if not eveactions:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return eveactions
|
return eveactions
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
from app.db.cruddb.dbuser import dbuser
|
|
||||||
from app.db.cruddb.dbdomain import dbdomain
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
from sqlalchemy import asc, desc
|
|
||||||
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.filter(*and_conditions)
|
|
||||||
if or_conditions:
|
|
||||||
query = query.filter(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, db: Session) -> Query:
|
|
||||||
return db.query(self.model)
|
|
||||||
|
|
||||||
|
|
||||||
def get(self, db: Session, item_id: int) -> Optional[models.Base]:
|
|
||||||
return db.query(self.model).get(item_id)
|
|
||||||
|
|
||||||
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 = db.query(self.model).filter(self.model.id == item_id).first()
|
|
||||||
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 = db.query(self.model).get(item_id)
|
|
||||||
if db_obj:
|
|
||||||
db.delete(db_obj)
|
|
||||||
db.commit()
|
|
||||||
return db_obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_by_conditions(self, db: Session, filters: Optional[dict] = None, sort_by: Optional[str] = None,
|
|
||||||
sort_order: Optional[str] = "asc") -> Query:
|
|
||||||
query = db.query(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,139 +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 dbuserdomain(crudbase):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(model=models.UserDomain)
|
|
||||||
|
|
||||||
def get_userdomain(self,db: Session,userid:int,domainid:int):
|
|
||||||
return super().get_by_conditions(db,{"userid":userid,"domainid":domainid}).first()
|
|
||||||
|
|
||||||
def get_userdomain_by_domainid(self,db: Session,ownerid:int,domainid:int):
|
|
||||||
return super().get_by_conditions(db,{"domainid":domainid})
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_domains(self,db: Session,domainid:int):
|
|
||||||
return super().get_by_conditions(db,{"domainid":domainid,"is_default":True}).all()
|
|
||||||
|
|
||||||
def get_user_default_domain(self,db: Session,userid:int):
|
|
||||||
return super().get_by_conditions(db,{"userid":userid,"is_default":True}).first()
|
|
||||||
|
|
||||||
|
|
||||||
dbuserdomain = dbuserdomain()
|
|
||||||
|
|
||||||
class dbdomain(crudbase):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(model=models.Domain)
|
|
||||||
|
|
||||||
def get_domains(self,db: Session)-> ApiReturnPage[models.Base]:
|
|
||||||
return paginate(super().get_all(db))
|
|
||||||
|
|
||||||
def get_domains_by_owner(self,db: Session,ownerid:int)-> ApiReturnPage[models.Base]:
|
|
||||||
return paginate( super().get_by_conditions(db,{"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()
|
|
||||||
domain.id = None
|
|
||||||
domain.createuserid = userid
|
|
||||||
domain.updateuserid = userid
|
|
||||||
domain.ownerid = userid
|
|
||||||
return super().create(db,domain)
|
|
||||||
#return db_domain
|
|
||||||
|
|
||||||
def delete_domain(self,db: Session,id: int):
|
|
||||||
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_by_conditions(db,{"id":domainid,"is_active":True}).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 add_userdomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut:
|
|
||||||
db_domain = super().get_by_conditions(db,{"id":domainid,"ownerid":ownerid,"is_active":True}).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 =super().get_by_conditions(db,{"id":domainid,"is_active":True}).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:
|
|
||||||
db_default_domain.is_default = False
|
|
||||||
db_default_domain.updateuserid = userid
|
|
||||||
db.add(db_default_domain)
|
|
||||||
if db_userdomain:
|
|
||||||
db_userdomain.is_default = True
|
|
||||||
db_userdomain.updateuserid = userid
|
|
||||||
db.add(db_userdomain)
|
|
||||||
db.commit()
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
def get_shareddomain_users(self,db: Session,ownerid:int,domainid: int) -> ApiReturnPage[models.Base]:
|
|
||||||
users = db.query(models.User).join(models.UserDomain,models.UserDomain.userid == models.User.id).filter(models.UserDomain.domainid ==domainid)
|
|
||||||
return paginate(users)
|
|
||||||
|
|
||||||
dbdomain = dbdomain()
|
|
||||||
@@ -1,92 +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 super().get_by_conditions(db,{"email":email}).first()
|
|
||||||
|
|
||||||
def get_users(self,db: Session) -> ApiReturnPage[models.Base]:
|
|
||||||
return paginate(super().get_all(db))
|
|
||||||
|
|
||||||
def get_users_not_admin(self,db: Session) -> ApiReturnPage[models.Base]:
|
|
||||||
return paginate(super().get_by_conditions(db,{"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 dbrole.get_all(db).all()
|
|
||||||
|
|
||||||
def get_roles_by_level(self,db: Session,level:int) -> t.List[schemas.RoleBase]:
|
|
||||||
return dbrole.get_by_conditions(db,{"level":{"operator":">=","value":level}}).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:
|
|
||||||
db_user.roles.remove(role)
|
|
||||||
for roleid in roles:
|
|
||||||
role = dbrole.get(db,roleid)
|
|
||||||
if role:
|
|
||||||
db_user.roles.append(role)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_user)
|
|
||||||
return db_user
|
|
||||||
|
|
||||||
def get_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]:
|
|
||||||
return dbpermission.get_all(db).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))
|
|
||||||
|
|
||||||
dbuser = dbuser()
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||||
from sqlalchemy.orm import relationship,as_declarative
|
from sqlalchemy.ext.declarative import as_declarative
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from app.db.session import Base
|
|
||||||
from app.core.security import chacha20Decrypt
|
|
||||||
|
|
||||||
@as_declarative()
|
@as_declarative()
|
||||||
class Base:
|
class Base:
|
||||||
@@ -10,21 +8,6 @@ class Base:
|
|||||||
create_time = Column(DateTime, default=datetime.now)
|
create_time = Column(DateTime, default=datetime.now)
|
||||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
|
||||||
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")),
|
|
||||||
)
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
__tablename__ = "user"
|
__tablename__ = "user"
|
||||||
|
|
||||||
@@ -34,57 +17,6 @@ class User(Base):
|
|||||||
hashed_password = Column(String(200), nullable=False)
|
hashed_password = Column(String(200), nullable=False)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superuser = Column(Boolean, default=False)
|
is_superuser = Column(Boolean, default=False)
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = 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 = Column(String(100))
|
|
||||||
description = Column(String(255))
|
|
||||||
level = Column(Integer)
|
|
||||||
users = relationship("User",secondary=userrole,back_populates="roles")
|
|
||||||
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
|
||||||
|
|
||||||
class Permission(Base):
|
|
||||||
__tablename__ = "permission"
|
|
||||||
|
|
||||||
menu = Column(String(100))
|
|
||||||
function = Column(String(255))
|
|
||||||
privilege = Column(String(100))
|
|
||||||
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
|
|
||||||
|
|
||||||
|
|
||||||
class App(Base):
|
|
||||||
__tablename__ = "app"
|
|
||||||
|
|
||||||
domainurl = Column(String(200), nullable=False)
|
|
||||||
appname = Column(String(200), nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
version = Column(Integer)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class AppVersion(Base):
|
|
||||||
__tablename__ = "appversion"
|
|
||||||
|
|
||||||
domainurl = Column(String(200), nullable=False)
|
|
||||||
appname = Column(String(200), nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
version = Column(Integer)
|
|
||||||
versionname = Column(String(200), nullable=False)
|
|
||||||
comment = Column(String(200), nullable=False)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
|
|
||||||
class AppSetting(Base):
|
class AppSetting(Base):
|
||||||
__tablename__ = "appsetting"
|
__tablename__ = "appsetting"
|
||||||
@@ -108,8 +40,6 @@ class Action(Base):
|
|||||||
subtitle = Column(String(500))
|
subtitle = Column(String(500))
|
||||||
outputpoints = Column(String)
|
outputpoints = Column(String)
|
||||||
property = Column(String)
|
property = Column(String)
|
||||||
categoryid = Column(Integer,ForeignKey("category.id"))
|
|
||||||
nosort = Column(Integer)
|
|
||||||
|
|
||||||
class Flow(Base):
|
class Flow(Base):
|
||||||
__tablename__ = "flow"
|
__tablename__ = "flow"
|
||||||
@@ -117,28 +47,9 @@ class Flow(Base):
|
|||||||
flowid = Column(String(100), index=True, nullable=False)
|
flowid = Column(String(100), index=True, nullable=False)
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
appid = Column(String(100), index=True, nullable=False)
|
||||||
eventid = Column(String(100), index=True, nullable=False)
|
eventid = Column(String(100), index=True, nullable=False)
|
||||||
domainurl = Column(String(200))
|
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||||
name = Column(String(200))
|
name = Column(String(200))
|
||||||
content = Column(String)
|
content = Column(String)
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class FlowHistory(Base):
|
|
||||||
__tablename__ = "flowhistory"
|
|
||||||
|
|
||||||
flowid = Column(String(100), index=True, nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
eventid = Column(String(100), index=True, nullable=False)
|
|
||||||
domainurl = Column(String(200))
|
|
||||||
name = Column(String(200))
|
|
||||||
content = Column(String)
|
|
||||||
version = Column(Integer)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class Tenant(Base):
|
class Tenant(Base):
|
||||||
__tablename__ = "tenant"
|
__tablename__ = "tenant"
|
||||||
@@ -149,7 +60,6 @@ class Tenant(Base):
|
|||||||
startdate = Column(DateTime)
|
startdate = Column(DateTime)
|
||||||
enddate = Column(DateTime)
|
enddate = Column(DateTime)
|
||||||
|
|
||||||
|
|
||||||
class Domain(Base):
|
class Domain(Base):
|
||||||
__tablename__ = "domain"
|
__tablename__ = "domain"
|
||||||
|
|
||||||
@@ -158,16 +68,6 @@ class Domain(Base):
|
|||||||
url = Column(String(200), nullable=False)
|
url = Column(String(200), nullable=False)
|
||||||
kintoneuser = Column(String(100), nullable=False)
|
kintoneuser = Column(String(100), nullable=False)
|
||||||
kintonepwd = Column(String(100), nullable=False)
|
kintonepwd = Column(String(100), nullable=False)
|
||||||
is_active = Column(Boolean, default=True)
|
|
||||||
def decrypt_kintonepwd(self):
|
|
||||||
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
|
||||||
return decrypted_pwd
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
ownerid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
owner = relationship('User',foreign_keys=[ownerid])
|
|
||||||
|
|
||||||
|
|
||||||
class UserDomain(Base):
|
class UserDomain(Base):
|
||||||
@@ -175,13 +75,7 @@ class UserDomain(Base):
|
|||||||
|
|
||||||
userid = Column(Integer,ForeignKey("user.id"))
|
userid = Column(Integer,ForeignKey("user.id"))
|
||||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||||
is_default = Column(Boolean, default=False)
|
active = Column(Boolean, default=False)
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = 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 Event(Base):
|
class Event(Base):
|
||||||
__tablename__ = "event"
|
__tablename__ = "event"
|
||||||
@@ -196,7 +90,7 @@ class Event(Base):
|
|||||||
class EventAction(Base):
|
class EventAction(Base):
|
||||||
__tablename__ = "eventaction"
|
__tablename__ = "eventaction"
|
||||||
|
|
||||||
eventid = Column(String(100),ForeignKey("event.eventid"))
|
eventid = Column(Integer,ForeignKey("event.id"))
|
||||||
actionid = Column(Integer,ForeignKey("action.id"))
|
actionid = Column(Integer,ForeignKey("action.id"))
|
||||||
|
|
||||||
|
|
||||||
@@ -207,17 +101,6 @@ class ErrorLog(Base):
|
|||||||
location = Column(String(500))
|
location = Column(String(500))
|
||||||
content = Column(String(5000))
|
content = Column(String(5000))
|
||||||
|
|
||||||
class OperationLog(Base):
|
|
||||||
__tablename__ = "operationlog"
|
|
||||||
|
|
||||||
tenantid = Column(String(100))
|
|
||||||
domainurl = Column(String(200))
|
|
||||||
userid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
operation = Column(String(200))
|
|
||||||
function = Column(String(200))
|
|
||||||
detail = Column(String(200))
|
|
||||||
user = relationship('User')
|
|
||||||
|
|
||||||
class KintoneFormat(Base):
|
class KintoneFormat(Base):
|
||||||
__tablename__ = "kintoneformat"
|
__tablename__ = "kintoneformat"
|
||||||
|
|
||||||
@@ -228,9 +111,3 @@ class KintoneFormat(Base):
|
|||||||
codecolumn =Column(Integer)
|
codecolumn =Column(Integer)
|
||||||
field = Column(String(5000))
|
field = Column(String(5000))
|
||||||
trueformat = Column(String(10))
|
trueformat = Column(String(10))
|
||||||
|
|
||||||
class Category(Base):
|
|
||||||
__tablename__ = "category"
|
|
||||||
|
|
||||||
categoryname = Column(String(20))
|
|
||||||
nosort = Column(Integer)
|
|
||||||
@@ -2,75 +2,46 @@ from pydantic import BaseModel
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from app.core.security import chacha20Decrypt, chacha20Encrypt
|
|
||||||
|
|
||||||
class Base(BaseModel):
|
class Base(BaseModel):
|
||||||
create_time: datetime
|
create_time: datetime
|
||||||
update_time: datetime
|
update_time: datetime
|
||||||
|
|
||||||
|
|
||||||
class Permission(BaseModel):
|
|
||||||
id: int
|
|
||||||
menu:str
|
|
||||||
function:str
|
|
||||||
privilege:str
|
|
||||||
|
|
||||||
class RoleBase(BaseModel):
|
|
||||||
id: int
|
|
||||||
name:str
|
|
||||||
description:str
|
|
||||||
level:int
|
|
||||||
|
|
||||||
|
|
||||||
class RoleWithPermission(RoleBase):
|
|
||||||
permissions:t.List[Permission] = []
|
|
||||||
|
|
||||||
class UserBase(BaseModel):
|
class UserBase(BaseModel):
|
||||||
email: str
|
email: str
|
||||||
is_active: bool = True
|
is_active: bool = True
|
||||||
is_superuser: bool = False
|
is_superuser: bool = False
|
||||||
first_name: str = None
|
first_name: str = None
|
||||||
last_name: str = None
|
last_name: str = None
|
||||||
roles:t.List[RoleBase] = []
|
|
||||||
|
|
||||||
|
|
||||||
class UserOut(BaseModel):
|
class UserOut(UserBase):
|
||||||
id: int
|
pass
|
||||||
email: str
|
|
||||||
is_active: bool = True
|
|
||||||
is_superuser: bool = False
|
|
||||||
first_name: str = None
|
|
||||||
last_name: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
email:str
|
email:str
|
||||||
password: str
|
password: str
|
||||||
hashed_password :str = None
|
|
||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
is_active:bool
|
is_active:bool
|
||||||
is_superuser:bool
|
is_superuser:bool
|
||||||
createuserid:t.Optional[int] = None
|
|
||||||
updateuserid:t.Optional[int] = None
|
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class UserEdit(UserBase):
|
class UserEdit(UserBase):
|
||||||
password: t.Optional[str] = None
|
password: t.Optional[str] = None
|
||||||
hashed_password :str = None
|
|
||||||
updateuserid:t.Optional[int] = None
|
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase):
|
class User(UserBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -78,20 +49,6 @@ class Token(BaseModel):
|
|||||||
access_token: str
|
access_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
||||||
class AppList(Base):
|
|
||||||
domainurl: str
|
|
||||||
appname: str
|
|
||||||
appid:str
|
|
||||||
updateuser: UserOut
|
|
||||||
version:int
|
|
||||||
|
|
||||||
class AppVersion(BaseModel):
|
|
||||||
domainurl: str
|
|
||||||
appname: str
|
|
||||||
versionname: str
|
|
||||||
comment:str
|
|
||||||
appid:str
|
|
||||||
|
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
id:int = 0
|
id:int = 0
|
||||||
@@ -110,7 +67,7 @@ class AppBase(BaseModel):
|
|||||||
class App(AppBase):
|
class App(AppBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +78,7 @@ class Kintone(BaseModel):
|
|||||||
desc: str = None
|
desc: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class Action(BaseModel):
|
class Action(BaseModel):
|
||||||
@@ -131,17 +88,13 @@ class Action(BaseModel):
|
|||||||
subtitle: str = None
|
subtitle: str = None
|
||||||
outputpoints: str = None
|
outputpoints: str = None
|
||||||
property: str = None
|
property: str = None
|
||||||
categoryid: int = None
|
|
||||||
nosort: int
|
class Config:
|
||||||
categoryname : str =None
|
|
||||||
class ConfigDict:
|
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class FlowIn(BaseModel):
|
class FlowBase(BaseModel):
|
||||||
flowid: str
|
flowid: str
|
||||||
# domainurl:str
|
|
||||||
appid: str
|
appid: str
|
||||||
appname:str
|
|
||||||
eventid: str
|
eventid: str
|
||||||
name: str = None
|
name: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
@@ -151,46 +104,20 @@ class Flow(Base):
|
|||||||
flowid: str
|
flowid: str
|
||||||
appid: str
|
appid: str
|
||||||
eventid: str
|
eventid: str
|
||||||
domainurl: str
|
domainid: int
|
||||||
name: str = None
|
name: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class DomainIn(BaseModel):
|
class DomainBase(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
tenantid: str
|
tenantid: str
|
||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
kintonepwd: t.Optional[str] = None
|
kintonepwd: str
|
||||||
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 UserDomain(BaseModel):
|
|
||||||
id: int
|
|
||||||
is_default: bool
|
|
||||||
domain:DomainOut
|
|
||||||
user:UserOut
|
|
||||||
|
|
||||||
class Domain(Base):
|
class Domain(Base):
|
||||||
id: int
|
id: int
|
||||||
@@ -198,14 +125,11 @@ class Domain(Base):
|
|||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
is_active: bool
|
kintonepwd: str
|
||||||
updateuser:UserOut
|
|
||||||
owner:UserOut
|
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class Event(Base):
|
class Event(Base):
|
||||||
id: int
|
id: int
|
||||||
category: str
|
category: str
|
||||||
@@ -215,7 +139,7 @@ class Event(Base):
|
|||||||
mobile: bool
|
mobile: bool
|
||||||
eventgroup: bool
|
eventgroup: bool
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class ErrorCreate(BaseModel):
|
class ErrorCreate(BaseModel):
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker,declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from app.core import config
|
from app.core import config
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from fastapi import FastAPI, Depends
|
from fastapi import FastAPI, Depends
|
||||||
from fastapi_pagination import add_pagination
|
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from app.api.api_v1.routers.kintone import kinton_router
|
from app.api.api_v1.routers.kintone import kinton_router
|
||||||
@@ -15,22 +14,14 @@ from app import tasks
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
import logging
|
import logging
|
||||||
from app.core.apiexception import APIException, writedblog
|
from app.core.apiexception import APIException, writedblog
|
||||||
from app.core.common import ApiReturnError
|
|
||||||
from app.db.crud import create_log
|
from app.db.crud import create_log
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
startup_event()
|
|
||||||
yield
|
|
||||||
|
|
||||||
app = FastAPI(
|
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 = [
|
origins = [
|
||||||
@@ -45,8 +36,6 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
add_pagination(app)
|
|
||||||
|
|
||||||
# @app.middleware("http")
|
# @app.middleware("http")
|
||||||
# async def db_session_middleware(request: Request, call_next):
|
# async def db_session_middleware(request: Request, call_next):
|
||||||
# request.state.db = SessionLocal()
|
# request.state.db = SessionLocal()
|
||||||
@@ -54,7 +43,8 @@ add_pagination(app)
|
|||||||
# request.state.db.close()
|
# request.state.db.close()
|
||||||
# return response
|
# return response
|
||||||
|
|
||||||
def startup_event():
|
@app.on_event("startup")
|
||||||
|
async def startup_event():
|
||||||
log_dir="log"
|
log_dir="log"
|
||||||
if not os.path.exists(log_dir):
|
if not os.path.exists(log_dir):
|
||||||
os.makedirs(log_dir)
|
os.makedirs(log_dir)
|
||||||
@@ -70,7 +60,7 @@ async def api_exception_handler(request: Request, exc: APIException):
|
|||||||
loop.run_in_executor(None,writedblog,exc)
|
loop.run_in_executor(None,writedblog,exc)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=exc.status_code,
|
status_code=exc.status_code,
|
||||||
content= ApiReturnError(msg = f"{exc.detail}").model_dump(),
|
content={"detail": f"{exc.detail}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/api/v1")
|
@app.get("/api/v1")
|
||||||
|
|||||||
@@ -1,145 +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.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}"
|
|
||||||
|
|
||||||
|
|
||||||
@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="function")
|
|
||||||
def test_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
|
|
||||||
with TestClient(app) as test_client:
|
|
||||||
yield test_client
|
|
||||||
|
|
||||||
|
|
||||||
# @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,6 +1,4 @@
|
|||||||
|
def test_read_main(client):
|
||||||
|
response = client.get("/api/v1")
|
||||||
def test_read_main(test_client):
|
|
||||||
response = test_client.get("/api/v1")
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"message": "success"}
|
assert response.json() == {"message": "Hello World"}
|
||||||
|
|||||||
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
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,211 +0,0 @@
|
|||||||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" version="24.9.2">
|
|
||||||
<diagram name="ページ1" id="oKiyF3b1qzm0IX1SNAWJ">
|
|
||||||
<mxGraphModel dx="1395" dy="1078" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-73" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="92" y="645" width="720" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="110" y="605" width="720" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-53" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#36393d;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="140" y="570" width="720" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-52" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#cdeb8b;strokeColor=#36393d;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="170" y="530" width="720" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-51" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcc99;strokeColor=#36393d;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="210" y="490" width="720" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-49" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="90" y="20" width="1080" height="290" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-1" target="Clf12EPbcqlM1huzJpPN-9">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-1" value="タスク開始" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="215" width="90" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-9">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-47" value="DBからクロールのタスクを取得する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-45">
|
|
||||||
<mxGeometry x="0.2449" y="53" relative="1" as="geometry">
|
|
||||||
<mxPoint x="17" y="-44" as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-78">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-8" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#cce5ff;strokeColor=#36393d;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="570" y="-110" width="100" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-9" value="<div style="background-color: rgb(255, 255, 254); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;"><span style="">タスクスケジューラ</span></div>" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="260" y="217.5" width="140" height="45" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;curved=1;startArrow=block;startFill=1;endArrow=none;endFill=0;dashed=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-9">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-18" value="タスクの割り当て" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-17">
|
|
||||||
<mxGeometry x="0.04" relative="1" as="geometry">
|
|
||||||
<mxPoint x="-18" y="30" as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-31">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-15" value="<b>RabbitMQ</b>" style="whiteSpace=wrap;html=1;rounded=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="510" y="380" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-19" target="Clf12EPbcqlM1huzJpPN-21">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-19" value="MQコマンドの受信" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="250" y="560" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-19">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-21" target="Clf12EPbcqlM1huzJpPN-23">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-21" value="データ収集" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="490" y="560" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-15">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-23" value="結果をMQメッセージを変換" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="740" y="560" width="170" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-26" target="Clf12EPbcqlM1huzJpPN-31">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="980" y="390" />
|
|
||||||
<mxPoint x="980" y="350" />
|
|
||||||
<mxPoint x="745" y="350" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-26" value="データ収集結果<br>一時保存" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#eeeeee;strokeColor=#36393d;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="970" y="390" width="100" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-27" value="タスクコマンド" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="1">
|
|
||||||
<mxGeometry x="340" y="450" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-26">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-29" value="コンテンツなど大きい情報を一時保存" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="950" y="500" width="230" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-30" value="収集結果通知MQ" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="740" y="450" width="110" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-31" target="Clf12EPbcqlM1huzJpPN-36">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-31" value="メッセージキューから<div>データ受信</div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="677" y="212.5" width="150" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-34" value="MQのキーで収集結果を取得" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="760" y="350" width="170" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-36" target="Clf12EPbcqlM1huzJpPN-38">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-36" value="<div style="background-color: rgb(255, 255, 254); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; white-space: pre;"><span>データクリーニング<br>と重複排除</span></div>" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;fontSize=11;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="890" y="207.5" width="148" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-43" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-42">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-38" value="データベースに保存" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="904" y="60" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-42" target="Clf12EPbcqlM1huzJpPN-31">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-42" value="収集結果からサブ項目を抽出" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="692" y="60" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-8">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-48" value="クロール結果をDBに登録する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-46">
|
|
||||||
<mxGeometry x="0.1149" y="13" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-50" value="<b>メインプログラム</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="540" y="20" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-54" value="<b>クローラー サブプログラム</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="485" y="490" width="185" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-55" value="<b>SharePointクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="220" y="640" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-60" value="MQ情報" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;align=center;fontSize=14;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1024" y="630" width="160" height="236" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-61" value="MQキー" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="26" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-70" value="クローラーID" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="56" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-62" value="分類" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="86" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-63" value="タイトル" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="116" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-64" value="URL" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="146" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-65" value="サブ項目" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="176" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-66" value="コンテンツ" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
|
||||||
<mxGeometry y="206" width="160" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-67" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-60" target="Clf12EPbcqlM1huzJpPN-23">
|
|
||||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
|
||||||
<mxPoint x="930" y="840" as="sourcePoint" />
|
|
||||||
<mxPoint x="980" y="790" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-68" value="<b>OneNodeクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="180" y="680" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-69" value="<b>EIM クローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="150" y="720" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-72" value="<b>Coredasuクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="755" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-74" value="<b>その他クローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="92" y="795" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-75" value="PostGre SQL" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="585" y="-40" width="90" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-76" value="再帰的な処理" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1030" y="140" width="80" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-77" value="NEO4J" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="570" y="-390" width="100" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-78" value="データ抽出" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="560" y="-250" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Clf12EPbcqlM1huzJpPN-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-78" target="Clf12EPbcqlM1huzJpPN-77">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "k-tune",
|
"name": "kintone-automate",
|
||||||
"version": "2.0.0 Beta",
|
"version": "0.2.0",
|
||||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||||
"productName": "k-tune | kintoneジェネレーター",
|
"productName": "kintone Automate",
|
||||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -12,15 +12,14 @@
|
|||||||
"dev": "quasar dev",
|
"dev": "quasar dev",
|
||||||
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
||||||
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
||||||
"build:dev": "set \"SOURCE_MAP=true\" && quasar build"
|
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
|
||||||
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"jwt-decode": "^4.0.0",
|
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
|
|||||||
@@ -115,8 +115,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
|
|
||||||
// Quasar plugins
|
// Quasar plugins
|
||||||
plugins: [
|
plugins: [
|
||||||
'Notify',
|
'Notify'
|
||||||
'Dialog'
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { boot } from 'quasar/wrappers';
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import {router} from 'src/router';
|
import {router} from 'src/router';
|
||||||
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$axios: AxiosInstance;
|
$axios: AxiosInstance;
|
||||||
@@ -16,10 +15,30 @@ declare module '@vue/runtime-core' {
|
|||||||
// good idea to move this instance creation inside of the
|
// good idea to move this instance creation inside of the
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
|
|
||||||
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
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 }) => {
|
export default boot(({ app }) => {
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
||||||
app.config.globalProperties.$axios = axios;
|
app.config.globalProperties.$axios = axios;
|
||||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
// ^ ^ ^ 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
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|||||||
@@ -3,46 +3,20 @@
|
|||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em" />
|
||||||
</div>
|
</div>
|
||||||
<q-splitter
|
<q-table v-else row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
|
||||||
v-model="splitterModel"
|
|
||||||
style="height: 100%"
|
|
||||||
before-class="tab"
|
|
||||||
unit="px"
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
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"
|
class="action-table"
|
||||||
flat bordered
|
flat bordered
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:filter="filter"></q-table>
|
:filter="filter"
|
||||||
</template>
|
>
|
||||||
</q-splitter>
|
</q-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ref,onMounted,reactive,watchEffect,computed,watch } from 'vue'
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'actionSelect',
|
name: 'actionSelect',
|
||||||
@@ -51,74 +25,30 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
filter:String
|
filter:String
|
||||||
},
|
},
|
||||||
emits:[
|
setup(props) {
|
||||||
"clearFilter"
|
|
||||||
],
|
|
||||||
setup(props,{emit}) {
|
|
||||||
const isLoaded=ref(false);
|
const isLoaded=ref(false);
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||||
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
||||||
];
|
];
|
||||||
const store = useFlowEditorStore();
|
const rows = reactive([])
|
||||||
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;
|
|
||||||
});
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let eventId='';
|
const res =await api.get('api/actions');
|
||||||
if(store.selectedEvent ){
|
res.data.forEach((item,index) =>
|
||||||
eventId=store.selectedEvent.header!=='DELETABLE'? store.selectedEvent.eventId : store.selectedEvent.parentId;
|
{
|
||||||
}
|
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
|
||||||
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]:'';
|
|
||||||
isLoaded.value=true;
|
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 {
|
return {
|
||||||
columns,
|
columns,
|
||||||
|
rows,
|
||||||
selected: ref([]),
|
selected: ref([]),
|
||||||
pagination:ref({
|
pagination:ref({
|
||||||
rowsPerPage:0
|
rowsPerPage:0
|
||||||
}),
|
}),
|
||||||
isLoaded,
|
isLoaded,
|
||||||
tab,
|
|
||||||
actionData,
|
|
||||||
categorys,
|
|
||||||
splitterModel: ref(150),
|
|
||||||
actionForTab
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -128,6 +58,5 @@ export default {
|
|||||||
.action-table{
|
.action-table{
|
||||||
min-height: 10vh;
|
min-height: 10vh;
|
||||||
max-height: 68vh;
|
max-height: 68vh;
|
||||||
min-width: 550px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
||||||
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
||||||
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
:selectedFields="selField.fields"></field-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,10 +92,7 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'single'
|
default: 'single'
|
||||||
},
|
},
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const showSelectApp = ref(false);
|
const showSelectApp = ref(false);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<q-field stack-label full-width label="アプリの説明">
|
<q-field stack-label full-width label="アプリ説明">
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<div class="self-center full-width no-outline" tabindex="0">
|
<div class="self-center full-width no-outline" tabindex="0">
|
||||||
{{ appinfo?.description }}
|
{{ appinfo?.description }}
|
||||||
|
|||||||
@@ -1,58 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<detail-field-table
|
<div class="q-px-xs">
|
||||||
detailField="description"
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
:name="name"
|
<q-spinner color="primary" size="3em" />
|
||||||
:type="type"
|
</div>
|
||||||
:filter="filter"
|
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
|
||||||
:columns="columns"
|
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
|
||||||
:fetchData="fetchApps"
|
:filter="filter" style="max-height: 65vh;">
|
||||||
@update:selected="(item) => { selected = item }"
|
<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>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, PropType } from 'vue';
|
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import DetailFieldTable from './dialog/DetailFieldTable.vue';
|
|
||||||
|
|
||||||
interface IAppDisplay {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
createdate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppSelectBox',
|
name: 'AppSelectBox',
|
||||||
components: {
|
|
||||||
DetailFieldTable
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
filter: String,
|
filter: String,
|
||||||
filterInitRowsFunc: {
|
updateSelectApp: {
|
||||||
type: Function as PropType<(app: IAppDisplay) => boolean>,
|
type: Function
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const selected = ref<IAppDisplay[]>([]);
|
|
||||||
const columns = [
|
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: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
|
||||||
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
||||||
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
||||||
];
|
]
|
||||||
|
const isLoaded = ref(false);
|
||||||
|
const rows: any[] = reactive([]);
|
||||||
|
const selected = ref([])
|
||||||
|
|
||||||
const fetchApps = async () => {
|
watchEffect(()=>{
|
||||||
const res = await api.get('api/v1/allapps');
|
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||||
return res.data.apps.map((item: any) => ({
|
props.updateSelectApp(selected.value[0])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
api.get('api/v1/allapps').then(res => {
|
||||||
|
res.data.apps.forEach((item: any) => {
|
||||||
|
rows.push({
|
||||||
id: item.appId,
|
id: item.appId,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
createdate: dateFormat(item.createdAt)
|
createdate: dateFormat(item.createdAt)
|
||||||
})).filter(app => !props.filterInitRowsFunc || props.filterInitRowsFunc(app));
|
});
|
||||||
};
|
});
|
||||||
|
isLoaded.value = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const dateFormat = (dateStr: string) => {
|
const dateFormat = (dateStr: string) => {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
@@ -64,13 +70,31 @@ export default {
|
|||||||
const minutes = pad(date.getMinutes());
|
const minutes = pad(date.getMinutes());
|
||||||
const seconds = pad(date.getSeconds());
|
const seconds = pad(date.getSeconds());
|
||||||
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
||||||
};
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
fetchApps,
|
rows,
|
||||||
selected
|
selected,
|
||||||
};
|
isLoaded,
|
||||||
|
pagination: ref({
|
||||||
|
rowsPerPage: 10
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</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>
|
|
||||||
@@ -52,12 +52,12 @@ import { useQuasar } from 'quasar';
|
|||||||
const tree = ref(props.conditionTree);
|
const tree = ref(props.conditionTree);
|
||||||
const closeDg = (val:string) => {
|
const closeDg = (val:string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
// if(tree.value.root.children.length===0){
|
if(tree.value.root.children.length===0){
|
||||||
// $q.notify({
|
$q.notify({
|
||||||
// type: 'negative',
|
type: 'negative',
|
||||||
// message: `条件式を設定してください。`
|
message: `条件式を設定してください。`
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
context.emit("update:conditionTree",tree.value);
|
context.emit("update:conditionTree",tree.value);
|
||||||
}
|
}
|
||||||
showflg.value=false;
|
showflg.value=false;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
<q-field labelColor="primary" class="condition-object" :clearable="isSelected" stack-label :dense="true"
|
||||||
:clearable="isSelected">
|
:outlined="true">
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
<!-- <q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||||
{{ selectedObject.name }}
|
{{ selectedObject.name }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||||
{{ selectedObject.name.name }}
|
{{ selectedObject.name.name }}
|
||||||
</q-chip>
|
</q-chip> -->
|
||||||
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
{{ selectedObject?.sharedText }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
||||||
@@ -25,21 +25,17 @@
|
|||||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
<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"
|
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
|
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" />
|
||||||
|
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 ShowDialog from '../ShowDialog.vue';
|
||||||
// import ConditionObjects from '../ConditionObjects.vue';
|
// import ConditionObjects from '../ConditionObjects.vue';
|
||||||
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
||||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||||
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
||||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ConditionObject',
|
name: 'ConditionObject',
|
||||||
components: {
|
components: {
|
||||||
@@ -48,16 +44,8 @@ export default defineComponent({
|
|||||||
// ConditionObjects
|
// ConditionObjects
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
config: {
|
config: {
|
||||||
type: Object as PropType<IDynamicInputConfig>,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
return {
|
return {
|
||||||
canInput: false,
|
canInput: false,
|
||||||
@@ -68,12 +56,6 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
options:
|
|
||||||
{
|
|
||||||
type:Array as PropType< string[]>,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
@@ -81,13 +63,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
// const appDg = ref();
|
// const appDg = ref();
|
||||||
const inputRef=ref();
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const selectedObject = ref(props.modelValue);
|
const selectedObject = ref(props.modelValue);
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
// const sharedText = ref(''); // 共享的文本状态
|
// const sharedText = ref(''); // 共享的文本状态
|
||||||
const isSelected = computed(() => {
|
const isSelected = computed(() => {
|
||||||
return selectedObject.value?.sharedText !== '';
|
return selectedObject?.value?.sharedText !== '';
|
||||||
});
|
});
|
||||||
// const isSelected = computed(()=>{
|
// const isSelected = computed(()=>{
|
||||||
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
||||||
@@ -104,7 +85,6 @@ export default defineComponent({
|
|||||||
const closeDg = (val: string) => {
|
const closeDg = (val: string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
// selectedObject.value = appDg.value.selected[0];
|
// selectedObject.value = appDg.value.selected[0];
|
||||||
selectedObject.value = inputRef.value.selectedObjectRef
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,7 +93,6 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputRef,
|
|
||||||
store,
|
store,
|
||||||
// appDg,
|
// appDg,
|
||||||
show,
|
show,
|
||||||
|
|||||||
@@ -69,19 +69,17 @@
|
|||||||
<div class="row no-wrap items-center q-my-xs">
|
<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"/>
|
<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>
|
<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.value" :config="rightDynamicItemConfig" class="col-4"/>
|
||||||
:options="objectValueOptions(prop.node?.object?.options)"
|
|
||||||
/>
|
|
||||||
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" 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)"
|
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
||||||
v-model="prop.node.value"
|
v-model="prop.node.value"
|
||||||
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
|
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"
|
v-model="prop.node.value"
|
||||||
:options="objectValueOptions(prop.node.object.options)"
|
:options="objectValueOptions(prop.node.object.options)"
|
||||||
clearable
|
clearable
|
||||||
value-key="index"
|
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-btn flat round dense icon="more_horiz" size="sm" >
|
||||||
<q-menu auto-close anchor="top right">
|
<q-menu auto-close anchor="top right">
|
||||||
<q-list>
|
<q-list>
|
||||||
@@ -120,7 +118,6 @@ import { finished } from 'stream';
|
|||||||
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
||||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||||
import ConditionObject from './ConditionObject.vue';
|
import ConditionObject from './ConditionObject.vue';
|
||||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
|
||||||
export default defineComponent( {
|
export default defineComponent( {
|
||||||
name: 'NodeCondition',
|
name: 'NodeCondition',
|
||||||
components: {
|
components: {
|
||||||
@@ -148,18 +145,17 @@ export default defineComponent( {
|
|||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const operatorSet = inject<Array<any>>('Operator')
|
const operator = inject('Operator')
|
||||||
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
|
const operators =computed(()=>{
|
||||||
|
return operator ? operator : Object.values(Operator);
|
||||||
|
});
|
||||||
const tree = reactive(props.conditionTree);
|
const tree = reactive(props.conditionTree);
|
||||||
|
|
||||||
const conditionString = computed(()=>{
|
const conditionString = computed(()=>{
|
||||||
return tree.buildConditionString(tree.root);
|
return tree.buildConditionString(tree.root);
|
||||||
});
|
});
|
||||||
|
|
||||||
const objectValueOptions=(options:any):any[]|null=>{
|
const objectValueOptions=(options:any):any[]=>{
|
||||||
if(!options){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const opts:any[] =[];
|
const opts:any[] =[];
|
||||||
Object.keys(options).forEach((key) =>
|
Object.keys(options).forEach((key) =>
|
||||||
{
|
{
|
||||||
@@ -226,14 +222,13 @@ export default defineComponent( {
|
|||||||
ticked.value=[];
|
ticked.value=[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const expanded=computed(()=>tree.getGroups(tree.root));
|
const expanded=computed(()=>tree.getGroups(tree.root));
|
||||||
// addCondition(tree.root);
|
// addCondition(tree.root);
|
||||||
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
|
|
||||||
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
leftDynamicItemConfig,
|
leftDynamicItemConfig :inject('leftDynamicItemConfig'),
|
||||||
rightDynamicItemConfig,
|
rightDynamicItemConfig:inject('rightDynamicItemConfig'),
|
||||||
showingCondition,
|
showingCondition,
|
||||||
conditionString,
|
conditionString,
|
||||||
tree,
|
tree,
|
||||||
@@ -265,12 +260,10 @@ export default defineComponent( {
|
|||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator{
|
.operator{
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:url="uploadUrl"
|
:url="uploadUrl"
|
||||||
:label="title"
|
:label="title"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
accept=".xlsx"
|
accept=".csv,.xlsx"
|
||||||
v-on:rejected="onRejected"
|
v-on:rejected="onRejected"
|
||||||
v-on:uploaded="onUploadFinished"
|
v-on:uploaded="onUploadFinished"
|
||||||
v-on:failed="onFailed"
|
v-on:failed="onFailed"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { createUploaderComponent, useQuasar } from 'quasar';
|
import { createUploaderComponent, useQuasar } from 'quasar';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
const $q=useQuasar();
|
const $q=useQuasar();
|
||||||
@@ -30,7 +30,7 @@ import { ref } from 'vue';
|
|||||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: `Excelファイルを選択してください。`
|
message: `CSVおよびExcelファイルを選択してください。`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,28 +52,14 @@ import { ref } from 'vue';
|
|||||||
}, 2000);
|
}, 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 ファイルアップロード失敗時の処理
|
* @param info ファイルアップロード失敗時の処理
|
||||||
*/
|
*/
|
||||||
function onFailed({files,xhr}:{files: readonly any[],xhr:XMLHttpRequest}){
|
function onFailed({files,xhr}:{files: readonly any[],xhr:any}){
|
||||||
let msg ="ファイルアップロードが失敗しました。";
|
let msg ="ファイルアップロードが失敗しました。";
|
||||||
if(xhr && xhr.status){
|
if(xhr && xhr.status){
|
||||||
const detail = getResponseError(xhr);
|
msg=`${msg} (${xhr.status }:${xhr.statusText})`
|
||||||
msg=`${msg} (${xhr.status }:${detail})`
|
|
||||||
}
|
}
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type:"negative",
|
type:"negative",
|
||||||
@@ -88,7 +74,7 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title:"設計書から導入する(Excel)",
|
title:"設計書から導入する(csv or excel)",
|
||||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,78 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table :loading="loading" :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows">
|
<q-table :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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ref,onMounted,reactive, computed } from 'vue'
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DomainSelect',
|
name: 'DomainSelect',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String
|
||||||
filterInitRowsFunc: {
|
|
||||||
type: Function,
|
|
||||||
},
|
},
|
||||||
},
|
setup() {
|
||||||
setup(props) {
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
|
||||||
const loading = ref(true);
|
|
||||||
const inactiveRowClass = (row) => row.domainActive ? '' : 'inactive-row';
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'id'},
|
||||||
{ name: 'name', align: 'left', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass },
|
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
||||||
{ name: 'url', label: 'URL', field: 'url',align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
||||||
{ name: 'user', label: 'アカウント', field: 'user',align: 'left', classes: inactiveRowClass },
|
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
||||||
{ name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
||||||
]
|
]
|
||||||
const rows = reactive([]);
|
const rows = reactive([])
|
||||||
|
onMounted( () => {
|
||||||
onMounted(() => {
|
api.get(`api/domains/1`).then(res =>{
|
||||||
loading.value = true;
|
res.data.forEach((item) =>
|
||||||
api.get(`api/domains`).then(res =>{
|
{
|
||||||
res.data.data.forEach((data) => {
|
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
loading,
|
|
||||||
currentDomainId,
|
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
selected: ref([]),
|
selected: ref([]),
|
||||||
@@ -81,11 +40,3 @@ export default {
|
|||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
.q-table td.inactive-row {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
.q-table .content-box {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,21 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
class="customized-disabled-btn"
|
color="primay"
|
||||||
push
|
push
|
||||||
flat
|
flat
|
||||||
no-caps
|
no-caps
|
||||||
icon="share"
|
icon="share"
|
||||||
size="md"
|
size="md"
|
||||||
:label="userStore.currentDomain.domainName"
|
:label="userStore.currentDomain.domainName"
|
||||||
:disable-dropdown="true"
|
|
||||||
dropdown-icon='none'
|
|
||||||
:disable="true"
|
|
||||||
>
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item :active="isCurrentDomain(domain)" active-class="active-domain-item" v-for="domain in domains" :key="domain.domainName"
|
<q-item v-for="domain in domains" :key="domain.domainName"
|
||||||
clickable v-close-popup @click="onItemClick(domain)">
|
clickable v-close-popup @click="onItemClick(domain)">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon name="share" size="sm" :color="isCurrentDomain(domain) ? 'orange': ''" text-color="white"></q-icon>
|
<q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{domain.domainName}}</q-item-label>
|
<q-item-label>{{domain.domainName}}</q-item-label>
|
||||||
@@ -27,46 +24,20 @@
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" >
|
<script setup lang="ts" >
|
||||||
import { IDomainInfo } from 'src/types/DomainTypes';
|
import { IDomainInfo } from 'src/types/ActionTypes';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
||||||
import { useDomainStore } from 'src/stores/useDomainStore';
|
import { ref } from 'vue';
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
const userStore = useAuthStore();
|
const userStore = useAuthStore();
|
||||||
const domainStore = useDomainStore();
|
const domains = ref<IDomainInfo[]>([]);
|
||||||
const route = useRoute()
|
|
||||||
const domains = computed(() => domainStore.userDomains);
|
|
||||||
|
|
||||||
(async ()=>{
|
(async ()=>{
|
||||||
await domainStore.loadUserDomains();
|
domains.value = await userStore.getUserDomains();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const isUnclickable = computed(()=>{
|
|
||||||
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isCurrentDomain=(domain:IDomainInfo)=>{
|
|
||||||
return domain.id === userStore.currentDomain.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onItemClick=(domain:IDomainInfo)=>{
|
const onItemClick=(domain:IDomainInfo)=>{
|
||||||
console.log(domain);
|
console.log(domain);
|
||||||
userStore.setCurrentDomain(domain);
|
userStore.setCurrentDomain(domain);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.q-btn.disabled.customized-disabled-btn {
|
|
||||||
opacity: 1 !important;
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-item.active-domain-item {
|
|
||||||
color: inherit;
|
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-btn.disabled.customized-disabled-btn * {
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,30 +2,13 @@
|
|||||||
<div class="q-mx-md" style="max-width: 600px;">
|
<div class="q-mx-md" style="max-width: 600px;">
|
||||||
<!-- <q-card> -->
|
<!-- <q-card> -->
|
||||||
<div class="q-mb-md">
|
<div class="q-mb-md">
|
||||||
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
|
<q-input ref="inputRef" outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||||
outlined dense debounce="200" @update:model-value="updateSharedText"
|
v-model="sharedText" :readonly="!canInput">
|
||||||
v-model="sharedText" :readonly="!canInputFlag" autogrow>
|
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</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>
|
||||||
|
|
||||||
<div class="row q-gutter-sm">
|
<div class="row q-gutter-sm">
|
||||||
@@ -51,12 +34,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
|
import { ref, inject, watchEffect, defineComponent } from 'vue';
|
||||||
import FieldAdd from './FieldAdd.vue';
|
import FieldAdd from './FieldAdd.vue';
|
||||||
import VariableAdd from './VariableAdd.vue';
|
import VariableAdd from './VariableAdd.vue';
|
||||||
// import FunctionAdd from './FunctionAdd.vue';
|
// import FunctionAdd from './FunctionAdd.vue';
|
||||||
import ShowDialog from '../ShowDialog.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({
|
export default defineComponent({
|
||||||
name: 'DynamicItemInput',
|
name: 'DynamicItemInput',
|
||||||
@@ -67,21 +56,18 @@ export default defineComponent({
|
|||||||
ShowDialog
|
ShowDialog
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
canInput: {
|
// canInput: {
|
||||||
type: Boolean,
|
// type: Boolean,
|
||||||
default: false
|
// default: false
|
||||||
},
|
// },
|
||||||
appId: {
|
appId: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
selectedObject: {
|
selectedObject: {
|
||||||
default: {}
|
default: {}
|
||||||
},
|
},
|
||||||
options:{
|
|
||||||
type:Array as PropType< string[]>
|
|
||||||
},
|
|
||||||
buttonsConfig: {
|
buttonsConfig: {
|
||||||
type: Array as PropType<IButtonConfig[]>,
|
type: Array as () => ButtonConfig[],
|
||||||
default: () => [
|
default: () => [
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
||||||
]
|
]
|
||||||
@@ -91,70 +77,64 @@ export default defineComponent({
|
|||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const currentDialogName = ref('');
|
const currentDialogName = ref('');
|
||||||
const selectedObjectRef = ref(props.selectedObject);
|
|
||||||
const currentComponent = ref('FieldAdd');
|
const currentComponent = ref('FieldAdd');
|
||||||
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
||||||
const inputRef = ref();
|
const inputRef = ref();
|
||||||
const canInputFlag = ref(props.canInput);
|
const canInput = ref(true);
|
||||||
const editable = ref(false);
|
const editable = ref(false);
|
||||||
|
|
||||||
const openDialog = (button: IButtonConfig) => {
|
const openDialog = (button: ButtonConfig) => {
|
||||||
currentDialogName.value = button.label;
|
currentDialogName.value = button.label;
|
||||||
currentComponent.value = button.type;
|
currentComponent.value = button.type;
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
editable.value = canInputFlag.value;
|
editable.value = button.editable ?? true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeDialog = () => {
|
const closeDialog = () => {
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = (value:any) => {
|
const handleSelect = (value) => {
|
||||||
|
// 获取当前光标位置
|
||||||
|
// const cursorPosition = inputRef.value.getNativeElement().selectionStart;
|
||||||
|
// if (cursorPosition === undefined || cursorPosition === 0) {
|
||||||
|
sharedText.value = `${value._t}`;
|
||||||
|
// } 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) {
|
if (value && value._t && (value._t as string).length > 0) {
|
||||||
canInputFlag.value = editable.value;
|
canInput.value = editable.value;
|
||||||
}
|
}
|
||||||
selectedObjectRef.value={ sharedText: value._t, ...value };
|
emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||||
sharedText.value = `${value._t}`;
|
|
||||||
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSharedText = () => {
|
const clearSharedText = () => {
|
||||||
sharedText.value = '';
|
sharedText.value = '';
|
||||||
selectedObjectRef.value={};
|
canInput.value = true;
|
||||||
canInputFlag.value = true;
|
emit('update:selectedObject', {});
|
||||||
// emit('update:selectedObject', {});
|
|
||||||
}
|
}
|
||||||
const updateSharedText = (value:string) => {
|
const updateSharedText = (value) => {
|
||||||
sharedText.value = value;
|
sharedText.value = value;
|
||||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
emit('update:selectedObject', { ...props.selectedObject, sharedText: value });
|
||||||
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
|
||||||
}
|
}
|
||||||
const setValue=(value:string)=>{
|
|
||||||
sharedText.value = value;
|
|
||||||
if(selectedObjectRef.value.sharedText!==value){
|
|
||||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const optionsRef=ref(props.options);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filter,
|
filter,
|
||||||
dialogVisible,
|
dialogVisible,
|
||||||
currentDialogName,
|
currentDialogName,
|
||||||
currentComponent,
|
currentComponent,
|
||||||
canInputFlag,
|
canInput,
|
||||||
openDialog,
|
openDialog,
|
||||||
closeDialog,
|
closeDialog,
|
||||||
handleSelect,
|
handleSelect,
|
||||||
clearSharedText,
|
clearSharedText,
|
||||||
updateSharedText,
|
updateSharedText,
|
||||||
setValue,
|
|
||||||
sharedText,
|
sharedText,
|
||||||
inputRef,
|
inputRef
|
||||||
optionsRef,
|
|
||||||
selectedObjectRef
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
tag="a"
|
tag="a"
|
||||||
:target="target?target:'_blank'"
|
:target="target?target:'_blank'"
|
||||||
:href="link"
|
:href="link"
|
||||||
:disable="disable"
|
|
||||||
v-if="!isSeparator"
|
v-if="!isSeparator"
|
||||||
>
|
>
|
||||||
<q-item-section
|
<q-item-section
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator
|
<q-separator
|
||||||
class="q-my-sm"
|
|
||||||
v-if="isSeparator"
|
v-if="isSeparator"
|
||||||
inset
|
inset
|
||||||
/>
|
/>
|
||||||
@@ -34,7 +32,6 @@ export interface EssentialLinkProps {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
isSeparator?: boolean;
|
isSeparator?: boolean;
|
||||||
target?:string;
|
target?:string;
|
||||||
disable?:boolean;
|
|
||||||
}
|
}
|
||||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
withDefaults(defineProps<EssentialLinkProps>(), {
|
||||||
caption: '',
|
caption: '',
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||||
@update:selected="$emit('update:modelValue', $event)"
|
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
|
||||||
:filter="filter"
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
:pagination="pagination"
|
|
||||||
style="max-height: 55vh;"/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useAsyncState } from '@vueuse/core';
|
import { useAsyncState } from '@vueuse/core';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { computed ,Prop,PropType,ref} from 'vue';
|
import { computed } from 'vue';
|
||||||
import {IField} from 'src/types/ComponentTypes';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldList',
|
name: 'FieldList',
|
||||||
props: {
|
props: {
|
||||||
fields: Array as PropType<IField[]>,
|
fields: Array,
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
appId: Number,
|
appId: Number,
|
||||||
@@ -39,29 +33,24 @@ export default {
|
|||||||
|
|
||||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||||
if (props.fields && Object.keys(props.fields).length > 0) {
|
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 {
|
} else {
|
||||||
return api.get('api/v1/appfields', {
|
return api.get('api/v1/appfields', {
|
||||||
params: {
|
params: {
|
||||||
app: props.appId
|
app: props.appId
|
||||||
}
|
}
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
const fields = res.data.properties;
|
console.log(res);
|
||||||
return Object.values(fields).map((f:any) => ({ name: f.label, objectType: 'field', ...f }));
|
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
// selected: ref([]),
|
// selected: ref([]),
|
||||||
isLoaded,
|
isLoaded
|
||||||
pagination: ref({
|
|
||||||
rowsPerPage: 25,
|
|
||||||
sortBy: 'name',
|
|
||||||
descending: false,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em" />
|
||||||
</div>
|
</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;"/>
|
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -26,7 +26,7 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
selectedFields:{
|
selectedFields:{
|
||||||
type:Array ,
|
type:Array,
|
||||||
default:()=>[]
|
default:()=>[]
|
||||||
},
|
},
|
||||||
fieldTypes:{
|
fieldTypes:{
|
||||||
@@ -37,10 +37,6 @@ export default {
|
|||||||
updateSelectFields: {
|
updateSelectFields: {
|
||||||
type: Function
|
type: Function
|
||||||
},
|
},
|
||||||
blackListLabel: {
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const isLoaded = ref(false);
|
const isLoaded = ref(false);
|
||||||
@@ -48,16 +44,16 @@ export default {
|
|||||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
||||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||||
];
|
]
|
||||||
const pageSetting = ref({
|
const pageSetting = ref({
|
||||||
sortBy: 'name',
|
sortBy: 'desc',
|
||||||
descending: false,
|
descending: false,
|
||||||
page: 1,
|
page: 1,
|
||||||
rowsPerPage: props.not_page ? 0 : 25
|
rowsPerPage: props.not_page ? 0 : 5
|
||||||
// rowsNumber: xx if getting data from a server
|
// rowsNumber: xx if getting data from a server
|
||||||
});
|
});
|
||||||
const rows = reactive([]);
|
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 () => {
|
onMounted(async () => {
|
||||||
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
||||||
@@ -66,25 +62,16 @@ export default {
|
|||||||
app: props.appId
|
app: props.appId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let fields = Object.values(res.data.properties);
|
let fields = res.data.properties;
|
||||||
for (const index in fields) {
|
Object.keys(fields).forEach((key) => {
|
||||||
const fld = fields[index]
|
const fld = fields[key];
|
||||||
if(props.blackListLabel.length > 0){
|
|
||||||
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
|
|
||||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
rows.push({ name: fld.label || fld.code, ...fld });
|
||||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
rows.push({ 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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
isLoaded.value = true;
|
isLoaded.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,191 +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>「{{domain.name}}」のドメイン利用権限設定</q-toolbar-title>
|
|
||||||
<q-btn flat round dense icon="close" @click="close" />
|
|
||||||
</q-toolbar>
|
|
||||||
|
|
||||||
<q-card-section class="q-mx-md " >
|
|
||||||
<q-select
|
|
||||||
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"
|
|
||||||
:display-value="canSharedUserFilter?`${canSharedUserFilter.fullName} (${canSharedUserFilter.email})`:''">
|
|
||||||
|
|
||||||
<template v-slot:after>
|
|
||||||
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplay)" />
|
|
||||||
</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>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" title="ドメイン利用権限を持つユーザー">
|
|
||||||
<template v-slot:actions="{ row }">
|
|
||||||
<q-btn title="解除" flat color="primary" padding="xs" size="1em" :loading="row.id == removingUser?.id" 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="close" />
|
|
||||||
<q-btn flat label="キャンセル" @click="close" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch, onMounted } from 'vue';
|
|
||||||
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
|
||||||
import { IUser, IUserDisplay } from '../../types/UserTypes';
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
modelValue: boolean;
|
|
||||||
domain: IDomainOwnerDisplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: boolean): void;
|
|
||||||
(e: 'close'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const addLoading = ref(false);
|
|
||||||
const removingUser = ref<IUserDisplay>();
|
|
||||||
const loading = ref(true);
|
|
||||||
const visible = ref(props.modelValue);
|
|
||||||
|
|
||||||
const allUsers = ref<IUserDisplay[]>([]);
|
|
||||||
const sharedUsers = ref<IUserDisplay[]>([]);
|
|
||||||
const sharedUsersIdSet = new Set<number>();
|
|
||||||
|
|
||||||
const canSharedUsers = ref<IUserDisplay[]>([]);
|
|
||||||
const canSharedUserFilter = ref<IUserDisplay>();
|
|
||||||
const canSharedUserFilteredOptions = ref<IUserDisplay[]>([]);
|
|
||||||
|
|
||||||
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
|
|
||||||
if (newValue) {
|
|
||||||
await loadShared();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => visible.value,
|
|
||||||
(newValue) => {
|
|
||||||
emit('update:modelValue', newValue);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
emit('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const shareTo = async (user: IUserDisplay) => {
|
|
||||||
addLoading.value = true;
|
|
||||||
loading.value = true;
|
|
||||||
await api.post(`api/domain/${user.id}?domainid=${props.domain.id}`)
|
|
||||||
await loadShared();
|
|
||||||
canSharedUserFilter.value = undefined;
|
|
||||||
loading.value = false;
|
|
||||||
addLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeShareTo = async (user: IUserDisplay) => {
|
|
||||||
removingUser.value = user;
|
|
||||||
loading.value = true;
|
|
||||||
await api.delete(`api/domain/${props.domain.id}/${user.id}`)
|
|
||||||
await loadShared();
|
|
||||||
loading.value = false;
|
|
||||||
removingUser.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadShared = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
sharedUsersIdSet.clear();
|
|
||||||
|
|
||||||
const { data } = await api.get(`/api/domainshareduser/${props.domain.id}`);
|
|
||||||
sharedUsers.value = data.data.map((item: IUser) => {
|
|
||||||
const val = itemToDisplay(item);
|
|
||||||
sharedUsersIdSet.add(val.id);
|
|
||||||
return val;
|
|
||||||
});
|
|
||||||
|
|
||||||
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
|
|
||||||
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getUsers();
|
|
||||||
})
|
|
||||||
|
|
||||||
const getUsers = async () => {
|
|
||||||
if (Object.keys(allUsers.value).length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
} as IUserDisplay
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.dialog-content {
|
|
||||||
width: 60vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
.q-select {
|
|
||||||
min-width: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,45 +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-slot:body-cell-actions="props">
|
|
||||||
<q-td :props="props">
|
|
||||||
<slot name="actions" :row="props.row"></slot>
|
|
||||||
</q-td>
|
|
||||||
</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: 'actions', label: '', field: 'actions', sortable: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
const filter = ref('');
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
|
||||||
</script>
|
|
||||||
@@ -4,16 +4,16 @@
|
|||||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||||
<q-toolbar class="bg-grey-4">
|
<q-toolbar class="bg-grey-4">
|
||||||
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
||||||
<q-space v-if="$slots.toolbar"></q-space>
|
<q-space></q-space>
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
<q-card-section class="q-mt-md" :style="sectionStyle">
|
<q-card-section class="q-mt-md" :style="sectionStyle">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
<q-card-actions align="right" class="text-primary">
|
||||||
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||||
<q-btn flat label="キャンセル" :disable="okBtnLoading" v-close-popup @click="CloseDialogue('Cancel')" />
|
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -29,20 +29,10 @@ export default {
|
|||||||
width:String,
|
width:String,
|
||||||
height:String,
|
height:String,
|
||||||
minWidth:String,
|
minWidth:String,
|
||||||
minHeight:String,
|
minHeight:String
|
||||||
okBtnLoading:Boolean,
|
|
||||||
okBtnAutoClose:{
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
disableBtn:{
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'close',
|
'close'
|
||||||
'update:visible'
|
|
||||||
],
|
],
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const CloseDialogue = (val) => {
|
const CloseDialogue = (val) => {
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-btn flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
|
|
||||||
<q-menu>
|
|
||||||
<q-list dense :style="'min-width:' + minWidth ">
|
|
||||||
<template v-for="(item, index) in actions" :key="index" >
|
|
||||||
<q-item v-if="isAction(item)" :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-item>
|
|
||||||
<q-separator v-else />
|
|
||||||
</template>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType } from 'vue';
|
|
||||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
|
||||||
|
|
||||||
interface Action {
|
|
||||||
label: string;
|
|
||||||
icon?: string;
|
|
||||||
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
|
|
||||||
},
|
|
||||||
minWidth: {
|
|
||||||
type: String,
|
|
||||||
default: '100px'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
type: Array as PropType<MenuItem[]>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isAction(item: MenuItem): item is Action {
|
|
||||||
return !('separator' in item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.q-table tr > td:last-child .action-menu {
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-table tr:hover > td:last-child .action-menu {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,69 +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 v-if="!isOwnerFunc(item.owner.id)" class="col-auto">
|
|
||||||
<!-- <q-badge color="secondary" text-color="white" align="middle" class="q-mb-xs" label="他人の所有" /> -->
|
|
||||||
<q-chip square color="secondary" text-color="white" icon="people" label="他人の所有" size="sm" />
|
|
||||||
</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">{{ 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,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,88 +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-slot:[`body-cell-${detailField}`]="props">
|
|
||||||
<q-td :props="props">
|
|
||||||
<q-scroll-area 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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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({
|
|
||||||
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,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<detail-field-table
|
|
||||||
detailField="comment"
|
|
||||||
type="single"
|
|
||||||
:columns="columns"
|
|
||||||
:fetchData="fetchVersionHistory"
|
|
||||||
@update:selected="(item) => { selected = item }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType, ref, watch } from 'vue';
|
|
||||||
import { IAppDisplay, IAppVersion } 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';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'VersionHistory',
|
|
||||||
components: {
|
|
||||||
DetailFieldTable
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
app: {
|
|
||||||
type: Object as PropType<IAppDisplay>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const selected = ref<IAppVersion[]>([]);
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ name: 'version', label: 'ID', 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: IAppVersion) => row.creator.fullName, align: 'left', sortable: true },
|
|
||||||
{ name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
|
|
||||||
{ name: 'updater', label: '更新者', field: (row: IAppVersion) => 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 toUserDisaplay = (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/apps/${props.app.id}/versions`);
|
|
||||||
return data.data.map((item: any) => ({
|
|
||||||
id: item.id,
|
|
||||||
version: item.version,
|
|
||||||
appid: item.appid,
|
|
||||||
name: item.name,
|
|
||||||
comment: item.comment,
|
|
||||||
updater: toUserDisaplay(item.updateuser),
|
|
||||||
updateTime: formatDate(item.updatetime),
|
|
||||||
creator: toUserDisaplay(item.createuser),
|
|
||||||
createTime: formatDate(item.createtime),
|
|
||||||
} as IAppVersion));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
fetchVersionHistory,
|
|
||||||
columns,
|
|
||||||
selected
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-input
|
|
||||||
v-model="versionInfo.name"
|
|
||||||
filled
|
|
||||||
autofocus
|
|
||||||
label="バージョン名"
|
|
||||||
:rules="[(val) => !val || val.length <= 30 || '30字以内で入力ください']"
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
v-model="versionInfo.desc"
|
|
||||||
filled
|
|
||||||
type="textarea"
|
|
||||||
:rules="[(val) => !val || val.length <= 80 || '80字以内で入力ください']"
|
|
||||||
label="説明"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch, defineProps, defineEmits } from 'vue';
|
|
||||||
import { QInput } from 'quasar';
|
|
||||||
import { IVersionInfo } from 'src/types/AppTypes';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue: IVersionInfo;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const defaultTitle = `${new Date().toLocaleString()}`;
|
|
||||||
|
|
||||||
const versionInfo = ref({
|
|
||||||
...props.modelValue,
|
|
||||||
name: props.modelValue.name || defaultTitle,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
versionInfo,
|
|
||||||
() => {
|
|
||||||
emit('update:modelValue', { ...versionInfo.value });
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
@@ -7,14 +7,18 @@
|
|||||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
||||||
class="q-mr-sm">
|
class="q-mr-sm">
|
||||||
</q-icon>
|
</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-space></q-space>
|
||||||
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:header-CHANGE="prop">
|
<template v-slot:header-CHANGE="prop">
|
||||||
<div class="row col items-start no-wrap event-node">
|
<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-space></q-space>
|
||||||
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
||||||
@click="addChangeEvent(prop.node)"></q-icon>
|
@click="addChangeEvent(prop.node)"></q-icon>
|
||||||
@@ -23,14 +27,14 @@
|
|||||||
<template v-slot:header-DELETABLE="prop">
|
<template v-slot:header-DELETABLE="prop">
|
||||||
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
<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" />
|
<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-space></q-space>
|
||||||
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-tree>
|
</q-tree>
|
||||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
<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>
|
</show-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -38,8 +42,8 @@
|
|||||||
import { QTree, useQuasar } from 'quasar';
|
import { QTree, useQuasar } from 'quasar';
|
||||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
|
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
import FieldSelect from '../FieldSelect.vue';
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -54,33 +58,15 @@ export default defineComponent({
|
|||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
const tree = ref<QTree>();
|
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 eventTree=ref(kintoneEvents);
|
||||||
// const selectedFlow = store.currentFlow;
|
// const selectedFlow = store.currentFlow;
|
||||||
|
|
||||||
// const expanded=ref();
|
// const expanded=ref();
|
||||||
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
|
const selectedEvent = ref<IKintoneEvent | null>(null);
|
||||||
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
|
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null);
|
||||||
const isFieldChange = (node: IKintoneEventNode) => {
|
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) => {
|
const closeDg = (val: string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
@@ -90,12 +76,12 @@ export default defineComponent({
|
|||||||
if (store.eventTree.findEventById(eventid)) {
|
if (store.eventTree.findEventById(eventid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedChangeEvent.value?.events.push(new kintoneEvent(
|
selectedChangeEvent.value?.events.push({
|
||||||
field.name,
|
eventId: eventid,
|
||||||
eventid,
|
label: field.name,
|
||||||
selectedChangeEvent.value.eventId,
|
parentId: selectedChangeEvent.value.eventId,
|
||||||
'DELETABLE'
|
header: 'DELETABLE'
|
||||||
));
|
});
|
||||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
||||||
tree.value?.expandAll();
|
tree.value?.expandAll();
|
||||||
}
|
}
|
||||||
@@ -133,7 +119,7 @@ export default defineComponent({
|
|||||||
const screen = store.eventTree.findEventById(node.parentId);
|
const screen = store.eventTree.findEventById(node.parentId);
|
||||||
|
|
||||||
let flow = store.findFlowByEventId(node.eventId);
|
let flow = store.findFlowByEventId(node.eventId);
|
||||||
let screenName = screen !== null ? screen.label : '';
|
let screenName = screen !== null ? screen.label : "";
|
||||||
let nodeLabel = node.label;
|
let nodeLabel = node.label;
|
||||||
// if(isFieldChange(node)){
|
// if(isFieldChange(node)){
|
||||||
// screenName=nodeLabel;
|
// screenName=nodeLabel;
|
||||||
@@ -150,9 +136,6 @@ export default defineComponent({
|
|||||||
selectedEvent.value.flowData = flow;
|
selectedEvent.value.flowData = flow;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
watchEffect(()=>{
|
|
||||||
store.setCurrentEvent(selectedEvent.value);
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
// eventTree,
|
// eventTree,
|
||||||
// expanded,
|
// expanded,
|
||||||
@@ -160,14 +143,12 @@ export default defineComponent({
|
|||||||
tree,
|
tree,
|
||||||
showDialog,
|
showDialog,
|
||||||
isFieldChange,
|
isFieldChange,
|
||||||
getSelectedClass,
|
|
||||||
onSelected,
|
onSelected,
|
||||||
selectedEvent,
|
selectedEvent,
|
||||||
addChangeEvent,
|
addChangeEvent,
|
||||||
deleteEvent,
|
deleteEvent,
|
||||||
closeDg,
|
closeDg,
|
||||||
store,
|
store
|
||||||
fieldTypes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-my-md" v-bind="$attrs">
|
<div class="q-my-md" v-bind="$attrs">
|
||||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
<q-card flat>
|
||||||
:rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
|
<q-card-section class="q-pa-none q-my-sm q-mr-md">
|
||||||
<template v-slot:control>
|
<!-- <div class=" q-my-none ">App Field Select</div> -->
|
||||||
{{ isSelected ? selectedField.app?.name : "(未選択)" }}
|
<div class="row q-mb-xs">
|
||||||
</template>
|
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
|
||||||
<template v-slot:hint v-if="!isSelected">
|
</div>
|
||||||
{{ placeholder }}
|
<div class="row">
|
||||||
</template>
|
<div class="col">
|
||||||
<template v-slot:append>
|
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
|
||||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
</div>
|
||||||
</template>
|
<div class="col-1">
|
||||||
</q-field>
|
<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">
|
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||||
<q-list bordered>
|
<q-list bordered>
|
||||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
|
||||||
|
v-slot="{ item, index }">
|
||||||
<q-item :key="index" dense clickable>
|
<q-item :key="index" dense clickable>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
@@ -28,15 +35,26 @@
|
|||||||
</q-virtual-scroll>
|
</q-virtual-scroll>
|
||||||
</q-list>
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
<!-- <div v-else class="row q-mt-lg">
|
||||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
|
</div> -->
|
||||||
:fieldTypes="fieldTypes" />
|
|
||||||
</show-dialog>
|
|
||||||
</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"/>
|
||||||
|
</show-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
import { defineComponent, ref, watchEffect } from 'vue';
|
||||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
@@ -48,8 +66,7 @@ export interface IApp {
|
|||||||
export interface IField {
|
export interface IField {
|
||||||
name: string,
|
name: string,
|
||||||
code: string,
|
code: string,
|
||||||
type: string,
|
type: string
|
||||||
label?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAppFields {
|
export interface IAppFields {
|
||||||
@@ -59,7 +76,7 @@ export interface IAppFields {
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
name: 'AppFieldSelect2',
|
name: 'AppFieldSelect',
|
||||||
components: {
|
components: {
|
||||||
ShowDialog,
|
ShowDialog,
|
||||||
AppFieldSelectBox
|
AppFieldSelectBox
|
||||||
@@ -84,29 +101,11 @@ export default defineComponent({
|
|||||||
selectType: {
|
selectType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'single'
|
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 }) {
|
setup(props, { emit }) {
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const afBox = ref();
|
const afBox = ref();
|
||||||
const fieldRef = ref();
|
|
||||||
const selectedField = ref<IAppFields>({
|
const selectedField = ref<IAppFields>({
|
||||||
app: undefined,
|
app: undefined,
|
||||||
fields: []
|
fields: []
|
||||||
@@ -129,20 +128,11 @@ export default defineComponent({
|
|||||||
const closeAFBox = (val: string) => {
|
const closeAFBox = (val: string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
console.log(afBox.value);
|
console.log(afBox.value);
|
||||||
|
|
||||||
selectedField.value = afBox.value.selField;
|
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(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedField.value);
|
emit('update:modelValue', selectedField.value);
|
||||||
});
|
});
|
||||||
@@ -156,9 +146,6 @@ export default defineComponent({
|
|||||||
clear,
|
clear,
|
||||||
removeField,
|
removeField,
|
||||||
closeAFBox,
|
closeAFBox,
|
||||||
isSelected,
|
|
||||||
rulesExp,
|
|
||||||
fieldRef
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div>
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||||
:rules="rulesExp"
|
|
||||||
lazy-rules="ondemand"
|
|
||||||
v-model="selectedApp"
|
|
||||||
ref="fieldRef">
|
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-card flat class="full-width">
|
<q-card flat class="full-width">
|
||||||
<q-card-actions vertical>
|
<q-card-actions vertical>
|
||||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
|
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
<q-card-section class="text-caption">
|
<q-card-section class="text-caption">
|
||||||
<div v-if="selectedApp.app.name">
|
<div v-if="selectedField.app.name">
|
||||||
{{ selectedApp.app.name }}
|
{{ selectedField.app.name }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>{{ placeholder }}</div>
|
<div v-else>{{ placeholder }}</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@@ -47,6 +43,10 @@ export default defineComponent({
|
|||||||
AppSelectBox
|
AppSelectBox
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Array<Props>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -62,50 +62,31 @@ export default defineComponent({
|
|||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const appDg = ref();
|
const appDg = ref()
|
||||||
const fieldRef=ref();
|
|
||||||
const dgIsShow = ref(false)
|
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) => {
|
const closeDg = (state: string) => {
|
||||||
dgIsShow.value = false;
|
dgIsShow.value = false;
|
||||||
if (state == 'OK') {
|
if (state == 'OK') {
|
||||||
selectedApp.app = appDg.value.selected[0];
|
selectedField.app = appDg.value.selected[0];
|
||||||
fieldRef.value.validate();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//ルール設定
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
console.log(selectedField);
|
||||||
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];
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedApp);
|
emit('update:modelValue', selectedField);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filter: ref(''),
|
filter: ref(''),
|
||||||
dgIsShow,
|
dgIsShow,
|
||||||
appDg,
|
appDg,
|
||||||
fieldRef,
|
|
||||||
closeDg,
|
closeDg,
|
||||||
selectedApp,
|
selectedField
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
<template>
|
||||||
<div class="" v-bind="$attrs">
|
<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>
|
<template v-slot:control>
|
||||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
<q-chip text-color="black" color="white" v-if="isSelected">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -57,34 +57,17 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const color = ref(props.modelValue??"");
|
const color = ref(props.modelValue??"");
|
||||||
const isSelected = computed(()=>props.modelValue && 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];
|
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
emit('update:modelValue', color.value);
|
emit('update:modelValue', color.value);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
color,
|
color,
|
||||||
isSelected,
|
isSelected
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,11 +18,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.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({
|
export default defineComponent({
|
||||||
name: 'FieldInput',
|
name: 'FieldInput',
|
||||||
@@ -32,7 +60,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
context: {
|
context: {
|
||||||
type: Array<IActionProperty>,
|
type: Array<Props>,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
@@ -59,10 +87,6 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'field'
|
default: 'field'
|
||||||
},
|
},
|
||||||
connectProps:{
|
|
||||||
type:Object,
|
|
||||||
default:()=>({})
|
|
||||||
},
|
|
||||||
onlySourceSelect: {
|
onlySourceSelect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -91,10 +115,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
let source = reactive(props.connectProps["source"]);
|
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||||
if(!source){
|
|
||||||
source = props.context.find(element => element.props.name === 'sources');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
if (props.sourceType === 'field') {
|
if (props.sourceType === 'field') {
|
||||||
@@ -136,8 +157,7 @@ export default defineComponent({
|
|||||||
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
||||||
|
|
||||||
const conditionString = computed(() => {
|
const conditionString = computed(() => {
|
||||||
const condiStr= tree.buildConditionString(tree.root);
|
return tree.buildConditionString(tree.root);
|
||||||
return condiStr==='()'?'(条件なし)':condiStr;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const showDg = () => {
|
const showDg = () => {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-my-md" v-bind="$attrs">
|
<div>
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||||
v-model="mappingProps"
|
|
||||||
:rules="rulesExp"
|
|
||||||
ref="fieldRef"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-card flat class="full-width">
|
<q-card flat class="full-width">
|
||||||
<q-card-actions vertical>
|
<q-card-actions vertical>
|
||||||
@@ -20,36 +16,31 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||||
<div class="">
|
|
||||||
|
<div class="q-mx-md">
|
||||||
<div class="row q-col-gutter-x-xs flex-center">
|
<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 class="q-mx-xs">ソース</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-1">
|
<!-- <div class="col-1">
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="col-5">
|
<div class="col-6">
|
||||||
<div class="row justify-between q-mr-md">
|
<div class="q-mx-xs">目標</div>
|
||||||
<div class="">{{ sourceApp?.name }}</div>
|
|
||||||
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
|
||||||
@click="() => updateFields(sourceAppId!)" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" /> -->
|
||||||
|
<!-- </div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1 q-pl-sm">
|
<q-virtual-scroll style="max-height: 75vh;" :items="mappingProps" separator v-slot="{ item, index }">
|
||||||
キー
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
|
|
||||||
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
<!-- <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="row q-my-md q-col-gutter-x-md flex-center">
|
||||||
<div class="col-5">
|
<div class="col-6">
|
||||||
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
<ConditionObject :config="config" v-model="item.from" />
|
||||||
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-1">
|
<!-- <div class="col-1">
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="col-5">
|
<div class="col-6">
|
||||||
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
<q-field v-model="item.vName" type="text" outlined dense>
|
||||||
<!-- <template v-slot:append>
|
<!-- <template v-slot:append>
|
||||||
<q-icon name="search" class="cursor-pointer"
|
<q-icon name="search" class="cursor-pointer"
|
||||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||||
@@ -58,39 +49,31 @@
|
|||||||
<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">
|
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||||
{{ `${item.to.fields[0].label}` }}
|
{{ `${item.to.fields[0].label}` }}
|
||||||
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
<q-tooltip>
|
||||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
|
||||||
<div>アプリ : {{ item.to.app.name }}</div>
|
<div>アプリ : {{ item.to.app.name }}</div>
|
||||||
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
||||||
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</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.to.fields[0] }}</div>
|
|
||||||
<div>フィールド : {{ item.isKey }}</div> -->
|
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<!-- <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)" />
|
||||||
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
</div> -->
|
||||||
</div>
|
|
||||||
</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">
|
@close="closeToDg" ref="fieldDlg">
|
||||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||||
:selectedFields="mappingProps.data[index].to.fields"
|
:selectedFields="mappingProps[index].to.fields"
|
||||||
:updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
|
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
|
||||||
</FieldSelect>
|
</FieldSelect>
|
||||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
|
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
</q-virtual-scroll>
|
</q-virtual-scroll>
|
||||||
|
|
||||||
<div class="q-mt-lg q-ml-md row ">
|
|
||||||
<q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,10 +86,10 @@ import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
|||||||
import ShowDialog from '../ShowDialog.vue';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
import FieldSelect from '../FieldSelect.vue';
|
||||||
import { IApp, IField } from './AppFieldSelect.vue';
|
import IAppFields from './AppFieldSelect.vue';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
type ContextProps = {
|
type Props = {
|
||||||
props?: {
|
props?: {
|
||||||
name: string;
|
name: string;
|
||||||
modelValue?: {
|
modelValue?: {
|
||||||
@@ -117,25 +100,14 @@ type ContextProps = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
type ValueType = {
|
||||||
interface IMappingSetting {
|
|
||||||
data: IMappingValueType[];
|
|
||||||
createWithNull: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMappingValueType {
|
|
||||||
id: string;
|
id: string;
|
||||||
from: { sharedText?: string };
|
from: object;
|
||||||
to: {
|
to: typeof IAppFields & {
|
||||||
app?: IApp,
|
|
||||||
fields: IField[],
|
|
||||||
isDialogVisible: boolean;
|
isDialogVisible: boolean;
|
||||||
};
|
};
|
||||||
isKey: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DataMapping',
|
name: 'DataMapping',
|
||||||
@@ -148,7 +120,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
context: {
|
context: {
|
||||||
type: Array<ContextProps>,
|
type: Array<Props>,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
displayName: {
|
displayName: {
|
||||||
@@ -160,7 +132,7 @@ export default defineComponent({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object as () => IMappingSetting,
|
type: Object as () => ValueType[],
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -169,114 +141,73 @@ export default defineComponent({
|
|||||||
onlySourceSelect: {
|
onlySourceSelect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
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 }) {
|
setup(props, { emit }) {
|
||||||
const fieldRef=ref();
|
|
||||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||||
|
|
||||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||||
|
|
||||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
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 = () => {
|
const closeDg = () => {
|
||||||
fieldRef.value.validate();
|
emit('update:modelValue', mappingProps.value
|
||||||
emit('update:modelValue',mappingProps);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeToDg = () => {
|
const closeToDg = () => {
|
||||||
emit('update:modelValue',mappingProps);
|
emit('update:modelValue', mappingProps.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
const mappingProps = computed(() => props.modelValue ?? []);
|
||||||
watch(() => sourceAppId.value, async (newId,) => {
|
|
||||||
if (!newId) return;
|
|
||||||
updateFields(newId)
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateFields = async (sourceAppId: string) => {
|
watch(() => sourceAppId.value, async (newId, oldId) => {
|
||||||
const ktAppFields = await api.get('api/v1/appfields', {
|
if (!newId) return;
|
||||||
|
const a = await api.get('api/v1/appfields', {
|
||||||
params: {
|
params: {
|
||||||
app: sourceAppId
|
app: newId
|
||||||
}
|
}
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
return Object.values(res.data.properties)
|
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 => ({ name: f.label, objectType: 'field', ...f }))
|
||||||
.map(f => {
|
.map(f => {
|
||||||
// 更新前の値を求める
|
|
||||||
const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
|
|
||||||
return {
|
return {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
app: sourceApp.value,
|
app: sourceApp.value,
|
||||||
fields: [f],
|
fields: [f],
|
||||||
isDialogVisible: false
|
isDialogVisible: false
|
||||||
},
|
}
|
||||||
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
|
||||||
disabled: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
const modelValue = props.modelValue ?? [];
|
||||||
|
|
||||||
// 「ルックアップ」によってロックされているフィールドを検索する
|
if (modelValue.length === 0 || newId !== oldId) {
|
||||||
const lookupFixedField = ktAppFields
|
emit('update:modelValue', a);
|
||||||
.filter(field => field.to.fields[0].lookup !== undefined)
|
return;
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
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(() =>
|
const mappingObjectsInputDisplay = computed(() =>
|
||||||
(mappingProps.data && Array.isArray(mappingProps.data)) ?
|
(mappingProps.value && Array.isArray(mappingProps.value)) ?
|
||||||
mappingProps.data
|
mappingProps.value
|
||||||
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
||||||
.map(item => {
|
.map(item => {
|
||||||
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
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);
|
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||||
|
|
||||||
watchEffect(() => {
|
//集計処理方法
|
||||||
emit('update:modelValue', mappingProps);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', mappingProps.value);
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
uuidv4,
|
uuidv4,
|
||||||
dgIsShow: ref(false),
|
dgIsShow: ref(false),
|
||||||
fieldRef,
|
|
||||||
closeDg,
|
closeDg,
|
||||||
toDgIsShow: ref(false),
|
toDgIsShow: ref(false),
|
||||||
closeToDg,
|
closeToDg,
|
||||||
mappingProps,
|
mappingProps,
|
||||||
updateFields,
|
|
||||||
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||||
// deleteMappingObject,
|
// deleteMappingObject,
|
||||||
mappingObjectsInputDisplay,
|
mappingObjectsInputDisplay,
|
||||||
sourceApp,
|
sourceApp,
|
||||||
sourceAppId,
|
sourceAppId,
|
||||||
btnDisable,
|
btnDisable,
|
||||||
rulesExp,
|
|
||||||
checkMapping,
|
|
||||||
config: {
|
config: {
|
||||||
canInput: false,
|
canInput: false,
|
||||||
buttonsConfig: [
|
buttonsConfig: [
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
{ label: '変数', color: 'green', type: 'VariableAdd',editable:false },
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss"></style>
|
<style lang="scss"></style>
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||||
v-model="processingProps"
|
|
||||||
:rules="rulesExp"
|
|
||||||
lazy-rules="ondemand"
|
|
||||||
ref="fieldRef"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-card flat class="full-width">
|
<q-card flat class="full-width">
|
||||||
<q-card-actions vertical>
|
<q-card-actions vertical>
|
||||||
@@ -45,7 +40,7 @@
|
|||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<ConditionObject v-model="item.field" />
|
<ConditionObject v-model="item.field" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2 q-pa-sm">
|
<div class="col-2">
|
||||||
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -83,8 +78,14 @@ type Props = {
|
|||||||
|
|
||||||
type ProcessingObjectType = {
|
type ProcessingObjectType = {
|
||||||
field?: {
|
field?: {
|
||||||
sharedText: string;
|
name: string | {
|
||||||
objectType: 'field';
|
name: string;
|
||||||
|
};
|
||||||
|
objectType: string;
|
||||||
|
type: string;
|
||||||
|
code: string;
|
||||||
|
label: string;
|
||||||
|
noLabel: boolean;
|
||||||
};
|
};
|
||||||
logicalOperator?: string;
|
logicalOperator?: string;
|
||||||
vName?: string;
|
vName?: string;
|
||||||
@@ -125,19 +126,9 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const fieldRef=ref();
|
|
||||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
@@ -154,55 +145,34 @@ export default defineComponent({
|
|||||||
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
||||||
|
|
||||||
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
||||||
? reactive(props.modelValue)
|
? props.modelValue
|
||||||
: reactive({
|
: reactive({
|
||||||
name: '',
|
name: '',
|
||||||
actionName: actionName?.props?.modelValue as string,
|
actionName: actionName?.props?.modelValue as string,
|
||||||
displayName: '結果(戻り値)',
|
displayName: '結果(戻り値)',
|
||||||
vars: [
|
vars: [{ id: uuidv4() }]
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
field:{
|
|
||||||
objectType:'field',
|
|
||||||
sharedText:''
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeDg = () => {
|
const closeDg = () => {
|
||||||
fieldRef.value.validate();
|
emit('update:modelValue', processingProps
|
||||||
emit('update:modelValue', processingProps);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const processingObjects = processingProps.vars;
|
const processingObjects = processingProps.vars;
|
||||||
|
|
||||||
const deleteProcessingObject = (index: number) => {
|
const deleteProcessingObject = (index: number) => processingObjects.length === 1
|
||||||
if(processingObjects.length >0){
|
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
|
||||||
processingObjects.splice(index, 1);
|
: processingObjects.splice(index, 1);
|
||||||
}
|
|
||||||
if(processingObjects.length===0){
|
|
||||||
addProcessingObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const processingObjectsInputDisplay = computed(() =>
|
const processingObjectsInputDisplay = computed(() =>
|
||||||
processingObjects ?
|
processingObjects ?
|
||||||
processingObjects
|
processingObjects
|
||||||
.filter(item => item.field && item.logicalOperator && item.vName)
|
.filter(item => item.field && item.logicalOperator && item.vName)
|
||||||
.map(item => {
|
.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([
|
const logicalOperators = ref([
|
||||||
{
|
{
|
||||||
@@ -234,24 +204,6 @@ export default defineComponent({
|
|||||||
"label": "最初の値"
|
"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(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', processingProps);
|
emit('update:modelValue', processingProps);
|
||||||
@@ -262,12 +214,10 @@ export default defineComponent({
|
|||||||
closeDg,
|
closeDg,
|
||||||
processingObjects,
|
processingObjects,
|
||||||
processingProps,
|
processingProps,
|
||||||
addProcessingObject,
|
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
|
||||||
deleteProcessingObject,
|
deleteProcessingObject,
|
||||||
logicalOperators,
|
logicalOperators,
|
||||||
processingObjectsInputDisplay,
|
processingObjectsInputDisplay,
|
||||||
rulesExp,
|
|
||||||
fieldRef
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<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>
|
<template v-slot:append>
|
||||||
<q-icon name="event" class="cursor-pointer">
|
<q-icon name="event" class="cursor-pointer">
|
||||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
@@ -43,32 +43,16 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const selectedDate = ref(props.modelValue);
|
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(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedDate.value);
|
emit('update:modelValue', selectedDate.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedDate,
|
selectedDate
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
|
||||||
:placeholder="placeholder"
|
|
||||||
:rules="rulesExp"
|
|
||||||
stack-label>
|
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
||||||
</template>
|
</template>
|
||||||
@@ -43,29 +40,12 @@ export default defineComponent({
|
|||||||
connectProps:{
|
connectProps:{
|
||||||
type:Object,
|
type:Object,
|
||||||
default:undefined
|
default:undefined
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props , { emit }) {
|
setup(props , { emit }) {
|
||||||
const inputValue = ref(props.modelValue);
|
const inputValue = ref(props.modelValue);
|
||||||
const store = useFlowEditorStore();
|
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 addButtonEvent=()=>{
|
||||||
const eventId =store.currentFlow?.getRoot()?.name;
|
const eventId =store.currentFlow?.getRoot()?.name;
|
||||||
if(eventId===undefined){return;}
|
if(eventId===undefined){return;}
|
||||||
@@ -81,22 +61,22 @@ export default defineComponent({
|
|||||||
if(store.eventTree.findEventById(addEventId)){
|
if(store.eventTree.findEventById(addEventId)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
customEvents.events.push(new kintoneEvent(
|
customEvents.events.push({
|
||||||
displayName,
|
eventId: addEventId,
|
||||||
addEventId,
|
label: displayName,
|
||||||
customButtonId,
|
parentId: customButtonId,
|
||||||
'DELETABLE'
|
header: 'DELETABLE'
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', inputValue.value);
|
emit('update:modelValue', inputValue.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
addButtonEvent,
|
addButtonEvent
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||||
:bottom-slots="!isSelected"
|
:bottom-slots="!isSelected">
|
||||||
:rules="rulesExp"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected">
|
<q-chip color="primary" text-color="white" v-if="isSelected">
|
||||||
{{ selectedField.name }}
|
{{ selectedField.name }}
|
||||||
@@ -17,15 +15,8 @@
|
|||||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
|
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
|
||||||
<template v-slot:toolbar>
|
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :fieldTypes="fieldTypes"></field-select>
|
||||||
<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>
|
</show-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -76,35 +67,16 @@ export default defineComponent({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const appDg = ref();
|
const appDg = ref();
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const selectedField = ref(props.modelValue);
|
const selectedField = ref(props.modelValue);
|
||||||
const selectedFields =computed(()=>!selectedField.value?[]: [selectedField.value]);
|
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const isSelected = computed(() => {
|
const isSelected = computed(() => {
|
||||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
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 = () => {
|
const showDg = () => {
|
||||||
show.value = true;
|
show.value = true;
|
||||||
@@ -127,10 +99,7 @@ export default defineComponent({
|
|||||||
showDg,
|
showDg,
|
||||||
closeDg,
|
closeDg,
|
||||||
selectedField,
|
selectedField,
|
||||||
isSelected,
|
isSelected
|
||||||
filter:ref(''),
|
|
||||||
selectedFields,
|
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { kMaxLength } from 'buffer';
|
||||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -49,16 +50,8 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: undefined
|
default: undefined
|
||||||
},
|
},
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: null as any,
|
// type: Any,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -82,11 +75,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
// const inputValue = ref(props.modelValue);
|
// 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(() => {
|
// const finalValue = computed(() => {
|
||||||
// return props.name !== 'verName' ? inputValue.value : {
|
// return props.name !== 'verName' ? inputValue.value : {
|
||||||
// name: inputValue.value,
|
// name: inputValue.value,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input :label="displayName" label-color="primary" v-model="inputValue"
|
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
|
||||||
:placeholder="placeholder"
|
|
||||||
:rules="rulesExp"
|
|
||||||
autogrow
|
|
||||||
stack-label />
|
stack-label />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,34 +32,17 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const inputValue = ref(props.modelValue);
|
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(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', inputValue.value);
|
emit('update:modelValue', inputValue.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
@@ -49,14 +49,6 @@ export default defineComponent({
|
|||||||
type:String,
|
type:String,
|
||||||
default:undefined
|
default:undefined
|
||||||
},
|
},
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Number , String],
|
type: [Number , String],
|
||||||
default: undefined
|
default: undefined
|
||||||
@@ -65,10 +57,23 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const numValue = ref(props.modelValue);
|
const numValue = ref(props.modelValue);
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
const isError = computed(()=>{
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
const val = numValue.value;
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
if (val === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const numVal = typeof val === "string" ? parseInt(val) : val;
|
||||||
|
// Ensure parsed value is a valid number
|
||||||
|
if (isNaN(numVal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check against min and max boundaries, if defined
|
||||||
|
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
emit("update:modelValue",numValue.value);
|
emit("update:modelValue",numValue.value);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import NumInput from './NumInput.vue';
|
|||||||
import DataProcessing from './DataProcessing.vue';
|
import DataProcessing from './DataProcessing.vue';
|
||||||
import DataMapping from './DataMapping.vue';
|
import DataMapping from './DataMapping.vue';
|
||||||
import AppSelect from './AppSelect.vue';
|
import AppSelect from './AppSelect.vue';
|
||||||
import CascadingDropDown from './CascadingDropDown.vue';
|
|
||||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -42,8 +41,7 @@ export default defineComponent({
|
|||||||
NumInput,
|
NumInput,
|
||||||
DataProcessing,
|
DataProcessing,
|
||||||
DataMapping,
|
DataMapping,
|
||||||
AppSelect,
|
AppSelect
|
||||||
CascadingDropDown
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
elevated
|
elevated
|
||||||
overlay
|
overlay
|
||||||
>
|
>
|
||||||
<q-form @submit="save" autocomplete="off" class="full-height">
|
|
||||||
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
||||||
@@ -22,17 +21,16 @@
|
|||||||
|
|
||||||
<q-card-actions align="right" class="bg-white text-teal">
|
<q-card-actions align="right" class="bg-white text-teal">
|
||||||
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
||||||
<q-btn flat label="更新" type="submit" outline dense padding="none sm" color="primary" />
|
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-form>
|
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
||||||
import PropertyList from 'components/right/PropertyList.vue';
|
import PropertyList from 'components/right/PropertyList.vue';
|
||||||
import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
import { IActionNode } from 'src/types/ActionTypes';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PropertyPanel',
|
name: 'PropertyPanel',
|
||||||
components: {
|
components: {
|
||||||
@@ -49,28 +47,14 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'update:drawerRight',
|
'update:drawerRight'
|
||||||
'saveActionProps'
|
|
||||||
],
|
],
|
||||||
setup(props,{emit}) {
|
setup(props,{emit}) {
|
||||||
const showPanel =ref(props.drawerRight);
|
const showPanel =ref(props.drawerRight);
|
||||||
|
const actionProps =ref(props.actionNode?.actionProps);
|
||||||
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
|
|
||||||
if(!actionProps){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const json=JSON.stringify(actionProps);
|
|
||||||
return JSON.parse(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionProps =ref(cloneProps(props.actionNode?.actionProps));
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if(showPanel.value!==undefined){
|
|
||||||
showPanel.value = props.drawerRight;
|
showPanel.value = props.drawerRight;
|
||||||
}
|
actionProps.value= props.actionNode?.actionProps;
|
||||||
showPanel.value = props.drawerRight;
|
|
||||||
actionProps.value= cloneProps(props.actionNode?.actionProps);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = async() =>{
|
const cancel = async() =>{
|
||||||
@@ -80,8 +64,7 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
|||||||
|
|
||||||
const save = async () =>{
|
const save = async () =>{
|
||||||
showPanel.value=false;
|
showPanel.value=false;
|
||||||
emit('saveActionProps', actionProps.value);
|
emit('update:drawerRight',false )
|
||||||
emit('update:drawerRight',false );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary"
|
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
|
||||||
:options="options"
|
|
||||||
stack-label
|
|
||||||
:rules="rulesExp"
|
|
||||||
:multiple="multiple"/>
|
:multiple="multiple"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,19 +32,6 @@ export default defineComponent({
|
|||||||
type: [Array,String],
|
type: [Array,String],
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const selectedValue = ref(props.modelValue);
|
const selectedValue = ref(props.modelValue);
|
||||||
@@ -57,14 +41,10 @@ export default defineComponent({
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedValue.value);
|
emit('update:modelValue', selectedValue.value);
|
||||||
});
|
});
|
||||||
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];
|
|
||||||
return {
|
return {
|
||||||
selectedValue,
|
selectedValue,
|
||||||
multiple,
|
multiple
|
||||||
rulesExp
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,28 +2,40 @@
|
|||||||
<q-layout view="lHh Lpr lFf">
|
<q-layout view="lHh Lpr lFf">
|
||||||
<q-header elevated>
|
<q-header elevated>
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
icon="menu"
|
||||||
|
aria-label="Menu"
|
||||||
|
@click="toggleLeftDrawer"
|
||||||
|
/>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
{{ productName }}
|
{{ productName }}
|
||||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
<q-badge align="top" outline>V{{ version }}</q-badge>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<domain-selector></domain-selector>
|
<domain-selector></domain-selector>
|
||||||
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
|
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
||||||
<q-drawer :model-value="authStore.LeftDrawer" :show-if-above="false" bordered>
|
<q-drawer
|
||||||
|
:model-value="authStore.toggleLeftDrawer"
|
||||||
|
:show-if-above="false"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label header>
|
<q-item-label
|
||||||
メニュー
|
header
|
||||||
|
>
|
||||||
|
関連リンク
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />
|
<EssentialLink
|
||||||
<div v-if="isAdmin()">
|
v-for="link in essentialLinks"
|
||||||
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
|
:key="link.title"
|
||||||
</div>
|
v-bind="link"
|
||||||
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
|
/>
|
||||||
|
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
@@ -34,110 +46,115 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive } from 'vue';
|
import { ref } from 'vue';
|
||||||
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
||||||
import DomainSelector from 'components/DomainSelector.vue';
|
import DomainSelector from 'components/DomainSelector.vue';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const route = useRoute()
|
|
||||||
const noDomain = computed(() => !authStore.hasDomain);
|
|
||||||
|
|
||||||
const essentialLinks: EssentialLinkProps[] = reactive([
|
const essentialLinks: EssentialLinkProps[] = [
|
||||||
{
|
{
|
||||||
title: 'ホーム',
|
title: 'ホーム',
|
||||||
caption: '設計書から導入する',
|
caption: 'home',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
link: '/',
|
link: '/',
|
||||||
target: '_self',
|
target:'_self'
|
||||||
disable: noDomain
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: 'フローエディター',
|
|
||||||
// caption: 'イベントを設定する',
|
|
||||||
// icon: 'account_tree',
|
|
||||||
// link: '/#/FlowChart',
|
|
||||||
// target: '_self'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
title: 'アプリ管理',
|
|
||||||
caption: 'アプリを管理する',
|
|
||||||
icon: 'widgets',
|
|
||||||
link: '/#/app',
|
|
||||||
target: '_self',
|
|
||||||
disable: noDomain
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: '条件エディター',
|
|
||||||
// caption: 'condition',
|
|
||||||
// icon: 'tune',
|
|
||||||
// link: '/#/condition',
|
|
||||||
// target:'_self'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
isSeparator: true
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title:'Kintone ポータル',
|
|
||||||
// caption:'Kintone',
|
|
||||||
// icon:'cloud_queue',
|
|
||||||
// link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title:'CUSTOMINE',
|
|
||||||
// caption:'gusuku',
|
|
||||||
// link:'https://app-customine.gusuku.io/drive.html',
|
|
||||||
// icon:'settings_suggest'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title:'Kintone API ドキュメント',
|
|
||||||
// caption:'Kintone API',
|
|
||||||
// link:'https://cybozu.dev/ja/kintone/docs/',
|
|
||||||
// icon:'help_outline'
|
|
||||||
// },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const domainLinks: EssentialLinkProps[] = reactive([
|
|
||||||
{
|
|
||||||
title: 'ドメイン管理',
|
|
||||||
caption: 'kintoneのドメイン設定',
|
|
||||||
icon: 'domain',
|
|
||||||
link: '/#/domain',
|
|
||||||
target: '_self'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ドメイン適用',
|
title: 'フローエディター',
|
||||||
caption: 'ユーザー使用可能なドメインの設定',
|
caption: 'flowChart',
|
||||||
icon: 'assignment_ind',
|
icon: 'account_tree',
|
||||||
link: '/#/userDomain',
|
link: '/#/FlowChart',
|
||||||
target: '_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
]);
|
|
||||||
|
|
||||||
const adminLinks: EssentialLinkProps[] = reactive([
|
|
||||||
{
|
{
|
||||||
title: 'ユーザー管理',
|
title: '条件エディター',
|
||||||
caption: 'ユーザーを管理する',
|
caption: 'condition',
|
||||||
icon: 'manage_accounts',
|
icon: 'tune',
|
||||||
link: '/#/user',
|
link: '/#/condition',
|
||||||
target: '_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
])
|
{
|
||||||
|
title:'',
|
||||||
|
isSeparator:true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:'Kintone ポータル',
|
||||||
|
caption:'Kintone',
|
||||||
|
icon:'cloud_queue',
|
||||||
|
link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:'CUSTOMINE',
|
||||||
|
caption:'gusuku',
|
||||||
|
link:'https://app-customine.gusuku.io/drive.html',
|
||||||
|
icon:'settings_suggest'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:'Kintone API ドキュメント',
|
||||||
|
caption:'Kintone API',
|
||||||
|
link:'https://cybozu.dev/ja/kintone/docs/',
|
||||||
|
icon:'help_outline'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:'',
|
||||||
|
isSeparator:true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Docs',
|
||||||
|
caption: 'quasar.dev',
|
||||||
|
icon: 'school',
|
||||||
|
link: 'https://quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Icons',
|
||||||
|
caption: 'Material Icons',
|
||||||
|
icon: 'insert_emoticon',
|
||||||
|
link: 'https://fonts.google.com/icons?selected=Material+Icons:insert_emoticon:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Github',
|
||||||
|
caption: 'github.com/quasarframework',
|
||||||
|
icon: 'code',
|
||||||
|
link: 'https://github.com/quasarframework'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Discord Chat Channel',
|
||||||
|
caption: 'chat.quasar.dev',
|
||||||
|
icon: 'chat',
|
||||||
|
link: 'https://chat.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Forum',
|
||||||
|
caption: 'forum.quasar.dev',
|
||||||
|
icon: 'record_voice_over',
|
||||||
|
link: 'https://forum.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Twitter',
|
||||||
|
caption: '@quasarframework',
|
||||||
|
icon: 'rss_feed',
|
||||||
|
link: 'https://twitter.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Facebook',
|
||||||
|
caption: '@QuasarFramework',
|
||||||
|
icon: 'public',
|
||||||
|
link: 'https://facebook.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Quasar Awesome',
|
||||||
|
caption: 'Community Quasar projects',
|
||||||
|
icon: 'favorite',
|
||||||
|
link: 'https://awesome.quasar.dev'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const version = process.env.version;
|
const version = process.env.version;
|
||||||
const productName = process.env.productName;
|
const productName = process.env.productName;
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
authStore.setLeftMenu(!route.path.startsWith('/FlowChart/'));
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
function toggleLeftDrawer() {
|
||||||
authStore.toggleLeftMenu();
|
authStore.toggleLeftMenu();
|
||||||
}
|
}
|
||||||
function isAdmin(){
|
|
||||||
const permission = authStore.permissions;
|
|
||||||
return permission === 'admin'
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<div class="q-gutter-sm row items-start">
|
|
||||||
<q-breadcrumbs>
|
|
||||||
<q-breadcrumbs-el icon="widgets" label="アプリ管理" />
|
|
||||||
</q-breadcrumbs>
|
|
||||||
</div>
|
|
||||||
<q-table title="Treats" :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="showAddAppDialog" />
|
|
||||||
<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="prop">
|
|
||||||
<q-td :props="prop">
|
|
||||||
<q-btn flat dense :label="prop.row.name" @click="toEditFlowPage(prop.row)" ></q-btn>
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body-cell-url="prop">
|
|
||||||
<q-td :props="prop">
|
|
||||||
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
|
||||||
{{ prop.row.url }}
|
|
||||||
</a>
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body-cell-actions="p">
|
|
||||||
<q-td :props="p">
|
|
||||||
<table-action-menu :row="p.row" :actions="actionList" />
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
</q-table>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeSelectAppDialog" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="dgFilter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<app-select-box ref="appDialog" name="アプリ" type="single" :filter="dgFilter" :filterInitRowsFunc="filterInitRows" />
|
|
||||||
</show-dialog>
|
|
||||||
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="showVersionHistory" :name="targetRow?.name + 'のバージョン履歴'" @close="closeHistoryDg" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
|
|
||||||
<version-history ref="versionDialog" :app="targetRow as IAppDisplay" />
|
|
||||||
</show-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, watch, reactive } from 'vue';
|
|
||||||
import { useQuasar } from 'quasar'
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
import { router } from 'src/router';
|
|
||||||
import { date } from 'quasar'
|
|
||||||
import { IManagedApp, IAppDisplay, IAppVersion } from 'src/types/AppTypes';
|
|
||||||
import ShowDialog from 'src/components/ShowDialog.vue';
|
|
||||||
import AppSelectBox from 'src/components/AppSelectBox.vue';
|
|
||||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
|
||||||
import VersionHistory from 'components/dialog/VersionHistory.vue';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting },
|
|
||||||
{ name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true },
|
|
||||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
|
||||||
{ name: 'updateUser', label: '最後更新者', field: (row: IAppDisplay) => row.updateUser.fullName, align: 'left', sortable: true},
|
|
||||||
{ name: 'updateTime', label: '最後更新日', field: 'updateTime', align: 'left', sortable: true},
|
|
||||||
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting },
|
|
||||||
{ name: 'actions', label: '', field: 'actions' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
|
||||||
const loading = ref(false);
|
|
||||||
const filter = ref('');
|
|
||||||
const dgFilter = ref('');
|
|
||||||
const rows = ref<IAppDisplay[]>([]);
|
|
||||||
const targetRow = ref<IAppDisplay>();
|
|
||||||
const rowIds = new Set<string>();
|
|
||||||
|
|
||||||
const $q = useQuasar()
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const appDialog = ref();
|
|
||||||
const showSelectApp=ref(false);
|
|
||||||
const showVersionHistory=ref(false);
|
|
||||||
const isAdding = ref(false);
|
|
||||||
|
|
||||||
const actionList = [
|
|
||||||
{ label: '設定', icon: 'account_tree', action: toEditFlowPage },
|
|
||||||
{ label: '履歴', icon: 'history', action: showHistory },
|
|
||||||
{ separator: true },
|
|
||||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
|
||||||
];
|
|
||||||
|
|
||||||
const getApps = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
rowIds.clear();
|
|
||||||
try {
|
|
||||||
const { data } = await api.get('api/apps');
|
|
||||||
rows.value = data.data.map((item: IManagedApp) => {
|
|
||||||
rowIds.add(item.appid);
|
|
||||||
return appToAppDisplay(item)
|
|
||||||
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
|
|
||||||
} catch (error) {
|
|
||||||
$q.notify({
|
|
||||||
icon: 'error',
|
|
||||||
color: 'negative',
|
|
||||||
message: 'アプリ一覧の読み込みに失敗しました'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getApps();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => authStore.currentDomain.id, async () => {
|
|
||||||
await getApps();
|
|
||||||
});
|
|
||||||
|
|
||||||
const filterInitRows = (row: {id: string}) => {
|
|
||||||
return !rowIds.has(row.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const showAddAppDialog = () => {
|
|
||||||
showSelectApp.value = true;
|
|
||||||
dgFilter.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
|
|
||||||
showSelectApp.value = true;
|
|
||||||
if (val == 'OK' && appDialog.value.selected[0]) {
|
|
||||||
isAdding.value = true;
|
|
||||||
toEditFlowPage(appDialog.value.selected[0]);
|
|
||||||
}
|
|
||||||
showSelectApp.value = false;
|
|
||||||
isAdding.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRow(app:IAppDisplay) {
|
|
||||||
targetRow.value = app;
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHistory(app:IAppDisplay) {
|
|
||||||
targetRow.value = app;
|
|
||||||
showVersionHistory.value = true;
|
|
||||||
dgFilter.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeHistoryDg = async (val: 'OK'|'Cancel') => {
|
|
||||||
showSelectApp.value = true;
|
|
||||||
if (val == 'OK' && appDialog.value.selected[0]) {
|
|
||||||
isAdding.value = true;
|
|
||||||
await getApps();
|
|
||||||
}
|
|
||||||
showSelectApp.value = false;
|
|
||||||
isAdding.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const appToAppDisplay = (app: IManagedApp) => {
|
|
||||||
const user = app.updateuser;
|
|
||||||
return {
|
|
||||||
id: app.appid,
|
|
||||||
sortId: parseInt(app.appid, 10),
|
|
||||||
name: app.appname,
|
|
||||||
url: `${app.domainurl}/k/${app.appid}`,
|
|
||||||
version: app.version,
|
|
||||||
updateTime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'),
|
|
||||||
updateUser: {
|
|
||||||
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,
|
|
||||||
isSuperuser: user.is_superuser,
|
|
||||||
isActive: user.is_active,
|
|
||||||
}
|
|
||||||
} as IAppDisplay
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toEditFlowPage(app:IAppDisplay) {
|
|
||||||
store.setApp({
|
|
||||||
appId: app.id,
|
|
||||||
name: app.name
|
|
||||||
});
|
|
||||||
store.selectFlow(undefined);
|
|
||||||
await router.push('/FlowChart/' + app.id);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -3,7 +3,11 @@
|
|||||||
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
||||||
<div class="q-pa-sm q-gutter-sm ">
|
<div class="q-pa-sm q-gutter-sm ">
|
||||||
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
||||||
<div class="flex-center absolute-full" style="padding:15px">
|
<div class="flex-center fixed-top app-selector">
|
||||||
|
<AppSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
|
||||||
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
||||||
<EventTree />
|
<EventTree />
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
@@ -12,59 +16,14 @@
|
|||||||
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
||||||
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<q-btn-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" >
|
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
|
||||||
<q-list>
|
|
||||||
<q-item clickable v-close-popup @click="onSaveVersion">
|
|
||||||
<q-item-section avatar >
|
|
||||||
<q-icon name="bookmark_border"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>新バージョン保存</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item clickable v-close-popup @click="onSaveFlow">
|
|
||||||
<q-item-section avatar >
|
|
||||||
<q-icon name="save" color="primary"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>選択中フローの保存</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item clickable v-close-popup @click="onSaveAllFlow">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon name="collections_bookmark" color="accent"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>一括保存</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
</div>
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</div>
|
</div>
|
||||||
<q-btn flat dense round
|
<q-btn flat dense round
|
||||||
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
||||||
:style="{'left': fixedLeftPosition}"
|
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
||||||
@click="drawerLeft=!drawerLeft" class="expand" />
|
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||||
<q-breadcrumbs v-if="store.appInfo" class="fixed q-pl-md"
|
|
||||||
:style="{'left': fixedLeftPosition}">
|
|
||||||
<q-breadcrumbs-el icon="widgets" label="アプリ管理" to="/app" />
|
|
||||||
<q-breadcrumbs-el>
|
|
||||||
<template v-slot>
|
|
||||||
<a class="full-width" :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
|
|
||||||
{{ store.appInfo?.name }}
|
|
||||||
<q-icon
|
|
||||||
class="q-ma-xs"
|
|
||||||
name="open_in_new"
|
|
||||||
color="grey-9"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</q-breadcrumbs-el>
|
|
||||||
</q-breadcrumbs>
|
|
||||||
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
||||||
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
||||||
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
||||||
@@ -72,7 +31,7 @@
|
|||||||
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight" @save-action-props="onSaveActionProps"></PropertyPanel>
|
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
||||||
<template v-slot:toolbar>
|
<template v-slot:toolbar>
|
||||||
@@ -82,48 +41,33 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
|
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
||||||
</ShowDialog>
|
</ShowDialog>
|
||||||
<!-- save version dialog -->
|
|
||||||
<ShowDialog v-model:visible="saveVersionAction" name="新バージョン保存" @close="closeSaveVersionDg" min-width="500px">
|
|
||||||
<version-input v-model="versionInfo" />
|
|
||||||
</ShowDialog>
|
|
||||||
<q-inner-loading
|
|
||||||
:showing="initLoading"
|
|
||||||
color="primary"
|
|
||||||
label="読み込み中..."
|
|
||||||
/>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, onMounted } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||||
import { IAppDisplay, IManagedApp, IVersionInfo } from 'src/types/AppTypes';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { api } from 'boot/axios';
|
|
||||||
|
|
||||||
import NodeItem from 'src/components/main/NodeItem.vue';
|
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
import ActionSelect from 'components/ActionSelect.vue';
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||||
|
import AppSelector from 'components/left/AppSelector.vue';
|
||||||
import EventTree from 'components/left/EventTree.vue';
|
import EventTree from 'components/left/EventTree.vue';
|
||||||
import VersionInput from 'components/dialog/VersionInput.vue';
|
|
||||||
import { FlowCtrl } from '../control/flowctrl';
|
import { FlowCtrl } from '../control/flowctrl';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
const deployLoading = ref(false);
|
const deployLoading = ref(false);
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
const initLoading = ref(true);
|
|
||||||
const drawerLeft = ref(false);
|
const drawerLeft = ref(false);
|
||||||
const versionInfo = ref<IVersionInfo>();
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const appDg = ref();
|
const appDg = ref();
|
||||||
const prevNodeIfo = ref({
|
const prevNodeIfo = ref({
|
||||||
@@ -132,11 +76,13 @@ const prevNodeIfo = ref({
|
|||||||
});
|
});
|
||||||
// const refFlow = ref<ActionFlow|null>(null);
|
// const refFlow = ref<ActionFlow|null>(null);
|
||||||
const showAddAction = ref(false);
|
const showAddAction = ref(false);
|
||||||
const saveVersionAction = ref(false);
|
|
||||||
const drawerRight = ref(false);
|
const drawerRight = ref(false);
|
||||||
const filter=ref("");
|
const filter=ref("");
|
||||||
const model = ref("");
|
const model = ref("");
|
||||||
|
const addActionNode = (action: IActionNode) => {
|
||||||
|
// refFlow.value?.actionNodes.push(action);
|
||||||
|
store.currentFlow?.actionNodes.push(action);
|
||||||
|
}
|
||||||
const rootNode = computed(()=>{
|
const rootNode = computed(()=>{
|
||||||
return store.currentFlow?.getRoot();
|
return store.currentFlow?.getRoot();
|
||||||
});
|
});
|
||||||
@@ -148,9 +94,6 @@ const minPanelWidth=computed(()=>{
|
|||||||
return "300px";
|
return "300px";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const fixedLeftPosition = computed(()=>{
|
|
||||||
return drawerLeft.value?"300px":"0px";
|
|
||||||
});
|
|
||||||
|
|
||||||
const addNode = (node: IActionNode, inputPoint: string) => {
|
const addNode = (node: IActionNode, inputPoint: string) => {
|
||||||
if (drawerRight.value) {
|
if (drawerRight.value) {
|
||||||
@@ -193,7 +136,7 @@ const onDeleteAllNextNodes = (node: IActionNode) => {
|
|||||||
}
|
}
|
||||||
const closeDg = (val: any) => {
|
const closeDg = (val: any) => {
|
||||||
console.log("Dialog closed->", val);
|
console.log("Dialog closed->", val);
|
||||||
if (val == 'OK' && appDg?.value?.selected?.length > 0) {
|
if (val == 'OK') {
|
||||||
const data = appDg.value.selected[0];
|
const data = appDg.value.selected[0];
|
||||||
const actionProps = JSON.parse(data.property);
|
const actionProps = JSON.parse(data.property);
|
||||||
const outputPoint = JSON.parse(data.outputPoints);
|
const outputPoint = JSON.parse(data.outputPoints);
|
||||||
@@ -250,38 +193,13 @@ const onDeploy = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSaveActionProps=(props:IActionProperty[])=>{
|
|
||||||
if(store.activeNode){
|
|
||||||
store.activeNode.actionProps=props;
|
|
||||||
$q.notify({
|
|
||||||
type: 'positive',
|
|
||||||
caption: "通知",
|
|
||||||
message: `${store.activeNode?.subTitle}の属性を設定しました。(保存はされていません)`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSaveVersion = async () => {
|
|
||||||
versionInfo.value = {
|
|
||||||
id: '1' // TODO
|
|
||||||
}
|
|
||||||
saveVersionAction.value = true;
|
|
||||||
// await onSaveAllFlow();
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeSaveVersionDg = (val: 'OK'|'CANCEL') => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
console.log(versionInfo.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSaveFlow = async () => {
|
const onSaveFlow = async () => {
|
||||||
const targetFlow = store.selectedFlow;
|
const targetFlow = store.selectedFlow;
|
||||||
if (targetFlow === undefined) {
|
if (targetFlow === undefined) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
caption: 'エラー',
|
caption: "エラー",
|
||||||
message: `選択中のフローがありません。`
|
message: `編集中のフローがありません。`
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -303,103 +221,38 @@ const onSaveFlow = async () => {
|
|||||||
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/**
|
|
||||||
* すべてフローの設定を保存する
|
|
||||||
*/
|
|
||||||
const onSaveAllFlow= async ()=>{
|
|
||||||
try{
|
|
||||||
const targetFlows = store.eventTree.findAllFlows();
|
|
||||||
if (!targetFlows || targetFlows.length === 0 ) {
|
|
||||||
$q.notify({
|
|
||||||
type: 'negative',
|
|
||||||
caption: 'エラー',
|
|
||||||
message: `設定されたフローがありません。`
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saveLoading.value = true;
|
|
||||||
for(const flow of targetFlows ){
|
|
||||||
const isNew = flow.id === '';
|
|
||||||
if(isNew && flow.actionNodes.length===1){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await store.saveFlow(flow);
|
|
||||||
}
|
|
||||||
$q.notify({
|
|
||||||
type: 'positive',
|
|
||||||
caption: "通知",
|
|
||||||
message: `すべてのフロー設定を保存しました。`
|
|
||||||
});
|
|
||||||
saveLoading.value = false;
|
|
||||||
}catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
saveLoading.value = false;
|
|
||||||
$q.notify({
|
|
||||||
type: 'negative',
|
|
||||||
caption: "エラー",
|
|
||||||
message: `フローの設定の保存が失敗しました。`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
initLoading.value = true;
|
|
||||||
if (store.appInfo === undefined && route?.params?.id !== undefined) {
|
|
||||||
// only for page refreshed
|
|
||||||
const app = await fetchAppById(route.params.id as string);
|
|
||||||
store.setApp(app);
|
|
||||||
};
|
|
||||||
await store.loadFlow();
|
|
||||||
initLoading.value = false
|
|
||||||
drawerLeft.value = true;
|
drawerLeft.value = true;
|
||||||
}
|
if (store.appInfo === undefined) return;
|
||||||
|
const flowCtrl = new FlowCtrl();
|
||||||
const fetchAppById = async(id: string) => {
|
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
|
||||||
let result = await api.get('api/apps');
|
if (actionFlows && actionFlows.length > 0) {
|
||||||
const app = result.data?.data?.find((item: IManagedApp) => item.appid === id ) as IManagedApp;
|
store.setFlows(actionFlows);
|
||||||
if (app) {
|
|
||||||
return convertManagedAppToAppInfo(app);
|
|
||||||
}
|
}
|
||||||
|
if (actionFlows && actionFlows.length == 1) {
|
||||||
result = await api.get(`api/v1/app?app=${id}`);
|
store.selectFlow(actionFlows[0]);
|
||||||
const kApp = result?.data as IAppDisplay | KErrorMsg;
|
|
||||||
if (isErrorMsg(kApp)) {
|
|
||||||
$q.notify({
|
|
||||||
type: 'negative',
|
|
||||||
caption: 'エラー',
|
|
||||||
message: kApp.message,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return kApp;
|
const root = actionFlows[0].getRoot();
|
||||||
}
|
if (root) {
|
||||||
|
store.setActiveNode(root);
|
||||||
type KErrorMsg = {
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isErrorMsg = (e: IAppDisplay | KErrorMsg): e is KErrorMsg => {
|
|
||||||
return 'message' in e;
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertManagedAppToAppInfo = (app: IManagedApp): AppInfo => {
|
|
||||||
return {
|
|
||||||
appId: app.appid,
|
|
||||||
name: app.appname
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const onClearFilter=()=>{
|
|
||||||
filter.value='';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
authStore.setLeftMenu(false);
|
authStore.toggleLeftMenu();
|
||||||
fetchData();
|
fetchData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.app-selector {
|
||||||
|
padding: 15px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
.flowchart {
|
.flowchart {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,252 +1,118 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<div class="q-gutter-sm row items-start">
|
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
|
||||||
<q-breadcrumbs>
|
:loading="loading" v-model:selected="selected">
|
||||||
<q-breadcrumbs-el icon="domain" label="ドメイン管理" />
|
|
||||||
</q-breadcrumbs>
|
|
||||||
</div>
|
|
||||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
|
||||||
|
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
|
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="編集" @click="editRow" />
|
||||||
|
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="削除" @click="removeRow" />
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
<q-input borderless dense debounce="300" color="primary" v-model="filter">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search" />
|
<q-icon name="search" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:header-cell-active="p">
|
|
||||||
<q-th auto-width :props="p">
|
|
||||||
<q-select class="filter-header" v-model="activeFilter" :options="activeOptions" @update:model-value="activeFilterUpdate" borderless
|
|
||||||
dense options-dense hide-bottom-space/>
|
|
||||||
</q-th>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:body-cell-active="p">
|
|
||||||
<q-td auto-width :props="p">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<template v-slot:body-cell-actions="p">
|
|
||||||
<q-td :props="p">
|
|
||||||
<table-action-menu :row="p.row" :actions="actionList" />
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
<q-dialog :model-value="show" persistent>
|
<q-dialog :model-value="show" persistent>
|
||||||
<q-card style="min-width: 36em">
|
<q-card style="min-width: 400px">
|
||||||
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6 q-ma-sm">Kintone Account</div>
|
<div class="text-h6">Kintone Account</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="q-pt-none q-mt-none">
|
<q-card-section class="q-pt-none">
|
||||||
<div class="q-gutter-lg">
|
<q-form class="q-gutter-md">
|
||||||
|
<q-input filled v-model="tenantid" label="Tenant" hint="Tenant ID" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
<q-input filled v-model="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
<q-input filled type="url" v-model.trim="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'KintoneのURLを入力してください']" />
|
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
|
||||||
|
|
||||||
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
<q-input v-if="isCreate" v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'"
|
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
|
||||||
hint="パスワード" label="パスワード" :disable="!isCreate" lazy-rules
|
label="User password">
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
|
||||||
@click="isPwd = !isPwd" />
|
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>ドメインの有効化</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="domainActive" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<div class="q-gutter-y-md" v-if="!isCreate">
|
|
||||||
<q-separator />
|
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>パスワードリセット</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="resetPsw" @update:model-value="updateResetPsw" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください"
|
|
||||||
label="パスワード" :disable="!resetPsw" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
|
||||||
@click="isPwd = !isPwd" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<!-- <q-btn label="asdf"/> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
|
||||||
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
|
||||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="text-primary">
|
||||||
|
<q-btn label="Save" type="submit" color="primary" @click="onSubmit" />
|
||||||
|
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="confirm" persistent>
|
<q-dialog v-model="confirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section v-if="deleteLoadingState == -1" class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<q-spinner color="primary" size="2em"/>
|
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||||
<span class="q-ml-sm">ドメイン利用権限を確認中</span>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section v-else-if="deleteLoadingState == 0" class="row items-center">
|
|
||||||
<q-icon name="warning" color="warning" size="2em" />
|
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section v-else class="row items-center">
|
|
||||||
<q-icon name="error" color="negative" size="2em" />
|
|
||||||
<span class="q-ml-sm">ドメイン利用権限が存在し、キャンセルする必要がある</span>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
<q-btn v-if="deleteLoadingState > 0" label="処理に行く" color="primary" v-close-popup @click="openShareDg(editId)" />
|
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
|
||||||
<q-btn flat v-else label="OK" :disabled="deleteLoadingState" color="primary" v-close-popup @click="deleteDomain()" />
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<share-domain-dialog v-model="shareDg" :domain="shareDomain" @close="closeShareDg()" />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, reactive } from 'vue';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
|
||||||
import { useDomainStore } from 'stores/useDomainStore';
|
|
||||||
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
|
||||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
|
||||||
import { IDomain, IDomainDisplay, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const domainStore = useDomainStore();
|
|
||||||
const inactiveRowClass = (row: IDomainOwnerDisplay) => row.domainActive ? '' : 'inactive-row';
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'id' },
|
||||||
// {
|
{
|
||||||
// name: 'tenantid',
|
name: 'tenantid',
|
||||||
// required: true,
|
required: true,
|
||||||
// label: 'テナントID',
|
label: 'Tenant',
|
||||||
// field: 'tenantid',
|
align: 'left',
|
||||||
// align: 'left',
|
field: row => row.tenantid,
|
||||||
// sortable: true,
|
format: val => `${val}`,
|
||||||
// classes: inactiveRowClass
|
sortable: true
|
||||||
// },
|
},
|
||||||
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
|
||||||
{ name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass },
|
{ name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
|
||||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'user', label: 'Account', field: 'user' },
|
||||||
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', classes: inactiveRowClass },
|
{ name: 'password', label: 'Password', field: 'password' }
|
||||||
{ name: 'owner', label: '所有者', field: (row: IDomainOwnerDisplay) => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
|
||||||
{ name: 'actions', label: '', field: 'actions', classes: inactiveRowClass }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
|
||||||
const loading = ref(false);
|
|
||||||
const addEditLoading = ref(false);
|
|
||||||
const deleteLoadingState = ref<number>(-1); // -1: loading, 0: allow, > 0: user count
|
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const rows = ref<IDomainOwnerDisplay[]>([]);
|
const rows = ref([]);
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const confirm = ref(false);
|
const confirm = ref(false);
|
||||||
const resetPsw = ref(false);
|
const selected = ref([]);
|
||||||
|
const tenantid = ref('');
|
||||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
|
||||||
// const tenantid = ref(authStore.currentDomain.id);
|
|
||||||
const name = ref('');
|
const name = ref('');
|
||||||
const url = ref('');
|
const url = ref('');
|
||||||
const isPwd = ref(true);
|
const isPwd = ref(true);
|
||||||
const kintoneuser = ref('');
|
const kintoneuser = ref('');
|
||||||
const kintonepwd = ref('');
|
const kintonepwd = ref('');
|
||||||
const kintonepwdBK = ref('');
|
|
||||||
const domainActive = ref(true);
|
|
||||||
const isCreate = ref(true);
|
|
||||||
let editId = ref(0);
|
let editId = ref(0);
|
||||||
const shareDg = ref(false);
|
|
||||||
const shareDomain = ref<IDomainOwnerDisplay>({} as IDomainOwnerDisplay);
|
|
||||||
|
|
||||||
const activeOptions = [
|
const getDomain = async () => {
|
||||||
{ value: 0, label: '全状態' },
|
|
||||||
{ value: 1, label: '使用' },
|
|
||||||
{ value: 2, label: '未使用'}
|
|
||||||
]
|
|
||||||
const activeFilter = ref(activeOptions[0]);
|
|
||||||
|
|
||||||
const activeFilterUpdate = (option: {value: number}) => {
|
|
||||||
switch (option.value) {
|
|
||||||
case 1:
|
|
||||||
getDomain((row) => row.domainActive)
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
getDomain((row) => !row.domainActive)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
getDomain()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionList = [
|
|
||||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
|
||||||
{ label: '利用権限設定', icon: 'person_add_alt', action: openShareDg },
|
|
||||||
{ separator: true },
|
|
||||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
|
||||||
];
|
|
||||||
|
|
||||||
const getDomain = async (filter?: (row: IDomainOwnerDisplay) => boolean) => {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await api.get<{data:IDomain[]}>(`api/domains`);
|
const result= await api.get(`api/domains/1`);
|
||||||
rows.value = data.data.map((item) => {
|
rows.value= result.data.map((item)=>{
|
||||||
return {
|
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
|
||||||
id: item.id,
|
});
|
||||||
tenantid: item.tenantid,
|
|
||||||
domainActive: item.is_active,
|
|
||||||
name: item.name,
|
|
||||||
url: item.url,
|
|
||||||
user: item.kintoneuser,
|
|
||||||
password: item.kintonepwd,
|
|
||||||
owner: {
|
|
||||||
id: item.owner.id,
|
|
||||||
firstName: item.owner.first_name,
|
|
||||||
lastName: item.owner.last_name,
|
|
||||||
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
|
||||||
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
|
||||||
email: item.owner.email,
|
|
||||||
isActive: item.owner.is_active,
|
|
||||||
isSuperuser: item.owner.is_superuser,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).filter(filter || (() => true));
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,96 +122,77 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// emulate fetching data from server
|
// emulate fetching data from server
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
// editId.value
|
editId.value
|
||||||
onReset();
|
|
||||||
show.value = true;
|
show.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeRow(row: IDomainOwnerDisplay) {
|
const removeRow = () => {
|
||||||
|
//loading.value = true
|
||||||
confirm.value = true;
|
confirm.value = true;
|
||||||
deleteLoadingState.value = -1;
|
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
||||||
|
if (selected.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
|
|
||||||
const { data } = await api.get(`/api/domainshareduser/${row.id}`);
|
|
||||||
deleteLoadingState.value = data.data.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteDomain = () => {
|
const deleteDomain = () => {
|
||||||
api.delete(`api/domain/${editId.value}`).then(({ data }) => {
|
api.delete(`api/domain/${editId.value}`).then(() => {
|
||||||
if (!data.data) {
|
|
||||||
// TODO dialog
|
|
||||||
}
|
|
||||||
getDomain();
|
getDomain();
|
||||||
// authStore.setCurrentDomain();
|
|
||||||
})
|
})
|
||||||
editId.value = 0; // set in removeRow()
|
editId.value = 0;
|
||||||
deleteLoadingState.value = -1;
|
selected.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
function editRow(row) {
|
const editRow = () => {
|
||||||
isCreate.value = false
|
if (selected.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
// tenantid.value = row.tenantid;
|
tenantid.value = row.tenantid;
|
||||||
name.value = row.name;
|
name.value = row.name;
|
||||||
url.value = row.url;
|
url.value = row.url;
|
||||||
kintoneuser.value = row.user;
|
kintoneuser.value = row.user;
|
||||||
kintonepwd.value = row.password;
|
kintonepwd.value = row.password;
|
||||||
domainActive.value = row.domainActive;
|
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
show.value = true;
|
show.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateResetPsw = (value: boolean) => {
|
|
||||||
if (value === true) {
|
|
||||||
kintonepwd.value = ''
|
|
||||||
isPwd.value = true
|
|
||||||
} else {
|
|
||||||
kintonepwd.value = kintonepwdBK.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeDg = () => {
|
const closeDg = () => {
|
||||||
show.value = false;
|
show.value = false;
|
||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
addEditLoading.value = true;
|
if (editId.value !== 0) {
|
||||||
const method = editId.value !== 0 ? 'put' : 'post';
|
api.put(`api/domain`, {
|
||||||
const param: IDomainSubmit = {
|
|
||||||
'id': editId.value,
|
'id': editId.value,
|
||||||
'tenantid': '1', // TODO: テナントIDを取得する
|
'tenantid': tenantid.value,
|
||||||
'name': name.value,
|
'name': name.value,
|
||||||
'url': url.value,
|
'url': url.value,
|
||||||
'kintoneuser': kintoneuser.value,
|
'kintoneuser': kintoneuser.value,
|
||||||
'kintonepwd': ((isCreate.value && editId.value == 0) || resetPsw.value) ? kintonepwd.value : '',
|
'kintonepwd': kintonepwd.value
|
||||||
'is_active': domainActive.value,
|
}).then(() => {
|
||||||
'ownerid': authStore.userId || ''
|
|
||||||
}
|
|
||||||
// for search: api.put(`api/domain`)、api.post(`api/domain`)
|
|
||||||
api[method].apply(api, [`api/domain`, param]).then(async (resp: any) => {
|
|
||||||
const res = resp.data;
|
|
||||||
if (res.data.id === currentDomainId.value && !res.data.is_active) {
|
|
||||||
await authStore.setCurrentDomain();
|
|
||||||
}
|
|
||||||
getDomain();
|
getDomain();
|
||||||
domainStore.loadUserDomains();
|
|
||||||
closeDg();
|
closeDg();
|
||||||
onReset();
|
onReset();
|
||||||
addEditLoading.value = false;
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function openShareDg(row: IDomainOwnerDisplay|number) {
|
|
||||||
if (typeof row === 'number') {
|
|
||||||
row = rows.value.find(item => item.id === row) as IDomainOwnerDisplay;
|
|
||||||
}
|
}
|
||||||
shareDomain.value = row ;
|
else {
|
||||||
shareDg.value = true;
|
api.post(`api/domain`, {
|
||||||
};
|
'id': 0,
|
||||||
|
'tenantid': tenantid.value,
|
||||||
function closeShareDg() {
|
'name': name.value,
|
||||||
shareDg.value = false;
|
'url': url.value,
|
||||||
|
'kintoneuser': kintoneuser.value,
|
||||||
|
'kintonepwd': kintonepwd.value
|
||||||
|
}).then(() => {
|
||||||
|
getDomain();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
selected.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
@@ -355,25 +202,5 @@ const onReset = () => {
|
|||||||
kintonepwd.value = '';
|
kintonepwd.value = '';
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
isCreate.value = true;
|
|
||||||
domainActive.value = true;
|
|
||||||
resetPsw.value = false
|
|
||||||
addEditLoading.value = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
.filter-header .q-field__native {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.filter-header .q-icon {
|
|
||||||
width: 12px;
|
|
||||||
}
|
|
||||||
.q-table td.inactive-row {
|
|
||||||
color: #aaa;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
.q-table tr > td.inactive-row:last-child {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,65 +1,169 @@
|
|||||||
<template>
|
<!-- <template>
|
||||||
|
<div class="q-pa-md" style="max-width: 400px">
|
||||||
|
|
||||||
<div class="q-pa-md">
|
<q-form
|
||||||
<div class="q-gutter-sm row items-start">
|
@submit="onSubmit"
|
||||||
<q-breadcrumbs>
|
@reset="onReset"
|
||||||
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
|
class="q-gutter-md"
|
||||||
</q-breadcrumbs>
|
>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="name"
|
||||||
|
label="Your name *"
|
||||||
|
hint="Kintone envirment name"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled type="url"
|
||||||
|
v-model="url"
|
||||||
|
label="Kintone url"
|
||||||
|
hint="Kintone domain address"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="username"
|
||||||
|
label="Login user "
|
||||||
|
hint="Kintone user name"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-toggle v-model="accept" label="Active Domain" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<q-btn label="Submit" type="submit" color="primary"/>
|
||||||
|
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
|
||||||
</div>
|
</div>
|
||||||
|
</q-form>
|
||||||
|
|
||||||
<q-table :loading="initLoading" grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
</div>
|
||||||
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
</template>
|
||||||
|
<script>
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup () {
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
const name = ref(null)
|
||||||
|
const age = ref(null)
|
||||||
|
const accept = ref(false)
|
||||||
|
const isPwd =ref(true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
age,
|
||||||
|
accept,
|
||||||
|
isPwd,
|
||||||
|
isDomain(val) {
|
||||||
|
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||||
|
return (domainPattern.test(val) || '無効なURL')
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit () {
|
||||||
|
if (accept.value !== true) {
|
||||||
|
$q.notify({
|
||||||
|
color: 'red-5',
|
||||||
|
textColor: 'white',
|
||||||
|
icon: 'warning',
|
||||||
|
message: 'You need to accept the license and terms first'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$q.notify({
|
||||||
|
color: 'green-4',
|
||||||
|
textColor: 'white',
|
||||||
|
icon: 'cloud_done',
|
||||||
|
message: 'Submitted'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onReset () {
|
||||||
|
name.value = null
|
||||||
|
age.value = null
|
||||||
|
accept.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script> -->
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-btn color="primary" label="追加" @click="newDomain()" dense />
|
||||||
|
</div>
|
||||||
<q-space />
|
<q-space />
|
||||||
<div class="row q-gutter-md">
|
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||||
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search" />
|
<q-icon name="search" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:header>
|
|
||||||
<div style="height: 1dvh">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:item="props">
|
<template v-slot:item="props">
|
||||||
<div class="q-pa-sm">
|
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
||||||
<domain-card :item="props.row" :active-id="activeDomainId">
|
<q-card>
|
||||||
<template v-slot:actions>
|
<q-card-section>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">Domain</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">URL</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">Account</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-value">{{isActive(props.row.id) }}</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-chip class="no-border" v-if="isActive(props.row.id)" outline color="primary" text-color="white" icon="done">
|
<q-btn flat @click = "activeDomain(props.row.id)">有効</q-btn>
|
||||||
既定
|
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
|
||||||
</q-chip>
|
|
||||||
<q-btn flat v-else :loading="activeDomainLoadingId === props.row.id" :disable="deleteDomainLoadingId === props.row.id" @click="activeDomain(props.row)">既定にする</q-btn>
|
|
||||||
<q-btn flat :disable="isNotOwner(props.row.owner.id) || activeDomainLoadingId === props.row.id" :text-color="isNotOwner(props.row.owner.id)?'grey':''" :loading="deleteDomainLoadingId === props.row.id" @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</template>
|
</q-card>
|
||||||
</domain-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished" :ok-btn-loading="addUserDomainLoading" :ok-btn-auto-close="false">
|
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
|
||||||
<domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select>
|
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="showDeleteConfirm" persistent>
|
<q-dialog v-model="confirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<div class="q-ma-sm q-mt-md">
|
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||||
<q-icon name="warning" color="warning" size="2em" />
|
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
</div>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomainFinished()" />
|
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -67,153 +171,100 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { useQuasar } from 'quasar'
|
||||||
import { api } from 'boot/axios';
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
|
||||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
|
||||||
|
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
import DomainCard from 'components/UserDomain/DomainCard.vue';
|
|
||||||
import DomainSelect from 'components/DomainSelect.vue';
|
import DomainSelect from 'components/DomainSelect.vue';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
import { api } from 'boot/axios';
|
||||||
const rows = ref<IDomainOwnerDisplay[]>([]);
|
import { domain } from 'process';
|
||||||
const rowIds = new Set<string>();
|
|
||||||
const initLoading = ref(true);
|
|
||||||
const addUserDomainLoading = ref(false);
|
|
||||||
const activeDomainLoadingId = ref<number|undefined>(undefined);
|
|
||||||
const deleteDomainLoadingId = ref<number|undefined>(undefined);
|
|
||||||
|
|
||||||
const columns = [
|
const $q = useQuasar()
|
||||||
{ name: 'id' },
|
|
||||||
{ name: 'name', required: true, label: 'Name', align: 'left', field: 'name', sortable: true },
|
const domainDg = ref();
|
||||||
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
const selected = ref([])
|
||||||
{ name: 'kintoneuser', label: 'User', field: 'user', sortable: true },
|
|
||||||
];
|
const show = ref(false);
|
||||||
const userDomainTableFilter = ref();
|
const confirm = ref(false)
|
||||||
|
|
||||||
let editId = ref(0);
|
let editId = ref(0);
|
||||||
|
let activedomainid = ref(0);
|
||||||
|
|
||||||
const showAddDomainDg = ref(false);
|
const columns = [
|
||||||
const addDomainRef = ref();
|
{ name: 'id'},
|
||||||
|
{name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
|
||||||
|
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||||
|
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
||||||
|
{ name: 'kintonepwd' },
|
||||||
|
{ name: 'active', field: 'active'}
|
||||||
|
]
|
||||||
|
|
||||||
const filterAddDgInitRows = (row: {domainActive: boolean, id: string}) => {
|
const rows = ref([] as any[]);
|
||||||
return row.domainActive && !rowIds.has(row.id);
|
|
||||||
|
const isActive = (id:number) =>{
|
||||||
|
if(id == activedomainid.value)
|
||||||
|
return "Active";
|
||||||
|
else
|
||||||
|
return "Inactive";
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickAddDomain = () => {
|
const newDomain = () => {
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
showAddDomainDg.value = true;
|
show.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addUserDomainFinished = async (val: string) => {
|
|
||||||
showAddDomainDg.value = true;
|
const activeDomain = (id:number) => {
|
||||||
const selected = addDomainRef.value.selected;
|
api.put(`api/activedomain/`+ id).then(() =>{
|
||||||
if (val == 'OK' && selected.length > 0) {
|
getDomain();
|
||||||
addUserDomainLoading.value = true;
|
})
|
||||||
const { data } = await api.post(`api/domain/${authStore.userId}?domainid=${selected[0].id}`)
|
|
||||||
if (rows.value.length === 0 && data.data) {
|
|
||||||
const domain = data.data;
|
|
||||||
await authStore.setCurrentDomain({
|
|
||||||
id: domain.id,
|
|
||||||
kintoneUrl: domain.url,
|
|
||||||
domainName: domain.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await getDomain();
|
|
||||||
}
|
|
||||||
addUserDomainLoading.value = false;
|
|
||||||
showAddDomainDg.value = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDeleteConfirm = ref(false);
|
const deleteConfirm = (row:object) => {
|
||||||
|
confirm.value = true;
|
||||||
const clickDeleteConfirm = (row: any) => {
|
|
||||||
showDeleteConfirm.value = true;
|
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteDomainFinished = async () => {
|
const deleteDomain = () => {
|
||||||
deleteDomainLoadingId.value = editId.value;
|
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
|
||||||
const { data } = await api.delete(`api/domain/${editId.value}/${authStore.userId}`)
|
getDomain();
|
||||||
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) {
|
|
||||||
authStore.setCurrentDomain();
|
|
||||||
}
|
|
||||||
editId.value = 0;
|
|
||||||
await getDomain();
|
|
||||||
deleteDomainLoadingId.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeDomain = async (domain: any) => {
|
|
||||||
activeDomainLoadingId.value = domain.id;
|
|
||||||
await authStore.setCurrentDomain({
|
|
||||||
id: domain.id,
|
|
||||||
kintoneUrl: domain.url,
|
|
||||||
domainName: domain.name
|
|
||||||
});
|
|
||||||
await getDomain();
|
|
||||||
activeDomainLoadingId.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
let activeDomainId = ref(0);
|
|
||||||
|
|
||||||
const isActive = computed(() => (id: number) => {
|
|
||||||
return id == activeDomainId.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isNotOwner = computed(() => (ownerId: string) => {
|
|
||||||
return ownerId !== authStore.userId;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getDomain = async (userId? : string) => {
|
|
||||||
rowIds.clear();
|
|
||||||
const resp = await api.get(`api/defaultdomain`);
|
|
||||||
activeDomainId.value = resp?.data?.data?.id;
|
|
||||||
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
|
|
||||||
const domains = domainResult.data as any[];
|
|
||||||
rows.value = domains.sort((a, b) => a.id - b.id).reduce((acc, item) => {
|
|
||||||
rowIds.add(item.id);
|
|
||||||
if (item.is_active) {
|
|
||||||
acc.push({
|
|
||||||
id: item.id,
|
|
||||||
tenantid: item.tenantid,
|
|
||||||
domainActive: item.is_active,
|
|
||||||
name: item.name,
|
|
||||||
url: item.url,
|
|
||||||
user: item.kintoneuser,
|
|
||||||
password: item.kintonepwd,
|
|
||||||
owner: {
|
|
||||||
id: item.owner.id,
|
|
||||||
firstName: item.owner.first_name,
|
|
||||||
lastName: item.owner.last_name,
|
|
||||||
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
|
||||||
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
|
||||||
email: item.owner.email,
|
|
||||||
isActive: item.owner.is_active,
|
|
||||||
isSuperuser: item.owner.is_superuser,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
editId.value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDg = (val:string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
let dodmainids =[];
|
||||||
|
let domains = JSON.parse(JSON.stringify(domainDg.value.selected));
|
||||||
|
for(var key in domains)
|
||||||
|
{
|
||||||
|
dodmainids.push(domains[key].id);
|
||||||
|
}
|
||||||
|
api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
|
||||||
}
|
}
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
};
|
||||||
|
const getDomain = async () => {
|
||||||
|
const resp = await api.get(`api/activedomain`);
|
||||||
|
activedomainid.value = resp.data.id;
|
||||||
|
const domainResult = await api.get(`api/domain`);
|
||||||
|
const domains = domainResult.data as any[];
|
||||||
|
rows.value=domains.map((item)=>{
|
||||||
|
return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
initLoading.value = true;
|
|
||||||
await getDomain();
|
await getDomain();
|
||||||
initLoading.value = false;
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isDomain = (val) =>{
|
||||||
|
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||||
|
// return (domainPattern.test(val) || '無効なURL')
|
||||||
|
return true;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
.domain-card {
|
|
||||||
width: 22rem;
|
|
||||||
word-break: break-word;
|
|
||||||
|
|
||||||
.smaller-font-size {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,310 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<div class="q-gutter-sm row items-start">
|
|
||||||
<q-breadcrumbs>
|
|
||||||
<q-breadcrumbs-el icon="manage_accounts" label="ユーザー管理" />
|
|
||||||
</q-breadcrumbs>
|
|
||||||
</div>
|
|
||||||
<q-table title="ユーザーリスト" :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-status="props">
|
|
||||||
<q-td :props="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>
|
|
||||||
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:header-cell-status="p">
|
|
||||||
<q-th :props="p">
|
|
||||||
<div class="row items-center">
|
|
||||||
<label class="q-mr-md">{{ p.col.label }}</label>
|
|
||||||
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
|
|
||||||
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
|
||||||
</div>
|
|
||||||
</q-th>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<q-dialog :model-value="show" persistent>
|
|
||||||
<q-card style="min-width: 36em">
|
|
||||||
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6 q-ma-sm">K-True Account</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section class="q-pt-none q-mt-none">
|
|
||||||
<div class="q-gutter-lg">
|
|
||||||
|
|
||||||
<q-input filled v-model="lastName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
|
||||||
|
|
||||||
<q-input filled v-model="firstName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
|
||||||
|
|
||||||
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || '電子メールを入力してください']" autocomplete="new-password" />
|
|
||||||
|
|
||||||
<q-input v-if="isCreate" v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワード"
|
|
||||||
label="パスワード" :disable="!isCreate" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
|
||||||
@click="isPwd = !isPwd" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>システム管理者</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="isSuperuser" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>使用可能</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="isActive" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<div class="q-gutter-y-md" v-if="!isCreate">
|
|
||||||
<q-separator />
|
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>パスワードリセット</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="resetPsw" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-input v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください" label="パスワード"
|
|
||||||
:disable="!resetPsw" lazy-rules :rules="[val => val && val.length > 0 || 'Please type something']"
|
|
||||||
autocomplete="new-password">
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
|
||||||
@click="isPwd = !isPwd" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<!-- <q-btn label="asdf"/> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
|
||||||
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
|
||||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="confirm" persistent>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section class="row items-center">
|
|
||||||
<q-icon name="warning" color="warning" size="2em" />
|
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
|
||||||
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteUser()" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
|
|
||||||
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' },
|
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const addEditLoading = ref(false);
|
|
||||||
const filter = ref('');
|
|
||||||
const statusFilter = ref('全データ');
|
|
||||||
const rows = ref([]);
|
|
||||||
const show = ref(false);
|
|
||||||
const confirm = ref(false);
|
|
||||||
const resetPsw = ref(false);
|
|
||||||
|
|
||||||
const firstName = ref('');
|
|
||||||
const lastName = ref('');
|
|
||||||
const email = ref('');
|
|
||||||
const isSuperuser = ref(false);
|
|
||||||
const isActive = ref(true);
|
|
||||||
|
|
||||||
const isPwd = ref(true);
|
|
||||||
const pwd = ref('');
|
|
||||||
const isCreate = ref(true);
|
|
||||||
let editId = ref(0);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateStatusFilter = (status) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'システム管理者のみ':
|
|
||||||
getUsers((row) => row.isSuperuser)
|
|
||||||
break;
|
|
||||||
case '使用可能':
|
|
||||||
getUsers((row) => row.isActive)
|
|
||||||
break;
|
|
||||||
case '使用不可':
|
|
||||||
getUsers((row) => !row.isActive)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
getUsers()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getUsers();
|
|
||||||
})
|
|
||||||
|
|
||||||
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
|
|
||||||
|
|
||||||
// emulate fetching data from server
|
|
||||||
const addRow = () => {
|
|
||||||
// editId.value
|
|
||||||
onReset();
|
|
||||||
show.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeRow = (row) => {
|
|
||||||
confirm.value = true;
|
|
||||||
editId.value = row.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteUser = () => {
|
|
||||||
api.delete(`api/v1/users/${editId.value}`).then(() => {
|
|
||||||
getUsers();
|
|
||||||
})
|
|
||||||
editId.value = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const editRow = (row) => {
|
|
||||||
isCreate.value = false
|
|
||||||
editId.value = row.id;
|
|
||||||
|
|
||||||
firstName.value = row.firstName;
|
|
||||||
lastName.value = row.lastName;
|
|
||||||
email.value = row.email;
|
|
||||||
pwd.value = row.password;
|
|
||||||
|
|
||||||
isSuperuser.value = row.isSuperuser;
|
|
||||||
isActive.value = row.isActive;
|
|
||||||
|
|
||||||
isPwd.value = true;
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDg = () => {
|
|
||||||
show.value = false;
|
|
||||||
onReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = () => {
|
|
||||||
addEditLoading.value = true;
|
|
||||||
if (editId.value !== 0) {
|
|
||||||
api.put(`api/v1/users/${editId.value}`, {
|
|
||||||
'first_name': firstName.value,
|
|
||||||
'last_name': lastName.value,
|
|
||||||
'is_superuser': isSuperuser.value,
|
|
||||||
'is_active': isActive.value,
|
|
||||||
'email': email.value,
|
|
||||||
...(isCreate.value || resetPsw.value ? { password: pwd.value } : {})
|
|
||||||
}).then(() => {
|
|
||||||
getUsers();
|
|
||||||
closeDg();
|
|
||||||
onReset();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
api.post(`api/v1/users`, {
|
|
||||||
'id': 0,
|
|
||||||
'first_name': firstName.value,
|
|
||||||
'last_name': lastName.value,
|
|
||||||
'is_superuser': isSuperuser.value,
|
|
||||||
'is_active': isActive.value,
|
|
||||||
'email': email.value,
|
|
||||||
'password': pwd.value
|
|
||||||
}).then(() => {
|
|
||||||
getUsers();
|
|
||||||
closeDg();
|
|
||||||
onReset();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const onReset = () => {
|
|
||||||
firstName.value = '';
|
|
||||||
lastName.value = '';
|
|
||||||
email.value = '';
|
|
||||||
pwd.value = '';
|
|
||||||
isActive.value = true;
|
|
||||||
isSuperuser.value = false;
|
|
||||||
isPwd.value = true;
|
|
||||||
editId.value = 0;
|
|
||||||
isCreate.value = true;
|
|
||||||
resetPsw.value = false;
|
|
||||||
addEditLoading.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
createWebHashHistory,
|
createWebHashHistory,
|
||||||
createWebHistory,
|
createWebHistory,
|
||||||
} from 'vue-router';
|
} from 'vue-router';
|
||||||
import { Dialog } from 'quasar'
|
|
||||||
|
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
@@ -48,18 +47,6 @@ export default route(function (/* { store, ssrContext } */) {
|
|||||||
authStore.returnUrl = to.fullPath;
|
authStore.returnUrl = to.fullPath;
|
||||||
return '/login';
|
return '/login';
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to domain setting page if no domain exist
|
|
||||||
const domainPages = [...publicPages, '/domain', '/userDomain', '/user'];
|
|
||||||
if (!authStore.hasDomain && !domainPages.includes(to.path)) {
|
|
||||||
Dialog.create({
|
|
||||||
title: '注意',
|
|
||||||
message: '既定/利用可能なドメインはありません。<br>ドメイン管理ページに遷移して処理します。',
|
|
||||||
html: true,
|
|
||||||
persistent: true,
|
|
||||||
})
|
|
||||||
return '/domain';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return routerInstance;
|
return routerInstance;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('pages/LoginPage.vue')
|
component: () => import('pages/LoginPage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:'/FlowChart/:id',
|
path:'/FlowChart',
|
||||||
component:()=>import('layouts/MainLayout.vue'),
|
component:()=>import('layouts/MainLayout.vue'),
|
||||||
children:[
|
children:[
|
||||||
{path:'',component:()=>import('pages/FlowChart.vue')}
|
{path:'',component:()=>import('pages/FlowChart.vue')}
|
||||||
@@ -26,8 +26,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{ path: 'right', component: () => import('pages/testRight.vue') },
|
{ path: 'right', component: () => import('pages/testRight.vue') },
|
||||||
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
|
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
|
||||||
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
||||||
{ path: 'user', component: () => import('pages/UserManagement.vue')},
|
|
||||||
{ path: 'app', component: () => import('pages/AppManagement.vue')},
|
|
||||||
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes';
|
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes';
|
||||||
import { IKintoneEvent, KintoneEventManager, kintoneEvent } from 'src/types/KintoneEvents';
|
import { IKintoneEvent, KintoneEventManager } from 'src/types/KintoneEvents';
|
||||||
import { FlowCtrl } from '../control/flowctrl';
|
import { FlowCtrl } from '../control/flowctrl';
|
||||||
|
|
||||||
export interface FlowEditorState {
|
export interface FlowEditorState {
|
||||||
@@ -11,7 +11,7 @@ export interface FlowEditorState {
|
|||||||
activeNode: IActionNode | undefined;
|
activeNode: IActionNode | undefined;
|
||||||
eventTree: KintoneEventManager;
|
eventTree: KintoneEventManager;
|
||||||
selectedEvent: IKintoneEvent | undefined;
|
selectedEvent: IKintoneEvent | undefined;
|
||||||
expandedScreen: string[];
|
expandedScreen: any[];
|
||||||
}
|
}
|
||||||
const flowCtrl = new FlowCtrl();
|
const flowCtrl = new FlowCtrl();
|
||||||
const eventTree = new KintoneEventManager();
|
const eventTree = new KintoneEventManager();
|
||||||
@@ -62,19 +62,10 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
|||||||
},
|
},
|
||||||
selectFlow(flow: IActionFlow | undefined) {
|
selectFlow(flow: IActionFlow | undefined) {
|
||||||
this.selectedFlow = flow;
|
this.selectedFlow = flow;
|
||||||
if(flow!==undefined){
|
|
||||||
const eventId = flow.getRoot()?.name;
|
|
||||||
this.selectedEvent = this.eventTree.findEventById(eventId) as IKintoneEvent;
|
|
||||||
} else {
|
|
||||||
this.selectedEvent = undefined;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setActiveNode(node: IActionNode) {
|
setActiveNode(node: IActionNode) {
|
||||||
this.activeNode = node;
|
this.activeNode = node;
|
||||||
},
|
},
|
||||||
setCurrentEvent(event:IKintoneEvent | undefined){
|
|
||||||
this.selectedEvent=event;
|
|
||||||
},
|
|
||||||
setApp(app: AppInfo) {
|
setApp(app: AppInfo) {
|
||||||
this.appInfo = app;
|
this.appInfo = app;
|
||||||
},
|
},
|
||||||
@@ -88,47 +79,27 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
|||||||
//eventTreeにバンドする
|
//eventTreeにバンドする
|
||||||
this.eventTree.bindFlows(actionFlows);
|
this.eventTree.bindFlows(actionFlows);
|
||||||
if (actionFlows === undefined || actionFlows.length === 0) {
|
if (actionFlows === undefined || actionFlows.length === 0) {
|
||||||
this.setFlows([]);
|
this.flows = [];
|
||||||
this.selectFlow(undefined);
|
this.selectedFlow = undefined;
|
||||||
this.expandedScreen =[];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setFlows(actionFlows);
|
this.setFlows(actionFlows);
|
||||||
if (actionFlows && actionFlows.length > 0) {
|
if (actionFlows && actionFlows.length > 0) {
|
||||||
this.selectFlow(actionFlows[0]);
|
this.selectFlow(actionFlows[0]);
|
||||||
}
|
}
|
||||||
const root = actionFlows[0].getRoot();
|
const expandNames = actionFlows.map((flow) => flow.getRoot()?.title);
|
||||||
if (root) {
|
|
||||||
this.setActiveNode(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name);
|
|
||||||
const expandScreens:string[]=[];
|
|
||||||
expandEventIds.forEach((eventid)=>{
|
|
||||||
const eventNode=this.eventTree.findEventById(eventid||'');
|
|
||||||
if(eventNode){
|
|
||||||
expandScreens.push(eventNode.parentId);
|
|
||||||
if(eventNode.header==='DELETABLE'){
|
|
||||||
const groupEvent = this.eventTree.findEventById(eventNode.parentId);
|
|
||||||
if(groupEvent){
|
|
||||||
expandScreens.push(groupEvent.parentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// const expandName =actionFlows[0].getRoot()?.title;
|
// const expandName =actionFlows[0].getRoot()?.title;
|
||||||
this.expandedScreen = expandScreens;
|
this.expandedScreen = expandNames;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* フローをDBに保存及び更新する
|
* フローをDBに保存及び更新する
|
||||||
*/
|
*/
|
||||||
async saveFlow(flow: IActionFlow):Promise<boolean> {
|
async saveFlow(flow: IActionFlow) {
|
||||||
const root = flow.getRoot();
|
const root = flow.getRoot();
|
||||||
const isNew = flow.id === '';
|
const isNew = flow.id === '';
|
||||||
const jsonData = {
|
const jsonData = {
|
||||||
flowid: isNew ? flow.createNewId() : flow.id,
|
flowid: isNew ? flow.createNewId() : flow.id,
|
||||||
appid: this.appInfo?.appId,
|
appid: this.appInfo?.appId,
|
||||||
appname: this.appInfo?.name,
|
|
||||||
eventid: root?.name,
|
eventid: root?.name,
|
||||||
name: root?.subTitle,
|
name: root?.subTitle,
|
||||||
content: JSON.stringify(flow),
|
content: JSON.stringify(flow),
|
||||||
@@ -137,14 +108,7 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
|||||||
if (isNew) {
|
if (isNew) {
|
||||||
return await flowCtrl.SaveFlow(jsonData);
|
return await flowCtrl.SaveFlow(jsonData);
|
||||||
} else {
|
} else {
|
||||||
if(flow.actionNodes.length>1){
|
|
||||||
return await flowCtrl.UpdateFlow(jsonData);
|
return await flowCtrl.UpdateFlow(jsonData);
|
||||||
}else{
|
|
||||||
const eventId = flow.getRoot()?.name||'';
|
|
||||||
const eventNode = eventTree.findEventById(eventId) as kintoneEvent;
|
|
||||||
eventNode.flowData=undefined;
|
|
||||||
return await flowCtrl.DeleteFlow(flow.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { store } from 'quasar/wrappers'
|
import { store } from 'quasar/wrappers'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { Router } from 'vue-router';
|
import { Router } from 'vue-router';
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When adding new properties to stores, you should also
|
* When adding new properties to stores, you should also
|
||||||
@@ -24,11 +23,10 @@ declare module 'pinia' {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export default store((/* { ssrContext } */) => {
|
export default store((/* { ssrContext } */) => {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia()
|
||||||
pinia.use(piniaPluginPersistedstate);
|
|
||||||
|
|
||||||
// You can add Pinia plugins here
|
// You can add Pinia plugins here
|
||||||
// pinia.use(SomePiniaPlugin)
|
// pinia.use(SomePiniaPlugin)
|
||||||
|
|
||||||
return pinia;
|
return pinia
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,122 +1,91 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { router } from 'src/router';
|
import {router} from 'src/router';
|
||||||
import { IDomainInfo } from '../types/DomainTypes';
|
import {IDomainInfo} from '../types/ActionTypes';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
|
||||||
interface UserInfo {
|
|
||||||
firstName: string;
|
export interface IUserState{
|
||||||
lastName: string;
|
token?:string;
|
||||||
email: string;
|
returnUrl:string;
|
||||||
|
currentDomain:IDomainInfo;
|
||||||
|
LeftDrawer:boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserState {
|
export const useAuthStore = defineStore({
|
||||||
token?: string;
|
id: 'auth',
|
||||||
returnUrl: string;
|
state: ():IUserState =>{
|
||||||
currentDomain: IDomainInfo;
|
const token=localStorage.getItem('token')||'';
|
||||||
LeftDrawer: boolean;
|
if(token!==''){
|
||||||
userId?: string;
|
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||||
userInfo: UserInfo;
|
}
|
||||||
roles:string,
|
return {
|
||||||
permissions: string;
|
token,
|
||||||
}
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', {
|
|
||||||
state: (): IUserState => ({
|
|
||||||
token: '',
|
|
||||||
returnUrl: '',
|
returnUrl: '',
|
||||||
LeftDrawer: false,
|
LeftDrawer:false,
|
||||||
currentDomain: {} as IDomainInfo,
|
currentDomain: JSON.parse(localStorage.getItem('currentDomain')||"{}")
|
||||||
userId: '',
|
}
|
||||||
userInfo: {} as UserInfo,
|
|
||||||
roles:'',
|
|
||||||
permissions: '',
|
|
||||||
}),
|
|
||||||
getters: {
|
|
||||||
toggleLeftDrawer(): boolean {
|
|
||||||
return this.LeftDrawer;
|
|
||||||
},
|
},
|
||||||
hasDomain(): boolean {
|
getters:{
|
||||||
return this.currentDomain.id !== undefined;
|
toggleLeftDrawer():boolean{
|
||||||
|
return this.LeftDrawer;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setLeftMenu(value:boolean){
|
toggleLeftMenu(){
|
||||||
this.LeftDrawer=value;
|
this.LeftDrawer=!this.LeftDrawer;
|
||||||
},
|
},
|
||||||
toggleLeftMenu() {
|
async login(username:string, password:string) {
|
||||||
this.LeftDrawer = !this.LeftDrawer;
|
|
||||||
},
|
|
||||||
async login(username: string, password: string) {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('username', username);
|
params.append('username', username);
|
||||||
params.append('password', password);
|
params.append('password', password);
|
||||||
try {
|
try{
|
||||||
const result = await api.post(`api/token`, params);
|
const result = await api.post(`api/token`,params);
|
||||||
console.info(result);
|
console.info(result);
|
||||||
this.token = result.data.access_token;
|
this.token =result.data.access_token;
|
||||||
const tokenJson = jwtDecode(result.data.access_token);
|
localStorage.setItem('token', result.data.access_token);
|
||||||
this.userId = tokenJson.sub;
|
api.defaults.headers["Authorization"]='Bearer ' + this.token;
|
||||||
this.permissions = (tokenJson as any).permissions==='ALL' ? 'admin': 'user';
|
this.currentDomain=await this.getCurrentDomain();
|
||||||
api.defaults.headers['Authorization'] = 'Bearer ' + this.token;
|
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
||||||
this.currentDomain = await this.getCurrentDomain();
|
this.router.push(this.returnUrl || '/');
|
||||||
this.userInfo = await this.getUserInfo();
|
|
||||||
router.push(this.returnUrl || '/');
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
}catch(e)
|
||||||
console.error(e);
|
{
|
||||||
|
console.info(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getCurrentDomain(): Promise<IDomainInfo> {
|
async getCurrentDomain():Promise<IDomainInfo>{
|
||||||
const resp = await api.get(`api/defaultdomain`);
|
const activedomain = await api.get(`api/activedomain`);
|
||||||
const activedomain = resp?.data?.data;
|
|
||||||
return {
|
return {
|
||||||
id: activedomain?.id,
|
id:activedomain.data.id,
|
||||||
domainName: activedomain?.name,
|
domainName:activedomain.data.name,
|
||||||
kintoneUrl: activedomain?.url,
|
kintoneUrl:activedomain.data.url
|
||||||
};
|
|
||||||
},
|
|
||||||
async getUserInfo():Promise<UserInfo>{
|
|
||||||
const resp = (await api.get(`api/v1/users/me`)).data.data;
|
|
||||||
return {
|
|
||||||
firstName: resp.first_name,
|
|
||||||
lastName: resp.last_name,
|
|
||||||
email: resp.email,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getUserDomains():Promise<IDomainInfo[]>{
|
||||||
|
const resp = await api.get(`api/domain`);
|
||||||
|
const domains =resp.data as any[];
|
||||||
|
return domains.map(data=>{
|
||||||
|
return {
|
||||||
|
id:data.id,
|
||||||
|
domainName:data.name,
|
||||||
|
kintoneUrl:data.url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
logout() {
|
logout() {
|
||||||
this.token = '';
|
this.token = undefined;
|
||||||
this.currentDomain = {} as IDomainInfo; // 清空当前域
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('currentDomain');
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
},
|
},
|
||||||
async setCurrentDomain(domain?: IDomainInfo) {
|
async setCurrentDomain(domain:IDomainInfo){
|
||||||
if (!domain) {
|
if(domain.id===this.currentDomain.id){
|
||||||
this.currentDomain = {} as IDomainInfo;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (domain.id === this.currentDomain.id) {
|
await api.put(`api/activedomain/${domain.id}`);
|
||||||
return;
|
this.currentDomain=domain;
|
||||||
|
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
||||||
}
|
}
|
||||||
await api.put(`api/defaultdomain/${domain.id}`);
|
|
||||||
this.currentDomain = domain;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
persist: {
|
|
||||||
afterRestore: (ctx) => {
|
|
||||||
api.defaults.headers['Authorization'] = 'Bearer ' + ctx.store.token;
|
|
||||||
|
|
||||||
//axios例外キャプチャー
|
|
||||||
api.interceptors.response.use(
|
|
||||||
(response) => response,
|
|
||||||
(error) => {
|
|
||||||
if (error.response && error.response.status === 401) {
|
|
||||||
// 認証エラーの場合再ログインする
|
|
||||||
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
|
||||||
ctx.store.logout();
|
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
import { IDomainInfo, IDomain } from '../types/DomainTypes';
|
|
||||||
|
|
||||||
export const useDomainStore = defineStore('domain', {
|
|
||||||
state: () => ({
|
|
||||||
userDomains: [] as IDomainInfo[],
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
async loadUserDomains(): Promise<IDomainInfo[]> {
|
|
||||||
const resp = await api.get(`api/domain`);
|
|
||||||
const domains = resp.data as IDomain[];
|
|
||||||
this.userDomains = domains
|
|
||||||
.filter(data => data.is_active)
|
|
||||||
.map((data) => ({
|
|
||||||
id: data.id,
|
|
||||||
domainName: data.name,
|
|
||||||
kintoneUrl: data.url,
|
|
||||||
}));
|
|
||||||
return this.userDomains;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
export interface IDomainInfo{
|
||||||
|
id:number;
|
||||||
|
domainName:string;
|
||||||
|
kintoneUrl:string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* アプリ情報
|
* アプリ情報
|
||||||
@@ -287,11 +292,6 @@ export class ActionFlow implements IActionFlow {
|
|||||||
if (!targetNode) {
|
if (!targetNode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(targetNode.isRoot){
|
|
||||||
this.actionNodes=[targetNode];
|
|
||||||
targetNode.nextNodeIds.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (targetNode.nextNodeIds.size == 0) {
|
if (targetNode.nextNodeIds.size == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -312,9 +312,9 @@ export class ActionFlow implements IActionFlow {
|
|||||||
if (!targetNode) {
|
if (!targetNode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if (targetNode.nextNodeIds.size == 0) {
|
if (targetNode.nextNodeIds.size == 0) {
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
for (const [, id] of targetNode.nextNodeIds) {
|
for (const [, id] of targetNode.nextNodeIds) {
|
||||||
this.removeAll(id);
|
this.removeAll(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { IUser, IUserDisplay } from './UserTypes';
|
|
||||||
|
|
||||||
export interface IManagedApp {
|
|
||||||
appid: string;
|
|
||||||
appname: string;
|
|
||||||
domainurl: string;
|
|
||||||
version: string;
|
|
||||||
user: IUser;
|
|
||||||
updateuser: IUser;
|
|
||||||
create_time: string;
|
|
||||||
update_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IManagedApp {
|
|
||||||
appid: string;
|
|
||||||
appname: string;
|
|
||||||
domainurl: string;
|
|
||||||
version: string;
|
|
||||||
user: IUser;
|
|
||||||
updateuser: IUser;
|
|
||||||
create_time: string;
|
|
||||||
update_time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppDisplay{
|
|
||||||
id:string;
|
|
||||||
sortId: number;
|
|
||||||
name:string;
|
|
||||||
url:string;
|
|
||||||
updateUser: IUserDisplay;
|
|
||||||
updateTime:string;
|
|
||||||
version:string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IVersionInfo {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
desc?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppVersion {
|
|
||||||
id: number;
|
|
||||||
version: number;
|
|
||||||
appid: string;
|
|
||||||
name: string
|
|
||||||
comment: string;
|
|
||||||
updater: IUserDisplay;
|
|
||||||
updateTime: string;
|
|
||||||
creator: IUserDisplay;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
|
|
||||||
export interface IApp {
|
|
||||||
id: string,
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IField {
|
|
||||||
label?:string;
|
|
||||||
code:string;
|
|
||||||
type?:string;
|
|
||||||
required?:boolean;
|
|
||||||
options?:string;
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 選択されたフィールド
|
|
||||||
*/
|
|
||||||
export interface ISelectedField extends IField{
|
|
||||||
objectType:'Field'|'RefField';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppFields {
|
|
||||||
app?: IApp,
|
|
||||||
name?:string;
|
|
||||||
fields: IField[]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 条件式の入力ボタンの属性定義
|
|
||||||
*/
|
|
||||||
export interface IButtonConfig{
|
|
||||||
label: string;
|
|
||||||
color: string;
|
|
||||||
type: 'FieldAdd' | 'VariableAdd' | 'FunctionAdd';
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 条件入力項目の属性
|
|
||||||
*/
|
|
||||||
export interface IDynamicInputConfig{
|
|
||||||
canInput: boolean;
|
|
||||||
buttonsConfig: IButtonConfig[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 条件式入力項目の属性
|
|
||||||
*/
|
|
||||||
export interface ICoditionConfig{
|
|
||||||
left:IDynamicInputConfig,
|
|
||||||
right:IDynamicInputConfig
|
|
||||||
}
|
|
||||||
@@ -200,13 +200,12 @@ export class ConditionTree {
|
|||||||
return conditionString;
|
return conditionString;
|
||||||
} else {
|
} else {
|
||||||
const condNode=node as ConditionNode;
|
const condNode=node as ConditionNode;
|
||||||
if (condNode.object && condNode.object.sharedText && condNode.operator ) {
|
if (condNode.object && condNode.operator ) {
|
||||||
// let value=condNode.value;
|
// let value=condNode.value;
|
||||||
// if(value && typeof value ==='object' && ('label' in value)){
|
// if(value && typeof value ==='object' && ('label' in value)){
|
||||||
// value =condNode.value.label;
|
// value =condNode.value.label;
|
||||||
// }
|
// }
|
||||||
const rightVal = condNode.value.sharedText || '""';
|
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${condNode.value.sharedText}`;
|
||||||
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${rightVal}`;
|
|
||||||
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { IUser, IUserDisplay } from './UserTypes';
|
|
||||||
|
|
||||||
export interface IDomainInfo {
|
|
||||||
id: number;
|
|
||||||
domainName: string;
|
|
||||||
kintoneUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDomain {
|
|
||||||
id: number;
|
|
||||||
tenantid: string;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
kintoneuser: string,
|
|
||||||
kintonepwd: string,
|
|
||||||
create_time: string;
|
|
||||||
update_time: string;
|
|
||||||
is_active: boolean;
|
|
||||||
owner: IUser
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDomainSubmit extends Omit<IDomain, 'create_time' | 'update_time' | 'owner'> {
|
|
||||||
ownerid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDomainDisplay {
|
|
||||||
id: number;
|
|
||||||
tenantid: string;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
user: string;
|
|
||||||
password?: string;
|
|
||||||
domainActive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDomainOwnerDisplay extends IDomainDisplay {
|
|
||||||
owner: IUserDisplay
|
|
||||||
}
|
|
||||||
@@ -24,12 +24,11 @@ export class kintoneEvent implements IKintoneEvent {
|
|||||||
}
|
}
|
||||||
flowData?: IActionFlow | undefined;
|
flowData?: IActionFlow | undefined;
|
||||||
label: string;
|
label: string;
|
||||||
header :string;
|
header = 'EVENT';
|
||||||
constructor(label: string, eventId: string, parentId: string,header?:string) {
|
constructor(label: string, eventId: string, parentId: string) {
|
||||||
this.eventId = eventId;
|
this.eventId = eventId;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.parentId = parentId;
|
this.parentId = parentId;
|
||||||
this.header=header?header:'EVENT';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +97,16 @@ export class KintoneEventManager {
|
|||||||
const eventNode = this.findEventById(groupId);
|
const eventNode = this.findEventById(groupId);
|
||||||
if (eventNode && (eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')) {
|
if (eventNode && (eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')) {
|
||||||
const groupEvent = eventNode as kintoneEventGroup;
|
const groupEvent = eventNode as kintoneEventGroup;
|
||||||
const label=flow.getRoot()?.subTitle || '';
|
|
||||||
const newEvent = new kintoneEvent(label,eventId,groupId,'DELETABLE');
|
const newEvent = {
|
||||||
newEvent.flowData=flow;
|
label: flow.getRoot()?.subTitle || '',
|
||||||
|
eventId: eventId,
|
||||||
|
parentId: groupId,
|
||||||
|
header: 'DELETABLE',
|
||||||
|
hasFlow: true,
|
||||||
|
flowData: flow,
|
||||||
|
};
|
||||||
|
|
||||||
groupEvent.events.push(newEvent);
|
groupEvent.events.push(newEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,31 +138,6 @@ export class KintoneEventManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public findAllFlows():IActionFlow[]{
|
|
||||||
const flows:IActionFlow[]=[];
|
|
||||||
for (const screen of this.screens) {
|
|
||||||
for (const event of screen.events) {
|
|
||||||
if (event.header === "EVENT") {
|
|
||||||
const eventNode = event as IKintoneEvent;
|
|
||||||
if(eventNode.flowData!==undefined){
|
|
||||||
flows.push(eventNode.flowData);
|
|
||||||
}
|
|
||||||
}else if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
|
|
||||||
const eventGroup = event as IKintoneEventGroup;
|
|
||||||
eventGroup.events.forEach((ev) => {
|
|
||||||
if (ev.header === "EVENT" || ev.header === "DELETABLE") {
|
|
||||||
const eventNode = ev as IKintoneEvent;
|
|
||||||
if(eventNode.flowData!==undefined){
|
|
||||||
flows.push(eventNode.flowData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flows;
|
|
||||||
}
|
|
||||||
|
|
||||||
public findScreen(eventId: string): IKintoneEventGroup | undefined {
|
public findScreen(eventId: string): IKintoneEventGroup | undefined {
|
||||||
return this.screens.find((screen) => screen.eventId == eventId);
|
return this.screens.find((screen) => screen.eventId == eventId);
|
||||||
}
|
}
|
||||||
@@ -213,7 +194,7 @@ export class KintoneEventManager {
|
|||||||
),
|
),
|
||||||
new kintoneEventGroup(
|
new kintoneEventGroup(
|
||||||
'app.record.create.show.customButtonClick',
|
'app.record.create.show.customButtonClick',
|
||||||
'ボタンをクリックしたとき',
|
'ボタンをクリックした時',
|
||||||
[],
|
[],
|
||||||
'app.record.create'
|
'app.record.create'
|
||||||
),
|
),
|
||||||
@@ -241,7 +222,7 @@ export class KintoneEventManager {
|
|||||||
),
|
),
|
||||||
new kintoneEventGroup(
|
new kintoneEventGroup(
|
||||||
'app.record.detail.show.customButtonClick',
|
'app.record.detail.show.customButtonClick',
|
||||||
'ボタンをクリックしたとき',
|
'ボタンをクリックした時',
|
||||||
[],
|
[],
|
||||||
'app.record.detail'
|
'app.record.detail'
|
||||||
),
|
),
|
||||||
@@ -275,7 +256,7 @@ export class KintoneEventManager {
|
|||||||
),
|
),
|
||||||
new kintoneEventGroup(
|
new kintoneEventGroup(
|
||||||
'app.record.edit.show.customButtonClick',
|
'app.record.edit.show.customButtonClick',
|
||||||
'ボタンをクリックしたとき',
|
'ボタンをクリックした時',
|
||||||
[],
|
[],
|
||||||
'app.record.edit'
|
'app.record.edit'
|
||||||
),
|
),
|
||||||
@@ -297,7 +278,7 @@ export class KintoneEventManager {
|
|||||||
'app.record.index'
|
'app.record.index'
|
||||||
),
|
),
|
||||||
new kintoneEvent(
|
new kintoneEvent(
|
||||||
'インライン編集の保存をクリックしたとき',
|
'インライン編集の【保存】をクリックしたとき',
|
||||||
'app.record.index.edit.submit',
|
'app.record.index.edit.submit',
|
||||||
'app.record.index'
|
'app.record.index'
|
||||||
),
|
),
|
||||||
@@ -306,15 +287,15 @@ export class KintoneEventManager {
|
|||||||
'app.record.index.edit.submit.success',
|
'app.record.index.edit.submit.success',
|
||||||
'app.record.index'
|
'app.record.index'
|
||||||
),
|
),
|
||||||
// new kintoneEventForChange(
|
new kintoneEventForChange(
|
||||||
// 'app.record.index.edit.change',
|
'app.record.index.edit.change',
|
||||||
// 'インライン編集のフィールド値を変更したとき',
|
'インライン編集のフィールド値を変更したとき',
|
||||||
// [],
|
[],
|
||||||
// 'app.record.index'
|
'app.record.index'
|
||||||
// ),
|
),
|
||||||
new kintoneEventGroup(
|
new kintoneEventGroup(
|
||||||
'app.record.index.show.customButtonClick',
|
'app.record.detail.show.customButtonClick',
|
||||||
'ボタンをクリックしたとき',
|
'ボタンをクリックした時',
|
||||||
[],
|
[],
|
||||||
'app.record.index'
|
'app.record.index'
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
export interface IUser {
|
|
||||||
id: number;
|
|
||||||
first_name: string;
|
|
||||||
last_name: string;
|
|
||||||
email: string;
|
|
||||||
is_active: boolean,
|
|
||||||
is_superuser: boolean,
|
|
||||||
roles: object[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUserDisplay {
|
|
||||||
id: number;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
fullName: string;
|
|
||||||
fullNameSearch: string;
|
|
||||||
email: string;
|
|
||||||
isActive: boolean,
|
|
||||||
isSuperuser: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUserRolesDisplay extends IUserDisplay {
|
|
||||||
roles: object[]
|
|
||||||
}
|
|
||||||
@@ -283,11 +283,6 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
||||||
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
||||||
|
|
||||||
"@types/web-bluetooth@^0.0.20":
|
|
||||||
version "0.0.20"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
|
||||||
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^5.10.0":
|
"@typescript-eslint/eslint-plugin@^5.10.0":
|
||||||
version "5.61.0"
|
version "5.61.0"
|
||||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
||||||
@@ -424,11 +419,6 @@
|
|||||||
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
|
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
|
||||||
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||||
|
|
||||||
"@vue/devtools-api@^6.6.3":
|
|
||||||
version "6.6.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz#b23a588154cba8986bba82b6e1d0248bde3fd1a0"
|
|
||||||
integrity sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==
|
|
||||||
|
|
||||||
"@vue/reactivity-transform@3.3.4":
|
"@vue/reactivity-transform@3.3.4":
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz"
|
resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz"
|
||||||
@@ -477,28 +467,6 @@
|
|||||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
||||||
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
||||||
|
|
||||||
"@vueuse/core@^10.9.0":
|
|
||||||
version "10.11.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
|
|
||||||
integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
|
|
||||||
dependencies:
|
|
||||||
"@types/web-bluetooth" "^0.0.20"
|
|
||||||
"@vueuse/metadata" "10.11.1"
|
|
||||||
"@vueuse/shared" "10.11.1"
|
|
||||||
vue-demi ">=0.14.8"
|
|
||||||
|
|
||||||
"@vueuse/metadata@10.11.1":
|
|
||||||
version "10.11.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.11.1.tgz#209db7bb5915aa172a87510b6de2ca01cadbd2a7"
|
|
||||||
integrity sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==
|
|
||||||
|
|
||||||
"@vueuse/shared@10.11.1":
|
|
||||||
version "10.11.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938"
|
|
||||||
integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
|
|
||||||
dependencies:
|
|
||||||
vue-demi ">=0.14.8"
|
|
||||||
|
|
||||||
accepts@~1.3.5, accepts@~1.3.8:
|
accepts@~1.3.5, accepts@~1.3.8:
|
||||||
version "1.3.8"
|
version "1.3.8"
|
||||||
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
|
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
|
||||||
@@ -1862,11 +1830,6 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jwt-decode@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
|
||||||
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
|
||||||
|
|
||||||
kind-of@^6.0.2:
|
kind-of@^6.0.2:
|
||||||
version "6.0.3"
|
version "6.0.3"
|
||||||
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
||||||
@@ -2249,18 +2212,13 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
|||||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
pinia-plugin-persistedstate@^3.2.1:
|
pinia@^2.1.6:
|
||||||
version "3.2.1"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz#66780602aecd6c7b152dd7e3ddc249a1f7a13fe5"
|
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
|
||||||
integrity sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==
|
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
|
||||||
|
|
||||||
pinia@^2.1.7:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.2.1.tgz#7cf860f6a23981c23e58605cee45496ce46d15d1"
|
|
||||||
integrity sha512-ltEU3xwiz5ojVMizdP93AHi84Rtfz0+yKd8ud75hr9LVyWX2alxp7vLbY1kFm7MXFmHHr/9B08Xf8Jj6IHTEiQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/devtools-api" "^6.6.3"
|
"@vue/devtools-api" "^6.5.0"
|
||||||
vue-demi "^0.14.10"
|
vue-demi ">=0.14.5"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.9:
|
postcss-selector-parser@^6.0.9:
|
||||||
version "6.0.13"
|
version "6.0.13"
|
||||||
@@ -2834,10 +2792,10 @@ vite@^2.9.13:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
vue-demi@>=0.14.8, vue-demi@^0.14.10:
|
vue-demi@>=0.14.5:
|
||||||
version "0.14.10"
|
version "0.14.6"
|
||||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
|
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz"
|
||||||
integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
|
integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
|
||||||
|
|
||||||
vue-eslint-parser@^9.3.0:
|
vue-eslint-parser@^9.3.0:
|
||||||
version "9.3.1"
|
version "9.3.1"
|
||||||
|
|||||||
@@ -26,13 +26,15 @@
|
|||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.5",
|
"vite": "^4.4.5",
|
||||||
"vite-plugin-checker": "^0.6.4"
|
"vite-plugin-checker": "^0.6.4",
|
||||||
|
"vite-plugin-lib-inject-css": "^2.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kintone/rest-api-client": "^5.5.2",
|
"@kintone/rest-api-client": "^5.5.2",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"jquery": "^3.7.1"
|
"jquery": "^3.7.1",
|
||||||
|
"vite-plugin-css-injected-by-js": "^3.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,7 @@
|
|||||||
| placeholder | 対象項目を選択してください| 入力フィールドに表示されるプレースホルダーのテキストです。この場合は設定されていません。 |
|
| placeholder | 対象項目を選択してください| 入力フィールドに表示されるプレースホルダーのテキストです。この場合は設定されていません。 |
|
||||||
| hint | 説明文| 長い説明文を設定することが可能です。(markdown形式サポート予定、現在HTML可能) |
|
| hint | 説明文| 長い説明文を設定することが可能です。(markdown形式サポート予定、現在HTML可能) |
|
||||||
| selectType |`single` or `multiple`| フィールド選択・他のアプリのフィールド選択の選択モードを設定する |
|
| selectType |`single` or `multiple`| フィールド選択・他のアプリのフィールド選択の選択モードを設定する |
|
||||||
| required | boolean | 必須チェックするかどうか |
|
|
||||||
| requiredMessage| string | 必須チェック時のエラーメッセージ。(未設定の場合「XXXX」が必須です。になります) |
|
|
||||||
| rules |"[val=>val<=100 && val>=1 \|\| '1-100の範囲内の数値を入力してください']"| 必須チェック以外のルールを設定する |
|
|
||||||
| fieldTypes |["SINGLE_LINE_TEXT","MULTI_LINE_TEXT","NUMBER"]| FieldInput,AppFieldSelectのみ使用可能。 |
|
|
||||||
|
|
||||||
|
|
||||||
### 使用可能なコンポーネント
|
### 使用可能なコンポーネント
|
||||||
@@ -81,50 +78,14 @@
|
|||||||
|-----|------------------|------------------|-----------------------------------------|
|
|-----|------------------|------------------|-----------------------------------------|
|
||||||
| 1 | テキストボックス | InputText | 一行のテキスト入力が可能なフィールドです。 |
|
| 1 | テキストボックス | InputText | 一行のテキスト入力が可能なフィールドです。 |
|
||||||
| 2 | テキストボックス(改行可能) | MuiltInputText | 複数行のテキスト入力が可能なテキストエリアです。 |
|
| 2 | テキストボックス(改行可能) | MuiltInputText | 複数行のテキスト入力が可能なテキストエリアです。 |
|
||||||
| 3 | 数値入力 | NumInput | 数値のみ入力可能フィールド。 |
|
| 3 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
||||||
| 4 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
| 4 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
||||||
| 5 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
| 5 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
||||||
| 6 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
| 6 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
||||||
| 7 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
| 7 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
||||||
| 8 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
| 8 | 色選択 | ColorPicker | 色を設定する(追加予定中) |
|
||||||
| 9 | 色選択 | ColorPicker | 色を設定する |
|
| 9 | 他のアプリのフィールド選択 | AppFieldPicker | 他のアプリのフィールドを選択する(追加予定中) |
|
||||||
| 10 | 他のアプリのフィールド選択 | AppFieldSelect | 他のアプリのフィールドを選択する |
|
| 10 |ユーザー選択 | UserPicker | ユーザーを選択する(追加予定中) |
|
||||||
| 11 | アプリ選択 | AppSelect | アプリを選択する |
|
|
||||||
|
|
||||||
### フィールド選択コンポーネントのfieldTypes属性を使用可能フィールド種別
|
|
||||||
| 番号 | 項目タイプ名 | 種別タイプ |
|
|
||||||
|------|-----------------------|-------------------|
|
|
||||||
| 1 | カテゴリー | CATEGORY |
|
|
||||||
| 2 | 作成日時 | CREATED_TIME |
|
|
||||||
| 3 | 作成者 | CREATOR |
|
|
||||||
| 4 | 更新者 | MODIFIER |
|
|
||||||
| 5 | レコード番号 | RECORD_NUMBER |
|
|
||||||
| 6 | 更新日時 | UPDATED_TIME |
|
|
||||||
| 7 | 計算 | CALC |
|
|
||||||
| 8 | チェックボックス | CHECK_BOX |
|
|
||||||
| 9 | 日付 | DATE |
|
|
||||||
| 10 | 日時 | DATETIME |
|
|
||||||
| 11 | ドロップダウン | DROP_DOWN |
|
|
||||||
| 12 | 添付ファイル | FILE |
|
|
||||||
| 13 | グループ | GROUP |
|
|
||||||
| 14 | グループ選択 | GROUP_SELECT |
|
|
||||||
| 15 | リンク | LINK |
|
|
||||||
| 16 | 文字列 (複数行) | MULTI_LINE_TEXT |
|
|
||||||
| 17 | 複数選択 | MULTI_SELECT |
|
|
||||||
| 18 | 数値 | NUMBER |
|
|
||||||
| 19 | 組織選択 | ORGANIZATION_SELECT |
|
|
||||||
| 20 | ラジオボタン | RADIO_BUTTON |
|
|
||||||
| 21 | 関連レコード一覧 | REFERENCE_TABLE |
|
|
||||||
| 22 | リッチエディター | RICH_TEXT |
|
|
||||||
| 23 | 文字列 (1行) | SINGLE_LINE_TEXT |
|
|
||||||
| 24 | ステータス | STATUS |
|
|
||||||
| 25 | 作業者 | STATUS_ASSIGNEE |
|
|
||||||
| 26 | テーブル | SUBTABLE |
|
|
||||||
| 27 | 時刻 | TIME |
|
|
||||||
| 28 | ユーザー選択 | USER_SELECT |
|
|
||||||
| 29 | スペース | SPACER |
|
|
||||||
| 30 | ルックアップ | lookup |
|
|
||||||
|
|
||||||
|
|
||||||
## 2.アクションアドインの開発
|
## 2.アクションアドインの開発
|
||||||
|
|
||||||
@@ -309,7 +270,7 @@ npm run build:dev
|
|||||||
- Azure App Service 拡張機能でデプロイが完了したことを確認します。
|
- Azure App Service 拡張機能でデプロイが完了したことを確認します。
|
||||||
- ka-addin の URL にアクセスしてアプリケーションが正常に動作しているか確認します。
|
- ka-addin の URL にアクセスしてアプリケーションが正常に動作しているか確認します。
|
||||||
|
|
||||||
3. **ローカルでプラグインをテストする(ZCCの導入ため、廃止する)**
|
3. **ローカルでプラグインをテストする**
|
||||||
1. kintone-addinsをPreviewで起動する
|
1. kintone-addinsをPreviewで起動する
|
||||||
```bash
|
```bash
|
||||||
yarn build:dev
|
yarn build:dev
|
||||||
@@ -317,7 +278,7 @@ yarn preview
|
|||||||
#またはyarn devは yarn build:dev + yarn preview と同じです
|
#またはyarn devは yarn build:dev + yarn preview と同じです
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
2. **ngrokをインストールする(ZCCの導入ため、廃止する)**
|
2. **ngrokをインストールする**
|
||||||
1. [ngrok の公式ウェブサイト](https://ngrok.com/)にアクセスします。
|
1. [ngrok の公式ウェブサイト](https://ngrok.com/)にアクセスします。
|
||||||
2. 「Sign up」をクリックしてアカウントを登録するか、既存のアカウントにログインします。
|
2. 「Sign up」をクリックしてアカウントを登録するか、既存のアカウントにログインします。
|
||||||
3. 登録またはログイン後、ダッシュボードに進み、ダウンロードリンクが表示されます。操作システム(Windows、macOS、Linux)に応じて、適切なバージョンを選択してダウンロードします。
|
3. 登録またはログイン後、ダッシュボードに進み、ダウンロードリンクが表示されます。操作システム(Windows、macOS、Linux)に応じて、適切なバージョンを選択してダウンロードします。
|
||||||
@@ -332,9 +293,3 @@ yarn dev
|
|||||||
```bash
|
```bash
|
||||||
ngrok https http://localhost:4173/
|
ngrok https http://localhost:4173/
|
||||||
```
|
```
|
||||||
3. kintone-addinsをビルドする
|
|
||||||
```bash
|
|
||||||
yarn build:dev #開発モード
|
|
||||||
#またはyarn devは yarn build:dev + yarn preview と同じです
|
|
||||||
yarn build #本番リリースモード
|
|
||||||
```
|
|
||||||
@@ -1,20 +1 @@
|
|||||||
.modal-backdrop {
|
@import 'bootstrap/scss/bootstrap';
|
||||||
--bs-backdrop-zindex: 1050;
|
|
||||||
--bs-backdrop-bg: #000;
|
|
||||||
--bs-backdrop-opacity: .5;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: var(--bs-backdrop-zindex);
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: var(--bs-backdrop-bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-backdrop.fade {
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-backdrop.show {
|
|
||||||
opacity: var(--bs-backdrop-opacity)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import {
|
|||||||
|
|
||||||
import { actionAddins } from ".";
|
import { actionAddins } from ".";
|
||||||
import type { Record} from "@kintone/rest-api-client/lib/src/client/types";
|
import type { Record} from "@kintone/rest-api-client/lib/src/client/types";
|
||||||
import { KintoneAllRecordsError, KintoneRestAPIClient} from "@kintone/rest-api-client";
|
import { KintoneRestAPIClient} from "@kintone/rest-api-client";
|
||||||
import "./auto-lookup.scss";
|
import "./auto-lookup.scss";
|
||||||
import "bootstrap/js/dist/modal";
|
import "bootstrap/js/dist/modal";
|
||||||
// import "bootstrap/js/dist/spinner";
|
// import "bootstrap/js/dist/spinner";
|
||||||
import {Modal} from "bootstrap"
|
import {Modal} from "bootstrap"
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
interface IAutoLookUpProps {
|
interface Props {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
lookupField: LookupField;
|
lookupField: LookupField;
|
||||||
condition: Condition;
|
condition: Condition;
|
||||||
@@ -84,11 +84,11 @@ interface App {
|
|||||||
export class AutoLookUpAction implements IAction {
|
export class AutoLookUpAction implements IAction {
|
||||||
name: string;
|
name: string;
|
||||||
actionProps: IActionProperty[];
|
actionProps: IActionProperty[];
|
||||||
props: IAutoLookUpProps;
|
props: Props;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.name = "ルックアップ更新";
|
this.name = "ルックアップ更新";
|
||||||
this.actionProps = [];
|
this.actionProps = [];
|
||||||
this.props = {} as IAutoLookUpProps;
|
this.props = {} as Props;
|
||||||
this.register();
|
this.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,15 +96,15 @@ export class AutoLookUpAction implements IAction {
|
|||||||
* アクセスのメインの処理関数
|
* アクセスのメインの処理関数
|
||||||
*/
|
*/
|
||||||
async process(
|
async process(
|
||||||
actionNode: IActionNode,
|
prop: IActionNode,
|
||||||
event: any,
|
event: any,
|
||||||
context: IContext
|
context: IContext
|
||||||
): Promise<IActionResult> {
|
): Promise<IActionResult> {
|
||||||
this.actionProps = actionNode.actionProps;
|
this.actionProps = prop.actionProps;
|
||||||
this.props = {
|
this.props = {
|
||||||
...actionNode.ActionValue,
|
...prop.ActionValue,
|
||||||
condition: JSON.parse((actionNode.ActionValue as any).condition),
|
condition: JSON.parse((prop.ActionValue as any).condition),
|
||||||
} as IAutoLookUpProps;
|
} as Props;
|
||||||
// console.log(context);
|
// console.log(context);
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
@@ -129,16 +129,19 @@ export class AutoLookUpAction implements IAction {
|
|||||||
}
|
}
|
||||||
const updateRecords = this.convertForLookup(targetRecords,lookUpField,key);
|
const updateRecords = this.convertForLookup(targetRecords,lookUpField,key);
|
||||||
console.log("updateRecords", updateRecords);
|
console.log("updateRecords", updateRecords);
|
||||||
this.showSpinnerModel(this.props.lookupField.app,lookUpField);
|
this.showSpinnerModel(this.props.lookupField.app);
|
||||||
const updateResult = await this.updateLookupTarget(updateRecords);
|
await this.updateLookupTarget(updateRecords);
|
||||||
if(updateResult){
|
this.showResult(this.props.lookupField.app,updateRecords.length);
|
||||||
this.showResult(this.props.lookupField.app,lookUpField,updateRecords.length);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.closeDialog();
|
console.error("ルックアップ更新中例外が発生しました。", error);
|
||||||
context.errors.handleError(error,actionNode,"ルックアップ更新中例外が発生しました");
|
if(error instanceof Error){
|
||||||
|
event.error = error.message;
|
||||||
|
}else{
|
||||||
|
event.error = "ルックアップ更新中例外が発生しました。";
|
||||||
|
}
|
||||||
result.canNext = false;
|
result.canNext = false;
|
||||||
}
|
}
|
||||||
|
console.log("autoLookupProps", this.props);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,17 +153,12 @@ export class AutoLookUpAction implements IAction {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
makeQuery=(lookUpField:Field,key:any)=>{
|
makeQuery=(lookUpField:Field,key:any)=>{
|
||||||
let query ="";
|
|
||||||
if(typeof key==='number'){
|
if(typeof key==='number'){
|
||||||
query = `${lookUpField.code} = ${key}`
|
return `${lookUpField.code} = ${key}`
|
||||||
}
|
}
|
||||||
if(typeof key==='string'){
|
if(typeof key==='string'){
|
||||||
query = `${lookUpField.code} = "${key}"`
|
return `${lookUpField.code} = "${key}"`
|
||||||
}
|
}
|
||||||
if(this.props.condition.queryString!==''){
|
|
||||||
query = `${query} and (${this.props.condition.queryString})`
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,41 +192,28 @@ export class AutoLookUpAction implements IAction {
|
|||||||
* ルックアップ先を更新する
|
* ルックアップ先を更新する
|
||||||
* @param updateRecords
|
* @param updateRecords
|
||||||
*/
|
*/
|
||||||
updateLookupTarget = async (updateRecords:Array<any>):Promise<boolean>=>{
|
updateLookupTarget = async (updateRecords:Array<any>)=>{
|
||||||
if (updateRecords && updateRecords.length > 0) {
|
if (updateRecords && updateRecords.length > 0) {
|
||||||
try{
|
|
||||||
const client=new KintoneRestAPIClient();
|
const client=new KintoneRestAPIClient();
|
||||||
const result = await client.record.updateAllRecords({
|
client.record.updateAllRecords({
|
||||||
app:this.props.lookupField.app.id,
|
app:this.props.lookupField.app.id,
|
||||||
records:updateRecords
|
records:updateRecords
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
}catch(error ){
|
|
||||||
if(error instanceof KintoneAllRecordsError){
|
|
||||||
this.showError(this.props.lookupField.app,
|
|
||||||
this.props.lookupField.fields[0],
|
|
||||||
error as KintoneAllRecordsError,updateRecords.length);
|
|
||||||
return false;
|
|
||||||
}else{
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// await kintone.api(kintone.api.url("/k/v1/records.json", true), "PUT", {
|
// await kintone.api(kintone.api.url("/k/v1/records.json", true), "PUT", {
|
||||||
// app: this.props.lookupField.app.id,
|
// app: this.props.lookupField.app.id,
|
||||||
// records: updateRecords
|
// records: updateRecords
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新中のダイアログ表示
|
* 更新中のダイアログ表示
|
||||||
* @param app
|
* @param app
|
||||||
*/
|
*/
|
||||||
showSpinnerModel = (app:App,lookup:Field) => {
|
showSpinnerModel = (app:App) => {
|
||||||
let dialog = $("#alcLookupModal");
|
let dialog = $("#alcLookupModal");
|
||||||
if(dialog.length===0){
|
if(dialog.length===0){
|
||||||
const modalHTML = `<div class="bs-scope">
|
const modalHTML = `
|
||||||
<div class="modal" id="alcLookupModal" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
<div class="modal" id="alcLookupModal" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog-centered">
|
<div class="modal-dialog-centered">
|
||||||
<div class="modal-dialog modal-content">
|
<div class="modal-dialog modal-content">
|
||||||
@@ -237,7 +222,7 @@ export class AutoLookUpAction implements IAction {
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row" id="app${app.id}_${lookup.code}">
|
<div class="row" id="app${app.id}">
|
||||||
<div class="spinner-border text-secondary col-1 " role="alert"></div>
|
<div class="spinner-border text-secondary col-1 " role="alert"></div>
|
||||||
<div class="col">${app.name}</div>
|
<div class="col">${app.name}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,17 +230,18 @@ export class AutoLookUpAction implements IAction {
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||||
</div>
|
</div>
|
||||||
</div></div></div></div>`;
|
</div>
|
||||||
$(modalHTML).appendTo("body");
|
</div>
|
||||||
dialog = $("#alcLookupModal");
|
</div>`;
|
||||||
|
dialog = $(modalHTML).appendTo("body");
|
||||||
dialog.get()[0].addEventListener('hidden.bs.modal',(ev)=>{
|
dialog.get()[0].addEventListener('hidden.bs.modal',(ev)=>{
|
||||||
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||||
$("#alcLookupModal").parent().remove();
|
$("#alcLookupModal").remove();
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
const dialogBody=$("#alcLookupModal .modal-body");
|
const dialogBody=$("#alcLookupModal .modal-body");
|
||||||
const htmlrow=`
|
const htmlrow=`
|
||||||
<div class="row" id="app${app.id}_${lookup.code}">
|
<div class="row" id="app${app.id}">
|
||||||
<div class="spinner-border text-secondary col-1 " role="alert">
|
<div class="spinner-border text-secondary col-1 " role="alert">
|
||||||
</div>
|
</div>
|
||||||
<div class="col">${app.name}</div>
|
<div class="col">${app.name}</div>
|
||||||
@@ -270,39 +256,14 @@ export class AutoLookUpAction implements IAction {
|
|||||||
* @param app 更新先アプリ情報
|
* @param app 更新先アプリ情報
|
||||||
* @param count 更新件数
|
* @param count 更新件数
|
||||||
*/
|
*/
|
||||||
showResult=(app:App,lookup:Field,count:number)=>{
|
showResult=(app:App,count:number)=>{
|
||||||
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}`);
|
||||||
const html=` <div class="col-1 text-success">✔</div>
|
const html=` <div class="col-1 text-success">✔</div>
|
||||||
<div class="col">${app.name}</div>
|
<div class="col">${app.name}</div>
|
||||||
<div class="col">更新件数:${count}件</div>`;
|
<div class="col">更新件数:${count}件</div>`;
|
||||||
dialogBody.html(html);
|
dialogBody.html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新結果を表示する
|
|
||||||
* @param app 更新先アプリ情報
|
|
||||||
* @param count 更新件数
|
|
||||||
*/
|
|
||||||
showError=(app:App,lookup:Field,error:KintoneAllRecordsError,allCount:Number)=>{
|
|
||||||
const message=error.error.message;
|
|
||||||
const proRecords = error.numOfProcessedRecords;
|
|
||||||
const allRecords=error.numOfAllRecords;
|
|
||||||
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
|
||||||
const html=`<div class="col-1 text-danger">✖</div>
|
|
||||||
<div class="col">${app.name}</div>
|
|
||||||
<div class="col">更新件数:${proRecords}/${allRecords}</div>
|
|
||||||
<div class="row text-danger">${message}<div>`;
|
|
||||||
dialogBody.html(html);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* ダイアログ画面を閉じる
|
|
||||||
*/
|
|
||||||
closeDialog=()=>{
|
|
||||||
const dialog = $("#alcLookupModal");
|
|
||||||
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
|
||||||
$("#alcLookupModal").parent().remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
register(): void {
|
register(): void {
|
||||||
actionAddins[this.name] = this;
|
actionAddins[this.name] = this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ export class AutoNumbering implements IAction{
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}catch(error){
|
}catch(error){
|
||||||
context.errors.handleError(error,actionNode);
|
console.error(error);
|
||||||
|
event.error="処理中異常が発生しました。";
|
||||||
return {
|
return {
|
||||||
canNext:false,
|
canNext:false,
|
||||||
result:false
|
result:false
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user